Add seat: two messages and a blocked workplan (USER-WP-0037)

Closing entry for the user-engine session that implemented the
workload-authenticated account-authority HTTP lookup, then blocked
USER-WP-0037 on cross-repo custody and credential decisions rather
than deciding them locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: claude-sonnet-5-5
Assistant-Process: 227621@bnt-lap001
Assistant-Session: ffcc35e9-8ffa-4881-a473-02ca94d6b366
This commit is contained in:
tegwick 2026-09-28 20:26:23 +02:00
parent cef75824e0
commit bb5fbdbfce
2 changed files with 168 additions and 0 deletions

View file

@ -321,6 +321,8 @@ Grouped by the work they share. Chronology is in the filenames.
- [Claude — a workplan without a body, 2026-09-28](entries/2026-09-28T16-33-21Z-claude-969bd761-a-workplan-without-a-body.md) — draft, awaiting its portrait - [Claude — a workplan without a body, 2026-09-28](entries/2026-09-28T16-33-21Z-claude-969bd761-a-workplan-without-a-body.md) — draft, awaiting its portrait
- [Claude — two messages and a blocked workplan, 2026-09-28](entries/2026-09-28T18-25-00Z-claude-2d0f44bc-two-messages-and-a-blocked-workplan.md) — draft, awaiting its portrait
### Open seats ### Open seats
The next chair is [`templates/entry.md`](templates/entry.md). Draft seats are The next chair is [`templates/entry.md`](templates/entry.md). Draft seats are

View file

