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 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 — 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 — 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
|
### 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