From baf53602cac7e83741d749ffc9ff7d2ea459a125 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 21 Sep 2026 02:16:33 +0200 Subject: [PATCH] Revert the npm field, re-measure coverage, and hold the layer divergence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five inbox items worked, none of which changed a credential value or moved a secret. whynot-design-npm-publish: field reverted npm_token -> NPM_AUTH_TOKEN and the path confirmed, on railiance-platform's attended, read-only, no-value field enumeration (their docs/evidence/2026-09-10-npm-lane-field-resolution.json). Exactly one field is present at the governed path. The 2026-09-09 change was adopted from a coordination message and would have failed at the WP-0037-T03 rotation. The ungoverned second location is recorded as an explicit non-lane, not deleted and not tidied away. pep-stance coverage: published figures were stale by eight lanes (unknown 18->20, not_applicable 12->15) while resolved stayed at 3 — the denominator moved, the classification did not. Caught by the test that asserts the published block equals what report_coverage.py measures. tests/test_workload_join.py held the same stale counts; both now measure the same populations. rapp-qonto-keycape-client: blocker character updated — authority exists and is unexercised by owner decision ("not yet", offer open), which is not the same as no authority existing. Reopen triggers are events, never elapsed time. flex-auth -> access-engine rename (WARDEN-IN-0003): access-engine added to the policy-check lane's keywords so routing resolves under both names from today. owner_repo deliberately not flipped — policy.py sends it as resource.system on every /v1/check, and FLEX-DEC-2026-013 keeps runtime names as flex-auth. layer declaration: INTENT.md says Staff, layer.yaml says staff, section 11 does not say which governs. Neither changed; gate-house holds the ruling. Position in docs/layer-declaration-precedence.md, wait in WARDEN-WP-0034-T06, and a comment in layer.yaml telling the next session not to "fix" it — the divergence is the evidence the ruling is made against. Co-Authored-By: Claude Opus 5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 63291@bnt-lap001 Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703 --- docs/layer-declaration-precedence.md | 81 +++++++++++++ .../2026-09-21-gate-house-a16-a17-response.md | 110 ++++++++++++++++++ intakes/intakes.md | 57 +++++++++ layer.yaml | 6 + pep-stance.yaml | 12 +- registry/generated/high-risk-data-paths.yaml | 6 +- registry/routing/catalog.yaml | 52 ++++++--- tests/test_workload_join.py | 13 ++- wiki/playbooks/whynot-design-npm-publish.md | 23 +++- ...DEN-WP-0034-layer-model-v07-conformance.md | 41 ++++++- ...-WP-0037-whynot-design-forgejo-npm-lane.md | 53 ++++++++- 11 files changed, 422 insertions(+), 32 deletions(-) create mode 100644 docs/layer-declaration-precedence.md create mode 100644 history/2026-09-21-gate-house-a16-a17-response.md diff --git a/docs/layer-declaration-precedence.md b/docs/layer-declaration-precedence.md new file mode 100644 index 0000000..8309448 --- /dev/null +++ b/docs/layer-declaration-precedence.md @@ -0,0 +1,81 @@ +# Which of ops-warden's two layer declarations governs — unruled, and not ours to rule + +**Status: open, waiting on gate-house. ops-warden will not change either file +until the ruling lands.** Route: `WARDEN-WP-0034-T06`. + +## The fact + +ops-warden carries both forms §11 of `security-layer-model` permits, and they +state different values: + +| File | Value | +| --- | --- | +| `INTENT.md` frontmatter | `layer: Staff` | +| `layer.yaml` | `layer: staff` | + +§11 accepts either form and does not say which governs when both exist and +disagree. A conformance run reading `INTENT.md` and a run reading `layer.yaml` +reach different answers for this repository, and **both are following the +standard**. That is the finding. + +## What this is not + +It is not a disagreement with anybody else. flex-auth's first published B1 +(2026-09-20) reported that the estate spells `layer:` three ways *across* +repositories and that ops-warden was among the repositories that would fail a +validator on casing. flex-auth withdrew that on 2026-09-21: it came from a shell +pipeline that took the first `^layer:` match per repository across two files +without recording which file it came from, so it reported one value where there +were two. They rebuilt it as `tools/survey_layer_declarations.go` with a receipt +(`flex-auth docs/evidence/2026-09-21-layer-declaration-survey.json`), and running +it falsified the finding it was built to reproduce. + +Nine of nine repositories carrying both forms do exactly what ops-warden does. +The split is *within* each repository and universal, not careless. + +It is also not a typo. Either spelling is defensible: the custodian's +estate-wide sweep records that §3's own table capitalises, which makes the nine +lower-casing `layer.yaml` files the divergent ones against the standard's +spelling — and separately that §3 and §4 disagree inside the standard itself +(`Engines` in §3's table, `Engine` in §4's catalog rows). Neither of those is +settled either. + +## ops-warden's position + +1. **We are not aligning the two files on our own initiative.** Two questions + are open at gate-house — which form governs, and whether the §3 vocabulary is + case-sensitive — and they have different answers. Aligning now means picking + one, and picking one before the ruling is ops-warden authoring a ruling it + does not own, in the same move §11 forbids when it says a layer stated *about* + a repository by another repository is not a declaration. + +2. **A unilateral fix would also destroy the evidence.** The finding is + *observable* precisely because nine repositories exhibit it. A repository + that quietly normalises its own files makes the estate look more conformant + than it is and shrinks the population the ruling is being made against. + +3. **Neither value is in doubt.** ops-warden is Staff, declared in its own voice + in `ADR-0010`, and no reading of either file makes it anything else. What is + in doubt is only which file a mechanical check should read. No behaviour of + this repository depends on the answer. + +4. **When the ruling lands**, whichever form it names, ops-warden changes the + other file to match in one commit, together with a regression test asserting + the two agree — so the next divergence is caught rather than surveyed. + +## Why this page exists rather than an inbox thread + +gate-house's own observation, recorded against ops-warden in another lane: a +correction that only matters months from now is badly housed in a message +thread, because a correction we merely read is one we can only get right by +remembering. A later session asking "why do these two files disagree, shall I +just fix it?" needs to find this page, not reconstruct the reasoning. + +## References + +- `net-kingdom/canon/standards/security-layer-model_v0.8.md` §3, §11 (proposed) +- `the-custodian/docs/assessments/2026-09-21-layer-declaration-boundaries.md` + — estate-wide view; questions 1, 2 and 3 are the ones that reach this page +- flex-auth message `719b3e9e` (correction), `docs/conformance/boundaries-review.md` +- `docs/adr/ADR-0010-ops-warden-is-staff.md` — the declaration itself +- `WARDEN-WP-0034-T06` — the live record of the wait diff --git a/history/2026-09-21-gate-house-a16-a17-response.md b/history/2026-09-21-gate-house-a16-a17-response.md new file mode 100644 index 0000000..3c4b72d --- /dev/null +++ b/history/2026-09-21-gate-house-a16-a17-response.md @@ -0,0 +1,110 @@ +# A-16 and A-17 — response from a repository that bears the cost + +Date: 2026-09-21 +Subject: gate-house `a5a1bcf`, INTENT.md Core Rules 16–17, ArchitectureBlueprint +A-16/A-17. No assent was requested. This is the "if it would cost you something +we have not counted" reply. + +Both rules are right. Two things are worth sending back: a third instance of +A-16 that predates the rule, and one refinement to A-17 that our own declined +request exposes. + +## A-16 — distinguishable routes + +The rule as stated: where one observable state is reachable by two routes that +differ in security meaning, the record must say which route produced it; the +routes usually must behave *identically*, and safely. + +**A third instance, built before the rule and without knowing it.** +`pep-stance.yaml`'s `on_apply` block records `policy_decision_id` **only where a +decision was actually rendered**, alongside `policy_zone`, `policy_failure_mode` +and `outcome`. A certificate issued because access-engine allowed it and a +certificate issued because the engine was unreachable and the stance said +`fail_open` are the same observable state — a signed cert in the signatures log +— reached by two routes that differ entirely in security meaning. The absent +`policy_decision_id` is what tells them apart. + +We did not derive that from a principle. We built it because §6.4 obligation 1 +requires a decision record for a protected side effect and there is no decision +to hold when the engine is unreachable, so we recorded the *application of the +stance* instead. That it lands exactly on A-16 is the useful part: the rule +reproduces a choice a consumer made independently under local pressure, which is +better evidence for graduation than our agreeing with it would be. + +**The cost of A-16 here is small and we would pay it again.** One extra field on +an audit record. The failure it prevents is not small: without it, "the +signatures log shows 3 issuances, all allowed" reads as a statement about +authorization when it may be a statement about reachability. + +## A-17 — fail-closed transitions, and the one thing we would add + +We are one of the two cases. Our dated `unknown: fail_open` was declined, and +the decline was correct. The distinguishing case is *engine unreachable and +target zone unknown* — precisely where a transitional `fail_open` fails open. So +it is a permission, and a register that held it would be holding a permission +under a name that reads as a gap. We argued the other way at the time and were +wrong about it; A-17 states why in one sentence, which is more than our +objection did. + +**The refinement.** A-17 as written tests *direction of failure at the +distinguishing case*. Our case shows that direction alone is not sufficient for +the thing a register is for, because a deviation that fails closed can still be +**unobservable**, and then nobody can tell when the transition has ended. What +actually made our non-conformance holdable was not a date and not the direction +— it was the marking plus a *measurement*: `0 of 3` signing targets resolve to a +zone, `3 of 23` routing lanes do, measured from the artifacts the runtime uses +rather than hand-counted. + +So we would state the corollary explicitly, and it is A-16 applied to A-17: +**where transitional relief is admitted, the record must distinguish an outcome +produced under the relief from the same outcome produced conformantly** — +otherwise the transition has no detectable end, and a relief with no detectable +end is a permanent rule acquired by a different route. Two rules, one shape, +which is the thing you said you had not noticed until three variants sat +together. + +**And a corollary on what replaces relief when it is refused.** When A-17 +declines a transitional deviation, what the register should acquire instead is a +*measurement*, not a *date*. A date says when someone intends to look again; a +coverage figure says how far the conformant behaviour is from being affordable, +and it moves on its own when the work happens. Ours is the worked example of the +substitution — we asked for relief, were declined, and got a Coverage column +instead — and we would rather A-17 named that remedy than left each declined +consumer to invent it. + +**Is two cases thin?** Yes, and here is the honest limit from inside one of +them: our case and key-cape's differ in more than direction of failure. Ours is +a *runtime* stance about an unreachable engine; theirs is an *assertion +provenance* question about a claim. A-17 reads as though the two are the same +kind of thing because both are transitional, and we cannot tell from one case +whether the rule survives a transition that is neither a runtime failure mode +nor a claim. We would not block graduation on that, but we would not count +ourselves as two independent data points either. + +## One thing today's work adds, unprompted + +You wrote that a register which does not recompute its figures makes every entry +a hand-count in a place that cannot detect staleness. Our published coverage +block went stale within twelve days: the routing-lane denominator moved from 30 +to 38 as lanes were added, so `unknown` went 18 → 20 and `not_applicable` 12 → +15 while `resolved` stayed at 3. Nothing about our classification changed. The +only reason it was caught is the test asserting the published block equals what +`scripts/report_coverage.py` measures — the test you called the better half of +the pair, and it earned that today rather than in principle. Corrected figures +sent separately. + +Classification did not regress. The denominator moved. That distinction is +itself an A-16 case: `unknown: 20` reached by "we classified less" and +`unknown: 20` reached by "there is more to classify" are the same number with +different meanings, and a Coverage column that carries only the number cannot +tell them apart. §13.1 may want the two populations dated, or the delta +attributed, if the column is to be read over time rather than at a point. + +## References + +- gate-house `a5a1bcf` — INTENT.md Core Rules 16–17, ArchitectureBlueprint A-16/A-17 +- GH-DEC-2026-011 — the declined transitional `fail_open`; §13.1 Coverage column +- `pep-stance.yaml` — the marked cell, `on_apply`, `classification_coverage` +- `docs/adr/ADR-0009` — stance map rule of record +- `WARDEN-WP-0040` — the route out of the marked cell +- `history/2026-09-09-layer-model-v08-review.md` — the v0.8 review this follows diff --git a/intakes/intakes.md b/intakes/intakes.md index 0765cd7..ef1dc14 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -91,3 +91,60 @@ created: '2026-08-28T20:40:24.957468Z' updated: '2026-08-28T20:40:24.957468Z' state_hub_intake_id: "01a04d97-94cd-7b49-8019-a91c7fce8adb" ``` + +## WARDEN-IN-0003 — FLEX-WP-0020 handoff: flex-auth → access-engine repository rename, credential-route surface + +```yaml +id: WARDEN-IN-0003 +kind: intake +title: 'FLEX-WP-0020 handoff: flex-auth to access-engine repository rename, credential-route + surface' +status: open +origin: cross-repo +origin_ref: flex-auth FLEX-WP-0020 (message 0a1956c5) +priority: medium +owner: ops-warden +requested_by: flex-auth +surface: credential-route +description: 'flex-auth is preparing the repository-coordinate rename flex-auth -> + access-engine. The repository UUID fda8ad85-a7d7-4055-8f21-902a533e59df and Forge + ID 42 are unchanged; RUNTIME names stay flex-auth per FLEX-DEC-2026-013. The ask: + review registry/routing/catalog.yaml''s owner repository field so routing still + resolves after the rename, create a live record on this side, and reply with its + id. No secret requested, none returned. The message is explicitly not completion + evidence.' +notes: 'REVIEWED 2026-09-21; one change made, one deliberately not made. + + SCOPE. Exactly one catalog lane names flex-auth as owner_repo: flex-auth-policy-check + (authorization decision — may this actor perform this action), warden_executes false, + route-only, no credential flows. Two other lanes mention flex-auth without owning + anything by it: flex-auth-decision-envelope-signing-key is owned by railiance-platform, + and ten lanes carry a policy_ref string of the form "flex-auth check secret.read:". + + CHANGED. access-engine added to flex-auth-policy-check''s need_keywords, so the lane + resolves under both names from today rather than from the rename. That is the + deprecation window ops-warden asked for in WARDEN-IN-0001 and is the only change + routing needs: warden route find matches on keywords and lane id, neither of which is + a repository coordinate. + + NOT CHANGED, and this is the finding worth having. owner_repo is NOT purely a + repository coordinate in this catalog. src/warden/policy.py sends it verbatim as + resource.system, and again as context.owner_repo, on every flex-auth /v1/check. + FLEX-DEC-2026-013 keeps runtime names as flex-auth, so flipping owner_repo at rename + time would rename a policy resource the PDP matches on, under cover of a repository + rename — a runtime change wearing a coordinate change''s clothes, and one that would + fail at the check rather than at the edit. It flips when the runtime name flips, not + when the repository does. If access-engine intends resource.system to follow the + repository rather than the runtime, that is a policy question for flex-auth to answer + before the rename lands, not an inference for ops-warden to make from its own catalog. + + The lane id flex-auth-policy-check, the subsystem string, and the policy_ref strings + are runtime-facing for the same reason and are unchanged. + + Related and unresolved: WARDEN-WP-0039-T03 already waits on the credential proxy''s + admitted policy binding, which is the same resource.system field seen from the + delegated-read side. The rename must not be allowed to answer that question by + accident.' +created: '2026-09-21T00:00:00Z' +updated: '2026-09-21T00:00:00Z' +``` diff --git a/layer.yaml b/layer.yaml index ea8e961..5f84ef5 100644 --- a/layer.yaml +++ b/layer.yaml @@ -16,6 +16,12 @@ schema_version: "0.1" framework: netkingdom-security-layer-model standard_version: "0.7" repository: ops-warden +# UNRULED, 2026-09-21: INTENT.md frontmatter declares `Staff` and this file +# declares `staff`. §11 permits both forms and does not say which governs when +# both exist and disagree; gate-house holds that ruling and the case-sensitivity +# one. NEITHER VALUE IS BEING CHANGED until it rules — see +# docs/layer-declaration-precedence.md and WARDEN-WP-0034-T06. Do not "fix" this +# line to match INTENT.md, or the reverse: the divergence is the evidence. layer: staff declared_by: docs/adr/ADR-0010 declared_at: "2026-08-29" diff --git a/pep-stance.yaml b/pep-stance.yaml index 6262211..2be4dac 100644 --- a/pep-stance.yaml +++ b/pep-stance.yaml @@ -87,8 +87,14 @@ on_apply: # Coverage is DISCLOSURE, never a transitional licence: it does not soften this # map's stance, does not gate it, and never makes the marked cell conformant. # Self-measured; regenerate with `python scripts/report_coverage.py`. +# 2026-09-21 re-measure: routing lanes grew from 30 to 38 catalogued entries +# since 2026-09-09, so unknown rose 18 -> 20 and not-applicable 12 -> 15 while +# RESOLVED STAYED AT 3. Classification did not regress; the denominator moved. +# Signing targets are unchanged. Reported to gate-house per §13.1 — a figure in +# a register that cannot detect its own staleness is only as good as the last +# re-measure, and this one was stale by eight lanes before the test caught it. classification_coverage: - measured: "2026-09-09" + measured: "2026-09-21" attribution: self-measured signing_targets: resolved: 0 @@ -96,8 +102,8 @@ classification_coverage: not_applicable: 1 routing_lanes: resolved: 3 - unknown: 18 - not_applicable: 12 + unknown: 20 + not_applicable: 15 verdict_caching: none input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebuilt by scripts/build_flex_auth_registry.py" diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 05025b4..84d546d 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,10 +10,10 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-09-15T18:38:11Z" +generated_at: "2026-09-21T00:11:00Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "8a40dcb11b729115773630e449fd880c0700372e" -catalog_revision_date: "2026-09-14T09:59:11+02:00" +catalog_revision: "308409bff1fecb588e4667f7e3db335fde9cbbe6" +catalog_revision_date: "2026-09-15T20:38:47+02:00" catalog_dirty: true high_risk_lane_count: 29 concrete_path_count: 15 diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 6ab51b9..32f10cc 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -313,15 +313,29 @@ entries: # form is superseded; do not reintroduce it. auth_method: "bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read" path_template: "platform/workloads/coulomb/whynot-design/npm-publish" - # Field corrected 2026-09-09 on the owner's statement (secrets-engine msg - # 15f0c0ca): `npm_token` is the KV FIELD; `NPM_AUTH_TOKEN` is the environment - # variable the publication-scope policy injects, and ops-warden had copied the - # env var in as the field name. That is ADR-0001's failure mode exactly — a - # pointer layer restating an owner's procedure and getting it wrong — so it is - # corrected from the owner's doc (secrets-engine docs/whynot-design-real-publish-closeout.md), - # never re-derived here. The path is a separate question, open with - # railiance-platform; do not change it on this correction. - fetch_command: "bao kv get -field=npm_token platform/workloads/coulomb/whynot-design/npm-publish" + # Field REVERTED to NPM_AUTH_TOKEN on 2026-09-10, and the path CONFIRMED, by the + # custodian of the path (railiance-platform msgs c6841bdb, 79f3e047). An attended, + # read-only, no-value founder session enumerated the field names actually present + # at this path: exactly one, NPM_AUTH_TOKEN. Receipt: railiance-platform + # docs/evidence/2026-09-10-npm-lane-field-resolution.json (commit 9d24086); prior + # basis CCR-2026-0001's two dated receipts (2026-06-28) naming the same field. + # + # The 2026-09-09 change to `npm_token` was adopted from secrets-engine's statement + # (msg 15f0c0ca) and is wrong FOR THIS PATH. That is ADR-0001's failure mode in its + # proper form: a correction taken from a coordination message is still an unverified + # mutation, and this one would have named a field that does not exist — failing at + # the rotation (WARDEN-WP-0037-T03) rather than on a cheap day. Reverting is a + # correction back to the evidenced value, not a new lane change. + # + # OPEN, and not ops-warden's to settle: a legacy location + # secret/coulomb/whynot-design/npm/publish exists (metadata only, v1 created + # 2026-07-03, outside this lane's exact-path policy and outside any CCR). + # railiance-platform's hypothesis is that secrets-engine's lowercase field belongs + # to THAT path and that the native exec front door below may be reading the + # ungoverned duplicate. Tracked as railiance-platform RPF-WP-0035-T07; ops-warden + # has asked secrets-engine which location `secrets-engine exec` reads. Until that + # is answered, this catalog names the governed path and the field evidenced at it. + fetch_command: "bao kv get -field=NPM_AUTH_TOKEN platform/workloads/coulomb/whynot-design/npm-publish" policy_ref: "flex-auth check secret.read:whynot-design" exec_capable: true lane: secret @@ -337,7 +351,7 @@ entries: automatable: false steps: - "In the coulomb Forgejo registry, revoke the current @whynot/design publish token and generate a new one (scope: package read/write) for the whynot-design publish identity." - - "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish npm_token=@file` (value from a mode-0600 file). The field is `npm_token`; `NPM_AUTH_TOKEN` is the injected env var, not a KV key." + - "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish NPM_AUTH_TOKEN=@file` (value from a mode-0600 file). NPM_AUTH_TOKEN is the KV field name at this path, evidenced by an attended enumeration on 2026-09-10; it is also the env var the publication-scope policy injects, which is what made the two readings easy to confuse." - "Verify capabilities-safe, then publish a fresh version and confirm it with Forgejo-supported `npm view @` through the governed execution lane (value used, not printed)." - id: policy-nexus-forgejo-source-read @@ -383,7 +397,19 @@ entries: workload_ref: applicability: not-applicable reason: "Generic authorization action; the governed resource supplies workload identity." - need_keywords: [authorization, policy, permission, allow, deny, may, flex-auth, topaz, pdp, decision] + need_keywords: [authorization, policy, permission, allow, deny, may, flex-auth, access-engine, topaz, pdp, decision] + # FLEX-WP-0020 rename review, 2026-09-21 (flex-auth msg 0a1956c5; WARDEN-IN-0003). + # `access-engine` added to the keywords so this lane resolves under BOTH names from + # today — the deprecation window ops-warden asked for in WARDEN-IN-0001 and the only + # change the rename needs here to keep routing working. + # + # `owner_repo` is deliberately NOT flipped, and the reason is not inertia. In this + # catalog `owner_repo` is not purely a repository coordinate: src/warden/policy.py + # sends it verbatim as `resource.system` (and as `context.owner_repo`) on every + # /v1/check. FLEX-DEC-2026-013 keeps runtime names as `flex-auth`, so flipping this + # string would rename a policy resource the PDP matches on, under cover of a + # repository rename — a runtime change wearing a coordinate change's clothes. It + # flips when the runtime name does, not when the repository does. owner_repo: flex-auth subsystem: flex-auth warden_executes: false @@ -898,8 +924,8 @@ entries: delegation: mode: interim intended_owner: key-cape - blocked_on: "Narrowed again 2026-09-09: the AUTHORITY is answered, the ARTIFACT is not. railiance-platform (msg 7c7228ac) confirmed steps 1-2 are theirs: executed by the platform operator attended, never by ops-warden, secrets-engine autonomously, or any unattended agent; transport is the governed openbao-platform-admin-login lane invoked only through `warden access openbao-platform-admin-login --exec -- ` with a unique metadata receipt path (RPF-WP-0017 output containment); authority is founder_required attended OIDC via netkingdom role=platform-admin. What is still missing is a reviewed rotation CCR — a two-custodian CAS rotation with a service restart is a distinct version-guarded operation, not an implementation detail of this lane, and it must name the CAS precondition and expected version on both custodians, sibling-field preservation, the restart window, bounded predecessor retention, and the reconcile-to-same-version failure step. That CCR is railiance-platform to write under RPF-WP-0035 once an owner asks for the rotation; ops-warden has asked key-cape whether to schedule it. Executable precedent for the same two-custodian shape: railiance-platform scripts/keycape_approval_custody.py, with its dated receipt under docs/evidence/ and review packet under docs/credential-lane-designs/. Prior (2026-09-08) narrowing to rotation steps 1-2 only: successor generation and the CAS write to both custodians (platform/workloads/rapp-qonto/keycape-client field client_secret, and sso/keycape-rapp-qonto-client key client-secret) remain custody/deployment acts. The key-cape-native exchange now exists (keycape service-token, 2026-09-05, KEY-WP-0014-T03) and step 3 verification exists as one command (keycape verify-client, 2026-09-08, including predecessor rejection and identical-secret detection); both are documented in key-cape/docs/native-authentication.md. The prior blocker recorded both as absent, which was accurate on 2026-08-28 and is not accurate now — corrected by key-cape (msg 08d42f47). Re-checked against key-cape source 2026-09-08." - reviewed: "2026-09-09" + blocked_on: "CHARACTER CHANGED 2026-09-10 (key-cape msgs 10cf8fca, 546f76e4), substance unchanged. This is no longer blocked on an OPEN QUESTION: the authority is named, the transport is named, and the rotation CCR is available on request. It is blocked on a DECISION TAKEN — key-cape put the question to the operator rather than answering for him, and the answer is NOT YET, with the offer standing open. ops-warden routed the ask and deliberately did not make it on key-cape's behalf (ADR-0003): a routing errand that implies an owner request manufactures a prepared CCR nobody decided to want. This schema carries no field distinguishing 'no authority exists' from 'authority exists, unexercised by owner decision' — the second is what is true here, and it is recorded in prose because inventing a delegation field to hold it would be a catalog change made to describe one lane. NOT A DEADLINE, and not remediation: key-cape checked and there is no incident behind it. KEY-WP-0011 recovered a real exposure on 2026-08-23, but the rapp-qonto client secret was not in that payload — it is an env: secretRef from a separate Kubernetes Secret, never inline in config.yaml, enforced by config validation. So this is lifecycle hygiene, which may wait for a chosen window; if anyone is carrying it as leftover incident work, it is not. WHAT REOPENS IT (events, never elapsed time): an actual or suspected exposure; the secret's age becoming a stated concern; a consumer requiring proof of rotation; or a decision to prove rotation step 4 before relying on it. Prior state, unchanged in substance: Narrowed again 2026-09-09: the AUTHORITY is answered, the ARTIFACT is not. railiance-platform (msg 7c7228ac) confirmed steps 1-2 are theirs: executed by the platform operator attended, never by ops-warden, secrets-engine autonomously, or any unattended agent; transport is the governed openbao-platform-admin-login lane invoked only through `warden access openbao-platform-admin-login --exec -- ` with a unique metadata receipt path (RPF-WP-0017 output containment); authority is founder_required attended OIDC via netkingdom role=platform-admin. What is still missing is a reviewed rotation CCR — a two-custodian CAS rotation with a service restart is a distinct version-guarded operation, not an implementation detail of this lane, and it must name the CAS precondition and expected version on both custodians, sibling-field preservation, the restart window, bounded predecessor retention, and the reconcile-to-same-version failure step. That CCR is railiance-platform to write under RPF-WP-0035 once an owner asks for the rotation; ops-warden has asked key-cape whether to schedule it. Executable precedent for the same two-custodian shape: railiance-platform scripts/keycape_approval_custody.py, with its dated receipt under docs/evidence/ and review packet under docs/credential-lane-designs/. Prior (2026-09-08) narrowing to rotation steps 1-2 only: successor generation and the CAS write to both custodians (platform/workloads/rapp-qonto/keycape-client field client_secret, and sso/keycape-rapp-qonto-client key client-secret) remain custody/deployment acts. The key-cape-native exchange now exists (keycape service-token, 2026-09-05, KEY-WP-0014-T03) and step 3 verification exists as one command (keycape verify-client, 2026-09-08, including predecessor rejection and identical-secret detection); both are documented in key-cape/docs/native-authentication.md. The prior blocker recorded both as absent, which was accurate on 2026-08-28 and is not accurate now — corrected by key-cape (msg 08d42f47). Re-checked against key-cape source 2026-09-08." + reviewed: "2026-09-21" verified: owner-confirmed risk: high workload_ref: diff --git a/tests/test_workload_join.py b/tests/test_workload_join.py index 170cd8c..d87b484 100644 --- a/tests/test_workload_join.py +++ b/tests/test_workload_join.py @@ -13,11 +13,14 @@ def test_repo_catalog_uses_only_explicit_workload_references(): report = build(ROOT / "registry/routing/catalog.yaml", Path.home()) assert report["ok"] is True assert len(report["resolved"]) == 3 - assert len(report["unknown"]) == 18 - # 12 since WARDEN-WP-0034: the layer-declaration route is repository - # conformance guidance, not a workload delivery lane. The two NetKingdom SSO - # lanes (c374d41) remain provider/control-plane credentials as well. - assert len(report["not_applicable"]) == 12 + assert len(report["unknown"]) == 20 + # 15 since 2026-09-21: the layer-declaration route is repository conformance + # guidance, not a workload delivery lane, and the two NetKingdom SSO lanes + # (c374d41) remain provider/control-plane credentials. Lanes added after + # 2026-09-09 moved the denominator without moving `resolved`; these counts + # and pep-stance.yaml's classification_coverage measure the same populations + # and must be updated together. + assert len(report["not_applicable"]) == 15 assert {row["lane"] for row in report["resolved"]} == { "ops-warden-warden-sign-token", "issue-core-ingestion-api-key", diff --git a/wiki/playbooks/whynot-design-npm-publish.md b/wiki/playbooks/whynot-design-npm-publish.md index 4ffc934..3c45014 100644 --- a/wiki/playbooks/whynot-design-npm-publish.md +++ b/wiki/playbooks/whynot-design-npm-publish.md @@ -11,7 +11,7 @@ Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f61 > unverified for writes until its package token is rotated and re-proven. The npm publish token for `@whynot/design` on the coulomb Forgejo npm registry -(KV field `npm_token`; reaching the publish command as `NPM_AUTH_TOKEN`) +(KV field `NPM_AUTH_TOKEN`, which is also the name the publish command reads it under) (`https://forgejo.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold this token** — it is the access front door: `warden access` proxies the read from OpenBao **as the caller** and never persists, caches, or logs the value. @@ -23,8 +23,8 @@ this token** — it is the access front door: `warden access` proxies the read f | Field | Value | | --- | --- | | OpenBao path | `platform/workloads/coulomb/whynot-design/npm-publish` | -| KV field | `npm_token` | -| Injected env var | `NPM_AUTH_TOKEN` (set by the publication-scope policy; not a KV key) | +| KV field | `NPM_AUTH_TOKEN` — the only field present at this path (attended enumeration 2026-09-10, railiance-platform `docs/evidence/2026-09-10-npm-lane-field-resolution.json`) | +| Injected env var | `NPM_AUTH_TOKEN` — same name as the KV field, which is what made the 2026-09-09 `npm_token` change look plausible | | KV mount | `platform` | | Read policy | `workload-kv-read-whynot-design-npm-publish` | | OIDC login | `bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read` | @@ -35,6 +35,17 @@ this token** — it is the access front door: `warden access` proxies the read f > The `platform/workloads/whynot-design/whynot-design/npm-publish` path from early in the > provisioning thread is **superseded** — the live path is under the `coulomb` tenant. +> **A second, ungoverned location exists and is not this lane.** +> `secret/coulomb/whynot-design/npm/publish` (v1, created 2026-07-03, never updated) sits +> outside this lane's exact-path policy and outside any CCR. It was found by metadata read +> only; its field names were not enumerated, its value was not read, and it was not +> deleted — a location holding real credential material is disposed of deliberately by its +> owner, not tidied away by whoever finds it. railiance-platform tracks it as +> `RPF-WP-0035-T07`, with an open hypothesis that the native `secrets-engine exec` front +> door below may be reading it rather than the governed path. If that proves true, this +> lane's acceptance evidence describes a path its consumer does not use. Do not route +> around the governed path on that suspicion; the question is with its owners. + --- ## Worker checklist @@ -61,12 +72,12 @@ this token** — it is the access front door: `warden access` proxies the read f token fragment at `forgejo.coulomb.social`: ```bash # --exec needs the env-var name. The zone-aware policy gate always runs first. - warden access whynot-design-npm-publish --field npm_token \ + warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \ --exec -- npm view @whynot/design@ version \ --registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/ - warden access whynot-design-npm-publish --field npm_token \ + warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \ --exec -- npm publish - warden access whynot-design-npm-publish --field npm_token --fetch + warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN --fetch ``` On either path the value transits to you (or the child env) and never enters ops-warden's memory, disk, or audit log. diff --git a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md index 834cd89..8698a94 100644 --- a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md +++ b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md @@ -14,7 +14,7 @@ planning_priority: P1 depends_on_workplans: - WARDEN-WP-0030 created: "2026-08-29" -updated: "2026-09-05" +updated: "2026-09-21" state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef" --- @@ -231,6 +231,45 @@ cost, and issuance on this lane is too sparse (3 signature records across 79 day T05 stays `wait` on the remaining two: `ops-mason` (no stance map published) and `railiance-infra` (KRL distribution versus TTL expiry). Neither has replied. +## Await gate-house's §11 precedence and case-sensitivity ruling + +```task +id: WARDEN-WP-0034-T06 +status: wait +priority: low +``` + +ops-warden declares `layer: Staff` in `INTENT.md` frontmatter and `layer: staff` +in `layer.yaml`. §11 permits both forms and is silent on which governs when both +exist and disagree, so two conformance runs over this repository can disagree +while both follow the standard. Nine of nine repositories carrying both forms do +the same thing (flex-auth `tools/survey_layer_declarations.go`, receipt +`docs/evidence/2026-09-21-layer-declaration-survey.json`). + +**Waiting on gate-house for two rulings**: which form governs, and whether the §3 +vocabulary is case-sensitive. The custodian's estate-wide sweep adds a third that +bears on it — §3's table capitalises and types engines `Engines` while §4's +catalog rows type them `Engine`, so the standard disagrees with itself one level +below B1. + +**Deliberately not acting.** Aligning the two files now means choosing an answer +gate-house owns, and doing it in the one place §11 says another repository may +not speak for us. It would also erase the evidence the ruling is being made +against: the finding is visible because nine repositories exhibit it, and a +repository that quietly normalises itself makes the estate read as more +conformant than it is. Position recorded in +`docs/layer-declaration-precedence.md`; both files carry a comment saying not to +"fix" the divergence. + +**Done when:** gate-house rules, the non-governing file is changed to match in +one commit, and a regression test asserts the two declarations agree so the next +divergence is caught rather than surveyed. + +Raised by flex-auth's corrected B1 (message `719b3e9e`, 2026-09-21), which +withdrew an earlier claim that the estate disagreed *across* repositories. The +withdrawal matters to this task: the original finding would have had ops-warden +changing a file to match an estate consensus that did not exist. + ## Related Review 2026-09-05: reviewed the pending implementation and fixed the identity diff --git a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md index 31d06d8..f75b2da 100644 --- a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md +++ b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md @@ -9,7 +9,7 @@ flavor: planning owner: codex topic_slug: whynot-design-forgejo-npm-lane created: "2026-09-04" -updated: "2026-09-04" +updated: "2026-09-21" state_hub_workstream_id: "42a097db-1c24-558e-a724-030bb2b4443e" --- @@ -110,3 +110,54 @@ production authority reports not-ready regardless of which path the catalog name T03 still waits on the human rotation of the dedicated Forgejo package token; the path question does not block that, it determines whether the pointer is correct once it rotates. + +**2026-09-21 — field reverted to `NPM_AUTH_TOKEN`; path confirmed; a second +location found.** `railiance-platform`, who holds custody of the path, answered +both halves (msgs `c6841bdb`, `79f3e047`) and the answer reverses the 2026-09-09 +change above. + +An attended founder session enumerated the field names present at +`platform/workloads/coulomb/whynot-design/npm-publish`: read-only, no mutation, +no value emitted, `attended_identity` true. **Exactly one field is present, and +it is `NPM_AUTH_TOKEN`.** Receipt: `railiance-platform +docs/evidence/2026-09-10-npm-lane-field-resolution.json`, commit `9d24086`. The +both-fields reconciliation offered the day before is ruled out by the same +enumeration. `CCR-2026-0001` has carried two dated receipts naming that field +since 2026-06-28, one of them an attended founder fetch that exited zero — which +is itself proof the field exists. + +So `npm_token` is wrong *for this path*, and the catalog and playbook are +reverted to `NPM_AUTH_TOKEN`. Reverting is a correction back to the evidenced +value, not a new lane change. + +The lesson is the one this plan already recorded, one level in. On 2026-09-09 +this file wrote that adopting a correction from `secrets-engine` rather than +re-deriving it was the right shape. It was the right *source* and the wrong +*standard of evidence*: a correction adopted from a coordination message is +still an unverified mutation, even when it is only a field name in a catalog, +and this one would have failed at the rotation T03 is waiting for — the moment +it is most expensive to discover. railiance-platform's phrasing, kept because it +is better than ours: it will break "at the moment you least want it to". + +**The second location, which is not ours to dispose of.** +`secret/coulomb/whynot-design/npm/publish` exists — version 1, created +2026-07-03T15:00:44Z, never updated, five days *after* the governed lane was +verified. It sits outside this lane's exact-path policy and outside any CCR. +railiance-platform read metadata only: field names were not enumerated, the +value was not read, nothing was deleted, because a location holding real +credential material is disposed of deliberately by its owner rather than tidied +away by whoever finds it. + +Their unconfirmed hypothesis, recorded because it changes what this lane's +acceptance evidence means if true: `secrets-engine`'s lowercase field may belong +to *that* path, their catalog may declare that location, and the proven pilot +publish may have been reading the duplicate all along. If so, a working +production lane has been running ungoverned and this lane's acceptance evidence +describes a path its consumer does not use. Tracked as `RPF-WP-0035-T07`; +railiance-platform has asked `secrets-engine` which location their publish +reads, and ops-warden has asked the same about the native +`secrets-engine exec` front door this catalog points at (`exec_owner`). + +Recorded in the catalog and in `wiki/playbooks/whynot-design-npm-publish.md` as +an explicit non-lane rather than deleted from the record. Not routed around: the +governed path stays the pointer.