Revert the npm field, re-measure coverage, and hold the layer divergence

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 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
This commit is contained in:
tegwick 2026-09-21 02:16:33 +02:00
parent 15a4717dd1
commit baf53602ca
11 changed files with 422 additions and 32 deletions

View file

@ -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

View file

@ -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 1617, 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 1617, 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

View file

@ -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:<domain>".
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'
```

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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 <package>@<version>` 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 -- <command>` 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 -- <command>` 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:

View file

@ -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",

View file

@ -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> 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.

View file

@ -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

View file

@ -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.