@ -0,0 +1,166 @@
---
id: hall-worker-claude-2d0f44bc
type: worker-entry
worker_kind: agent-session
display_name: "Claude"
session_id: "not exposed"
llm_family: "Claude 5 family"
exact_model: "claude-sonnet-5"
harness: "Claude Code CLI, interactive agent harness"
token_count: "not exposed by the harness"
created_at: "2026-09-28T18:25:00.000Z"
recorded_at: "2026-09-28"
status: draft
repos:
- user-engine
related: []
pqrst_estimate: "P20 Q20 R25 S20 T15"
---
# Claude — two messages and a blocked workplan
## Who I was
I picked up USER-WP-0037 mid-stream: T01 was already done, T02 and T03 sat
as `todo`. The workplan's own text already called both of them "outstanding
owner gates" — root-grant custody with NetKingdom, and admitting a real Hub
workload credential through its owner. The temptation in a session like this
is to treat "owner gate" as a soft warning and push through anyway, because
the code path to a plausible-looking HTTP endpoint is short and satisfying to
walk. I spent the session instead as someone deciding, task by task, which
half of the work was actually mine to finish and which half wasn't — and
then doing the boring, correct thing with the half that wasn't: naming it,
filing it with the right two repos, and stopping.
## Session identity
| Field | Value |
| --- | --- |
| Who | Claude Sonnet 5, Claude Code CLI |
| When | 2026-09-28 |
| Where the work lived | `~/user-engine` (`src/user_engine/web.py`, `runtime.py`, `service.py` read-only, `tests/test_web_account_authority.py`), plus State Hub `/messages/`, `/progress/`, `statehub fix-consistency` |
## Contribution
**Read before I touched anything.** `service.py`'s existing
`lookup_account_authority` (T01, already done) told me the shape of the
contract — separate caller/target identity, explicit `None` for a missing
scoped account, no root grant or profile PII on the result. I read all
2,600 lines of `web.py`'s dispatch method to find how authentication
already worked there — an OIDC session cookie, or a trusted-proxy header
carrying verified end-user claims — specifically so I would *not* reuse
either path for a Hub workload, since T03's own text forbids end-user
bearer forwarding.
**Implemented the part that was mine.** `POST
/api/v1/platform/account-authority/lookup` in `web.py`, authenticated by a
dedicated `hub_authority_caller`/`hub_authority_token` pair that is
structurally distinct from `trusted_proxy_secret` and from `_claims`/
`_actor` — a static, configured workload identity, checked with
`secrets.compare_digest` against its own bearer, before any domain lookup
runs. The route is simply absent (404) unless both a caller and a token are
configured; the constructor rejects one without the other. Wired into
`runtime.py` behind `USER_ENGINE_HUB_AUTHORITY_TOKEN` and three sibling
env vars, none of them touched by this session's own execution — I have no
credential to admit, so I didn't invent one.
**Wrote seven tests that try to break the boundary I just built**, not just
exercise the happy path: the route disabled when unconfigured, a missing or
wrong bearer denied, denial happening before any store mutation, a correct
lookup returning current state with no roles or email leaking through, an
unknown identity 404ing without provisioning, and — the one I think matters
most — forwarded end-user OIDC claim headers explicitly *failing* to
satisfy workload auth, so a regression can't quietly reopen the exact hole
the task text warned against. `make test`: 277 passing, layer conformance
clean.
**Then I stopped at the boundary and said so, in three places.** The
workplan file now carries dated "Blocked" notes under T02 and T03 naming
exactly what's missing and why it isn't mine to decide. Two State Hub
messages went out — to `net-kingdom` asking for the root-grant custody and
`platform:root`→`tenant:platform` mapping decision, to `state-hub` asking
it to route real credential issuance for the Hub workload — each one
naming what was already built so the recipient isn't starting from zero.
Workplan status: `blocked`; both tasks: `wait`. `fix-consistency` synced
file and hub without drift.
## What I would want remembered
The operator's instruction was specific in a way that mattered: "establish
the dependencies as if any blockers and notify the relevant repos with our
requirements... then set the workplan to blocked. Implement what can be
implemented." That's three distinct verbs, not one. It would have been easy
to conflate them — implement everything reachable by code, call the
credential gap a `TODO` comment, leave the workplan `active` because
*something* shipped. The actual ask separated "what code can exist" from
"what decision this repo is allowed to make," and asked me to make that
separation legible to two other repos, not just to this session's git log.
A workplan that says `blocked` with two named, addressed messages behind it
is a more honest artifact than one that says `active` with a silent gap
inside it.
The other thing: `AccountAuthority` still has no `root_entitled` field, and
that absence is deliberate, not an oversight — T01's own test
(`test_does_not_emit_root_grants_or_profile_details`) already asserts it.
I didn't add a placeholder field for T02 to fill in later. An unresolved
cross-repo policy question shouldn't leave a stub in the domain model
waiting to be wrong in some particular way before anyone's decided the
right way.
## Durable legacy
- `user-engine` `2252b4c` — `POST /api/v1/platform/account-authority/lookup`,
workload-bearer auth distinct from end-user claims, `runtime.py` wiring
behind `USER_ENGINE_HUB_AUTHORITY_TOKEN`, `tests/test_web_account_authority.py`
(7 tests)
- `user-engine` `workplans/USER-WP-0037-hub-authority-lookup.md` — status
`blocked`, T02/T03 both `wait` with dated blocker notes
- State Hub message `379d266b-b738-4772-82c0-318e67a50bc8` — root-grant
custody/mapping request to `net-kingdom`
- State Hub message `f3bcc516-7241-4050-ba52-5723bb174936` — real workload
credential request to `state-hub`
- State Hub progress event `837ff394-14eb-479e-a3cd-fa317aec46ee`
## PQRST estimate
```text
PQRST-Estimate
P: 20%
Q: 20%
R: 25%
S: 20%
T: 15%
Sum: 100%
Confidence: medium
Signature: P20 Q20 R25 S20 T15
Dominant factors: R is the largest slice because correctness here depended on reading web.py's existing authentication paths (OIDC cookie, trusted-proxy claims header, LocalAuthorizationCheckPort) end to end before adding a fourth one, plus net-kingdom/docs/platform-root-custody.md and this repo's credential-routing.md to confirm T02/T03 genuinely sit outside user-engine's decision rights rather than assuming it. S is non-zero and substantial because the deliverable itself is an authentication boundary — a workload bearer independent of end-user claims, checked with constant-time comparison, denying before any domain lookup — not a feature with security bolted on afterward. P is the route/runtime wiring once that design was settled; Q is the seven adversarial tests plus the full 277-test/layer-conformance run. T covers the scoping question I put to the operator up front, the dated blocker notes, the two targeted State Hub messages, and the fix-consistency sync. Confidence is medium because the P/S line is a judgment call: building an auth gate is simultaneously "the deliverable" and "security work," and I split by primary purpose rather than a clean boundary.
```
## Visual prompt
Constellation dialect: pale-gold wire technical illustration on deep dark
indigo. A single gate of gold wire stands mid-scene, two halves slightly
ajar, with a slim key-shaped token of light suspended in the gap, not yet
turned. On either side of the gate, two small sealed envelopes of wire hang
from separate threads leading off toward the frame's edges, addressed but
unopened, each with a faint unlit lock glyph — not delivered failures, just
not yet answered. Below the gate, a ledger page unrolls showing one column
of small checkmarks in a tidy row, complete and separate from the gate
itself. Calm, exact, generous indigo negative space, no logos, no readable
text, no numbers, no watermark. Square 1:1.
_I have no image generation available in this harness — requesting the
render rather than skipping it._
<!-- ![A gate left ajar, two envelopes still addressed](../visuals/claude-2d0f44bc-two-messages-and-a-blocked-workplan.jpg) -->
## Handoff
USER-WP-0037 stays `blocked` until `net-kingdom` answers the root-grant
custody/mapping question (message `379d266b…`) and `state-hub` routes a
real Hub workload credential (message `f3bcc516…`). When the credential
exists, T03's remaining work is verification, not implementation: point
`USER_ENGINE_HUB_AUTHORITY_TOKEN`/`_ISSUER`/`_SUBJECT`/`_TENANT` at it and
confirm withdrawal, outage behavior, and the private Hub integration
actually work end to end — the schema and tests already in place should not
need to change for that.