Commit Graph

33 Commits

Author SHA1 Message Date
Joseph Doherty 404c06b2fa Merge feat/tst-24-client-wire-tests: .NET/Python real-server wire tests, Python channel-loop fix, TST-05 revisit (partial)
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m45s
ci / portable (push) Successful in 11m39s
ci / windows-x86 (push) Failing after 21m2s
# Conflicts:
#	archreview/remediation/00-tracking.md
2026-08-10 08:23:39 -04:00
Joseph Doherty a8f86b5336 test(tst-24): drive the .NET and Python clients against real in-process gRPC servers
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m19s
ci / portable (push) Successful in 23m59s
ci / windows-x86 (push) Failing after 58m42s
TST-24 asked for per-client wire tests against a fake gateway. An audit first
corrected the finding's premise: Go, Rust, and Java already had them — bufconn,
a loopback tonic server, and InProcessServerBuilder respectively — each already
asserting the round trip, the server-observed bearer header, and the ReplayGap
sentinel. The two genuine gaps were .NET (every test substituted the transport
interface; the test project had no server package at all) and Python (stub
monkeypatching everywhere but one opt-in TLS test).

Both now serve mxaccess_gateway.v1.MxAccessGateway over a real transport —
Kestrel h2c and grpc.aio, each on an ephemeral loopback port — and drive the
ordinary public client API against it. Only the gateway's behaviour is canned;
the framing, serialization, metadata, and status codes are genuine. Four shapes
each: full round trip with every reply field asserted, the authorization header
as received by the server (including on the streaming RPC), the ReplayGap
sentinel surfaced as the client's typed signal, and a real PERMISSION_DENIED
mapping to the typed authorization error.

The .NET client was only ever compiled in CI, never tested, so the portable job
gains a dotnet test step.

