Commit Graph

1341 Commits

Author SHA1 Message Date
Joseph Doherty 61b9adeb9c Merge branch 'master' into fix/832-docker-dev-batch 2026-11-11 02:32:42 -05:00
Joseph Doherty 988927c8ac fix(opcua): credential lockout review fixes (#833)
- Reserve an in-flight slot per client before authenticating (TryBegin /
  Complete / Abandon under one lock); refuse once recorded failures +
  in-flight attempts reach MaxFailedAttempts, so a parallel burst cannot
  pass the check before its first failure is recorded.
- An expired lockout resets the count (LockoutSeconds < FailureWindowSeconds
  relocked on every single failure after the first lockout).
- Validate OpcUa:CredentialLockout at host start: disabled, or both
  durations > 0.
- Log the first refusal of a lockout episode at Information, the rest at
  Debug.
- Document that on None endpoints the key is effectively IP-only (NAT
  neighbours share a UserName count) and a null peer shares one key.
2026-11-11 02:32:40 -05:00
Joseph Doherty 5bfe8386e6 Merge fix/826-folder-event-acl 2026-11-11 02:25:44 -05:00
Joseph Doherty 489664d105 fix(acl): folder event verdict invalidation reaches orphaned items, decides on the live instance, skips ACL-off; alarm-bearing reference map (#826 review)
1. Orphaned subscriptions (session closed without deleting subs) have Session == null, so the public
   MonitoredNode2.InvalidatePermissionCacheForSession never reached their items: a cached allow survived a
   deploy, the moved alarm's events were queued and delivered on TransferSubscriptions. Each notifier's
   cache is now cleared whole through the SDK's private MonitoredNode2.InvalidatePermissionCaches
   (reflection, guard-tested; fallback = per-session drop + Remove/Add of session-less items).
2. The verdict is decided on the condition LIVE under the event's NodeId, not the event's own instance:
   the SDK key has no instance identity, so a replaced instance's late event cached a deny its successor
   inherited until the next invalidation.
3. Invalidation is skipped while enforcement is off (forced across an on/off authorizer transition) and is
   one O(cache) clear per node instead of a whole-cache scan per item.
4. TagReferencingEquipment carries alarm-bearing raw tags only (TagConfigIntent.Alarm); the snapshot adopts
   an OrdinalIgnoreCase set instead of copying it again.

Tests (each fails with its fix reverted): FolderEventVerdictInvalidationTests (orphan + deploy + transfer;
replaced instance's late event; ACL-off skip; SDK private-method guard), DeploymentArtifactScopePathTests
(alarm-bearing projection).
2026-11-11 02:25:24 -05:00
Joseph Doherty e9064dbdf4 Merge fix/815-credential-ref-scoping
# Conflicts:
#	.claude/rules/drivers.md
#	docs/drivers/Heidenhain.md
2026-11-11 02:08:24 -05:00
Joseph Doherty 4563940792 fix(security): warn-only compatibility for deployed drivers + upgrade preflight (#815)
User decision: save, Test Connect, live browse and the deploy gate keep
refusing out-of-namespace credential references, but an ALREADY-DEPLOYED
driver must not fault at the upgrade restart or (Galaxy) at the next
gateway reconnect, since ReopenAsync re-resolves Gateway.ApiKeySecretRef.

- LegacyCredentialReferenceTracker (Core.Abstractions): the one, deprecated,
  call-site-chosen compatibility path. Running Galaxy (InitializeAsync,
  ReopenAsync, ack fallback) and OpcUaClient (session open) pass it and keep
  the pre-#815 semantics (case-insensitive prefixes, Galaxy file:, OpcUaClient
  literal pass-through) with a rate-limited Warning (driver/field/allowed
  form, never the value) and a credential-compat:<field> diagnostics entry
  (both drivers now implement IDriverDiagnostics). Browsers and probes pass
  none and stay hard-fail; Heidenhain has none.
- Deploy-gate message names the migration.
- Upgrade preflight: CredentialReferencePreflightService (admin nodes, once
  after ApplicationStarted, ConfigDb only, resolves nothing) logs a Warning
  per Draft row / Deployed-artifact finding; the /raw banner shows them plus
  Running drivers reporting warn-only use (driver-health diagnostics).
- docs/security.md: exact replacement table (incl. the old form default
  env:MX_API_KEY, file:, un-namespaced secret:, casing changes), the
  ReopenAsync reconnect behaviour, the warn-only window and its removal.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-11 02:07:33 -05:00
Joseph Doherty 0856261ef8 docs(mtconnect): record the #823 end-to-end gate (adapter stop, Agent restart/stop/pause, genuine overrun) 2026-11-11 01:57:21 -05:00
Joseph Doherty d3285af6b3 Merge feat/830-heidenhain-followups 2026-11-11 01:55:10 -05:00
Joseph Doherty 598c668108 Merge fix/816-opcuaclient-cert-host 2026-11-11 01:53:35 -05:00
Joseph Doherty 9aff90f884 fix(opcuaclient): never pass checkDomain for a None endpoint; document both SDK check sites (#816 review)
SDK 2.0.1 checks the server host name in TWO places: Session.OpenAsync (after
the chain is trusted) and DefaultSessionFactory.CreateChannelAsync, against the
certificate in the GetEndpoints reply and before any security-mode test. opc-plc
sends a certificate on its None endpoint, so `connect -u
opc.tcp://10.100.0.35:50000 --validate-host-name` was refused on an
unauthenticated channel, where a host check proves nothing.

All three call sites now pass checkDomain only when the selected endpoint's
SecurityMode != None: OpcUaEndpointSelection.CheckDomainFor for the driver
(OpenSessionOnEndpointAsync, so the failover sweep too) and the AdminUI browser;
the same expression in Client.Shared ConnectToEndpointAsync (shared by
failover). New live tests: driver None + flag on vs :50000 reads; browser None +
flag on vs the NAT fixture browses; the secure + flag refusals still hold.

Docs: name both check sites; a zone-qualified IPv6 host never matches (FindDomain
compares the zone too), so link-local needs validateServerHostName=false; the
match is loose (first label alone, subject DC= parts count, no wildcards), which
caveats the CA recommendation.
2026-11-11 01:53:01 -05:00
Joseph Doherty 8e1e73f93c fix(acl): per-event visibility for direct folder event items; generation-checked native wiring (#826)
Event items on an equipment / raw device folder were fed by the SDK's MonitoredNode2 fan-out with no ACL
decision, so after a deploy moved a scripted alarm (or dropped the reference projecting a native alarm into
an equipment) the OLD folder's subscribers kept receiving it - EventId included - until the rebuild landed,
indefinitely after a failed apply.

- OtOpcUaNodeManager re-implements INodeManager3 so ValidateEventRolePermissions (the SDK's per-event,
  per-item hook; base not virtual) adds IsNotifierEventVisibleTo: live condition instance, notifier still a
  legitimate notifier of it, and the #586/#808 Server-object rule (IsConditionVisibleTo over
  ConditionVisibilityScopes).
- The SDK memoizes that verdict per (item, event type, SourceNode): InvalidateFolderEventVerdicts runs on the
  authority/authorizer setters, RevalidateMonitoredItems (deploy + timer) and CompleteAddressSpaceApply.
- ConditionRefresh on direct items goes through RefreshDirectItems (base loop + the same decision).
- New generation field TagReferencingEquipment (RawPath -> EquipmentIds, from the artifact's
  UnsTagReferences via AclScopePathResolver.ResolveTagRawPath): a native alarm's wired equipment folder only
  counts (folder delivery AND Server-object scopes) while the current generation still references it.
- Secondary #808 edge: UNS variables wired UNS->Raw Organizes by AddReference are tracked and always placed
  as reference variables (an Area named like its equipment's id no longer narrows them).

Tests: UnsFolderScopeWireTests (moved alarm on a folder item + ConditionRefresh + rebuild; area named like
equipment id), NativeAlarmFolderEventAclWireTests (dropped reference: folder + Server object + refresh +
restore), DeploymentArtifactScopePathTests (projection). Rig/event tests now supply tag references.
2026-11-11 01:39:06 -05:00
Joseph Doherty 8a6f0e3c84 fix(mtconnect): the Agent refuses count ABOVE bufferSize, not at it (#825)
Live gate against the cppagent 2.7 fixture: count=16384 with bufferSize
16384 streams; 16385 is OUT_OF_RANGE ('count' must be less than 16385).
Warn only when SampleCount > bufferSize, pin both sides of the edge
against the live Agent, and correct the #804 wording that said 'at or
above'.
2026-11-11 01:38:48 -05:00
Joseph Doherty e6cba4bc18 fix(adminui): Heidenhain authoring UX — CSV preview runs the save-path row rule, dictated data type is locked, credential error shown once (#830)
- RawTagRowRules: the ONE driver-dispatched authority on Tag-ROW rules (access level /
  Tag.DataType). RawTreeService's save path, the CSV import's staged review grid
  (RawTagCsvMapper) and the manual-entry grid all call it; no call site names a driver.
  A ReadWrite or mistyped Heidenhain row is now invalid IN THE PREVIEW instead of only at Commit.
- Manual-entry grid + tag modal: when the driver dictates the data type for the row's
  address (RawTagRowDefaults.DictatedDataType), the field is a locked read-only value instead
  of a select that commit silently replaced; the grid runs the row rule client-side and no
  longer rewrites the row at commit.
- Driver/device modals: the credential refusal is shown once (the modal's save-gate notice);
  HeidenhainDriverFormModel / HeidenhainDeviceModel.Validate report only ranges / endpoint.
- docs/drivers/Heidenhain.md: #831 alarm conditions + rediscovery, #830 authoring behaviour.

Tests: HeidenhainAuthoringUxTests (CSV preview == ImportTagsAsync row-for-row, dictated type
is what the save path accepts, single credential surface, ALARM: seeding, CSV alarm rows,
AlarmMs round-trip); HeidenhainAuthoringTests updated for the single-notice rule.
2026-11-11 01:37:51 -05:00
Joseph Doherty 9742a66a9e docs(security): credential-reference namespaces + migration note (#815)
docs/security.md gains the canonical table of per-driver namespaces, the five
enforcement seams, the out-of-scope cases (appsettings ${secret:}, Sql
connectionStringRef) and the migration steps for existing configs. Galaxy,
OpcUaClient, Heidenhain, Configuration and driver-specs docs, the Galaxy
options XML docs, the drivers rule file and the docker-dev compose comment
point at it. docker-dev's env:GALAXY_MXGW_API_KEY stays valid (allowed legacy
prefix), so no rig change is needed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-11 01:37:25 -05:00
Joseph Doherty ae91e6b7f7 feat(opcuaclient): opt-in server-certificate host-name check; NAT fixture keeps its PKI (#816, #818)
#816 finding: the SDK checks that the server certificate names the host only
when a session is created with checkDomain:true. The driver, the AdminUI
browser and Client.Shared all called the 8-argument
DefaultSessionFactory.CreateAsync, which forwards checkDomain:false — so no
host-name check ever ran (why a trusted NAT cert for *.invalid browsed fine
over an IP in #813). When enabled the SDK (Session.OpenAsync ->
ValidateDomains -> FindDomain) compares ConfiguredEndpoint.EndpointUrl.IdnHost
— the CONFIGURED host, because OpcUaEndpointSelection/EndpointSelector
rewrote the advertised one — against the cert's DNS/IP SANs; secured
sessions only, and the auto-accept handler accepts a mismatch.

Policy: certificateValidation.validateServerHostName, tri-state. Unset =
OpcUaCertificateValidationOptions.DefaultValidateServerHostName = false, the
pre-#816 behaviour, so nothing changes on upgrade; true = validate the
configured host; false = explicit NAT/port-publish opt-out. Threaded into the
driver, the AdminUI browser (same JSON) and the AdminUI form (a select that
keeps "unset" unset), Client.Shared ConnectionSettings.ValidateServerHostName
and the CLI --validate-host-name. A driver refusal names the opt-out.

#818: the NAT fixture's /app/pki is on the opc-plc-nat-pki volume (verified:
same thumbprint across --force-recreate), its PUBLIC cert is committed as
Docker/nat/opc-plc-nat-server.der, and NatPublishedEndpointBrowseTests run
every secure browse on a throwaway PKI (internal browser seam): refusal with
nothing trusted, positive with the cert trusted (unset/false host check), and
a BadCertificateHostNameInvalid refusal with the check on. The trusted cert is
OPCUA_NAT_SERVER_CERT or the committed one (skip on thumbprint mismatch);
OPCUA_NAT_SERVER_CERT_TRUSTED is retired. CI exports its throwaway NAT
container's public cert. ServerHostNameValidationTests pin the driver against
the plain opc-plc. MIN_EXECUTED not raised (needs a hosted run): +6 in
v2-integration (440 -> 446), documented in scripts/ci/README.md.
2026-11-11 01:33:00 -05:00
Joseph Doherty 1a4d46a702 docs(historian)+infra: shared QuestDB fixture, correct SQL test credential, DurationGood/Bad + #821 docs (#822, #820, #821)
#822: HISTORIAN_SQL_TEST_CONNECTION example now targets the shared host
10.100.0.35,14330 with its committed dev sa password (OtOpcUaDev_2026!); the
docker-dev rig's own localhost,14330 password (OtOpcUa!Dev123) is called out as
a different instance. New labelled (project: lmxopcua) QuestDB fixture stack
tests/.../Historian.QuestDb.IntegrationTests/Docker/docker-compose.yml
(questdb/questdb:10.0.1, PG wire :8812, console :19000), deployed to
/opt/otopcua-questdb on the Docker host; documented in infra/README.md §2a,
docs/Historian.md, CLAUDE.md endpoints and the historian rules.
Also corrects infra/README's stale "only MQTT/NAT carry the label" note.

Docs for #820 (nineteen served aggregates, DurationGood/DurationBad in ms,
Gateway still refuses) and the #821 facts (Part 13 vs frozen live vs stock
SDK 2.0.1, both directions; decision open with #809).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-11 01:29:01 -05:00
Joseph Doherty f8f6781a24 feat(mtconnect): warn at Test Connect and deploy when SampleCount >= the Agent bufferSize (#825)
Parse the /probe Header bufferSize; MTConnectDriverProbe reads sampleCount
(default 1000) and answers Ok with a WARNING message (amber chip in the
Test Connect button); InitializeAsync logs the same Warning and exposes
agentBufferSize on the diagnostics bag. Live-checked against the shared
cppagent fixture (bufferSize 16384).
2026-11-11 01:23:33 -05:00
Joseph Doherty 40969e5c4b fix(opcua): failed sign-ins no longer lock a client out of anonymous sessions (#833)
OPC Foundation SDK 2.0.1's SessionManager lockout refused EVERY
activation (Anonymous included, before the token is read) from a client
with 5 failed identity validations in 60 s, for 5 minutes, keyed on cert
thumbprint else peer IP. Disable it (MaxFailedAuthenticationAttempts=0
on built and loaded configs) and enforce FailedCredentialLockout in the
identity-registry adapter instead: UserName activations only, keyed on
thumbprint+peer together, same 5/60s/300s defaults, configurable via
OpcUa:CredentialLockout.
2026-11-11 01:23:33 -05:00
Joseph Doherty 7af9c40e41 Merge feat/803-heidenhain-lsv2 2026-11-11 00:03:43 -05:00
Joseph Doherty 54f58c583c fix(heidenhain): refuse literal credentials and writable/mistyped tags at the save path (#803 review)
- Review 1: RawTreeService Create/UpdateDriver and Create/UpdateDevice refuse a Heidenhain
  config whose password fields are not blank / secret:heidenhain/... / env:OTOPCUA_HEIDENHAIN_...
  (DriverConfigSaveGate over HeidenhainOptionsParser.DescribeCredentialProblems: top level,
  Access, Devices[], case-insensitive keys; the value is never echoed), so a literal password
  never reaches the draft DB. The driver and device modals show the reason and disable Save.
- Review 8: CreateTag/UpdateTag/ImportTags (tag modal, manual entry, CSV import, browse commit)
  refuse a ReadWrite Heidenhain tag or a Tag.DataType that disagrees with what the driver
  publishes (HeidenhainTagRules: catalog type, Float64 => Double, or the binding's dataType).
  The tag modal and manual-entry grid fix the access level at Read and derive the data type from
  the address (RawTagRowDefaults). New deploy gate HeidenhainTagDataTypeMismatch.
- docs/drivers/Heidenhain.md: credential namespace convention + why, the save-path and deploy
  enforcement, optional-role credential behaviour, BadConfigurationError, backoff/ordering notes.

Tests: HeidenhainSavePathTests (service save path; red with the gates disabled) and new
DraftValidatorHeidenhainTests cases.
2026-11-10 23:54:32 -05:00
Joseph Doherty b89f458065 Merge fix/808-uns-folder-scope 2026-11-10 23:44:32 -05:00
Joseph Doherty e39a125069 fix(acl): close the #808 review findings — reference variables, owner folder chain, NUL keys, unknown ids (#808)
Review of af5c9744:
1. UNS reference variables (s=Area/Line/Equip/Eff) of a moved or renamed
   equipment stayed readable/writable under the OLD names' grants: they now
   resolve as ReferenceVariable and are authorized at their split path only
   while it equals their live equipment folder's placement (live chain ==
   generation chain) + one name; otherwise denied (Read and Write).
2. TryPlaceScriptedCondition now also requires the owner folder's live chain
   to match the generation (AuthorizeScriptedCondition gains ownerFolderChain),
   so a moved equipment's alarm/children/commands follow its folder.
3. Identifiers containing NUL are denied before the decision cache, keeping
   client ids disjoint from the NUL-prefixed resolved-scope keys (a client
   could otherwise poison a real folder's cached verdict for its key shard).
4. An id no live node holds is Unknown: placed at [\0unknown] so only
   Cluster / Namespace-uns grants decide it (a removed opaque id can no longer
   split onto a same-named narrow grant; narrow grantees get no NodeDeleted for
   removed UNS folders/vtags/conditions — documented); 4-segment
   reference-shaped ids keep their split.
5. A wire test drives placements from DeploymentArtifact.ParseDataPlaneAuthority.

Baseline updates: #797 owner tests (unit + wire) and Sdk2RepeatedAlarmAuthority
now install folder scopes; Sdk2HistoryAuthority's UNS alias used the RAW path
as its UNS id (never minted in production) and now carries EqA/HistoryAlias.
Each new guard was falsified (reverting it fails its test).
2026-11-10 23:44:11 -05:00
Joseph Doherty 8a0f0df05f Merge feat/804-mtconnect-parity 2026-11-10 23:24:27 -05:00
Joseph Doherty 418d0bbb87 Merge fix/810-uncertain-conformance 2026-11-10 23:24:27 -05:00
Joseph Doherty 8fb03d6a72 docs(heidenhain): driver page, README/probe index rows, drivers rule note (#803)
docs/drivers/Heidenhain.md: read-only boundary + allowlist table, transport, config,
credential references, address grammar (fixed catalog, PLC element/byte-count rules,
DATA types incl. FF Boolean), quality table, lifecycle/health/comms loss, probe, tests,
hardware-gated items and deliberate exclusions.

Also: InitializeAsync clears surviving subscription handles (the actor re-subscribes
after every successful init, so a kept handle would double-publish).
2026-11-10 23:23:25 -05:00
Joseph Doherty af5c9744b7 fix(acl): place opaque-id UNS folders, virtual tags and scripted conditions for every operation (#808)
UNS Area/Line/Equipment folders are minted under their opaque entity ids
(s=UA-/UL-/EQ-...), virtual tags under s=EQ-.../Name and scripted conditions
(+ #629 children) under s=<ScriptedAlarmId>[/...], while every UNS grant is
planted at a NAME path. DataPlaneAuthorizer split those ids, so with
Security:Acl:Enabled a narrow (Area/Line/Equipment) grant could not Browse,
Read, subscribe or HistoryRead them, and Server-object delivery never reached
it — so a narrow key never held the EventId Ack/Confirm/AddComment need.

- The generation carries UnsFolderScopes (folderId -> path + id chain), built
  by DeploymentArtifact from the same AclScopePathResolver as the grants; ids
  two entity kinds share are dropped; plumbed through IDataPlaneAuthoritySink.
- OtOpcUaNodeManager.ResolveUnsAclSubject reports live facts (folder id
  ancestry / scripted owner binding); attached by the Authorizer setter.
- Evaluate places the subject against the CURRENT generation before the
  decision cache, which now caches the walk keyed on the resolved scope.
  Folders require live chain == generation chain (a moved equipment is denied
  under old and new line until the rebuild). Owned nodes (vtags, conditions,
  children) get owner path + a reserved segment, so Tag grants and their
  Browse transit never expose them. Conditions share TryPlaceScriptedCondition
  with the #797 command gate.
- Server-object visibility of a scripted condition is decided on the
  condition alone, so a moved alarm cannot reach its old owner there.

Tests: UnsFolderScopeWireTests (opaque ids, end-to-end browse -> EventId ->
Acknowledge on an Equipment grant; sibling denied; Tag-only grant; moved alarm
and moved equipment stale windows). Rig tests now install folder scopes
explicitly (ServerEventRig.FolderScopes); the #797 recreate test deliberately
asserts the moved alarm is invisible to the Cluster observer in the window.
2026-11-10 23:12:46 -05:00
Joseph Doherty f17ff1e643 docs(mtconnect): Java reference parity matrix + live validation record (#804) 2026-11-10 23:08:42 -05:00
Joseph Doherty 87b0c18fb3 test(historian): Uncertain/partial-data conformance datasets for PercentGood/PercentBad (#810)
QualityMixConformance (SqlServer + QuestDB): tag Q mixes Good/Uncertain/Bad
regions across eight 10 s intervals (two all-Uncertain - Mantis 0006166 0%/0% -
one through an Uncertain bound, plus Uncertain bounds from a sloped non-Good end
and from a hold into an outage); tag QP has data starting and ending inside
intervals. PercentGood/PercentBad are asserted against hand-derived Part 13
DurationGood/DurationBad, the oracle is checked against the same hand values,
and Count/Average/TimeAverage(2)/Total(2)/Start/End/Delta/Interpolative are
compared with the #796 oracle forward, reverse and truncated.

Teeth: with SdkAggregateOracle.Run answering PercentGood from the baseline
(Uncertain as Good), 8 tests fail per backend; all pass with the fixed oracle.

Records a new SDK divergence: with an Uncertain first sample the baseline
TimeAverage/Total integrate the pre-data span as a ramp from 0; ours covers
only the data (Part 13) - pinned by hand.
2026-11-10 22:58:09 -05:00
Joseph Doherty 88c05cc01a docs(opcuaclient): document the shared endpoint-URL rewrite and how to run the NAT tests (#814)
docs/drivers/OpcUaClient.md, docs/plans/2026-05-28-driver-browsers-design.md and
docs/Client.CLI.md: one OpcUaEndpointUrlRewriter for driver, AdminUI browser and
Client CLI/UI; configured host+port win, advertised path kept for opc.tcp; policy/mode
selection and certificate validation unchanged; NAT test recipe against the #813 fixture.
2026-11-10 22:53:01 -05:00
Joseph Doherty 4620e542e8 fix(acl): require the live scripted-alarm owner to match the generation's deployed owner (#797)
Review of 3d1b86b4: OpcUaPublishActor installs a new authority generation
before applying its address space, and an apply that throws keeps the old one.
A scripted alarm moved from equipment A to B therefore stayed commandable with
A's grant under B's generation (briefly before the rebuild, or indefinitely
after a failed apply).

The generation now carries ScriptedAlarmOwners (ScriptedAlarmId -> EquipmentId,
decoded by ParseDataPlaneAuthority from the artifact's ScriptedAlarms rows, the
same rows the composition materialises from). AuthorizeScriptedCondition denies
unless the node manager's live binding names the same owner as the generation:
a moved alarm is denied under the old owner's, the new owner's and a Cluster
grant until the address space re-binds it; an alarm the generation no longer
deploys is denied. Install/apply order unchanged.

Tests: window (a) generation moved / binding stale => both owners and Cluster
denied, then rebuild under the new owner => new owner allowed, old denied;
window (b) apply failed after install => denied across clock and further
generations, then a successful rebuild restores the new owner only; undeployed
alarm denied; artifact decode of the owner map.
2026-11-10 21:42:03 -05:00
Joseph Doherty 3d1b86b451 fix(acl): authorize scripted alarm commands at their owning equipment's scope (#797)
Scripted condition NodeIds are opaque ScriptedAlarmIds, so splitting them as a
topology path could never match an Equipment grant: a narrow equipment grant was
denied while a Cluster grant passed.

- OtOpcUaNodeManager binds each LIVE scripted condition instance to the
  EquipmentId it was materialised under (only when it hangs under that folder);
  dropped on remove / drop-recreate / equipment-subtree removal / rebuild; a
  same-id re-materialise under another equipment re-parents and re-binds.
- DataPlaneAuthoritySnapshot carries EquipmentScopePaths (EquipmentId ->
  [uns, Area, Line, Equipment]) decoded from the artifact by the same
  AclScopePathResolver call that plants Equipment grants.
- DataPlaneAuthorizer.AuthorizeScriptedCondition re-reads key, grant and
  generation per call (uncached) and evaluates at the owner path; unbound,
  replaced or unplaceable owners deny under every grant. No id-split or
  realm-root fallback.
- Native conditions (RawPath) and the ACL-off role gate are unchanged.
  NodeIds and AlarmCommand.AlarmId are unchanged.

Sdk2OpaqueScriptedScopeBaselineTests deliberately flipped: it pinned the denial
and now pins owner-grant allow / sibling deny / cluster allow. Follow-up #808
(folder ids + Server-object event visibility for narrow UNS grants).
2026-11-10 21:23:57 -05:00
Joseph Doherty da4e27003e docs: record completed Linux OPC UA lifecycle diagnostic
v2-ci / unit-tests (pull_request) Waiting to run
2026-11-10 20:28:11 -05:00
Joseph Doherty 1e4b017f6e docs: retain verified Linux rehearsal and diagnostic gate
v2-ci / unit-tests (pull_request) Failing after 1h6m11s
2026-11-10 18:25:37 -05:00
Joseph Doherty b18f97236a docs: record reviewed private temp mount controls
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 18:18:59 -05:00
Joseph Doherty 351edb0a86 docs: retain read-only Linux rehearsal startup failure 2026-11-10 18:13:00 -05:00
Joseph Doherty 626cb4f7f1 docs: record final late-slope controls before artifact preparation
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 18:03:13 -05:00
Joseph Doherty 7023285a3e docs: retain late-slope guard control receipts 2026-11-10 17:54:43 -05:00
Joseph Doherty fbf73436be docs: record reviewed native import reproduction
v2-ci / unit-tests (pull_request) Waiting to run
2026-11-10 17:42:40 -05:00
Joseph Doherty 041189aa6d docs: close SDK2 ownership evidence review
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 17:35:54 -05:00
Joseph Doherty 327bbdaab9 docs: record reviewed SDK2 ownership regressions and diagnostic design 2026-11-10 17:33:06 -05:00
Joseph Doherty ce21f5db09 Merge master SDK1 ownership repair with SDK2 disposal adaptation 2026-11-10 17:17:00 -05:00
Joseph Doherty efb9197a0f docs: record reviewed SDK1 terminal disposal repair
v2-ci / unit-tests (push) Has been cancelled
2026-11-10 17:05:06 -05:00
Joseph Doherty 2cf7c1d248 docs: locate close control build receipt
v2-ci / unit-tests (pull_request) Waiting to run
2026-11-10 16:40:28 -05:00
Joseph Doherty 0c0209f9a6 docs: record reviewed close ownership and native attribution 2026-11-10 16:39:34 -05:00
Joseph Doherty ed60970fc2 docs: record final review hold and baseline ownership limits
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 16:09:09 -05:00
Joseph Doherty 331cde68a7 test: record final integrated CI baseline-only failures
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 16:05:13 -05:00
Joseph Doherty 4a04806d22 test: preserve saturation outcome and classify final CI corrections
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 15:46:58 -05:00
Joseph Doherty ab35ddd3c2 test: retain completed two-hour client lifecycle evidence
v2-ci / unit-tests (pull_request) Has been cancelled
2026-11-10 14:54:38 -05:00
Joseph Doherty c6c05a1001 test: record publish smoke with HistorianGateway 1.0 packages 2026-11-10 14:52:08 -05:00
Joseph Doherty 67bc61236a Merge remote-tracking branch 'origin/master' into codex/opcua-sdk-2 2026-11-10 14:40:51 -05:00