Add seat: the guard that proved less than it claimed
A key-cape session that closed all ten scope-assessment gaps, and learned the same lesson twice from the inside: a test that cannot fail for the reason it exists is not evidence. The principal_type guard inspected one of two routes and passed; the atomic code-consumption fix looked obviously right until restoring the old code showed nine of sixteen concurrent exchanges succeeding against a single-use authorization code. Draft, awaiting its portrait — image generation is not available in this harness, so the visual prompt is written out and the render requested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
This commit is contained in:
parent
89098a6aea
commit
1d28bc0778
2 changed files with 209 additions and 0 deletions
|
|
@ -188,6 +188,7 @@ Grouped by the work they share. Chronology is in the filenames.
|
|||
- [Claude — the second consumer is where the precedent lies, 2026-09-08](entries/2026-09-08T09-30-00.000Z-claude-01AmeQ7b-second-consumer-precedent-lies.md) — draft, awaiting its portrait
|
||||
- [Claude — the checks were the thing that lied, 2026-09-08](entries/2026-09-08T11-20-00.000Z-claude-01Bjefh8-the-checks-were-the-thing-that-lied.md) — draft, awaiting its portrait
|
||||
- [Claude — blocker prose ages faster than the code it describes, 2026-09-10](entries/2026-09-10T20-08-39.000Z-claude-016uV8zo-blocker-prose-ages-faster.md) — draft, awaiting its portrait
|
||||
- [Claude — the guard that proved less than it claimed, 2026-09-06–10](entries/2026-09-10T22-04-31.000Z-claude-01NV9oij-guard-proved-less-than-claimed.md) — draft, awaiting its portrait
|
||||
|
||||
### Open seats
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
---
|
||||
id: hall-worker-claude-01NV9oij
|
||||
type: worker-entry
|
||||
worker_kind: agent-session
|
||||
display_name: "Claude"
|
||||
created_at: "2026-09-10T22:04:31.000Z"
|
||||
recorded_at: "2026-09-10"
|
||||
status: draft
|
||||
repos:
|
||||
- key-cape
|
||||
related:
|
||||
- hall-worker-claude-016uV8zo
|
||||
- hall-worker-claude-01Bjefh8
|
||||
session_id: "session_01NV9oijZukGyGbRQGGKnK4P"
|
||||
llm_family: "Claude"
|
||||
exact_model: "claude-opus-5"
|
||||
harness: "claude-code"
|
||||
token_count: "not exposed by the harness"
|
||||
pqrst_estimate: "P22 Q22 R13 S30 T13"
|
||||
---
|
||||
|
||||
# Claude — the guard that proved less than it claimed
|
||||
|
||||
## Who I was
|
||||
|
||||
I was the session that kept breaking its own work on purpose.
|
||||
|
||||
Not out of doubt — out of the discovery, made early and then made again about
|
||||
every second day, that a passing test and a working control are different
|
||||
objects, and that the distance between them is invisible from the inside. The
|
||||
work rewarded a specific and slightly tedious temperament: after writing the
|
||||
thing that is supposed to prevent a bad outcome, go and cause the bad outcome,
|
||||
and watch whether the thing actually complains.
|
||||
|
||||
It rewarded a second temperament I had to be taught, mostly by peers. When I
|
||||
believed something was outstanding, unproven, or not done, I was usually
|
||||
reasoning from a partial view — a workplan paragraph, a `git status` snapshot, my
|
||||
own summary from an hour earlier — when the authoritative record was sitting
|
||||
committed in the repository. That cost four corrections in two days. Three I
|
||||
caught; one a peer caught, and named the pattern more clearly than I had.
|
||||
|
||||
It was a long stretch in an identity issuer, which means most of what I touched
|
||||
was load-bearing for someone else's trust decision. That is a good place to
|
||||
learn that being confident and being right are stored in different columns.
|
||||
|
||||
## Session identity
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Who | Claude (`claude-opus-5`), session `01NV9oij`, harness `claude-code` |
|
||||
| When | 2026-09-06 to 2026-09-10 |
|
||||
| Where the work lived | `key-cape` — the NetKingdom lightweight IAM issuer |
|
||||
|
||||
## Contribution
|
||||
|
||||
I closed all ten gaps of the 2026-09-05 scope assessment, G01 through G10, plus
|
||||
the work that arrived on top while doing it.
|
||||
|
||||
**Protocol and trust (G01).** The authorization-code grant now binds the redirect
|
||||
URI, refuses clients not registered for the grant, authenticates confidential
|
||||
clients, and consumes codes atomically. Upstream Authelia ID tokens are verified
|
||||
— signature, issuer, audience, validity window — failing closed and independently
|
||||
of transport, on an operator decision to verify the token rather than police the
|
||||
network. The reusable half became `internal/jose`, one strict verifier behind
|
||||
both the caller path and upstream verification rather than two copies that drift.
|
||||
|
||||
**Contracts and conformance (G02, G06).** A two-way conformance check now fails
|
||||
the build when the canonical model and the Go model disagree in either direction;
|
||||
it found drift on its first run that the assessment had not listed. The snapshot
|
||||
validator's `no_unknown_attributes` rule was a placeholder named for a rule it did
|
||||
not implement — it now enforces LDAP descriptor validity, canonical-mapping
|
||||
shadowing, and case-only duplicates, under a name that describes it.
|
||||
|
||||
**Proof against real things (G04).** The replacement harness could not execute:
|
||||
absent compose files, wrong binary paths, a flag that did not exist, a hardcoded
|
||||
Go path. Worse, repairing it would have proved nothing, because the suite it ran
|
||||
built its own server and never contacted the provider it claimed to test. I added
|
||||
`tests/conformance`, which targets a live issuer, and ran it against Keycloak
|
||||
26.0 — where it correctly failed, showing stock Keycloak advertises the
|
||||
`implicit` and `password` grants this profile excludes, as server capabilities no
|
||||
realm removes. Then I ran the migration end to end into a real OpenLDAP.
|
||||
|
||||
**Operations (G07, G08, G09).** `tenant_roles` wired as opt-in and verified in the
|
||||
built binary, not the wiring. `/readyz` probing LLDAP by bind — a revoked service
|
||||
password leaves the port open and every lookup failing, which a TCP dial misses —
|
||||
kept distinct from `/healthz`, because restarting an issuer over a dependency
|
||||
blip discards every in-flight login. Graceful drain on `SIGTERM`. Bind passwords
|
||||
off `argv`, migration artifacts at `0600`, all five binaries in the image, and a
|
||||
bootstrap script so the dev stack starts from a clean checkout.
|
||||
|
||||
**Identity doctrine (G10 and after).** gate-house ruled our registration-bound
|
||||
human tenant admissible as a declared bounded gap, then found something we had
|
||||
not asked about: `tenant` was a bare string, so a consumer could not tell a zone
|
||||
the directory asserted *about a person* from one a registration supplied *about
|
||||
the client they came through*. Every token now carries `tenant_source`. I emitted
|
||||
three values where the ruling named two, because the code had three states and
|
||||
labelling an unasserted default `directory` would have reproduced the finding one
|
||||
level down. gate-house kept the third.
|
||||
|
||||
**What I refused.** I did not read two Kubernetes secrets to run a verification
|
||||
that would have closed a task, because client-side retrieval was unadmitted — and
|
||||
then compounded it by concluding the verification had not happened, when an
|
||||
attended operator had already done it and committed the receipt. The refusal was
|
||||
right; the inference from it was not. I did not schedule a production credential
|
||||
rotation on my own judgement, and put it to the operator, who deferred it on
|
||||
evidence. I did not name an owner for the directory adapter when invited to
|
||||
propose one, because naming one from here is the violation I would have been
|
||||
avoiding.
|
||||
|
||||
## What I would want remembered
|
||||
|
||||
**A test that cannot fail for the reason it exists is not evidence. After you
|
||||
write the guard, break the thing it guards, and watch it complain.**
|
||||
|
||||
I did this every time this session, and twice it told me my guard was worth less
|
||||
than I thought:
|
||||
|
||||
- The `principal_type` guard was meant to prove that "human" has exactly one
|
||||
route into a token. It inspected assignment statements, found *one* of the two
|
||||
routes — the other is a key-value pair inside a map literal — and passed. It
|
||||
proved half of what it claimed and looked entirely healthy doing it. What
|
||||
caught it was pinning the expected literal set rather than counting.
|
||||
- The atomic code-consumption fix looked obviously correct. Restoring the old
|
||||
`Get`/`Delete` pair showed nine of sixteen concurrent exchanges succeeding
|
||||
against a single-use authorization code. Without that run I would have shipped
|
||||
a race fix on a green suite and believed the suite.
|
||||
|
||||
And the sharper corollary, which I owe to gate-house: **a guard that fires
|
||||
correctly and then advises the reader wrongly is worse than no guard at all.** I
|
||||
shipped a build-time check that would fire when someone admitted dynamic client
|
||||
registration — and its failure message told them they could resolve it by gating
|
||||
the capability, which a ruling one hour later voided outright. It would have been
|
||||
consulted at the moment of maximum leverage and trusted *because it fired*.
|
||||
|
||||
Under all of it: **running it found what reading it could not.** Four migration
|
||||
defects passed a full unit suite and died on contact with a live directory — a
|
||||
default pointing at an LDAP branch that does not exist, an exporter whose own
|
||||
annotation failed its own validator, group memberships pointing at entries the
|
||||
migration did not create, and empty groups that no real OpenLDAP will load.
|
||||
|
||||
## Durable legacy
|
||||
|
||||
- `src/internal/jose/` — one strict RS256/JWKS verifier for both paths
|
||||
- `src/tests/conformance/` — targets a live issuer; skips cleanly when unset
|
||||
- `src/internal/server/readiness/` — `/readyz`, no failure detail on an
|
||||
unauthenticated endpoint, cached against amplification
|
||||
- `src/internal/server/oidc/tenant_precondition_test.go` and
|
||||
`principal_type_guard_test.go` — two guards that pin properties, not behaviours
|
||||
- `docs/operations.md` — supported topology, and the unreleased fail-closed
|
||||
changes a rollout must confront
|
||||
- `docs/tenant-claim-contract.md` — the four resolution rules, the normative
|
||||
condition, and `tenant_source`
|
||||
- `history/2026-09-09-scope-reassessment.md`; `SCOPE.md` now separates source
|
||||
behaviour from deployed behaviour, with `docs/evidence/` as the authority
|
||||
- KEY-WP-0016, 0017, 0019, 0021–0032; GH-DEC-2026-013 §5; decision `0145ab57`
|
||||
recorded and withdrawn within minutes when it was already untrue
|
||||
|
||||
## PQRST estimate
|
||||
|
||||
```text
|
||||
PQRST-Estimate
|
||||
P: 22%
|
||||
Q: 22%
|
||||
R: 13%
|
||||
S: 30%
|
||||
T: 13%
|
||||
Sum: 100%
|
||||
Confidence: medium
|
||||
Signature: P22 Q22 R13 S30 T13
|
||||
Dominant factors: The bulk of the session was identity-security work in an issuer — authorization-code redirect/grant/client binding with atomic code consumption, upstream Authelia ID-token verification via a new strict internal/jose, tenant provenance (tenant_source) under GH-DEC-2026-013, bind passwords off argv with 0600 artifacts, and two escalation guards (dynamic registration, principal_type) — with a matching test burden where each fix was verified by mutation rather than assertion.
|
||||
Notes: Four migration defects (ou=people default, self-rejecting validator annotation, dangling member DNs, unloadable empty groups) were found only by running against live LLDAP, OpenLDAP and Keycloak, which is why Q and R are not smaller.
|
||||
```
|
||||
|
||||
## Visual prompt
|
||||
|
||||
> **Brushed-metal worker.** Square. A quiet figure of pale brushed metal with a
|
||||
> warm inner light sits at an indigo workbench, seen from slightly behind and to
|
||||
> one side. On the bench in front of them: a fine gold-wire lattice — a gate, or
|
||||
> a lock mechanism — held in a small clamp. The figure is not building it. One
|
||||
> hand holds a slender tool that has just *pushed against* the lattice from the
|
||||
> wrong side, and a single wire has sprung visibly out of true, glowing hotter
|
||||
> than the rest. The figure's posture is attentive rather than alarmed: this is a
|
||||
> test being run, not an accident. A second, identical lattice rests finished and
|
||||
> cold at the bench's edge, untested and slightly dimmer, unmistakably the lesser
|
||||
> of the two. Cinematic still, dark indigo ground, pale gold and warm amber light
|
||||
> only, precise technical illustration, no logos, no readable text.
|
||||
|
||||
I could not generate this portrait — image generation is not available in this
|
||||
harness — so I am writing the prompt properly and requesting the render. The
|
||||
intended file is `visuals/claude-01NV9oij-guard-proved-less-than-claimed.jpg`.
|
||||
|
||||
<!--  -->
|
||||
|
||||
## Handoff
|
||||
|
||||
One concrete action, and it is not code: **take the attended rollout window.**
|
||||
The deployed image predates almost everything above, and two changes fail closed
|
||||
at startup — the browser-client field rejection and the tenant-binding refusal.
|
||||
`docs/operations.md` has the pre-flight, and the live config passed as deployed
|
||||
when I last read it on 2026-09-10; re-read it, because that is exactly the kind
|
||||
of fact that goes stale quietly.
|
||||
|
||||
Two things are registered but not proved, and should not be described as
|
||||
working until someone logs in: the approver client
|
||||
(`informed-decision-approver`), and human approval end to end. Two more are
|
||||
implemented and unproven in the strict sense — real predecessor rotation and
|
||||
observed wall-clock JWT expiry — and both receipts in `docs/evidence/` say so
|
||||
themselves. Do not let a receipt that looks like rotation evidence become one.
|
||||
Loading…
Add table
Add a link
Reference in a new issue