diff --git a/.claude/rules/data-plane-auth.md b/.claude/rules/data-plane-auth.md index 1125ca200..6488dfddd 100644 --- a/.claude/rules/data-plane-auth.md +++ b/.claude/rules/data-plane-auth.md @@ -87,6 +87,22 @@ Two things to know before relying on any of this: identifier is an ordinary Area), and its Browse considers transit in **either** realm because it fronts both trees. Before this every narrow Browse grant was unreachable by navigation — the Browse gate denies the whole call when its source node is denied, so clients were stopped at the front door. + - **Scripted-alarm commands are authorized at the OWNING EQUIPMENT, never by splitting the id (#797).** + Production `ScriptedAlarmId`s are opaque (`^[A-Za-z0-9_-]+$`, e.g. `scripted-eqa-hi`) and become the + condition NodeId, so `BuildScope` on them can never match an Equipment grant. Two halves, both re-read + per call and uncached: (1) the node manager binds each live condition **instance** to its `EquipmentId` + in `MaterialiseAlarmCondition` (only when parented under that equipment's folder; cleared on every + teardown + rebuild); (2) the authority generation carries `EquipmentScopePaths` (built by the same + `AclScopePathResolver` call that places Equipment grants) **and** `ScriptedAlarmOwners` + (alarmId → EquipmentId from the artifact). `DataPlaneAuthorizer.AuthorizeScriptedCondition` denies + unless the bound instance is the live one AND its equipment equals the generation's owner — this + closes the install-authority-then-rebuild window and a failed apply leaving a moved alarm on its old + equipment. ⚠️ **Fail closed, even for Cluster grants:** an unbound / replaced / undeployed / line-less + owner is denied under every grant (no realm-root fallback). Native alarms still use RawPath; ACL-off + is unchanged. ⚠️ **Not end-to-end yet — #808:** UNS folders and scripted condition children still + split opaque ids for Read/Browse/Subscribe/Server-object event visibility, so a narrow principal can't + obtain the EventId Ack/Confirm/AddComment need (Shelve/Enable work). `ServerEventRig`'s folder id + `EqA` == scope path `[uns, EqA]` hides this — tests must use opaque `UA-/UL-/EQ-` ids. - **Revocation reaches live subscriptions** (stage 6): every other operation re-asks per request, but a subscription is authorized once and then streams — and streaming is how machine consumers actually read this server. `DataPlaneAuthorityState.Changed` fires `RevalidateMonitoredItems`. ⚠️ A denied