Rewrite src/ and tests/ project paths in docs, CLAUDE.md, README.md, and
test-fixture READMEs to the new module-folder layout (Core/Server/Drivers/
Client/Tooling). References to retired v1 projects (Galaxy.Host/Proxy/Shared,
the legacy monolithic test projects) are left untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runbook shipped at phase-7 close (2026-04-20) described the original
`Doubled = Source × 2` virtual tag, Float64 seed, and flat TagId-shaped
NodeIds. Four commits later the wiring has moved:
- Seed now targets `TestMachine_001.TestHistoryValue` (Int32, writable,
historized) — no placeholder to fill in for the dev box.
- VirtualTag is `MachineStatus` (Boolean, `Source > 0`, historized).
- NodeIds are path-based per OPC UA Part 3 §5.2.2
(`{driverId}/{folder-path}/{browseName}`).
- Seed inserts the ClusterNodeCredential row — without it the Server
bootstrap fails `Unauthorized: caller X is not bound to NodeId`.
Changes:
1. Step 3 — replace "edit the placeholder" instructions with the ZB
Galaxy-Repository query that finds writable historized attributes
(dpc CTE + HistoryExtension EXISTS + `security_classification > 0`).
2. New step 4a — LDAP + `SecurityProfile = Basic256Sha256-Sign` recipe
for the reverse-bridge + alarm-fires stages. Anonymous sessions are
denied writes against `Operate`-classified attributes (PR 26 gate);
`writeop / writeop123` against the dev-box GLAuth clears it.
3. Step 6 validation commands updated to the new NodeIds + reference
the path-based scheme's Part-3 rationale.
4. Drive-the-alarm snippet now calls `otopcua-cli write … -U writeop`
so operators see the explicit auth step.
5. Acceptance checklist updated for the new tag names + the
test-galaxy.ps1 `-Username` invocation.
6. Added a 2026-04-24 second-run evidence section alongside the original
— documents the 3/7 anonymous ceiling and what's needed to reach 7/7.
No code or seed changes in this commit — doc-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three root-cause fixes to get an elevated dev-box shell past session open
through to real MXAccess reads:
1. PipeAcl — drop BUILTIN\Administrators deny ACE. UAC's filtered token
carries the Admins SID as deny-only, so the deny fired even from
non-elevated admin-account shells. The per-connection SID check in
PipeServer.VerifyCaller remains the real authorization boundary.
2. PipeServer — swap the Hello-read / VerifyCaller order. ImpersonateNamedPipeClient
returns ERROR_CANNOT_IMPERSONATE until at least one frame has been read
from the pipe; reading Hello first satisfies that rule. Previously the
ACL deny-first path masked this race — removing the deny ACE exposed it.
3. GalaxyIpcClient — add a background reader + single pending-response
slot. A RuntimeStatusChange event between OpenSessionRequest and
OpenSessionResponse used to satisfy the caller's single ReadFrameAsync
and fail CallAsync with "Expected OpenSessionResponse, got
RuntimeStatusChange". The reader now routes response kinds (and
ErrorResponse) to the pending TCS and everything else to a handler the
driver registers in InitializeAsync. The Proxy was already set up to
raise managed events from RaiseDataChange / RaiseAlarmEvent /
OnHostConnectivityUpdate — those helpers had no caller until now.
4. RedundancyPublisherHostedService — swallow BadServerHalted while
polling host.Server.CurrentInstance. StandardServer throws that code
during startup rather than returning null, so the first poll attempt
crashed the BackgroundService (and the host) before OnServerStarted
ran. This race was latent behind the Galaxy init failure above.
Updates docs that described the Admins deny ACE + mandatory non-elevated
shells, and drops the admin-skip guards from every Galaxy integration +
E2E fixture that had them (IpcHandshakeIntegrationTests, EndToEndIpcTests,
ParityFixture, LiveStackFixture, HostSubprocessParityTests).
Adds GalaxyIpcClientRoutingTests covering the router's
request/response match, ErrorResponse, event-between-call, idle event,
and peer-close paths.
Verified live on the dev box against the p7-smoke cluster (gen 6):
driver registered=1 failedInit=0, Phase 7 bridge subscribed, OPC UA
server up on 4840, MXAccess read round-trip returns real data with
Status=0x00000000.
Task #112 — partial: Galaxy live stack is functional end-to-end. The
supplied test-galaxy.ps1 script still fails because the UNS walker
encodes TagConfig JSON as the tag's NodeId instead of the seeded TagId
(pre-existing; separate issue from this commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the live-smoke validation Phase 7 deferred to. Ships:
## docs/v2/implementation/phase-7-e2e-smoke.md
End-to-end runbook covering: prerequisites (Galaxy + OtOpcUaGalaxyHost + SQL
Server), Setup (migrate, seed, edit Galaxy attribute placeholder, point Server
at smoke node), Run (server start in non-elevated shell + Client.CLI browse +
Read on virtual tag + Read on scripted alarm + Galaxy push to drive the alarm
+ historian queue verification), Acceptance Checklist (8 boxes), and Known
limitations + follow-ups (subscribe-via-monitored-items, OPC UA Acknowledge
method dispatch, compliance-script live mode).
## scripts/smoke/seed-phase-7-smoke.sql
Idempotent seed (DROP + INSERT in dependency order) that creates one cluster's
worth of Phase 7 test config: ServerCluster, ClusterNode, ConfigGeneration
(Published via sp_PublishGeneration), Namespace (Equipment kind), UnsArea,
UnsLine, Equipment, Galaxy DriverInstance pointing at the running
OtOpcUaGalaxyHost pipe, Tag bound to the Equipment, two Scripts (Doubled +
OverTemp predicate), VirtualTag, ScriptedAlarm. Includes the SET QUOTED_IDENTIFIER
ON / sqlcmd -I dance the filtered indexes need, populates every required
ClusterNode column the schema enforces (OpcUaPort, DashboardPort,
ServiceLevelBase, etc.), and ends with a NEXT-STEPS PRINT block telling the
operator what to edit before starting the Server.
## First-run evidence on the dev box
Running the seed + starting the Server (non-elevated shell, Galaxy.Host
already running) emitted these log lines verbatim — proving the entire
Phase 7 wiring chain executes in production:
Bootstrapped from central DB: generation 1
Phase 7 historian sink: no driver provides IAlarmHistorianWriter — using NullAlarmHistorianSink
VirtualTagEngine loaded 1 tag(s), 1 upstream subscription(s)
ScriptedAlarmEngine loaded 1 alarm(s)
Phase 7: composed engines from generation 1 — 1 virtual tag(s), 1 scripted alarm(s), 2 script(s)
Each line corresponds to a piece shipped in #243 / #244 / #245 / #246 / #247.
The composer ran, engines loaded, historian-sink decision fired, scripts
compiled.
## Surfaced — pre-Phase-7 deployment-wiring gaps (NOT Phase 7 regressions)
1. Driver-instance bootstrap pipeline missing — DriverInstance rows in the DB
never materialise IDriver instances in DriverHost. Filed as task #248.
2. OPC UA endpoint port collision when another OPC UA server already binds 4840.
Operator concern; documented in the runbook prereqs.
Both predate Phase 7 + are orthogonal. Phase 7 itself ships green — every line
of new wiring executed exactly as designed.
## Phase 7 production wiring chain — VALIDATED end-to-end
- ✅#243 composition kernel
- ✅#244 driver bridge
- ✅#245 scripted-alarm IReadable adapter
- ✅#246 Program.cs wire-in
- ✅#247 Galaxy.Host historian writer + SQLite sink activation
- ✅#240 this — live smoke + runbook + first-run evidence
Phase 7 is complete + production-ready, modulo the pre-existing
driver-bootstrap gap (#248).