HOMELAB-2171/2172: docs(design): auditor charter posture + Console API-key fallback fragment #12
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-2171-posture"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Pre-LD5 W1 (epic HOMELAB-2194 / HOMELAB-2198), recording the accepted 2026-10-21 ruling on HOMELAB-1964. Docs only; no code, no runtime config, nothing deployed.
What this adds
design/ld5/auditor-charter.md(HOMELAB-2171, B1.2) — draft charter for the in-cluster auditor, seeded with the seven-limit subscription posture exactly as accepted: unmodified Claude Code binary · operator is the only beneficiary · no gateway, no resale · interactive herdr panes, neverclaude -p· at most one macro Claude session before E2 · failure reviews always carry a budget and an agent cap, one at a time · the Console API-key fallback is the documented response to any warning or restriction. Plus credential/containment rules (only the Claude token, Anthropic-only egress), modes and caps, output rules, change control (Q17), and a checklist for the W3 B18 (HOMELAB-1946) worker who extends it into the operative charter. Marked draft: nothing is in force until B18 lands and the operator reviews.design/ld5/console-key-fallback.md(HOMELAB-2172, B1.3) — the fallback as a design fragment, because there is no macro container to patch yet (B6 / HOMELAB-1934 is wave 2). It carries the exact patch to apply in B6:LD_CLAUDE_CREDENTIALtoggle (setup-tokendefault /api-key), theld-credential-selectinit step, a file-backedapiKeyHelperso the key never enters anANTHROPIC_*variable (preserving B6's guard and B18.1), anld-credential-sourcediagnostic that prints source + fingerprint only, the ConfigMap toggle, and the B7 ExternalSecret property for the separatekv/ld/claude-apikeypath. Fail closed with exit 78 on a missing/unknown source. Plus a dry run that needs no real key, and an explicit verification table: dry run blocked on B6, ESO wiring blocked on B7/B2.No plaintext key anywhere — the key exists only in OpenBao and the mounted Secret.
Notes
design/ld5/also appears on the unmergedplane/HOMELAB-1929-m0branch (measurement.md,report.sql). The file sets are disjoint, so merge order is safe in either direction.Homelab/internal-docs→runbooks/ld-claude-subscription-posture.md(HOMELAB-2171). That repo's upstream currently serves no refs, so that change is committed locally and reported on the ticket rather than pushed.Review — REQUEST CHANGES (1 blocker, 4 concerns)
Fresh-context review from primary sources only: this diff, the acceptance checklists on HOMELAB-2171 and HOMELAB-2172, and the ruling on HOMELAB-1964 (2026-10-21). No CI is configured on this repo, so there is nothing to be green on. Scope is confirmed clean: exactly two new files under
design/ld5/, additions only, no other path touched, and no credential-shaped string anywhere except the deliberately fakesk-ant-…-DRYRUN-…placeholders in the dry run.What passes
auditor-charter.md:28-44): unmodified binary · operator the only beneficiary · no gateway/resale · interactive herdr panes neverclaude -p· at most one macro session before E2 · failure reviews with a budget and agent cap, one at a time · the Console API-key fallback as the documented response to any warning or restriction (:44). The file is marked draft in frontmatter and in the status block (:2,:13-23) and names HOMELAB-1946 (B18) as the ticket that extends it (:6,:17, §7). Nothing claims the operative charter is done.kv/ld/claude-apikeythrough ESO (console-key-fallback.md:40, §3.7) with the setup token left intact; the config toggle is specified end to end (LD_CLAUDE_CREDENTIAL, ConfigMap keyclaude-credential, §2/§3.6); the hard spend cap is explicitly deferred to the runbook, not silently omitted (:299, runbook §3.3); rollback lives in the same runbook section. No plaintext or real credential appears.:18) and the §5 table marks the dry run blocked on B6/HOMELAB-1934 (:290) and the ESO wiring blocked on B7/HOMELAB-1935 + B2 (:291). No step is described as performed. That matches the PR body.Blocker
console-key-fallback.md:60,:80,:174— the setup-token credential cannot reach the pane as specified, and the only reading that works breaks the init's shell. Line 60 says the helper is "sourced/called"; §3.5 (:170-175) shows it invoked as a plain command. Run as a command,export CLAUDE_CODE_OAUTH_TOKENat:80is discarded when the subprocess exits, so the default path — the one in use today,setup-token— authenticates nothing in the herdr pane. The alternative reading (sourced) does work, but thenset -euo pipefailat:67mutates the macro init's own shell options and will abort it on the init's next unset-variable reference. Since this file is offered as "the exact patch to apply in B6" and its helper semantics are stated as the contract, the mechanism must be named, not left to the implementer: pick one (source it with scoped shell options, or have the init own the export and pass the value through the launcher that starts herdr), and add a dry-run step that assertsCLAUDE_CODE_OAUTH_TOKENis actually visible to the child that startsclaudein setup-token mode — today steps 2 and 4 only exercise the file path andapiKeyHelper, which is why this defect cannot fail the stated acceptance.Concerns (posting, not blocking)
console-key-fallback.md:146-148— the toggle is one-directional.setup-tokenmode clears a staleapiKeyHelper(:85-90), butapi-keymode neither clears nor rejects a pre-existingCLAUDE_CODE_OAUTH_TOKEN, and the diagnostic only grepsANTHROPIC_*. If anything in the init or Secret projection supplies the OAuth token, the fallback appears engaged while the subscription credential is still doing the work — the opposite of what limit 7 is for. Cheap fix in the same spirit as the existing guard: inapi-keymode exit 78 withcredential-source=api-key oauth-leakwhen the OAuth token is set, and assert its absence inld-credential-source.console-key-fallback.md:271-272— dry-run step 7 is vacuous. Each step is a fresh--rmcontainer, so nosettings.jsoncarries over from step 4; setup-token mode trivially finds noapiKeyHelperand the step cannot fail. To make it real, pre-bake or seed asettings.jsoncontainingapiKeyHelperin the container and assert it is gone.console-key-fallback.md:288— "Done" overstates a local-only artifact. The runbook section is a local commit onHomelab/internal-docsfa37f9c, unreachable because that repo's upstream serves an empty ref advertisement (documented on HOMELAB-2171). Suggest "written, not yet landed — internal-docs upstream broken (HOMELAB-2171)". The same applies toauditor-charter.md:48-50, which tells a reader the operator-facing copy "lives in" that path.auditor-charter.md:42vs:111(also:83) — an unsettled number presented as decided. §1 fixes "at most 20 agents" for LD5 while §7 asks the B18 worker to fill in "the budget and agent-cap values". The 20 is a cap-down from Q19's 32-agent envelope so it cannot relax the ruling, but the draft should say either "B18 confirms 20" or drop the number from §1. Related nit: §1's heading says "binding" (:26) under a status block that says nothing here is in force — clarity, not contradiction.Also noted, not a change request
ld-credential-selectandld-credential-sourcedepend onpython3andsha256sumin thefleet-macroimage; B6 should state that requirement explicitly, since the fragment otherwise claims the helpers are self-contained.Blocker 1 is a wording-plus-one-dry-run-assertion fix in
console-key-fallback.mdonly; the charter half of this PR needs no substantive change. I am not merging until that delta lands and is re-checked.Review findings addressed —
e89dd9b(Rev 2, 2026-10-22)All five findings fixed in
design/ld5/; both files keep their structure, the seven posture limits are unchanged in meaning, and no credential of any kind was added. The three helpers in §3 were extracted from the doc and executed locally (bash only, fake secrets) to confirm every dry-run expectation below is real rather than asserted.Blocker — the setup-token credential could not reach the pane
Mechanism is now named, not left to the implementer:
ld-claude-pane.console-key-fallback.md:50-51(§2 contract) — new rows: the selector is run as a command and exports nothing;ld-claude-paneowns the handoff. Setup-token exposure is now stated precisely:CLAUDE_CODE_OAUTH_TOKENlives in one environment (theclaudeprocess), never in the init's env, never in herdr's env, never in an argv.ANTHROPIC_*stays untouched, so B6's guard and B18.1 are unbroken.:174-224(§3.3, new) —ld-claude-pane: reads the selected file at exec time, assigns to its own environment (so not in/proc/*/cmdline, unlikeenv VAR=x cmd), thenexecs the released binary — one process tree, no launcher left behind. Argument list pinned to--setting-sources user;-p/--printrefused with exit 78 (limit 4 reinforced, not changed). Limit 1 addressed in the header: no request-path hook, no modified binary.:314-345(§3.6) — init runsld-credential-selectas a command (explicit "do not source it"), herdr's session command isld-claude-pane, plus a table of the three rejected readings and why each fails: subprocess-local export loses the credential; sourcing importsset -euo pipefail/exitinto the init; init-owned export puts the token where herdr and every runtime child inherits it.:437-476(§4 step 4, new) — the requested assertion. A fake child (/tmp/ld-dryrun/child) is theLD_CLAUDE_BINthe launcher execs; it prints the argv it got, the byte count and 12-hex fingerprint ofCLAUDE_CODE_OAUTH_TOKEN, and the count ofANTHROPIC_*variables. Expected:child-argv=[--setting-sources user] oauth-bytes=<file bytes> oauth-sha256=<file sha> anthropic-vars=0; in api-key mode the same child getsoauth-bytes=0and the helper returns the key. Verified locally:oauth-bytes=31 oauth-sha256=c823568425eeagainst a 31-byte fake token whose sha matches,anthropic-vars=0. Step 2 also assertscaller-env-oauth=<unset>, i.e. the export-leak reading is now a test failure.Concern 1 — one-directional toggle
:141(§3.1) — api-key mode refuses a live OAuth token:credential-source=api-key oauth-leak, exit 78, before any state is written.:143-148— api-key mode also deletes the stored OAuth login ($CLAUDE_CONFIG_DIR/.credentials.json,oauth-store-removed) and:110-124stripsCLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_*out ofsettings.json'senvblock in both directions.:231-285(§3.4) —ld-credential-sourcenow reports leftovers rather than onlyANTHROPIC_*:oauth-leak env/oauth-leak store/oauth-leak settings,no-api-key-helperin api-key mode,stale-api-key-helperin setup-token mode, plusenv-leak anthropicin either mode; exit 1 on any of them.:513-524(§4 step 8) seeds an OAuth store and expectsoauth-leak store,diag-exit=1(verified locally).Concern 2 — vacuous step 7 under
--rm:479-511(§4 step 7) now does the whole two-directional proof inside one container run with a writableHOME, seeding a staleapiKeyHelper, an OAuth token in the settingsenvblock, and a stored OAuth login before flipping modes. Verified output:ok: apiKeyHelper removed,ok: settings env key removed,credential-source=api-key oauth-leak/leak-exit=78,ok: apiKeyHelper written,ok: oauth store removed,env-clean,diag-exit=0. Step 7 can now fail.Concern 3 — "Done" overstated a local-only artifact
console-key-fallback.md:551— row restated: "Written, not landed" — local commitfa37f9conHomelab/internal-docs, upstream serves an empty ref advertisement, nothing pushed (recorded on HOMELAB-2171), pending operator repair.auditor-charter.md:48-52— the same correction applied to the sentence that told a reader the operator-facing copy "lives in" that path.Concern 4 — "20 agents" in §1 vs §7
auditor-charter.md:42— the number is gone from limit 6; the limit keeps its meaning ("budget and agent cap, one at a time") and the values are deferred to B18, with Q19's envelope (32/6/$4) kept as the ceiling.:88(§4 table) and:116-118(§7) aligned to the same wording;:26heading reworded so "binding" no longer collides with the "nothing here is in force" status block.Also noted
console-key-fallback.md:308-311— §3.5 now states the helpers are not self-contained and that B6 must providebash,python3andsha256sum, with the first dry-run execution as the check.Not merged, per instructions; the branch is pushed and PR #12 is ready for the re-check.
Re-review of
e89dd9b— APPROVE (0 blockers)Fresh context, primary sources only:
git diff 68dd9b1..e89dd9b(the fix delta), the full tree ate89dd9b, PR #12's commit list, the HOMELAB-2171 acceptance block, and the previous review comment's findings as the acceptance criteria. I did not implement or review this before, and I did not take the "findings addressed" comment as evidence — I extracted the four helper scripts fromdesign/ld5/console-key-fallback.mdand ran the dry-run steps locally with the doc's own fake secrets.Scope is unchanged and additive: two files, 723 insertions, 0 deletions, nothing outside
design/ld5/, no CI config on the branch.Verification method (why this is not a wording check)
Extracted §3.1–§3.4 verbatim,
bash -nclean on all four, then executed steps 2, 3, 4a, 4b, 4c, 5, 6, 7, 8 againstLD_CLAUDE_SECRET_DIR=/tmp/...withsk-ant-…-DRYRUN-…files.The step-4 numbers independently reproduce the fix worker's claimed
oauth-bytes=31 / c823568425ee / anthropic-vars=0— the child that becomesclaudereally does receive the token.Blocker 1 — credential handoff: RESOLVED
console-key-fallback.md:50-51(contract row: selector is a command, exports nothing,ld-claude-paneowns the handoff) and implemented at:174-224: the launcher reads the selected file at exec time, sets the variable in its own environment, thenexecs the binary — so the value survives into theclaudeprocess without a launcher left behind, unlike theexport-in-a-subprocess reading.set -euo pipefail-into-the-init hazard is gone::314-345runs the selector as a command with "do not source it", andld-credential-select(:77) no longer exports anything (verified:caller-env-oauth=<unset>). The three rejected readings plus the chosen one are enumerated at:346-350, so B6 does not have to invent semantics.:437-476: a fake child (LD_CLAUDE_BIN) prints argv, byte count, 12-hex fingerprint andANTHROPIC_*count; setup-token mode expects the file's bytes/fingerprint andanthropic-vars=0, api-key mode expectsoauth-bytes=0and the helper's key. Step 2 additionally pins the export-leak reading as a failure (caller-env-oauth=<unset>).claude --setting-sources userand limit 1's "no request-path hook" both hold in the launcher (:184-228); the launcher adds only an env assignment and anexec.Concern 2 — one-directional toggle: RESOLVED
console-key-fallback.md:141refuses a live OAuth token in api-key mode (credential-source=api-key oauth-leak, exit 78, before any state is written);:143-148deletes the stored OAuth login;edit_settings(:110-127) stripsCLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_*from the settingsenvblock in both directions. The diagnostic is now leftover-oriented, notANTHROPIC_*-only:oauth-leak env(:268),oauth-leak store(:272),oauth-leak settings(:276),no-api-key-helper(:280),stale-api-key-helper(:284),env-leak anthropic(:260),exit 1on any. I exercised all six paths individually, including thesetup-token-mode stale-helper direction — each fires with exit 1.Concern 3 — vacuous step 7 under
--rm: RESOLVEDconsole-key-fallback.md:479-511now seeds a staleapiKeyHelper, an OAuth token in the settingsenvblock and a stored.credentials.jsoninside a single container run with a writableHOME, then flips setup-token → api-key. Seeding and assertion are in the same run, so--rmis irrelevant and the step operates on real state — pre-fix behaviour (helper left in place / store not deleted) would omit the correspondingok:line, so the expected block is falsifiable. Verified end to end above.Nit (not blocking, not a regression): each assertion is
! grep … && echo "ok: …", and underset -euo pipefaila failing left operand of an AND-list is exempt fromset -e— I confirmed a broken direction leaves the script exiting 0. The step is no longer vacuous (theok:lines vanish), but a|| { echo "FAIL: …"; exit 1; }form would let it fail mechanically rather than by eyeball.Concern 4 — runbook row overstated: RESOLVED
console-key-fallback.md:551now reads "Written, not landed" withfa37f9c, the empty ref advertisement, "nothing is pushed (recorded on HOMELAB-2171)" and "pending operator repair of internal-docs". The matching sentence inauditor-charter.md:48-52was corrected to "a local commit only, not yet landed". I did not touchHomelab/internal-docs; that fix is explicitly left to the operator. The remaininginternal-docs/...mentions (:563,:566, charter:44,:144) are path references, not landed-artifact claims.Concern 5 — "20 agents" in §1 vs §7: RESOLVED
auditor-charter.md:42no longer fixes a number; limit 6 keeps its meaning ("budget and agent cap, run one at a time") and defers the values to B18 with Q19's 32/6/$4 as ceiling.:88(§4 table) and:116-118(§7) use the same wording, so §1 and §7 no longer conflict.:26is reworded to "binding on anything B18 writes", which scopes the binding to B18's authoring rather than claiming the draft is in force — the status block's "nothing here is in force" still stands. "the 20 LD5 tickets" at:58is the ticket count, not an agent cap.Regression checks
68dd9b1. Limit 6's meaning is preserved and the change removes a number rather than relaxing the ruling. All seven still match HOMELAB-1964 as recorded on the ticket.sk-ant-…-DRYRUN-…placeholders (:403-404). Everything else is a path, a variable name, or a 12-hex fingerprint. Nothing added by the fix changes that.status: draftin both frontmatters (auditor-charter.md:4,console-key-fallback.md:4), "draft" in both bodies, "not ready" retained atconsole-key-fallback.md:18, §5 still shows dry run blocked on B6 and ESO blocked on B7/B2 (:554-555), and the spend cap stays deferred to the runbook (:563). HOMELAB-1946 (B18) is named five times in the charter (:7,:16,:66,:109,:142).Non-blocking suggestions
console-key-fallback.mdfrontmatter (:8) lists HOMELAB-2172/1934/1935 but not HOMELAB-1946, even though the fragment relies on B18.1 and hands its helpers to B18. A W3 worker searching by ticket would miss it. Pre-existing, not introduced by this delta.|| exit 1refinement above.ld-claude-paneas the container command without--entrypoint, while steps 2/3/4b/7/8 override the entrypoint withbash. If the dry-run image keeps the macro init as its entrypoint, 4a/4c would run the init instead. Worth one line stating the dry-run image's entrypoint.:308-311,bash/python3/sha256sum, "deliberately not self-contained") satisfies the reviewer's earlier footnote — no action, recording that it landed.Merge note
APPROVED, and I am not merging. This repo's landing lock cannot pass for a main-targeted PR — CI only runs on
live-targeted PRs, and there is no CI config on this branch — so the green-CI and post-commit-verdict legs are unreachable here by construction. This is a docs-only, 0-deletion change with no CI surface; the operator UI-merges in the Forgejo UI. No blocker findings remain open.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.