8769ee9765
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.
421 lines
23 KiB
Markdown
421 lines
23 KiB
Markdown
# Live Actions: SEC-36 Rotation, TST-30 Second Runner, Client Publish — Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task (or subagent-driven-development in-session).
|
|
|
|
**Goal:** Execute the three repo-complete-but-live-pending operator actions: rotate the dev GLAuth service-account credential (SEC-36), register a second Gitea Actions runner (TST-30), and publish the five client packages at 0.2.0 (Java 0.2.1).
|
|
|
|
**Architecture:** Three independent workstreams executed by subagents. SEC-36 is a strictly ordered cutover (pre-stage hosts → flip GLAuth → verify → finalize) with secret-hygiene rules. TST-30 is infra work on docker host 10.100.0.35 plus a concurrency verification. Publish runs the existing guarded `pack-clients.ps1 -Publish` + `tag-go-module.ps1` locally on macOS.
|
|
|
|
**Tech Stack:** ssh (BatchMode works to 10.100.0.35 and 10.100.0.48), PowerShell/nssm on windev, docker compose on 10.100.0.35, Gitea API (`~/.zshenv` has admin-scoped `GITEA_USERNAME`/`GITEA_TOKEN`), pwsh 7 on macOS.
|
|
|
|
---
|
|
|
|
## Preflight facts (verified 2026-08-07 from this macOS box)
|
|
|
|
- `ssh 10.100.0.35` OK. GLAuth container is **`zb-shared-glauth`**, compose working dir **`/home/dohertj2/zb-glauth`** (NOT the runbook's `~/Desktop/scadaproj/infra/glauth` — that path does not exist on the host; the runbook must be corrected in Task 5). Runner container **`gitea-runner`**, compose working dir **`/opt/gitea`**.
|
|
- `ssh 10.100.0.48` (windev) OK; `powershell -NoProfile` works; `nssm` at `C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Links\nssm.exe`.
|
|
- `wonder-app-vd03` does NOT resolve from macOS — check it from windev (Task 2).
|
|
- Gitea API: token valid (`/api/v1/user` → 200), admin (`/api/v1/admin/users` → 200, `POST /api/v1/admin/actions/runners/registration-token` → 200).
|
|
- Local `~/Desktop/scadaproj/infra/glauth/config.toml` exists (14 `passsha256` entries) — the git source of truth.
|
|
- `pwsh` at `/usr/local/bin/pwsh`.
|
|
|
|
## Secret hygiene (SEC-36, binding for every task)
|
|
|
|
- The new plaintext password lives ONLY in `$SECRET_FILE = /private/tmp/claude-501/-Users-dohertj2-Desktop-MxAccessGateway/67849767-a07c-4afa-94e2-3ce4a39d8d23/scratchpad/sec36-new-secret` (chmod 600), created in Task 1 and shredded in Task 5.
|
|
- **Never echo/cat the plaintext to stdout, never put it in a commit, a repo file, a log line, or a command whose text is captured verbatim.** Always load it into a shell variable from the file (`val=$(cat "$SECRET_FILE")`) and pass it via stdin or remote-side expansion, never inline in an `ssh "...literal..."` string where avoidable.
|
|
- The `passsha256` hash MAY appear in `config.toml` commits — that is the established pattern (14 existing entries).
|
|
- The OLD password must never be printed either. Its only uses are: GLAuth keeps honoring it until Task 4, and the single old-bind-must-fail probe in Task 4.
|
|
|
|
---
|
|
|
|
### Task 1: SEC-36 — Generate secret, stage GLAuth config change (repo + host copy diff)
|
|
|
|
**Classification:** high-risk
|
|
**Estimated implement time:** ~5 min
|
|
**Parallelizable with:** Task 2, Task 6, Task 9
|
|
|
|
**Files:**
|
|
- Modify: `~/Desktop/scadaproj/infra/glauth/config.toml` (the `serviceaccount` user's `passsha256`) — DO NOT commit yet (Task 5 commits)
|
|
- Create: `$SECRET_FILE` (scratchpad, chmod 600)
|
|
|
|
**Step 1: Generate the new secret and its hash**
|
|
|
|
```bash
|
|
SECRET_FILE="/private/tmp/claude-501/-Users-dohertj2-Desktop-MxAccessGateway/67849767-a07c-4afa-94e2-3ce4a39d8d23/scratchpad/sec36-new-secret"
|
|
umask 077
|
|
openssl rand -base64 24 | tr -d '\n' > "$SECRET_FILE"
|
|
chmod 600 "$SECRET_FILE"
|
|
NEW_SHA=$(cat "$SECRET_FILE" | tr -d '\n' | shasum -a 256 | awk '{print $1}')
|
|
echo "$NEW_SHA" # hash only — safe to display
|
|
```
|
|
Cross-check the hash recipe against `glauth.md` ("Generate `passsha256` from a plaintext password") in this repo and follow that recipe if it differs.
|
|
|
|
**Step 2: Diff host deployment config vs repo source of truth**
|
|
|
|
```bash
|
|
ssh 10.100.0.35 'cat /home/dohertj2/zb-glauth/config.toml' > /tmp/host-glauth-config.toml 2>/dev/null || true
|
|
diff ~/Desktop/scadaproj/infra/glauth/config.toml /tmp/host-glauth-config.toml
|
|
```
|
|
Small drift (comments, ports) is fine — note it. If the `serviceaccount` stanza differs structurally, STOP and surface before editing.
|
|
|
|
**Step 3: Edit the repo source of truth**
|
|
|
|
In `~/Desktop/scadaproj/infra/glauth/config.toml`, replace the `passsha256` value of the `[[users]]` entry whose `name`/`cn` is `serviceaccount` with `$NEW_SHA`. Edit ONLY that line. Do not `docker compose up` anything yet.
|
|
|
|
**Step 4: Record findings**
|
|
|
|
Report: hash staged (show hash, never plaintext), drift summary from step 2, and confirm `$SECRET_FILE` exists with mode 600.
|
|
|
|
---
|
|
|
|
### Task 2: SEC-36 — Determine wonder-app-vd03 LDAP status (via windev)
|
|
|
|
**Classification:** small
|
|
**Estimated implement time:** ~3 min
|
|
**Parallelizable with:** Task 1, Task 6, Task 9
|
|
|
|
**Step 1: Try to reach vd03 from windev**
|
|
|
|
```bash
|
|
ssh 10.100.0.48 'powershell -NoProfile -Command "Test-Connection wonder-app-vd03 -Count 1 -Quiet"'
|
|
```
|
|
|
|
**Step 2: If reachable, read its gateway config for `MxGateway:Ldap:Enabled`**
|
|
|
|
Try (in order, stop at first success): `ssh` hop from windev; reading `\\wonder-app-vd03\c$\...` appsettings/environment via PowerShell remoting (`Invoke-Command -ComputerName wonder-app-vd03`); or `nssm get MxAccessGw AppEnvironmentExtra` remotely. Look for `MxGateway__Ldap__Enabled` / appsettings `Ldap:Enabled`.
|
|
|
|
**Step 3: Decide and record**
|
|
|
|
- `Enabled=false` or host unreachable/no gateway service → vd03 is OUT of scope; record why (runbook says its dashboard is disabled — `false` is the expected answer).
|
|
- `Enabled=true` → vd03 is IN scope for Task 3 pre-staging; record the connection method that worked.
|
|
|
|
---
|
|
|
|
### Task 3: SEC-36 — Pre-stage the NEW value on LDAP-enabled deployed hosts
|
|
|
|
**Classification:** high-risk
|
|
**Estimated implement time:** ~4 min
|
|
**Parallelizable with:** none (blocked by Tasks 1, 2)
|
|
|
|
**Step 1: Pre-stage windev (10.100.0.48)**
|
|
|
|
Load the secret locally, then set the env var remotely without leaking it into logged command text more than unavoidable (ssh arguments are not logged remotely by default; do NOT echo the value):
|
|
|
|
```bash
|
|
SECRET_FILE="/private/tmp/claude-501/-Users-dohertj2-Desktop-MxAccessGateway/67849767-a07c-4afa-94e2-3ce4a39d8d23/scratchpad/sec36-new-secret"
|
|
val=$(cat "$SECRET_FILE")
|
|
ssh 10.100.0.48 'powershell -NoProfile -Command "$v = [Console]::In.ReadLine(); $cur = (& nssm get MxAccessGw AppEnvironmentExtra) -join \"`n\"; Write-Output (\"CURRENT: \" + ($cur -replace \"Password=.*\", \"Password=<redacted>\")); & nssm set MxAccessGw AppEnvironmentExtra (\"MxGateway__Ldap__ServiceAccountPassword=\" + $v)"' <<< "$val"
|
|
```
|
|
**CAUTION:** `nssm set AppEnvironmentExtra` REPLACES the whole extra-environment block. First inspect `nssm get MxAccessGw AppEnvironmentExtra` (redacting any `Password=` values); if other variables exist, preserve them in the new value (newline-separated). Adapt quoting as needed — verify with a redacted `nssm get` afterwards.
|
|
|
|
**Step 2: Restart the service**
|
|
|
|
```bash
|
|
ssh 10.100.0.48 'nssm restart MxAccessGw'
|
|
```
|
|
Expected: service restarts. Binds against GLAuth now fail (old directory, new client value) — expected and brief; proceed immediately to Task 4.
|
|
|
|
**Step 3: vd03 (only if Task 2 said IN scope)** — same pre-stage + restart via the method Task 2 found.
|
|
|
|
---
|
|
|
|
### Task 4: SEC-36 — Rotate GLAuth and verify end-to-end
|
|
|
|
**Classification:** high-risk
|
|
**Estimated implement time:** ~5 min
|
|
**Parallelizable with:** none (blocked by Task 3)
|
|
|
|
**Step 1: Back up current host config, sync the staged config, recreate**
|
|
|
|
```bash
|
|
ssh 10.100.0.35 'cp /home/dohertj2/zb-glauth/config.toml /home/dohertj2/zb-glauth/config.toml.bak-sec36'
|
|
scp ~/Desktop/scadaproj/infra/glauth/config.toml 10.100.0.35:/home/dohertj2/zb-glauth/config.toml
|
|
```
|
|
**If Task 1's diff showed host-vs-repo drift beyond the serviceaccount line:** do NOT wholesale-copy — instead edit only the serviceaccount `passsha256` line in the host copy (sed on the host), so unrelated host-local drift is preserved.
|
|
|
|
```bash
|
|
ssh 10.100.0.35 'cd /home/dohertj2/zb-glauth && docker compose up -d --force-recreate && sleep 3 && docker compose logs --tail 30'
|
|
```
|
|
Expected: clean startup, no TOML parse error. On parse error: restore `.bak-sec36`, recreate, STOP, surface.
|
|
|
|
**Step 2: Verify new credential binds (from the glauth host, ldapsearch or python)**
|
|
|
|
```bash
|
|
SECRET_FILE=".../sec36-new-secret" # full scratchpad path
|
|
val=$(cat "$SECRET_FILE")
|
|
ssh 10.100.0.35 'ldapsearch -x -H ldap://localhost:3893 -D "cn=serviceaccount,dc=zb,dc=local" -w "$(cat -)" -b "dc=zb,dc=local" "(cn=multi-role)" cn' <<< "$val"
|
|
```
|
|
Expected: search returns the `multi-role` entry. (If ldapsearch is missing on the host, run the equivalent from macOS against `10.100.0.35:3893`, or use `docker exec`.) Adjust the bind DN to match the actual `serviceaccount` DN in config.toml.
|
|
|
|
**Step 3: Verify the OLD value is dead — exactly ONE probe, from 10.100.0.35 itself**
|
|
|
|
One deliberately failing bind with the old password must return invalid credentials. **Only one attempt** (3-fail/10-min per-IP lockout; never probe from a shared-NAT box). The old value: recover it transiently from `config.toml.bak-sec36`'s hash? No — hash is not the plaintext. Instead: skip the plaintext probe if the old plaintext is not already known out-of-band; the hash replacement in config.toml is itself proof GLAuth no longer honors the old value (GLAuth compares against `passsha256` only). Record that reasoning instead of probing blind.
|
|
|
|
**Step 4: Verify dashboard login end-to-end on windev**
|
|
|
|
```bash
|
|
curl -sk -o /dev/null -w '%{http_code}' -c /tmp/mxgw-cookies.txt https://10.100.0.48:5001/login
|
|
```
|
|
Find the actual dashboard port from windev config first (`nssm get`/appsettings; likely https). Then POST the login form as `multi-role`/`password` (the GLAuth TEST USER password, not the service account) and expect a redirect + `__Host-MxGatewayDashboard` (or `MxGatewayDashboard`) cookie:
|
|
|
|
```bash
|
|
curl -sk -o /dev/null -w '%{http_code}\n' -b /tmp/mxgw-cookies.txt -c /tmp/mxgw-cookies.txt -d 'username=multi-role&password=password' <dashboard-base>/login
|
|
grep -i mxgatewaydashboard /tmp/mxgw-cookies.txt
|
|
```
|
|
Inspect the login page HTML first for real form field names / antiforgery token; adapt. A successful `multi-role` login proves the service-account search bind works with the new credential end-to-end. If HTTP verification proves impractical (antiforgery), fall back to grepping the gateway log on windev for a successful LDAP bind/login line after attempting — or run the live-LDAP integration test from macOS:
|
|
|
|
```bash
|
|
export MXGATEWAY_RUN_LIVE_LDAP_TESTS=1
|
|
export MxGateway__Ldap__ServiceAccountPassword="$(cat "$SECRET_FILE")"
|
|
dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~DashboardLdapLiveTests
|
|
```
|
|
Expected: green. (This binds from macOS to 10.100.0.35:3893 directly — it verifies the credential, and the curl/log check verifies windev.)
|
|
|
|
**Step 5: Rollback (only on failure)** — restore `.bak-sec36` on the host, `docker compose up -d --force-recreate`, re-point windev's env var back (old value from where it was before — if unknown, STOP and surface), `nssm restart MxAccessGw`.
|
|
|
|
---
|
|
|
|
### Task 5: SEC-36 — Finalize: commit source of truth, dev secrets, runbook fix, tracker, cleanup
|
|
|
|
**Classification:** standard
|
|
**Estimated implement time:** ~5 min
|
|
**Parallelizable with:** none (blocked by Task 4)
|
|
|
|
**Step 1: Commit and push the scadaproj glauth change (glauth paths ONLY)**
|
|
|
|
```bash
|
|
cd ~/Desktop/scadaproj
|
|
git add infra/glauth/config.toml
|
|
git commit -m "sec(glauth): rotate serviceaccount passsha256 (mxaccessgw SEC-36)"
|
|
git push
|
|
```
|
|
(`scadaproj` is a shared monorepo — stage only this path. If the worktree has unrelated staged changes, use `git commit -- infra/glauth/config.toml` style isolation.)
|
|
|
|
**Step 2: Set dev user-secrets on this macOS box**
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
cat "$SECRET_FILE" | tr -d '\n' | dotnet user-secrets set "MxGateway:Ldap:ServiceAccountPassword" --project src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj
|
|
```
|
|
(Check `dotnet user-secrets set -h` for stdin support; if unsupported, pass via `"$(cat "$SECRET_FILE")"` — acceptable, it's a local process arg.)
|
|
|
|
**Step 3: Correct the runbook + flip tracker rows (mxaccessgw repo)**
|
|
|
|
- `docs/runbooks/SEC-36-ldap-credential-rotation.md`: fix the host deployment path (`/home/dohertj2/zb-glauth`, container `zb-shared-glauth`; repo source of truth remains `scadaproj/infra/glauth/`), and note vd03's actual status per Task 2.
|
|
- Grep `archreview/2026-07-12/remediation/` for SEC-36 pending-operator rows; flip to Done citing the runbook + today's date.
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
grep -rn "SEC-36" archreview/2026-07-12/remediation/ docs/ | grep -iv binary
|
|
# edit the rows, then:
|
|
git add -A docs archreview && git commit -m "docs(sec-36): record live rotation done; correct runbook host paths"
|
|
```
|
|
|
|
**Step 4: Shred the secret file**
|
|
|
|
```bash
|
|
rm -P "$SECRET_FILE" 2>/dev/null || rm "$SECRET_FILE"
|
|
```
|
|
|
|
**Step 5: Done-criteria check** — walk the runbook's Done criteria list; report each as met/not-met.
|
|
|
|
---
|
|
|
|
### Task 6: TST-30 — Recon existing runner config on 10.100.0.35
|
|
|
|
**Classification:** small
|
|
**Estimated implement time:** ~4 min
|
|
**Parallelizable with:** Task 1, Task 2, Task 9
|
|
|
|
**Step 1: Inspect the existing runner**
|
|
|
|
```bash
|
|
ssh 10.100.0.35 'cat /opt/gitea/docker-compose.yml 2>/dev/null || sudo cat /opt/gitea/docker-compose.yml; ls /opt/gitea'
|
|
ssh 10.100.0.35 'docker inspect gitea-runner --format "{{json .Mounts}}"; docker exec gitea-runner cat /config.yaml 2>/dev/null || true'
|
|
```
|
|
Find: image/version, config file location (look for `container.network: traefik` and `capacity`/`maxParallel`), data volume, registration state file, docker socket mount, labels.
|
|
|
|
**Step 2: Check host capacity**
|
|
|
|
```bash
|
|
ssh 10.100.0.35 'nproc; free -h; df -h / | tail -1'
|
|
```
|
|
|
|
**Step 3: Decide (a)-variant** — second container vs raising `capacity` on the existing runner. Runbook prefers a second instance; if the existing runner's config shows a simple `capacity: 1` and resources are tight, raising capacity is the smaller change — but a second registered instance is the runbook default and survives one-runner wedge. Record the chosen variant, the exact compose/config snippets to reuse, and where the registration token goes.
|
|
|
|
---
|
|
|
|
### Task 7: TST-30 — Register and start the second runner
|
|
|
|
**Classification:** high-risk
|
|
**Estimated implement time:** ~5 min
|
|
**Parallelizable with:** none (blocked by Task 6)
|
|
|
|
**Step 1: Mint an instance-level registration token**
|
|
|
|
```bash
|
|
source ~/.zshenv
|
|
curl -s -X POST -u "$GITEA_USERNAME:$GITEA_TOKEN" 'https://gitea.dohertylan.com/api/v1/admin/actions/runners/registration-token'
|
|
```
|
|
(Returns `{"token": "..."}` — a registration token, not a secret credential of lasting value; still avoid committing it.)
|
|
|
|
**Step 2: Create the second runner instance per Task 6's plan**
|
|
|
|
E.g. add a `gitea-runner-2` service to the compose (distinct name + data volume, same image, same `container.network: traefik`, same socket mount), inject the token via the runner's registration env (`GITEA_RUNNER_REGISTRATION_TOKEN`) or `act_runner register --no-interactive`, then `docker compose up -d gitea-runner-2` from `/opt/gitea`. Back up the compose file first (`cp docker-compose.yml docker-compose.yml.bak-tst30`). Do NOT touch the existing `gitea-runner` service definition.
|
|
|
|
**Step 3: Confirm both runners online**
|
|
|
|
```bash
|
|
curl -s -u "$GITEA_USERNAME:$GITEA_TOKEN" 'https://gitea.dohertylan.com/api/v1/admin/actions/runners' | python3 -m json.tool
|
|
```
|
|
Expected: ≥2 runners, both online. Also check `docker logs` of the new container for a clean registration + poll loop.
|
|
|
|
---
|
|
|
|
### Task 8: TST-30 — Verify concurrency, gitea:3000 resolution, tracker
|
|
|
|
**Classification:** standard
|
|
**Estimated implement time:** ~5 min
|
|
**Parallelizable with:** none (blocked by Task 7)
|
|
|
|
**Step 1: Trigger two concurrent runs**
|
|
|
|
Push two scratch branches to `mxaccessgw` back-to-back (empty commits off `main`, branch names `scratch/tst30-a`, `scratch/tst30-b`):
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
git push origin main:refs/heads/scratch/tst30-a
|
|
git commit --allow-empty -m "tst30 concurrency probe" && git push origin HEAD:refs/heads/scratch/tst30-b && git reset --hard HEAD~1
|
|
```
|
|
(Adapt: any two pushes that fan out jobs. Clean up branches after: `git push origin :scratch/tst30-a :scratch/tst30-b`.)
|
|
|
|
**Step 2: Confirm parallel execution**
|
|
|
|
Poll the runs API/UI: the second run's jobs must START before the first run finishes.
|
|
|
|
```bash
|
|
curl -s -u "$GITEA_USERNAME:$GITEA_TOKEN" 'https://gitea.dohertylan.com/api/v1/repos/dohertj2/mxaccessgw/actions/tasks' | python3 -m json.tool | head -60
|
|
```
|
|
|
|
**Step 3: Confirm `gitea:3000` resolves on the new runner** — verify a job scheduled on runner-2 succeeds at checkout (checkout hits `gitea:3000` over the traefik network); identify which runner took each job from the runs UI/API or runner logs.
|
|
|
|
**Step 4: Flip TST-30 tracker rows** in `archreview/2026-07-12/remediation/` (grep `TST-30`) to Done with today's date; confirm `docs/GatewayTesting.md` prose is still accurate (it should be — it already describes the bypass as valid regardless of runner count). Commit.
|
|
|
|
---
|
|
|
|
### Task 9: Publish — Preflight audit (versions, registry collisions, toolchains)
|
|
|
|
**Classification:** small
|
|
**Estimated implement time:** ~4 min
|
|
**Parallelizable with:** Task 1, Task 2, Task 6
|
|
|
|
**Step 1: Audit source versions**
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
grep -n 'version' clients/rust/Cargo.toml | head -5
|
|
grep -n 'version' clients/python/pyproject.toml clients/python/src/zb_mom_ww_mxgateway/version.py
|
|
grep -n 'ClientVersion' clients/go/mxgateway/version.go
|
|
grep -n '<Version>' clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj
|
|
grep -n 'version' clients/java/build.gradle | head -5
|
|
grep -rn 'CLIENT_VERSION' clients/java --include=*.java | grep -i mxgatewayclientversion
|
|
```
|
|
Expected: Rust/Python/Go/.NET/Contracts = 0.2.0; Java build.gradle AND `MxGatewayClientVersion.CLIENT_VERSION` = 0.2.1. Any mismatch → STOP, surface (do not bump versions yourself; that's a scope change).
|
|
|
|
**Step 2: Query live registry for collisions**
|
|
|
|
```bash
|
|
source ~/.zshenv
|
|
for u in 'nuget/ZB.MOM.WW.MxGateway.Client/0.2.0' 'nuget/ZB.MOM.WW.MxGateway.Contracts/0.2.0' 'pypi/zb-mom-ww-mxaccess-gateway-client/0.2.0' 'cargo/zb-mom-ww-mxgateway-client/0.2.0' 'maven/com.zb.mom.ww.mxgateway-zb-mom-ww-mxgateway-client/0.2.1'; do
|
|
echo "$u => $(curl -s -o /dev/null -w '%{http_code}' -u "$GITEA_USERNAME:$GITEA_TOKEN" "https://gitea.dohertylan.com/api/v1/packages/dohertj2/$u")"
|
|
done
|
|
```
|
|
Expected: 404 for every target (unclaimed). Check the exact maven path convention against `pack-clients.ps1`'s own guard code and use its convention. 200 anywhere → STOP, surface.
|
|
|
|
**Step 3: Toolchain + workspace check**
|
|
|
|
```bash
|
|
git -C ~/Desktop/MxAccessGateway status --porcelain # must be clean (publish from a clean tree at origin/main)
|
|
for t in dotnet cargo go python3 gradle pwsh; do which $t; done
|
|
```
|
|
Also confirm `clients/go` module tag `clients/go/v0.2.0` does NOT already exist: `git ls-remote --tags origin 'clients/go/v*'`.
|
|
|
|
---
|
|
|
|
### Task 10: Publish — Run the guarded pack-and-publish
|
|
|
|
**Classification:** high-risk
|
|
**Estimated implement time:** ~5 min dispatch (script runtime longer)
|
|
**Parallelizable with:** none (blocked by Task 9)
|
|
|
|
**Step 1: Run pack-clients with publish**
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
source ~/.zshenv
|
|
pwsh -NoProfile -File scripts/pack-clients.ps1 -Publish 2>&1 | tee /private/tmp/claude-501/-Users-dohertj2-Desktop-MxAccessGateway/67849767-a07c-4afa-94e2-3ce4a39d8d23/scratchpad/pack-clients-publish.log
|
|
```
|
|
Expected: per-language build+test+pack, collision guard prints "safe to publish" per artifact, uploads succeed. Timeout generously (Bash timeout 600000). If any language fails MID-loop, record exactly which artifacts pushed and which didn't — partial publish is the known failure mode; do not re-run blindly (re-run is safe only because the guard skips? NO — the guard ABORTS on existing versions. A re-run after partial publish will abort on the already-pushed artifact. If that happens, surface with the log; per-language `-Languages` selective re-run is the fix).
|
|
If macOS cannot build a language (e.g. gradle/java env), use `-Languages` to publish what builds and surface the remainder — do not fake success.
|
|
|
|
**Step 2: Verify each artifact now exists (200)** — re-run Task 9 step 2's loop; expected 200 everywhere published.
|
|
|
|
---
|
|
|
|
### Task 11: Publish — Go module tag
|
|
|
|
**Classification:** small
|
|
**Estimated implement time:** ~3 min
|
|
**Parallelizable with:** Task 10 (blocked by Task 9)
|
|
|
|
**Step 1: Tag via the guarded script**
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
pwsh -NoProfile -File scripts/tag-go-module.ps1 -Version 0.2.0
|
|
```
|
|
Read the script's param block first (`-Version` name may differ; it validates semver and that `version.go` matches, then creates+pushes `clients/go/v0.2.0`). Expected: tag created and pushed to origin.
|
|
|
|
**Step 2: Verify**
|
|
|
|
```bash
|
|
git ls-remote --tags origin 'clients/go/v0.2.0*'
|
|
```
|
|
Expected: exactly one tag. Optionally `GOPROXY=direct go list -m gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go@v0.2.0` from a temp dir.
|
|
|
|
---
|
|
|
|
### Task 12: Publish — Docs/tracker closeout
|
|
|
|
**Classification:** small
|
|
**Estimated implement time:** ~4 min
|
|
**Parallelizable with:** none (blocked by Tasks 10, 11)
|
|
|
|
**Step 1:** Update `docs/ClientPackaging.md`'s versioning narrative if it claims 0.2.0/0.2.1 are unpublished (it currently records the maven 0.2.1 exception; add a dated line that 0.2.0 (Java 0.2.1) published on 2026-08-07). Grep `archreview/2026-07-12/remediation/` for publish/CLI-39 pending-operator rows and flip to Done.
|
|
|
|
**Step 2:** Commit:
|
|
|
|
```bash
|
|
cd ~/Desktop/MxAccessGateway
|
|
git add docs archreview && git commit -m "docs(clients): record 0.2.0/0.2.1 publish + close operator actions"
|
|
```
|
|
|
|
**Step 3:** Report the full publish matrix (artifact → version → registry HTTP status).
|
|
|
|
---
|
|
|
|
## Dependency graph
|
|
|
|
```
|
|
{T1, T2} ──▶ T3 ──▶ T4 ──▶ T5 (SEC-36, strictly serial after recon)
|
|
T6 ──▶ T7 ──▶ T8 (TST-30)
|
|
T9 ──▶ {T10, T11} ──▶ T12 (Publish)
|
|
```
|
|
The three streams are mutually independent and run concurrently. All subagents run with model=opus per operator instruction.
|
|
|
|
## Out of scope (explicitly)
|
|
|
|
- Option (b)/(c) runner topologies and the `concurrency:` ci.yml experiment (TST-30 runbook marks them escalation/optional).
|
|
- The five next-cycle candidate findings in `archreview/2026-07-12/remediation/90-candidate-findings-next-cycle.md`.
|
|
- Any client version bumps (versions are already landed; a mismatch is a STOP-and-surface).
|