From da074adce94156e9437d2343dfe865bc89f8dd4a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 3 Jun 2026 16:22:08 -0400 Subject: [PATCH] =?UTF-8?q?docs(audit):=20Reservations.md=20=E2=80=94=20ac?= =?UTF-8?q?curacy=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit STALE-STATUS / CODE-REALITY fixes: - Table row ReleasedAt/ReleasedBy: "FleetAdmin" → "Administrator" (AdminRole enum renamed in CanonicalizeAdminRoles migration). ReleasedBy now documents that it is the LDAP operator name passed as explicit @ReleasedBy param — not SUSER_SNAME() — per migration 20260522000001_AddReleasedByToReleaseExternalIdReservation. - §4 Release: "FleetAdmin" → "Administrator"; added @ReleasedBy required param requirement matching the updated stored-proc signature; replaced "SUSER_SNAME()" attribution claim with the correct explicit-param description. - §The Admin page: replaced entirely. Actual Reservations.razor uses bare [Authorize] (not [Authorize(Policy="FleetAdmin")] and not "CanPublish"). The page is a read-only flat list (no Active/Released split, no Release row action, no Release dialog). Redirected release-flow readers to docs/v2/admin-ui.md §"Release an external-ID reservation". Evidence: src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Reservations.razor:2 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/AdminRole.cs:36 src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs:130 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260522000001_AddReleasedByToReleaseExternalIdReservation.cs --- docs/Reservations.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/Reservations.md b/docs/Reservations.md index d835e50b..0b48980b 100644 --- a/docs/Reservations.md +++ b/docs/Reservations.md @@ -52,7 +52,7 @@ is refreshed, and they are eventually *released* — but never silently deleted. | `ClusterId` | The first cluster to publish the reservation. | | `FirstPublishedAt` / `FirstPublishedBy` | When and by whom the claim was first made. | | `LastPublishedAt` | Refreshed on every subsequent publish that re-asserts the same `(Kind, Value, EquipmentUuid)`. | -| `ReleasedAt` / `ReleasedBy` / `ReleaseReason` | Non-null once a FleetAdmin explicitly releases the claim. A row with `ReleasedAt IS NULL` is *active*. | +| `ReleasedAt` / `ReleasedBy` / `ReleaseReason` | Non-null once an Administrator explicitly releases the claim. `ReleasedBy` is the LDAP operator name (passed explicitly as `@ReleasedBy`; not `SUSER_SNAME()`). A row with `ReleasedAt IS NULL` is *active*. | There is no foreign key from `EquipmentUuid` / `ClusterId` to their tables — by design, so a reservation survives the deletion or disabling of the equipment @@ -99,14 +99,16 @@ being disabled, the generation being superseded, or a rollback. ### 4. Release -Reusing an identifier for a **different** piece of equipment requires a -FleetAdmin to explicitly release the existing claim. Release runs +Reusing an identifier for a **different** piece of equipment requires an +Administrator to explicitly release the existing claim. Release runs `sp_ReleaseExternalIdReservation`, which: - Requires a non-empty **reason** — a hard audit invariant; the procedure raises an error without one. -- Stamps `ReleasedAt`, `ReleasedBy` (`SUSER_SNAME()`), and `ReleaseReason` - rather than deleting the row, so the history is preserved. +- Requires a non-empty **`@ReleasedBy`** — the LDAP operator name supplied + by the caller; the procedure raises an error without it. +- Stamps `ReleasedAt`, `ReleasedBy` (the supplied operator name), and + `ReleaseReason` rather than deleting the row, so the history is preserved. - Once released, the `(Kind, Value)` pair is free — a different `EquipmentUuid` can claim it on a future publish. @@ -116,20 +118,19 @@ permanent for the life of the asset. ## The Admin page -`/reservations` (Admin UI) is the operator surface. It is **FleetAdmin-only** -(the `CanPublish` policy). +`/reservations` (Admin UI) is the operator surface. It requires authentication +(`[Authorize]`) but is not restricted to a specific Admin UI role — any signed-in +user can view it. -- **Active** table — every reservation with `ReleasedAt IS NULL`: kind, value, - owning `EquipmentUuid`, cluster, and the first/last publish stamps. Each row - has a **Release…** action. -- **Released** table — the 100 most recently released reservations, with the - releasing user and reason. -- **Release dialog** — opened from an active row; it requires a reason before - the Release button will submit, mirroring the procedure's audit invariant. +The page is a **read-only flat list** of all `ExternalIdReservation` rows, +ordered by Kind then Value. It shows Kind, Value, owning `EquipmentUuid`, and +Cluster. There is no Active/Released split, no Release action, and no Release +dialog on this page. You cannot *create* a reservation from this page — reservations only ever come -into existence as a side-effect of publishing a generation. The page is for -inspection and for the release flow. +into existence as a side-effect of publishing a generation. The release flow +is described in `docs/v2/admin-ui.md` § "Release an external-ID reservation" +and runs via `sp_ReleaseExternalIdReservation`. ## Related