deferment.md §8.1 asked for a decision: wire IPermissionEvaluator into the node
manager, or say plainly that ACLs are not enforced. Decision: the latter. The
evaluator stays in the tree; #520 tracks the wire-up.
Verifying the claim turned up four things worse than the audit recorded:
- docs/security.md, not ReadWriteOperations.md, was the highest-risk doc. It
claimed an anonymous session is "default-denie[d] any node a session has no
ACL grant for". The opposite holds: anonymous can Browse/Read/Subscribe/
HistoryRead the entire address space, and is refused writes and alarm acks
only because it carries no roles.
- Three of five documented data-plane role strings do nothing.
OpcUaDataPlaneRoles declares exactly WriteOperate and AlarmAck; ReadOnly,
WriteTune and WriteConfigure are compared against nowhere in src/. The doc
called all five "exact, case-insensitive, and code-true".
- ReadWriteOperations.md was fiction beyond the ACL claims. OnReadValue has
zero occurrences in src/ — the documented read path did not exist. Reads
never reach a driver: the node manager is push-model and the SDK serves a
Read from the cached pushed value. WriteAuthzPolicy, _sourceByFullRef,
_writeIdempotentByFullRef and IRoleBearer are likewise zero-hit, and
CapabilityInvoker is not referenced by the OpcUaServer project at all. This
file is rewritten rather than bannered.
- v2-release-readiness.md claimed a whole enforcement layer shipped —
FilterBrowseReferences, GateCallMethodRequests, MapCallOperation,
AuthorizationBootstrap, Node:Authorization:* keys. None exist. Whatever
landed on the v2 branch did not survive into the shipped tree.
The UI change is the operative one: ClusterAcls.razor and AclEdit.razor now
warn that a grant is saved and shipped in every deployment artifact but never
evaluated, so an operator cannot author a deny rule believing it works.
deferment.md gains a §9 execution log tracking §8 remediation.
Final batch of F15.2. After this commit every entity surfaced by the
Phase A-D read views has a matching new/edit/delete form.
- AclEdit.razor /clusters/{id}/acls/{new|aclId}
- NodePermissions [Flags] enum surfaced as per-bit checkboxes plus
one-click bundle buttons (ReadOnly / Operator / Engineer / Admin)
- ScopeKind select + ScopeId free-text target (null = cluster-wide)
- VirtualTagEdit.razor /virtual-tags/{new|virtualTagId}
- Trigger validation: enforces at least one of ChangeTriggered or
TimerIntervalMs is set
- ScriptedAlarmEdit.razor /scripted-alarms/{new|scriptedAlarmId}
- AlarmType select with OPC UA Part 9 subtypes
- MessageTemplate is a textarea (template tokens are server-resolved)
- ScriptEdit.razor /scripts/{new|scriptId}
- SHA-256 hash computed from SourceCode on save (operator never sees
or edits SourceHash directly)
- InputTextArea now; Monaco syntax editor is a future enhancement
List pages (ClusterAcls / VirtualTags / ScriptedAlarms / Scripts) all
gain New + per-row Edit affordances.
Tally: F15.2 shipped CRUD for 11 entities — Cluster, ClusterNode,
UnsArea, UnsLine, Namespace, DriverInstance, Equipment, Tag, NodeAcl,
VirtualTag, ScriptedAlarm, Script.
All 9 integration tests still green.
Per Q3 of the rebuild plan, each v1 ClusterDetail tab becomes a separate
route under /clusters/{id}/<tab>. This batch adds read-only table views
for the six core config entity types; live-edit forms with RowVersion
concurrency land in Phase C.2 once the read-view shape is reviewed.
- ClusterEquipment /clusters/{id}/equipment — joins via DriverInstance
so the cluster scope works
- ClusterUns /clusters/{id}/uns — Areas + Lines tables
- ClusterNamespaces /clusters/{id}/namespaces — Kind + URI + Enabled chip
- ClusterDrivers /clusters/{id}/drivers — collapsed list with JSON
config expandable per Q1
(typed editors deferred)
- ClusterTags /clusters/{id}/tags — first 200 by name + filter
- ClusterAcls /clusters/{id}/acls — LDAP group + scope +
NodePermissions bits
Shared ClusterNav.razor extracted; ClusterOverview + ClusterRedundancy
updated to use it. _Imports.razor adds Components.Shared so the shared
nav is in scope across pages.