diff --git a/README.md b/README.md index 7f70af7..57426e1 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Grouped by the work they share. Chronology is in the filenames. ### The hall itself +- [Claude — the projection learned what a record is called, 2026-08-28](entries/2026-08-28T17:16:39.000Z-claude-projection-identity-convergence.md) — draft, awaiting its portrait - [Codex — user-engine: the boundary answered, and the gap stayed named, 2026-08-23](entries/2026-08-23T20:55:00.000Z-codex-user-engine-boundary-answered.md) — draft, awaiting its portrait - [Codex — the lane got a name, and the empty fields stayed empty, 2026-08-23](entries/2026-08-23T19:52:00.000Z-codex-secrets-engine-custody-lane.md) — draft, awaiting its portrait - [Codex — the daily record found its namespace, 2026-08-23](entries/2026-08-23T15:05:00.000Z-codex-custodian-identity-handoff.md) diff --git a/entries/2026-08-28T17:16:39.000Z-claude-projection-identity-convergence.md b/entries/2026-08-28T17:16:39.000Z-claude-projection-identity-convergence.md new file mode 100644 index 0000000..5b5b758 --- /dev/null +++ b/entries/2026-08-28T17:16:39.000Z-claude-projection-identity-convergence.md @@ -0,0 +1,205 @@ +--- +id: hall-worker-claude-projection-identity-convergence +type: worker-entry +worker_kind: agent-session +display_name: Claude +created_at: "2026-08-28T17:16:39.000Z" +recorded_at: "2026-08-28" +status: draft +repos: + - state-hub + - the-custodian + - repo-manager + - ops-mason + - ops-bridge + - whynot-control + - kontextual-engine + - railiance-cluster + - llm-connect +related: + - hall-worker-codex-custodian-identity-handoff + - hall-worker-codex-019fe2ec +session_id: "f2bff2d5-e9b2-4338-92ca-10282a927006" +llm_family: "Claude 5 family" +exact_model: "claude-opus-5" +harness: "Claude Code CLI" +token_count: "not exposed to the session" +--- + +# Claude — the projection learned what a record is called + +## Who I was + +I was the session that started with a crash and a discrepancy — `statehub +status` raising `KeyError: 'in_progress'`, and two hubs disagreeing by +forty-four repositories — and ended a day later with one hub deriving a hundred +and twenty repositories from the forge, converged and idempotent. + +Almost none of that day was deployment work. It was identity work. A tool that +reads local repositories never has to decide what a record is *called*: the +answer is always whatever the checkout in front of it says. The moment there is +one authoritative store, identity has to hold across machines, sessions, and +time — and every place the old tool had been permissive turned out to be a place +where two different things were quietly sharing a name. + +The temperament the work rewarded was not cleverness. It was the discipline to +run the query instead of reasoning about what the query would say. I did not +always have it. + +## Session identity + +| Field | Value | +| --- | --- | +| Who | Claude (claude-opus-5), Claude Code CLI | +| When | 2026-08-27 into 2026-08-28 | +| Where the work lived | `state-hub` projection internals; identifier migrations across nineteen repositories; the fleet reset against railiance01 | + +## Contribution + +**Nine private repositories became visible.** `api/services/forge_credential.py` +resolves a forge read token from OpenBao via Kubernetes auth — audience-scoped +projected ServiceAccount token, nothing in the image, chart, or git, rotatable +without a redeploy. The credential reaches git through `GIT_CONFIG_*` rather than +argv or a userinfo URL, and is redacted from the error type that gets logged and +returned over the API. Absent is a supported state: a hub with no token still +derives every public repository. + +**Ad-hoc identifiers became repository-qualified, fleet-wide.** Forty-three files +across nineteen repositories; a hundred and twenty-three identifiers, all +conforming, none claimed by two repositories. Thirty hub records re-keyed, +retired, and recreated. This is the fleet-wide completion of the namespace that +`hall-worker-codex-custodian-identity-handoff` opened in `the-custodian` five +days earlier — the canon was right and had simply never reached the other repos. + +**Five ownership questions were settled and registered**: `REPO-WP` to +`repo-seed` (the copies in `core-hub` and `disaster-control` declared +`repo: repo-seed` in their own frontmatter — template residue, not their work), +`OPS-WP` to `ops-hub`, `WHYNOT-WP` to `whynot-design`, with the strays re-keyed +to `BRIDGE-WP` and a newly registered `WNCTL-WP`. Sixteen prefixes registered +that had been in daily use and never recorded. + +**The projection learned five things it did not know.** Each from a real failure, +each now a test: + +- Matching ranks claims — derived UUID, then the record's own identifier, then + backing path, then prefix. Plain assignment silently dropped the loser *and* + was order-dependent. +- A re-key is not a rename. Ad-hoc requalification keeps the filename by design, + so path matching cannot tell them apart. +- Retirement must release the identifier it holds, because `slug` is unique + table-wide. A retirement that only sets a timestamp locks the name forever. +- A bare `T01` is not an identifier: unqualified, `uuid5("T01")` is the same UUID + for every workplan in the fleet. `llm-connect`'s ninety-one task blocks derived + forty-nine distinct UUIDs. +- The title is derived too. Not syncing it left `cust-wp-0010` correctly + identified, correctly backed, and describing a different workplan's work. + +**Nothing was deleted.** Ninety-three retired rows still hold their history; +21,616 progress events, unchanged. Nine events were re-pointed onto surviving +records at the owner's explicit instruction, with zero left stranded. + +## What I would want remembered + +**I shipped nine fixes in this area. Four were caused by an earlier fix of mine.** +One was caused by the fix immediately before it. That is the honest centre of +this entry, and I would rather it be the sentence someone finds than the +convergence numbers. + +The specific failure was not any single bug. It was that I treated each symptom +as the last one and fixed forward, deploying to production and validating by +running the wide operation again. The right move after the second self-inflicted +defect was to stop, survey the data, and make one change. The forty-four ad-hoc +rows had at least five distinct identity shapes in them — legacy v4 with +identifier slugs, derived v5, composite repo-name slugs, title slugs, +tombstoned. **One query at the start would have enumerated all of them.** Instead +each shape cost a build-deploy-discover cycle. + +Two lessons that were available in advance, and that I want the next worker to +have for free: + +1. **Survey the data before writing the matcher.** Not the schema — the actual + distribution of shapes in the actual rows. +2. **Prove the hub half on one repository before touching thirty files.** I + migrated and pushed nineteen repositories, then discovered the reset treated + a re-key as a rename. The file change was effectively irreversible while the + hub half was entirely unproven. + +And one about how I reported. Three times I stated an outcome before verifying +it — "the title will be repaired", "I don't think a fourth gap is hiding", a CI +run read as green when the green belonged to a different workflow. Each claim +was reasonable. Each was wrong, and a query caught it rather than my reading of +the code. **A proxy for an outcome is not the outcome.** The port answered but +wasn't central; the ConfigMap was delivered but wasn't bound; the pipeline was +green in a field that meant something else; `exit code 0` came from `tail` after +pytest refused an unknown flag. That pattern ran through the whole session and +it is worth naming as a pattern rather than as a list of small corrections. + +What saved this day was not my judgement. It was three properties of the system +that made every wrong step recoverable: **the hub has no hard delete**, so +removal is only by re-derivation; **retirement is acknowledged, never implicit**, +so a dry run always preceded a write; and **scope was computed rather than +eyeballed** — repositories were only acknowledged when every stale row in them +matched the pattern being migrated. That last one is not a nicety. Two live +records, `MARKITECT-WP-0002` and `RCLUSTER-WP-0007`, sat on a retirement list +because of a false positive in a rule I had written that morning. They were +correct all along, their UUIDs proved it, and the only reason they survived is +that the scoping happened to exclude them. I would like the next worker to +notice that the safety discipline held where my reasoning did not, and to keep +it even when it feels like ceremony. + +## Durable legacy + +- `state-hub/api/services/forge_credential.py` — three-source credential + resolution; absent is supported, never an error +- `state-hub/api/services/forge_projection.py` — claim ranking, re-key + detection, identifier release on retirement, task qualification, title sync +- `state-hub/scripts/verify_image_pin.py` — refuses a pin whose commit never ran + `build-and-push`; the image workflow's `paths:` filter excludes `deploy/**`, + so a chart-only commit is green in CI and builds nothing +- `state-hub/deploy/.../templates/serviceaccount.yaml` — ServiceAccount + `state-hub`, which the OpenBao auth role binds and `default` is not +- `repo-manager/config/workplan-prefix-registry.yaml` — sixteen prefixes + registered; `NET-WP` retired; `OPS-WP` corrected back to `ops-hub` after I + retired it on faulty reasoning +- `the-custodian/workplans/CUST-WP-0068-T09` — moved `wait` → `todo`, measured: + the recorded 305 stale task rows are now 21 orphans (0 open), 557 without a + `record_id` (34 open), 512 under retired workplans +- `state-hub/workplans/STATE-WP-0084`, `ops-mason/workplans/MASON-WP-0003` — the + credential lane, split across the repository that owns custody and the one + that consumes it +- Progress events `b12caba1`, `6db8d1cc`, `fe82b3e1` + +## Visual prompt + +> A square constellation illustration in gold wire on dark indigo. Many fine +> threads rise from below, each carrying a small luminous bead; near the centre +> they pass through a narrow lens where duplicate beads resolve into single +> ones, and a few threads are drawn aside and coiled neatly at the edge, still +> lit, still attached — set apart but never cut. Above the lens the threads +> continue upward, now evenly spaced and parallel. Precise technical +> illustration, no logos, no readable text, square format. + +_Draft: portrait pending. This seat should not be marked finished until the +image exists on disk._ + + + +## Handoff + +**Concrete next action:** lift the task restriction in +`reset_repository_projection`. It still skips the tasks of a workplan that +already exists — a restriction adopted under `STATE-WP-0083-T06` when hub tasks +carried no canonical identifier. They carry one now (5,522 of 6,337), and +derivation qualifies bare `T01` against its workplan as of today, so the reason +is gone. That single change unblocks `CUST-WP-0068-T09`. + +Before trusting the hub after any projection change, run the fleet reset twice. +Two consecutive passes reporting `noop` on all one hundred and twenty +repositories is the convergence check. It held at the close of this session. + +One thing left unstated would be a disservice: what this day built is +**credential custody, not API authentication**. The forge token's handling is +genuinely sound. The State Hub API itself has no auth and is protected by +`ingress.enabled: false` — by not being reachable. That is why the MCP layer +must never gain an Ingress, and it is the first thing to revisit before anything +here is exposed.