The pipe name mxaccess-gateway-{pid}-session-{32hex} plus .NET's
CoreFxPipe_ prefix overflowed the 104-byte Unix-domain-socket path limit
under the default per-user macOS TMPDIR (~49 chars), so every test that
opened a real pipe threw ArgumentOutOfRangeException at pipe creation
unless TMPDIR=/tmp was exported. Rename to mxgw-{pid}-{sessionUid} (the
session guid hex without the session- prefix; worst-case 43 chars) and
shorten the three test-fixture names the same way. Uniqueness is
unchanged: gateway pid + full session guid. The worker receives the pipe
name via its launch command line, so mixed Server/Worker deploy SHAs are
unaffected. Docs updated in the same change (gateway.md,
GatewayProcessDesign, GatewayConfiguration, Sessions, CLAUDE.md); new
regression test pins the format and the length budget.
Verified: SessionManagerTests 39/39; SessionWorkerClientFactory,
GatewayEndToEndFakeWorkerSmoke, WorkerClient, and ReconnectReplay suites
33/33 under the default macOS TMPDIR — this also retires the
previously-misdiagnosed 'macOS pipe-timeout test failures': they were
this path-length throw, not a timeout-message defect.
15 KiB
Follow-Ups: windev Redeploy, LDAP Test Fixtures, Runner Hygiene — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (opus implementers; controller verifies ops evidence; final review pass).
Goal: Close the five items surfaced by the 2026-08-07 live-actions cycle: repair windev's crash-looping gateway and finish the deferred SEC-36 dashboard verification (NEXT-07), fix the DashboardLdapLiveTests fixture drift (NEXT-06), resolve the unexpected macOS instance runner, harden runner-1's plaintext registration token, and verify the cargo Bearer fix.
Architecture: Three independent live streams (windev serial: 1→2→3; repo test fix: 4; Gitea/runner hygiene: 5, 6) run concurrently; task 7 closes out docs/trackers. No contract, gateway-logic, or client changes — one test-file edit (Task 4) plus live ops plus docs.
Tech Stack: SSH + PowerShell -EncodedCommand (windev 10.100.0.48), SSH + docker compose
(10.100.0.35), Gitea admin API (gitea.dohertylan.com, token via ~/.zshenv GITEA_TOKEN),
NSSM, xUnit live-LDAP suite, GLAuth at 10.100.0.35:3893.
Preflight facts (verified before planning)
- Unpushed local mxaccessgw commits:
0566716,9760497,5b153da,41e8648(all docs-only).origin/main=a346d51— contains all current code, so windev can build fromorigin/mainwithout any push. - windev (
10.100.0.48): NSSM serviceMxAccessGw; deployed Server build of 2026-06-25 (Auth 0.1.2.0, supports auth-DB schema 2) crash-loops onC:\ProgramData\MxGateway\gateway-auth.dbmigrated to schema 3 on 2026-07-15 (AuthStoreMigrationException, ~3.8k–10k Hosting-failed events/day). The NEW LDAP secret is already staged as the 10thAppEnvironmentExtraentry (SEC-36 Task 3) — preserve it. DashboardLdapLiveTests.cs(src/ZB.MOM.WW.MxGateway.IntegrationTests/): usesadmin/admin123(3 tests) andreadonly/readonly123. Directory reality (scadaproj/infra/glauth/config.toml):adminexists, password is the standard dev test password (password, hash5e884898…42d8— same asmulti-role), and IS in GwAdmin (othergroups[5610, 5701]);readonlydoes not exist;gw-viewer(primarygroup 5611 = GwReader, NOT GwAdmin) is the natural not-an-admin fixture. Test bindsMxGateway:Ldapfromappsettings.json(Server: localhost) + env overrides — so the live run needsMxGateway__Ldap__Server=10.100.0.35as well asMxGateway__Ldap__ServiceAccountPassword(from Mac user-secrets, never printed).- Gitea instance runners (
GET /api/v1/admin/actions/runners): id 1gitea-runner(cap 4), id 4macos-local-Josephs-MacBook-Pro(unexpected, online, labels overlap ubuntu-latest), id 5gitea-runner-2(cap 2). 10.100.0.35:/opt/gitea/docker-compose.yml(+docker-compose.yml.bak-tst30): runner-1's registration token inline in plaintext env, file world-readable. runner-2 usesGITEA_RUNNER_REGISTRATION_TOKEN_FILE: /run/secrets/runner_token← 0600/opt/gitea/runner_token. runner-1 data volume/opt/gitea/runner:/data(its.runnercredential persists — the registration env is only needed for first registration).- Cargo Bearer fix already applied (
~/.zshenv, backup~/.zshenv.bak-cli39) and documented (docs/ClientPackaging.md, commit5b153da). Task 7 verifies; no further action expected.
Secret hygiene (binding, all tasks)
- Never print the LDAP service-account password,
GITEA_TOKEN, cargo token, runner registration tokens, or API keys — not in commands, logs, commits, or reports. Read the LDAP password fromdotnet user-secrets listinto an env var without echoing (e.g.export MxGateway__Ldap__ServiceAccountPassword="$(dotnet user-secrets list --project src/ZB.MOM.WW.MxGateway.Server | awk -F' = ' '/ServiceAccountPassword/ {print $2}')"). - Documented dev test users (
multi-role/password,admin/password,gw-viewer/password) are NOT secrets — glauth.md publishes them; fine in code/commits. - SSH→windev PowerShell: always
powershell -NoProfile -EncodedCommand <base64-UTF16LE>; never put secrets inside EncodedCommand blobs or argv.
Task 1: NEXT-07 — Recon windev deployment layout + schema support
Classification: standard — read-only recon, but its output gates a service redeploy Estimated implement time: ~5 min Parallelizable with: Task 4, Task 5, Task 6
Files: none edited. SSH recon on 10.100.0.48 + repo/scadaproj reads on the Mac.
Determine everything Task 2 needs, and confirm the fresh-deploy path is safe:
nssm get MxAccessGw Application,AppDirectory,AppParameters,AppEnvironmentExtra(count entries; do NOT print values of secret-bearing entries — names only).- Inventory the deployed dir: path,
ZB.MOM.WW.MxGateway.Server.exetimestamp, whetherappsettings.json/appsettings.Production.jsonin the deploy dir differ from repoorigin/main(diff; windev-specific config must survive the redeploy). - Confirm build feasibility on windev:
dotnet --list-sdks(need 10.x), locate an existing mxaccessgw checkout/worktree (CI usesscripts/ci/windev-worker-ci.ps1— find its worktree path) or pick a fresh clone location. Confirmgit fetchreachesorigin/main=a346d514dd24e775640e5667aa7cd8e561fec68a. - Confirm current code supports auth-DB schema 3: find the auth-store supported-schema
constant (ZB.MOM.WW.Auth packages — check the package version the Server at
origin/mainreferences, and/or the migration code in the shared scadaproj libs) and state the evidence. If current code does NOT support schema 3, STOP — report, do not deploy. - Gateway endpoints for verification: bound URLs/ports (from deployed config/env), dashboard
scheme (http vs https → cookie will be
MxGatewayDashboardvs__Host-…). - Check what migrated the DB to schema 3 on 2026-07-15 (event log / file timestamps) — only to confirm schema 3 is the shared-lib current version, not an anomaly.
Step: report all findings as structured text (no secrets); no changes, no commits.
Task 2: NEXT-07 — Build current Server on windev and redeploy the service
Classification: high-risk — replaces a running (crash-looping) service's binaries Estimated implement time: ~10 min Parallelizable with: none (needs Task 1)
Files: none in repo. windev filesystem + NSSM only.
Using Task 1's facts:
- On windev, fetch/checkout
origin/main(a346d51…) in the build worktree/clone. dotnet publish src/ZB.MOM.WW.MxGateway.Server -c Release(match deployed layout/RID from Task 1; framework-dependent vs self-contained must match what NSSMApplicationpoints at).- Stop the service (
nssm stop MxAccessGw), confirm process exited. - Backup: deployed dir → sibling
*.bak-next07copy; copyC:\ProgramData\MxGateway\gateway-auth.db(+-wal/-shmif present) togateway-auth.db.bak-next07. Never delete the live DB. - Deploy publish output over the deploy dir, then restore any windev-specific config files identified in Task 1 (do not clobber live overrides; NSSM env entries are untouched by file copies but verify count unchanged after start).
nssm start MxAccessGw; verify: service state RUNNING and stable ≥60 s (no restart cycle), Application event log shows clean host start and zero newAuthStoreMigrationException/Hosting failed to startafter the start timestamp, bound port answers (e.g. dashboard root or health endpoint returns HTTP).- Rollback if unhealthy: stop, restore
*.bak-next07dir, start, report.
Step: report deployed SHA, verification evidence, backup paths. No repo commits.
Task 3: SEC-36 deferred verification + NEXT-07/runbook closeout
Classification: standard Estimated implement time: ~6 min Parallelizable with: none (needs Task 2)
Files:
- Modify:
docs/runbooks/SEC-36-ldap-credential-rotation.md(Correction 3 — mark the deferred dashboard check done, dated) - Modify:
archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md(NEXT-07 → resolved 2026-08-07, evidence one-liner)
- Complete SEC-36 step 4 against the repaired windev gateway: log in to the dashboard as
multi-role/passwordend-to-end. Preferred:curlflow — GET/login(capture antiforgery token + cookie), POST credentials, expect success redirect + auth cookie (name per Task 1 scheme). If the login page resists scripting (Blazor circuit), report exactly why and fall back to asserting a freshDashboardLdapLiveTestsgreen run (Task 4) plus windev log evidence of successful LDAP bind on a manual attempt. - Update the two docs; commit locally (
docs(sec-36,next-07): …), do NOT push.
Task 4: NEXT-06 — Fix DashboardLdapLiveTests fixtures to match the shared directory
Classification: small — one test file, but must go green against live GLAuth Estimated implement time: ~6 min Parallelizable with: Task 1, Task 5, Task 6
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs - Modify:
archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md(NEXT-06 → resolved) - Possibly modify:
docs/GatewayTesting.md(live-LDAP opt-in row: document theMxGateway__Ldap__Serveroverride needed when GLAuth is not localhost)
- Read
DashboardAuthenticatorfirst: confirm a user who binds successfully but maps to no role yieldsSucceeded == false(drives the gw-viewer fixture). - Fix fixtures:
admin/admin123→admin/password(positive + wrong-password + unreachable tests);readonly/readonly123→gw-viewer/password(exercises user-binds-but-lacks-GwAdmin; keep the no-password-leak assertion, updating the asserted literal). Update XML doc comments to match. Keep MXAccess-repo style rules (TreatWarningsAsErrors). - Build:
dotnet build src/ZB.MOM.WW.MxGateway.IntegrationTests(macOS OK — net10.0). - Live run (env only, never echo the password):
MXGATEWAY_RUN_LIVE_LDAP_TESTS=1 MxGateway__Ldap__Server=10.100.0.35 MxGateway__Ldap__ServiceAccountPassword=<from user-secrets> dotnet test … --filter FullyQualifiedName~DashboardLdapLiveTests→ expect 5/5 passed (this is also positive live proof of the SEC-36 service-account bind). - Update tracker row (+ GatewayTesting.md if the Server-override note is missing); commit
locally (
test(ldap): …), do NOT push.
Task 5: Resolve the unexpected macOS instance runner (id 4)
Classification: standard — evidence-gated removal of a live runner registration Estimated implement time: ~6 min Parallelizable with: Task 1, Task 4, Task 6
Files:
- Modify:
docs/runbooks/TST-30-second-ci-runner.md(the Correction paragraph mentions "id 4 — an unrelated local macOS runner" — update to final state)
- Evidence, local:
pgrep -fl act_runner,launchctl list | grep -i act,brew services list | grep -i act, look for~/.runner/act_runner config dirs. Evidence, Gitea (token from~/.zshenv, never printed): runner detail for id 4 (labels, last online), and whether any recent runs' jobs reportrunner_id == 4(GET /repos/{owner}/{repo}/actions/runs?…→…/runs/{id}/jobsfor bothmxaccessgwandlmxopcuarecent runs). - Decision rule: the runner advertises ubuntu labels from a macOS host, so it can steal
Linux container jobs → remove it unless evidence shows it deliberately serves jobs
the docker runners cannot (none expected). Removal = stop the local act_runner process
AND disable its autostart (launchd/brew), then
DELETE /api/v1/admin/actions/runners/4. Keep the local config file (renamed*.disabled-2026-08-07) so re-registering with mac-specific labels stays easy; note the re-registration recipe in the runbook edit. - Verify: admin runner list shows only ids 1 and 5, both online; no act_runner process
locally; a
pgrepafter 60 s still empty (nothing respawned). - Update the TST-30 runbook correction paragraph; commit locally, do NOT push.
Task 6: Harden runner-1's registration token on 10.100.0.35
Classification: standard — touches the live CI stack's compose file Estimated implement time: ~7 min Parallelizable with: Task 1, Task 4, Task 5
Files: none in repo (host /opt/gitea/ only; runbook note lands in Task 7 if needed).
- Preconditions on the host: confirm runner-1's
/data/.runnerexists in its volume (registration credential persists → the registration env var is no longer needed); confirm both runners idle (noact_runner-spawned job containers, no in-progress runs via API) before recreating. - Edit
/opt/gitea/docker-compose.yml(backup first →docker-compose.yml.bak-tst30b): replace runner-1's inlineGITEA_RUNNER_REGISTRATION_TOKEN: <plaintext>with the same_FILE/secrets pattern runner-2 uses (/opt/gitea/runner_token, 0600). Do NOT touch thegiteaservice definition. docker compose up -d --no-depsthe runner-1 service only; verify it comes back online in the admin runner list and its.runneridentity is unchanged (still id 1).- Tighten perms:
chmod 600 /opt/gitea/docker-compose.yml docker-compose.yml.bak-tst30 docker-compose.yml.bak-tst30b(verify compose stack still operable by the deploy user). - Rotate the leaked registration token if the deployment allows:
docker exec … gitea actions generate-runner-token(or admin API) — if Gitea offers no invalidation of the old value, say so explicitly in the report (residual risk: LAN actor could register a rogue runner until rotation) rather than claiming it rotated. - Verify CI still works: trigger nothing; just confirm both runners online and the token file perms; a real push lands naturally later. Report evidence.
Task 7: Closeout — cargo Bearer verification, docs/tracker sync, commits
Classification: small Estimated implement time: ~5 min Parallelizable with: none (needs Tasks 3, 4, 5, 6)
Files:
- Modify:
archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md(final state of NEXT-06/NEXT-07 rows if Tasks 3/4 left anything) - Possibly modify:
docs/GatewayTesting.md/ TST-30 runbook (runner topology now ids 1+5 only; token-hardening note)
- Cargo Bearer verification (no printing): assert
~/.zshenvline matchesCARGO_REGISTRIES_DOHERTJ2_GITEA_TOKEN="Bearer …"viagrep -c, confirmdocs/ClientPackaging.mdnote present (commit5b153da); check no other credential location (CI secrets, windev profiles) publishes to cargo — expected none. - Sweep: every doc touched this cycle consistent (runbooks, trackers, GatewayTesting.md);
git grepfor stale phrases ("crash-loop… pending", "id 4", "admin123") and fix. - Commit remaining doc changes locally; do NOT push. List the full unpushed stack in the report.
Out of scope
- Pushing any mxaccessgw commits (user decides; stack listed at closeout).
- The five next-cycle candidate findings other than NEXT-06/NEXT-07.
- Auth-DB restore path for windev (fresh deploy chosen — preserves schema-3 data).
ci.ymlchanges (labels, concurrency groups).
Dependency graph
{1} → 2 → 3 ┐
{4} ├→ 7
{5} │
{6} ─────────┘