Fixes a bug the new tests caught on their first run: Python's connect() built the
grpc.aio channel inside asyncio.to_thread, and a grpc.aio channel binds to the
event loop current on the constructing thread, so every non-stub connection
raised 'There is no current event loop in thread'. No mock-based test could see
it, and the test guarding the off-loop behaviour patched create_channel and so
asserted the bug. Split resolve_channel_security (blocking TOFU probe, off-loop)
from create_channel (on-loop); the guard tests now assert both halves.
2026-08-10 08:22:25 -04:00
Joseph Doherty 30c92e8e59 fix(ci): give the nightly-windev issue body a reachable run link (TST-25 Check 6)
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Failing after 1m13s
ci / java (push) Successful in 2m7s
ci / portable (push) Successful in 8m30s
Exercising TST-25 acceptance Check 6 end-to-end showed the on-failure issue
step works — it has filed an issue on every red nightly since 2026-07-17
(#126-#139) — but that every one of those issues links `http://gitea:3000`,
the runner-internal origin Gitea hands the runner. That URL is correct for
the issue-creation API call (the job container resolves `gitea` only on the
docker network and has no LAN egress to the public origin) and unreachable
for anyone clicking out of the issue.

Keep `github.server_url` for the API call; add a `PUBLIC_SERVER_URL` job env
used only for the browser-facing link in the body. Verified with a
forced-failure probe run (677) whose issue (#140) carries a public link that
returns 200.

Also records Check 6 as done in scripts/ci/README.md and corrects the
2026-07-13 tracking entry that wrote the check off as abandoned.
2026-08-10 08:10:08 -04:00
Joseph Doherty d4302c6ac4 docs(tst-05): revisit under the restored windev tier — scheduled half closed, control-command coverage still open
The nightly-windev job (cycle-2 TST-25) discharges TST-05's scheduling design:
cron 0 6 * * * runs run-windev-ci.sh live, which sets
MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1, runs WorkerLiveMxAccessSmokeTests on windev,
and opens a Gitea issue on failure. That converts 'run by memory' into 'runs
nightly, reports failures'.

The finding's other half — audit the live suite for coverage of each of the eleven
late-added command kinds — is not discharged. The audit's answer is negative for
five: the suite covers all six COM commands and none of the five control commands
(Ping/GetSessionState/GetWorkerInfo/DrainEvents/ShutdownWorker), which are exactly
the kinds the finding calls masked by FakeWorkerHarness canned replies. Recorded as
Partially done with the residual test work specified, rather than claiming closure.
2026-08-10 08:08:01 -04:00
Joseph Doherty 6d26ed094c docs(tracking): close old-tracker CLI-24, CLI-34 as Done (2026-07-12 review old-tracker actions; both incidentally fixed)
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m22s
ci / windows-x86 (push) Successful in 1m20s
ci / portable (push) Failing after 4m26s
2026-08-07 08:11:57 -04:00
Joseph Doherty 8c312c717c fix(SEC-36): scrub committed dev LDAP service-account password; add user-secrets channel + rotation runbook
Repo-side half of SEC-36. The appsettings.json plaintext was already discharged
before this branch (HEAD ships the fail-closed ${secret:ldap/mxgateway/bind}
store reference), so the residual leak was the literal value in glauth.md,
docs/GatewayTesting.md, and the historical archreview SEC-06 evidence -- all
scrubbed to <service-account-password> placeholders pointing at the source of
truth scadaproj/infra/glauth/.

- csproj: add <UserSecretsId>mxaccessgw-server</UserSecretsId> (dev channel)
- GatewayOptionsValidator: blank-password message now names both channels
  (dev user-secrets, deployed MxGateway__Ldap__ServiceAccountPassword)
- test: assert the message names both channels
- docs: GatewayConfiguration.md (three channels + rotation note), glauth.md
  (placeholders + rotation-required + runbook pointer), GatewayTesting.md
- new operator runbook docs/runbooks/SEC-36-ldap-credential-rotation.md
  (live rotation + NSSM staging remain operator-pending)
- tracking: SEC-36 -> Done (repo-side) in both registers + change-log

Deviation: kept the ${secret:} reference in appsettings.json rather than
deleting it (spec step 2 assumed the stale plaintext baseline); deleting it
would regress the shipped/documented/tested secret-store channel.

git grep -i for the old value is empty across all tracked files.
2026-08-07 07:40:41 -04:00
Joseph Doherty d6b2f24c3f fix(CLI-37,CLI-38): make status/HRESULT reply validation conformant across all five clients
One cross-client conformance pass; also closes first-cycle CLI-08.

CLI-37: an MxStatusProxy entry is a failure iff `category !=
MX_STATUS_CATEGORY_OK`. The proto contract has always said so — `success` is
the raw 16-bit COM member carried verbatim for diagnostics, not a boolean — but
four clients branched on `success` alone and .NET required both, so the same
gateway reply produced opposite verdicts per language. An absent entry stays
success; a present entry with an UNSPECIFIED category is a failure, because the
worker always maps a category and an unmapped one is not proven OK.

CLI-38: a reply fails on HRESULT iff `hresult` is present and negative, so
positive COM success codes such as S_FALSE (1) pass. .NET/Go/Java used `!= 0`,
which errored on a parity-preserving S_FALSE that Python and Rust accepted.
This makes the existing ClientLibrariesDesign.md claim true rather than
rewriting the doc to describe the divergence.

Four shared fixtures pin both rules cross-client, and each language suite also
carries a table test for the two edges a fixture cannot express (absent entry,
UNSPECIFIED category). A Java test fake that built a status with a bare
`setSuccess(1)` and no category is fixed — under the category rule that reply
was never a success.
2026-08-07 06:00:58 -04:00
Joseph Doherty ead921cace docs: truth sweep — Galaxy adoption, Auth 0.1.5, redaction seam, resolved A2 caveats
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m16s
ci / java (push) Successful in 3m21s
ci / portable (push) Successful in 8m21s
Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
2026-08-07 01:57:01 -04:00
Joseph Doherty 4f5371fdec Merge branch 'fix/archreview-p2' into main (P2 tier: completeness & polish)
# Conflicts:
#	archreview/remediation/00-tracking.md
#	clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs
#	clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs
#	src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs
2026-07-12 22:12:41 -04:00
dohertj2 d6c2ca33f1 CI: stand up Gitea Actions (TST-03) + fix the defects it caught (#123)
ci / windows (push) Failing after 13m38s
ci / java (push) Successful in 2m4s
ci / portable (push) Successful in 9m18s
ci / live-mxaccess (push) Has been cancelled
Make the authored CI pipeline execute and go green on the co-located runner (portable + java), fix the five real latent defects the first real run surfaced (codegen-check null, stale rust proto, orphan-terminator Linux path, stale java worker codegen, py3.12 event loop), provision pwsh + Gradle for the self-hosted act image, and disable the Windows jobs (act host-mode broken). TST-03 -> Done.

https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-10 05:11:01 -04:00
Joseph Doherty dca09e7e06 docs(archreview): flesh out TST-15 dashboard per-session ACL design
Add docs/plans/2026-07-10-dashboard-session-acl-tst15.md: the fleshed-out
Phase-4 design for the deferred TST-15 finding. Resolves the crux the deferral
left open — the dashboard authenticates LDAP users (Admin/Viewer) while sessions
are API-key-owned (OwnerKeyId), two disjoint identity domains — via a session tag
sourced from the owning API key (carried in the existing ApiKeyConstraints JSON
blob, no SQLite migration). Admin-sees-all; a Viewer may SubscribeSession iff
session.Tags intersects the Viewer's granted tags (new Dashboard:GroupToTag map
-> hub-token tag claims); untagged sessions Admin-only by default. Includes the
enforcement path, task breakdown (epic Tasks 16-19), test plan incl. live-LDAP,
and rejected alternatives.

Design only — TST-15 stays Not started (no implementation). The tracker and the
60-testing-docs-gaps TST-15 section point at the design doc; the change-log also
records the TST-03 finding (zero registered runners; needs a runner co-located on
the gitea Docker network).

Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-10 03:25:26 -04:00
Joseph Doherty 2201fd8828 docs(archreview): wrap P2 epic — defer TST-15 + TST-24 (user decision)
ci / portable (push) Failing after 41s
ci / java (push) Failing after 51s
ci / portable (pull_request) Failing after 47s
ci / java (pull_request) Failing after 50s
ci / windows (push) Failing after 8s
ci / windows (pull_request) Failing after 7s
ci / live-mxaccess (push) Has been cancelled
ci / live-mxaccess (pull_request) Has been cancelled
Epic bucket: 5/7 done (CLI-15, CLI-04, CLI-30, TST-01, TST-04). TST-15
(Phase-4 ACL feature; SEC-25 already prevents value leakage) and TST-24
(CI-gated on TST-03) deferred to a follow-up. Net P2: 35/38 Done.

Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 17:04:22 -04:00
Joseph Doherty f1d9ad5ca2 docs(archreview): CLI-15/CLI-04/CLI-30/TST-01 -> Done (Java completes 5/5)
Session-resilience epic Phase 3 fully closed (reconnect protocol + client
consumers across all five clients + server e2e test).

Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 16:57:47 -04:00
Joseph Doherty 908951be9d docs(archreview): E2 CLI-04 In progress (4/5) + CLI-30 Done; Java now local
Java client toolchain (homebrew openjdk@17) works on the Mac, so the remaining
Java halves of CLI-15/CLI-04 are done locally this session, not batched to windev.

Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 16:44:24 -04:00
Joseph Doherty 4090a478c8 docs(archreview): TST-04 -> Done (epic governance decision landed)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 16:33:51 -04:00
Joseph Doherty ef498c80c2 docs(archreview): E1 reconnect-replay — CLI-15 + TST-01 In progress
CLI-15 (4/5 clients, commit 0c6e5b3) and TST-01 server e2e test (commit fed0685)
landed; both gated on the Java client (windev batch) before flipping to Done.

Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 16:27:47 -04:00
Joseph Doherty c823a7b60b docs(archreview): mark WRK-06/11/12 + IPC-15 Done (P2 worker hot-path, windev-verified)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 16:00:29 -04:00
Joseph Doherty 73ce824f6c docs(archreview): mark TST-11/WRK-15/TST-23 Done (P2 quick items)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 15:33:52 -04:00
Joseph Doherty 579282f015 docs(archreview): mark SEC-25/SEC-30 Done (P2 dashboard value redaction + value-log doc)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 15:29:12 -04:00
Joseph Doherty 96702321b1 docs(archreview): mark gateway hot-path P2 findings Done (GWC-06/07/08/14/15, IPC-05/13/14)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 15:19:38 -04:00
Joseph Doherty 34aede6767 docs(archreview): mark SEC-03 Done (P2 dashboard __Host- cookie prefix)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 14:53:47 -04:00
Joseph Doherty 5a3951bcfd docs(archreview): mark P2 Wave A findings Done (13: doc-drift + version alignment)
Claude-Session: https://claude.ai/code/session_01DMXXvNuPekkkrTEyPNxEkW
2026-07-09 14:47:11 -04:00
Joseph Doherty a038363e74 feat(security): apikey --expires CLI flag + dashboard expiry/staleness badge (SEC-10 polish)
ci / portable (push) Failing after 44s
ci / java (push) Failing after 50s
ci / java (pull_request) Failing after 37s
ci / portable (pull_request) Failing after 55s
ci / windows (push) Failing after 8s
ci / windows (pull_request) Failing after 8s
ci / live-mxaccess (push) Has been cancelled
ci / live-mxaccess (pull_request) Has been cancelled
Gateway-side follow-up to the shared auth-lib expiry core (delivered via G-2):

- apikey create-key gains optional --expires — absolute ISO-8601 UTC or relative
  <N>d/<N>h from now; omitted means non-expiring (opt-in, unchanged default).
  Threaded ApiKeyAdminCommand -> parser -> runner into the library's
  CreateKeyAsync(..., expiresUtc, ...). list-keys shows an expiry column and an
  active/expired/revoked status.
- Dashboard API Keys page surfaces expiry: an Expires column (Never when unset)
  and a status badge reading Expired (red) / Expiring (<=7d, amber) / Revoked /
  Active. DashboardApiKeySummary.ExpiresUtc projected in DashboardSnapshotService;
  StatusBadge maps the new states.

Tests: parser (absolute/relative/invalid/none) + end-to-end past-expiry rejection
through the live verifier; dashboard summary suite green. Docs: Authentication.md
(verification-flow expiry step, CLI table + examples, dashboard badge).

Closes the tracked SEC-10 polish (SEC-10 core was already Done).
2026-07-09 09:48:09 -04:00
Joseph Doherty de57d834b1 docs(archreview-p1): mark GWC-04 Done — Wave 3 complete
ci / portable (push) Failing after 3m27s
ci / java (push) Failing after 3m30s
ci / java (pull_request) Failing after 46s
ci / portable (pull_request) Failing after 51s
ci / windows (push) Failing after 12m55s
ci / windows (pull_request) Failing after 38s
ci / live-mxaccess (push) Has been cancelled
ci / live-mxaccess (pull_request) Has been cancelled
2026-07-09 09:28:39 -04:00
Joseph Doherty bfbfcdd112 docs(archreview-p1): mark IPC-02/03/04 + WRK-04/07 Done (Wave 3, windev-verified) 2026-07-09 09:21:48 -04:00
Joseph Doherty e1a505d662 docs(archreview-p1): record Windows temp-file-lock flakiness fix + windev verification
3 consecutive windev full-suite runs: 793 passed / 2 failed, 0 orphans (was flaky
2-4 fails). The 2 stable failures are pre-existing windev-environmental (cert SAN
machine-name assertion; event-stream concurrency timing) and pass on macOS.
2026-07-09 08:20:43 -04:00
Joseph Doherty 8c06635ee5 docs(archreview-p1): TST-08 orphaned-testhost leak does not reproduce
Empirically verified the full ZB.MOM.WW.MxGateway.Tests suite exits cleanly:
- macOS: 0 surviving testhost after a full run.
- windev (origin/main worktree): 0 new testhost and 0 new worker processes
  after a full run, isolated by process StartTime.

Static audit confirms the prime-suspect fixtures already dispose deterministically
(WorkerClient cancels _stopCts + awaits its read/write/heartbeat tasks with a 5s
timeout; GatewaySession disposes the client; harness/E2E fixtures use await using).

Drop the stale 'leaves orphaned testhost processes' rationale from CLAUDE.md's
Source Update Workflow; keep filtered-run guidance as a speed guideline only.

Separately discovered (tracked in 00-tracking.md change log, NOT part of TST-08):
Windows-only temp-file-lock flakiness in ~4 full-host test classes (Sqlite
connection-pool retaining a temp .db handle; SelfSignedCertificateProvider's
fixed-name gw.pfx.tmp racing teardown) that macOS never surfaces.
2026-07-09 08:02:33 -04:00
Joseph Doherty 74e815c4d7 fix(archreview-p1): SEC-02/12/20 dashboard + observability hardening
- SEC-02: DashboardAuthorizationHandler restricts the loopback and
  Authentication:Mode=Disabled bypasses to read-only. They now satisfy only a
  Viewer-bearing requirement (AnyDashboardRole), never AdminOnly, so anonymous
  localhost can view the dashboard but cannot reach API-key CRUD or session
  Close/Kill at the policy layer (previously guarded only by service re-checks).
- SEC-12: DashboardSessionAdminService emits canonical AuditEvents through
  IAuditWriter (actions dashboard-close-session / dashboard-kill-worker, category
  SessionAdmin) on Success/Failure/Denied, mirroring the API-key audit path so
  destructive session actions leave durable, queryable rows.
- SEC-20: drop the unbounded session_id tag from the exported
  mxgateway.heartbeats.failed counter (per-session detail stays in the snapshot/log).

Docs updated same-change: CLAUDE.md (read-only loopback + 5-min bearer),
GatewayDashboardDesign.md (bypass scoping + session-admin audit), Metrics.md.
Server build clean; 30/30 targeted + 295/295 Dashboard/Security/App/Metrics sweep.
2026-07-09 07:47:51 -04:00
Joseph Doherty c2df4a0aae docs(archreview): P1 Wave 2b status (SEC-05/07/08/11 Done) 2026-07-09 07:32:14 -04:00
Joseph Doherty 970613eebd docs(archreview): P1 Wave 2a status (SEC-01/04/06 Done; SEC-10 Done via G-2 Auth 0.1.4) 2026-07-09 06:41:40 -04:00
Joseph Doherty 8fb1a814d4 docs(archreview): P1 Wave 1 status (IPC-01/09/19/20, CLI-02 Done; TST-03 In review) 2026-07-09 06:18:03 -04:00
Joseph Doherty f726458086 docs(archreview): mark P0 tier Done in remediation tracker
All 8 P0 findings (GWC-01/02/03, WRK-01, CLI-01, CLI-03, TST-02, TST-12)
recorded as Done with verification notes and change-log entries.
2026-07-09 05:51:57 -04:00
Joseph Doherty 59856b8c63 docs(archreview): add architecture review + per-domain remediation designs and tracker
Adds the 2026-07-08 architecture review (00-overall + six domain reports)
and a remediation/ tree: one design+implementation doc per domain covering
every finding, plus 00-tracking.md as the master progress tracker.

- 153 findings with stable IDs (GWC/WRK/IPC/SEC/CLI/TST), each with
  design rationale, implementation steps, tests, docs, and verification.
- Tracker rolls findings up by severity and P0/P1/P2 roadmap tier, records
  cross-cutting clusters and per-finding status (all Not started).
- Planning docs only; no source changes.
2026-07-09 00:39:00 -04:00