The anonymous JWT login inherited BAO_TOKEN/VAULT_TOKEN and the bao CLI
token helper, so it presented an env/operator token alongside the JWT.
Run that login with no env token and a token helper that returns none,
proven against the real bao CLI. This closes secrets-engine's part of the
CCR-2026-0038 no-fallback check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 544698@bnt-lap001
Assistant-Session: 279b8520-335f-4041-ba2a-9491335473db
SCOPE, layer.yaml and the custody doc said the platform JWT role was
unpublished. The role is applied, its consumer contract is published
(railiance-platform 21b8af3) and login was proven on 2026-10-10. The role
is login-only, so engine actions still use the named providers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 544698@bnt-lap001
Assistant-Session: 279b8520-335f-4041-ba2a-9491335473db
Record the founder-run probe-openbao result as non-secret evidence,
finish SECRETS-WP-0012 and point SECRETS-WP-0008-T06 at the remaining
platform negative checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 544698@bnt-lap001
Assistant-Session: 279b8520-335f-4041-ba2a-9491335473db
Closes the FLEX-WP-0020-T11 residual: a relative ../flex-auth checkout
path that the absolute-path survey missed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 544698@bnt-lap001
Assistant-Session: 279b8520-335f-4041-ba2a-9491335473db
The platform JWT mount/role is live, so the only remaining step is the
founder's attended probe. Document the local non-secret login contract
input the probe needs, since the CCR publishes its own copy only after
the proof passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 544698@bnt-lap001
Assistant-Session: 279b8520-335f-4041-ba2a-9491335473db
bao.coulomb.social was retired on 2026-09-15. The in-memory source accepts a
loopback address.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
Founder direction 2026-10-06 and ops-mason plan
keycape-secrets-engine-openbao-client-custody (658cdbc): no workstation copy.
service-jwt now gets CLIENT_SECRET from platform/workloads/keycape/
secrets-engine-openbao through one attended OIDC login on
keycape-secrets-engine-openbao-operator-workload-kv-read (-no-store, token from
a pipe, header-only GET, unconditional revoke-self). The consumer file is
refused for this profile and kept for the approval client.
service-auth probe-openbao prints value-free first-use evidence.
SECRETS-WP-0012: T02 cancelled (D1-D4 superseded), T04 done, T03 is the
founder-run first live use. Synthetic tests only; 524 passed, 1 skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
Adds the consumer custody packet for KEY-WP-0037-T02 / RPF-WP-0035-T02,
strict value-free client-secret file checks with a check-secret-file CLI,
synthetic tests, and SECRETS-WP-0012 tracking founder decisions and the
attended delivery as needs_human wait tasks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 226514@bnt-lap001
Assistant-Session: 26ba103d-05fe-45a1-9cd7-9475bf239df6
A configured exec owner may receive fields from other consenting kv lanes.
Each lane is gated, consumed and read through its own AppRole; any refusal
starts no child. Companions are part of the owner digest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 226514@bnt-lap001
Assistant-Session: 26ba103d-05fe-45a1-9cd7-9475bf239df6
ops-warden and railiance-platform asked, independently, which OpenBao location
`secrets-engine exec --catalog whynot-design-npm-publish` reads. Answered from
this repository's own code and catalog with no OpenBao read and no value.
It reads `secret/coulomb/whynot-design/npm/publish` — the legacy, ungoverned
duplicate. `_fetch_value` concatenates the catalog's `mount` and `path` with no
override or fallback, so the proven pilot published from the duplicate and the
lowercase `npm_token` field is the field there. ops-warden's front door names
this repository as exec_owner, so it currently routes callers at a path no CCR
covers.
Recorded in SECRETS-WP-0006 with what a move to the governed lane requires, and
flagged that the duplicate must not be destroyed until the lane moves. Whether
the two locations hold the same value is a value comparison and is not answered
here.
Also records GH-DEC-2026-017 against SECRETS-WP-0008: INTENT.md governs, the
sidecar is derived, the vocabulary is case-insensitive so nothing is re-spelled,
and standard_version comes out of layer.yaml once ops-warden updates the
reference form this repository copied.
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
The live proof in 03c0569 showed validate_decision_envelope rejecting every
real allow. The evaluator normalizes before hashing -- the request tenant is
copied onto subject and resource, and a registry hit copies type, tenant and
selected attributes onto the refs -- so binding.request_digest covers
material we never sent. Byte-equality against our unenriched request was
unsatisfiable, not merely mismatched.
THE RULE WAS ALREADY PUBLISHED. flex-auth's canonical-request-digest.md
section "Normalization" states the enrichment and tells consumers what to do
instead: compare structured binding fields to the proposed action, treat
request_digest as the evaluator's statement of what it hashed, and recompute
independently over the tuple the binding carries. I raised this with them as
an unpublished gap and asked them to pick between three shapes; it was in
their contract already and the answer was the first of the three. Nothing
was blocked on them, and this follows the published rule rather than one I
inferred.
- _require_binding_corresponds: everything we proposed must survive
unchanged -- tenant, action, context, subject.id/type,
resource.id/type/system, and every attribute we sent.
- Enrichment may add only type, tenant, attributes. Any other added field is
refused, and an enriched tenant must be the request tenant, so a
cross-tenant binding cannot arrive wearing our request's clothes.
- request_digest is still verified, now against binding_tuple(binding) for
self-consistency rather than against material we never sent.
- The envelope's top-level subject/resource get the same rule; they are
enriched too.
Proved against the artifact: the real decision:0f9c98f14545c42d now
validates, and the unrefreshed envelope is refused on lifetime -- reaching
the lifetime check at all is the evidence the binding checks pass on a real
decision. Negatives cover a restated resource.attributes.stage, a foreign
subject.tenant, and an unexpected enrichment field.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4tNMAYcSQmZWUE4wqP4ij
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715726@bnt-lap001
Assistant-Session: 80a42b32-cba6-4b23-8be0-68819b1a6092
Adoption asked for by flex-auth (FLEX-WP-0021-T05) and glas-harness
(GLAS-WP-0015), plus the first real decision this engine has obtained from
the deployed pin -- which found a defect the fixtures could not.
ACCESS PATH. require_supported_pdp_address refuses in-cluster Service names
and any non-loopback host. This is no longer a unilateral call: the owner
path is documented as loopback kubectl port-forward over the authenticated
Kubernetes API, which is what authenticates the responder transitively
(FLEX-DEC-2026-010). A Service name from a workstation does not fail, it
resolves through the DNS search suffix to an unrelated public host, and
since decision records carry no signature, a responder knowing the
published package and version can return an allow that passes every check
we make. Fail-closed protects against a PDP that is absent, not one that
lies. The guard runs before the token is read, so a misdirected request
cannot leak it; a test pins that ordering.
LIVE PROOF. Minted a 10-minute TokenRequest token (audience flex-auth, SA
secrets-engine/secrets-engine, mode 0600 outside the worktree, shredded
after), forwarded to the named pod, and sent a real CheckRequest for
glas-claude-agent-dev-anthropic. Result: allow, catalog_lane_policy_matched,
served by v2 (sha256:bd11c5fe...) -- so the redeploy flex-auth flagged as
outstanding has landed and the pin no longer serves the tenant-blind v1.
Our tenant fix is confirmed against the real service: binding.tenant is
tenant:platform.
THE DEFECT IT FOUND. The evaluator enriches from its registry before
hashing -- subject gains attributes and tenant, resource gains tenant --
so binding.request_digest is over material we never sent and cannot
reproduce. validate_decision_envelope rejects every real allow.
Every replay test passes because _request_from() rebuilds the request out
of the binding, i.e. the enriched form: a self-consistent fake agreeing
with itself, which hid this through three rounds of digest work. Third
time a real artifact has beaten a fake in this integration.
NOT FIXED, DELIBERATELY. Rejecting a valid allow is wrong in the safe
direction. Which fields may be enriched is flex-auth's contract to publish;
inferring it means accepting a binding that differs from our proposal in a
way we decided was benign -- the fail-open shape GH-DEC-2026-008 rejected
for vocabularies and FLEX-DEC-2026-007 for digests. Raised with them.
Tenant question closed by operator decision 5ed3fb35: tenant:platform
exactly, and service_auth.TENANT stays tenant:coulomb because the two
identity layers are to remain distinct. Declining to author that mapping
was right -- the answer was neither reading offered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4tNMAYcSQmZWUE4wqP4ij
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715726@bnt-lap001
Assistant-Session: 80a42b32-cba6-4b23-8be0-68819b1a6092
Probing the handed-over Service DNS name from the workstation found that
every *.svc.cluster.local name resolves here to one unrelated public
address via the ad.binect.de search suffix -- including names of services
that do not exist, which proves it is suffix expansion and not a record.
Pointing SECRETS_ENGINE_PDP_URL at the Service name would send the
CheckRequest body (subject, tenant, lane ids, stage, field names, purpose)
and the static Bearer token to that host. Decision envelopes are
structurally validated but not signed, so a responder knowing the package
and version can return a well-formed allow; the fail-closed posture
assumes the PDP is the PDP.
Not mitigated here: choosing the transport control is the owners' call,
not this consumer's. Recommended to FLEX-WP-0021-T05 that the handover be
a trailing-dot FQDN or explicit address, with the response channel's
authentication stated. Our pin stays unset, so the hazard is theoretical.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4tNMAYcSQmZWUE4wqP4ij
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715726@bnt-lap001
Assistant-Session: 80a42b32-cba6-4b23-8be0-68819b1a6092
build_action_request emitted no tenant field at all. The deployed
secrets-engine.catalog-lane.lifecycle v2 package reads
known_tenant := "tenant:platform"
request_tenant := object.get(input, "tenant", "")
so an absent tenant is not an ignored field, it matches the wrong_tenant
first-denial branch. Every gated action this engine sent would have been
denied -- and the omission also produced a request_digest that could match
no correctly issued decision, since tenant is hashed material. That is the
same class of defect as hashing excluded fields, arriving from the other
direction, and again only a real artifact exposed it.
Found by answering the GLAS-WP-0015 tenant-alignment question instead of
assuming the values lined up.
- REQUEST_TENANT is pinned against the vendored allow envelopes, so a
package retenanting fails a test rather than denying production.
- An empty tenant is refused at build time.
- Accepted policy version moves v1 -> v2. v1 had no tenant rule and failed
open: a rotate under tenant:coulomb returned allow against the deployed
package. flex-auth superseded rather than amended it, because a fail-open
correction has to be visible as a version change. A test pins that a v1
decision is refused.
- Vendored decision_wrong_tenant_deny.json as the denial evidence glas
asked for, with tests that we refuse it on effect before anything else
and that a deny legally carries no lifetime.
docs/tenant-alignment.md states the three tenant values as this repo holds
them. It does not resolve the JWT/store mapping: service_auth.TENANT is
tenant:coulomb, which is exactly the value the package denies. That is
either two layers sharing a namespace format or one wrong constant, and
picking between them without an owner ruling is the fail-open shape
GH-DEC-2026-008 rejected for action vocabularies. Both constants stay as
they are, deliberately not unified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4tNMAYcSQmZWUE4wqP4ij
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715726@bnt-lap001
Assistant-Session: 80a42b32-cba6-4b23-8be0-68819b1a6092
The vocabulary mapping this path was waiting on is not coming: gate-house
rejected it in GH-DEC-2026-008, because a translation can be confidently
wrong and fails open by accepting a claim approved for a different action.
The stronger option arrived instead, and both halves are enforced here.
flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) to
fix the circularity this repo reported: a pdp_digest recorded at issue time
can never equal the request_digest of the request that carries the claim in
its hashed context, so with GH-DEC-2026-008 requiring that equality, destroy
would have failed closed forever on a check no correct record could pass.
- authorization.approval_binding_digest implements the published exclusion
rule, including Go's context,omitempty behaviour when stripping empties
the context; digest_material drops an empty context for the same reason.
- validate_decision_envelope recomputes the field rather than trusting it,
refuses a claim-bearing request whose decision records none, and compares
the claim's digest from step 1 against it -- never against request_digest,
which still covers the claim so it stays a sound replay identity.
- validate_approval_claim requires binding.pdp_path true before using
pdp_digest at all. Path intent is never inferred from a digest that
happens to be present; pre-schema-v3 approvals carry pdp_path false
regardless of any digest they hold.
Replay fixtures re-vendored from dd3ce4c. The destroy pins moved a second
and final time; approval_binding_digest did not, which is the point. The
fixture now demonstrates the property instead of asserting it: we rederive
fa07becf... from its own request through our canonical implementation,
proving we hash the same material flex-auth does rather than pinning a
constant we cannot reproduce.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4tNMAYcSQmZWUE4wqP4ij
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 715726@bnt-lap001
Assistant-Session: 80a42b32-cba6-4b23-8be0-68819b1a6092