docs(security): close the stage-7 gate's UNS gap — legs 12-15
v2-ci / build (push) Successful in 4m51s
v2-ci / unit-tests (push) Failing after 15m25s

The first pass could not exercise UNS-realm scope selection because SITE-A had
no UNS. Authored one on docker-dev (area -> line -> equipment) and re-ran that
part: the picker's UNS column offers UnsArea/UnsLine/Equipment, selecting the
equipment resolves, and the grant deploys Sealed.

Leg 15 is the one worth keeping. Re-pointing the SITE-A grant at MAIN's
equipment is the only rejection whose id is REAL, so a check that asked only
"does a row with this id exist?" would accept it and confer authority across a
cluster boundary. The list page and the deploy gate both catch it and both name
the owning cluster.

Also found and filed (Gitea #535, not fixed here): the /uns area and line modals
do not validate the UNS segment format, so "Packaging" and "Line 1" saved
happily and then failed the deploy with UnsSegmentInvalid. Only the equipment
form carries the rule. A deployment snapshots the whole database, so one bad
area name blocks every cluster's deploy - the same authoring-vs-gate gap stage 7
just closed for NodeAcl rows.

Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
Joseph Doherty
2026-08-01 11:03:57 -04:00
parent 3796c0bf22
commit a88cefd5ee
@@ -401,6 +401,10 @@ stale-central-2 trap from the stage-6 gate.
| 9 | Re-picking a valid scope unblocks it | ✅ `NAMESPACE raw` → saved |
| 10 | The authored grant **deploys** | ✅ `POST /api/deployments` → `Sealed` |
| 11 | The deploy gate rejects what the page rejects | ✅ same row broken by SQL → HTTP 422 `NodeAclScopeUnresolved` |
| 12 | UNS column offers area / line / equipment | ✅ `UNSAREA` / `UNSLINE` / `EQUIPMENT`, eager, after a UNS was authored under SITE-A |
| 13 | Selecting an equipment resolves | ✅ `EQUIPMENT` + `EQ-f3dd0e8e27a4`, "✓ Equipment 'packer-1' in cluster 'SITE-A'" |
| 14 | A UNS-realm grant deploys | ✅ `Sealed` |
| 15 | A cross-cluster scope is caught at both ends | ✅ re-pointed at MAIN's equipment → list marks it `UNRESOLVED` naming `MAIN`; deploy → HTTP 422 `NodeAclScopeCrossCluster` |
Legs 8 and 11 are the pair that matters: the page and the deploy gate reject the same row for the same
reason, which is the claim "`CheckScopeAsync` mirrors `DraftValidator`" reduces to. Leg 11 also
@@ -410,12 +414,22 @@ message named `DriverInstance`.
Legs 5 and 9 are the positive controls. Without them the gate passes against a page that refuses
everything, which is the same failure shape stage 6's `resolvedScopes=N` leg exists to catch.
**Not covered:** a UNS-realm scope (SITE-A has no UNS areas, so the picker's UNS column correctly showed
"no UNS areas yet" and only the namespace row was selectable there). `UnsArea` / `UnsLine` / `Equipment`
selection is covered offline only.
Legs 12–15 were added in a second pass after authoring a UNS (area → line → equipment) under SITE-A,
which had none when the first pass ran. **Leg 15 is the one worth keeping:** it is the only rejection
whose id is *real*, so a check that asked only "does a row with this id exist?" would accept it and
confer authority across a cluster boundary. Both ends name the owning cluster.
Rig restored afterwards: the gate's ACL row deleted and a further deployment sealed, leaving SITE-A at
zero ACL rows as found.
**Found while authoring that UNS, unrelated to this stage:** the `/uns` **area** and **line** modals do
**not** validate the UNS segment format, so "Packaging" and "Line 1" saved happily and then failed the
deploy with `UnsSegmentInvalid` — fleet-wide, since a deployment snapshots the whole database, so one
bad area name blocks every cluster's deploy. Only `EquipmentPage.razor:570` carries the
`^[a-z0-9-]{1,32}$` rule. Exactly the authoring-vs-gate gap this stage closed for ACLs. Filed
separately; not fixed here.
Rig state afterwards: the gate's ACL rows deleted and a further deployment sealed, leaving SITE-A at
zero ACL rows as found. The **UNS structure was kept** (area `packaging` → line `line-1` → equipment
`packer-1`, renamed to valid segments), since SITE-A having a UNS is what made legs 12–15 possible and
keeps them re-runnable.
## Known consequence to design around