docs: preserve reviewed NetKingdom session artifacts
This commit is contained in:
parent
24ae04fa80
commit
573f707348
50 changed files with 5107 additions and 0 deletions
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
id: KEY-WP-0016
|
||||
type: workplan
|
||||
title: "Authorization-code protocol hardening and token verification bindings"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: authorization-code-protocol-hardening
|
||||
created: "2026-09-06"
|
||||
updated: "2026-09-06"
|
||||
state_hub_workstream_id: "22698171-f283-59a9-9443-183faaeb6287"
|
||||
---
|
||||
|
||||
Closes gap G01 of `history/2026-09-05-011726-scope-intent-assessment.md`, the
|
||||
assessment's first recommended item. The browser grant validates PKCE, client id
|
||||
and scopes but does not bind the redirect URI, authenticate confidential clients,
|
||||
enforce grant-type eligibility, or consume the authorization code atomically.
|
||||
UserInfo verification is weaker than the caller CLI's contract.
|
||||
|
||||
Scope is the local protocol surface only. Upstream provider-token verification
|
||||
(the Authelia adapter's deliberate no-signature-check trust assumption) is a
|
||||
separate trust-contract decision and is not addressed here.
|
||||
|
||||
## Consume authorization codes atomically
|
||||
|
||||
```task
|
||||
id: KEY-WP-0016-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "a5171df0-4905-518d-a52f-7152b980af79"
|
||||
```
|
||||
|
||||
`SessionStore.Get` reads a code and `Delete` removes it in separate lock
|
||||
acquisitions, with signing in between, so two simultaneous exchanges can both
|
||||
observe the same session before either deletes it. Replace the token path's
|
||||
lookup with a single-operation consume that removes the session under the same
|
||||
lock that reads it, so exactly one concurrent exchange can succeed. A failed
|
||||
exchange must not leave a replayable code. Cover with a concurrent-reuse test
|
||||
that asserts exactly one success across parallel requests.
|
||||
|
||||
Added `SessionStore.Consume`, which reads and deletes under one lock, and moved
|
||||
the token path onto it. Verified the defect was real before claiming the fix:
|
||||
with `Get`/`Delete` restored, 9 of 16 concurrent exchanges succeeded and a
|
||||
PKCE-failed exchange left the code replayable. Both now fail closed —
|
||||
`TestConcurrentCodeExchangeSucceedsExactlyOnce`, `TestFailedExchangeConsumesTheCode`.
|
||||
|
||||
## Bind redirect URI, grant type and confidential client authentication
|
||||
|
||||
```task
|
||||
id: KEY-WP-0016-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "1d0fb108-273f-5c9a-a7c7-7640f19bad9d"
|
||||
```
|
||||
|
||||
On the authorization-code path: require `redirect_uri` and compare it for exact
|
||||
equality with the value stored at authorization time; reject a client whose
|
||||
registration does not permit `authorization_code` (an empty `grantTypes` stays an
|
||||
implicit authorization-code client, matching config validation, but a
|
||||
`client_credentials`-only client must be refused); and authenticate confidential
|
||||
clients with a constant-time secret comparison, accepting the same
|
||||
`client_secret_basic` and form-encoded credentials as the service grant. Public
|
||||
clients continue to present no secret and must not be able to supply one to
|
||||
impersonate a confidential registration. Negative tests per condition.
|
||||
|
||||
Implemented all three in `token.go`, with `basicClientCredentials` and
|
||||
`secretsEqual` shared with the service grant (digest-first comparison, so a
|
||||
wrong-length secret is indistinguishable from a wrong value). Tests:
|
||||
`TestCodeExchangeRejectsRedirectURIMismatchAndOmission` (omitted, different,
|
||||
traversal-shaped and trailing-slash variants),
|
||||
`TestCodeExchangeRejectsServiceOnlyClient`,
|
||||
`TestCodeExchangeAllowsImplicitAuthorizationCodeClient`,
|
||||
`TestConfidentialCodeClientRequiresItsSecret` and
|
||||
`TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected`. The
|
||||
`keycape login` CLI already sent `redirect_uri`, so no caller change was needed;
|
||||
the authclient test stub was recording no redirect and is now faithful to real
|
||||
`/authorize`.
|
||||
|
||||
## Harden UserInfo token verification
|
||||
|
||||
```task
|
||||
id: KEY-WP-0016-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "336db63a-e4fd-522d-86b6-0bf54a31af1c"
|
||||
```
|
||||
|
||||
UserInfo verifies an RSA signature and expiry but ignores its own `Issuer` field
|
||||
and does not check the JOSE header algorithm, the issuer claim, or the token
|
||||
purpose, so an ID token or a foreign-issuer token of the right shape is accepted
|
||||
where an access token is required. Enforce `alg=RS256` from the header, reject
|
||||
`none` and any other algorithm, require the configured issuer, and reject tokens
|
||||
that are not access tokens. Negative tests per condition.
|
||||
|
||||
`validateJWT` became `validateAccessToken`, checking the JOSE header before
|
||||
trusting the signature, requiring the configured issuer (an empty configured
|
||||
issuer is a misconfiguration, not a skipped check), and requiring the `scope`
|
||||
claim the token endpoint sets only on access tokens. Purpose is decided
|
||||
verification-side deliberately: adding a `token_use` claim would change the
|
||||
issued-token contract that consumers pin exactly. Tests:
|
||||
`TestUserinfoRejectsUnverifiedTokenShapes` (foreign issuer, missing issuer, ID
|
||||
token presented as an access token) and `TestUserinfoRejectsNonRS256Algorithms`,
|
||||
which restates the header algorithm over a genuine RS256 signature so it
|
||||
isolates the header check.
|
||||
|
||||
## Reconcile scope and assessment records
|
||||
|
||||
```task
|
||||
id: KEY-WP-0016-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "90efa6ef-9495-5716-bee2-49d01c9ce05d"
|
||||
```
|
||||
|
||||
Update `SCOPE.md`'s protocol-hardening limit and G01's closure status in the
|
||||
assessment to state exactly which bindings are now enforced and which remain
|
||||
open, naming the upstream-trust item as still outstanding. Do not claim complete
|
||||
profile conformance: G01 closure covers these bindings, not the whole profile.
|
||||
|
||||
Updated `SCOPE.md`'s protocol-hardening limit and recorded a partial-closure
|
||||
status under G01 in the assessment, naming the Authelia upstream-trust item as
|
||||
still open. G01 is explicitly not fully closed.
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
id: KEY-WP-0017
|
||||
type: workplan
|
||||
title: "Reconcile the canonical model and discovery metadata with the runtime"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: canonical-model-and-discovery-conformance
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "0904caad-ad8b-573f-9cdb-b6da09262a18"
|
||||
---
|
||||
|
||||
Closes gap G02 of `history/2026-09-05-011726-scope-intent-assessment.md`.
|
||||
`spec/canonical-model.yaml` and `src/internal/domain/model.go` both claim to be
|
||||
the source of truth for the client registration shape, and they disagree: the
|
||||
spec restricts grants to `authorization_code`, requires redirect URIs for every
|
||||
client, and omits the service-subject, tenant, audience, lifetime, role and
|
||||
MFA/handoff fields the runtime actually reads. Discovery advertises a fixed basic
|
||||
scope and claim list that omits core profile claims and every configured resource
|
||||
scope.
|
||||
|
||||
The point of the work is the executable link, not the edit: reconciling the two
|
||||
files once leaves them free to drift again the same afternoon.
|
||||
|
||||
## Reconcile the client entity with the runtime model
|
||||
|
||||
```task
|
||||
id: KEY-WP-0017-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "996f6895-f36b-585e-844d-3e55d02eefda"
|
||||
```
|
||||
|
||||
Bring the `Client` entity in `spec/canonical-model.yaml` in line with
|
||||
`domain.Client`: add `audience`, `serviceSubject`, `tenant`, `roles`,
|
||||
`mfaRequired`, `registrationUrl` and `enrollmentUrl`; extend the `grantTypes`
|
||||
enum to include `client_credentials`; and make `redirectUris` required only for
|
||||
clients that use the authorization-code grant, matching what config validation
|
||||
already enforces. Record which fields are deliberately absent from the canonical
|
||||
model rather than silently omitted — `clientSecret` and `tokenLifetime` are
|
||||
runtime policy, not identity data, and both are `yaml:"-"` in the Go model.
|
||||
|
||||
Added all seven fields, extended the grant enum, and made `redirectUris`
|
||||
optional at the schema level with the authorization-code condition stated in its
|
||||
description. `tokenProfile` and `environments` were declared in the spec but read
|
||||
by nothing; they are kept and marked `runtime: false` rather than deleted, since
|
||||
removing them would discard intent the check can now hold honest.
|
||||
|
||||
## Add an executable conformance check
|
||||
|
||||
```task
|
||||
id: KEY-WP-0017-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "77df5043-2b23-56cd-947e-317f7117ac18"
|
||||
```
|
||||
|
||||
Add a test that compares the canonical model's `Client` field set against
|
||||
`domain.Client`'s YAML tags by reflection and fails on divergence in either
|
||||
direction: a Go field with no spec entry, or a spec field the runtime does not
|
||||
read. Fields intentionally excluded must be named in one list the test reads, so
|
||||
excluding a field is a deliberate, reviewable act rather than an omission. This
|
||||
is the generation/conformance link the assessment asks for.
|
||||
|
||||
`src/internal/domain/conformance_test.go` checks `Client`, `User`, `Group` and
|
||||
`Membership` by reflection over YAML tags. It earned its place on the first run
|
||||
by finding drift outside the assessment's list: `User.tenant` was read by the
|
||||
runtime and undeclared in the spec. `yaml:"-"` fields are excluded by
|
||||
construction, which is what keeps `clientSecret` and `tokenLifetime` out without
|
||||
a hand-maintained exception list.
|
||||
|
||||
## Reconcile discovery metadata with the profile surface
|
||||
|
||||
```task
|
||||
id: KEY-WP-0017-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "5eb36f9b-a8e0-541e-8f81-310db49b77a0"
|
||||
```
|
||||
|
||||
`claims_supported` omits `tenant`, `principal_type`, `assurance`, `scope` and
|
||||
`nonce`, which the token endpoint emits on every token, and `scopes_supported` is
|
||||
a fixed list that omits every configured resource scope. Advertise the core
|
||||
profile claims and derive the scope list from the registered clients. Keep the
|
||||
distinction the assessment asks for: an omitted core claim is a contract defect,
|
||||
while optional discovery metadata is not, so document which is which rather than
|
||||
treating every omission alike.
|
||||
|
||||
`claims_supported` now lists the core profile claims (`tenant`, `principal_type`,
|
||||
`assurance`, `scope`, `nonce`) alongside the scope-gated human claims and the
|
||||
optional cached `tenant_roles`. `scopes_supported` is derived from the registered
|
||||
clients — baseline OIDC scopes first, configured resource scopes sorted after, so
|
||||
the document is stable regardless of map iteration order — with the baseline
|
||||
still advertised when no clients are configured.
|
||||
`TestDiscoveryAdvertisesEveryCoreProfileClaim` states the distinction the
|
||||
assessment asked for: a missing core claim fails, optional metadata does not.
|
||||
|
||||
## State a single source of truth
|
||||
|
||||
```task
|
||||
id: KEY-WP-0017-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "63290e2a-bae1-51c3-9cb9-d49994d804b6"
|
||||
```
|
||||
|
||||
Two files currently claim to be authoritative. State in both that the Go model is
|
||||
the runtime authority and the canonical model is the reviewed contract the
|
||||
conformance check holds it to, so the next reader knows which one to edit first.
|
||||
Update `SCOPE.md` and G02's status in the assessment to say what is now enforced
|
||||
and what remains — this closes the client-registration and discovery drift, not
|
||||
schema enforcement in general (G06 is a separate gap).
|
||||
|
||||
Both files now state the split: the Go model is the runtime authority, the YAML
|
||||
the reviewed contract, and the conformance test the link. SCOPE.md and G02's
|
||||
status say what is enforced and that G06 remains open.
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
---
|
||||
id: KEY-WP-0018
|
||||
type: workplan
|
||||
title: "Make the LLDAP export report its own completeness"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: export-completeness-evidence
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "82ff99a1-65cc-5e1f-93fc-5bc8911ce77a"
|
||||
---
|
||||
|
||||
Closes gap G05 of `history/2026-09-05-011726-scope-intent-assessment.md`.
|
||||
|
||||
`src/internal/migration/lldapexport/exporter.go` discovers groups by walking each
|
||||
user's memberships, so a group nobody belongs to is absent from the snapshot and
|
||||
nothing says so. A `LookupGroups` failure is skipped by a `continue` under a
|
||||
comment claiming it is recorded in the incompatibility report — it is not. The
|
||||
run then emits `result: "success"` and the CLI reports a clean export. Group
|
||||
order comes from map iteration, so two exports of an unchanged directory differ.
|
||||
|
||||
A second defect falls out of reading the adapter: `LDAPAdapter.LookupGroups`
|
||||
never populates `Group.Members`, and the exporter builds every membership from
|
||||
that field. Against a real LLDAP directory the `memberships` block is therefore
|
||||
always empty, while the fixture-based tests pass because the mock fills it in.
|
||||
|
||||
The unit of work is the evidence, not the enumeration: an export that cannot
|
||||
prove it saw the whole directory must say which one it is.
|
||||
|
||||
## Enumerate groups independently of membership
|
||||
|
||||
```task
|
||||
id: KEY-WP-0018-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "abb11f82-06e9-58ee-8be2-6d20491e9524"
|
||||
```
|
||||
|
||||
Add an optional `domain.GroupLister` capability (`ListGroups`) and implement it
|
||||
on `LDAPAdapter` with a direct group-subtree search that reads `member` and
|
||||
`uniqueMember`, so `Group.Members` is populated from the directory rather than
|
||||
left empty. Keep it optional rather than widening `UserRepository`: the OIDC
|
||||
layer has no use for it, and every existing implementation and test double would
|
||||
otherwise have to grow a method it never calls.
|
||||
|
||||
Added `domain.GroupLister` and `LDAPAdapter.ListGroups`, searching the group
|
||||
subtree for `groupOfNames`/`groupOfUniqueNames` and reading `member` with
|
||||
`uniqueMember` as the fallback attribute. Compile-time assertions on the adapter
|
||||
now state both contracts it satisfies. The adapter tests assert the filter is
|
||||
*not* a membership filter and that a member-less group survives the mapping,
|
||||
which is the property the whole task exists for.
|
||||
|
||||
## Report or fail on incomplete reads
|
||||
|
||||
```task
|
||||
id: KEY-WP-0018-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "d08f62dd-42e8-5c22-ba50-15b53f7473fd"
|
||||
```
|
||||
|
||||
Record on the result which enumeration actually ran — a complete directory
|
||||
enumeration or the membership-derived fallback — and treat a failed group
|
||||
enumeration as fatal rather than as a silent gap. In the fallback path, record
|
||||
each skipped user in the incompatibility report instead of discarding the error,
|
||||
and derive memberships from the discovered user/group pairs. Emit `partial`
|
||||
telemetry, and have the CLI say which enumeration produced the snapshot, so a
|
||||
degraded export is legible without reading the YAML.
|
||||
|
||||
`ExportResult` gained `groupEnumeration` — written unconditionally, so nobody has
|
||||
to infer completeness from an absent field — and a `Complete()` predicate that
|
||||
requires both a directory enumeration and an empty report. The fallback path
|
||||
records its own incompleteness as a report entry before it starts, so the
|
||||
snapshot says so even when every user lookup succeeds.
|
||||
|
||||
The fallback also had to stop deriving memberships from `Group.Members`: the
|
||||
LLDAP adapter never populated that field, so against a real directory the
|
||||
`memberships` block was always empty while the fixture-backed tests passed. It
|
||||
now derives each membership from the user/group pair actually observed.
|
||||
|
||||
## Define ordering and test the failure modes
|
||||
|
||||
```task
|
||||
id: KEY-WP-0018-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "3cd47458-9469-5cd9-a0b1-f2894402e1f8"
|
||||
```
|
||||
|
||||
Sort users, groups and memberships on stable keys so an unchanged directory
|
||||
exports byte-identically. Cover the cases the assessment names and the current
|
||||
suite does not: an empty group, a group enumeration failure, a per-user lookup
|
||||
failure in the fallback path, and repeat-run determinism.
|
||||
|
||||
Sorted users and groups by ID and memberships by group then user. Four exporter
|
||||
tests and two adapter tests cover the named cases; the determinism test runs the
|
||||
export three times over deliberately unsorted input and compares the full
|
||||
ordering, rather than asserting on a single sorted field.
|
||||
|
||||
Recorded in `SCOPE.md` and in G05's status what this does and does not establish:
|
||||
completeness evidence for the user/group/membership surface, not for credential
|
||||
migration, which stays under G03.
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
id: KEY-WP-0019
|
||||
type: workplan
|
||||
title: "Verify upstream Authelia ID tokens"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: upstream-provider-token-verification
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "87ea18ea-dc60-52ce-baf4-476b46aa06f9"
|
||||
---
|
||||
|
||||
Closes the remaining half of gap G01 in
|
||||
`history/2026-09-05-011726-scope-intent-assessment.md`. The Authelia adapter
|
||||
decodes upstream ID-token claims without verifying the signature, issuer,
|
||||
audience or expiry, justified in a comment by a server-to-server TLS boundary.
|
||||
|
||||
**Operator decision (2026-09-07, Bernd):** verify the token itself. The
|
||||
KeyCape→Authelia hop is to be HTTPS as defence in depth, but KeyCape will not
|
||||
monitor, check or gate on that — transport enforcement is friction without
|
||||
protection value here, and a check that must be configured correctly to help is
|
||||
itself a failure mode. Verification is chosen precisely because it does not
|
||||
depend on the transport being what we believe it is. No HTTPS validation, opt-in
|
||||
flag or transport telemetry is to be added.
|
||||
|
||||
## Extract a shared RS256/JWKS verifier
|
||||
|
||||
```task
|
||||
id: KEY-WP-0019-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "68c1b099-96b4-5065-a4be-8592438a0ca9"
|
||||
```
|
||||
|
||||
`internal/authclient` already contains a strict RS256 verifier — kid required,
|
||||
no `crit`, RSA-only keys of at least 2048 bits with an odd exponent. The upstream
|
||||
verification needs the same rules. Duplicating security-critical verification is
|
||||
how two copies drift and one silently misses a fix, so extract the reusable half
|
||||
into `internal/jose`: strict JWK-set parsing and signature verification returning
|
||||
claims. Claim policy stays with each caller, whose audience, nonce and issuer
|
||||
rules genuinely differ.
|
||||
|
||||
Added `internal/jose`: strict JWK-set parsing (RSA signing keys only, at least
|
||||
2048 bits, odd exponent 3 or greater, no duplicate key ids) and RS256 signature
|
||||
verification returning claims. All failures collapse to one `ErrVerification`
|
||||
so a caller cannot leak which check failed back to whoever supplied the token.
|
||||
`authclient` is not yet migrated onto it — see T05.
|
||||
|
||||
## Verify the Authelia ID token
|
||||
|
||||
```task
|
||||
id: KEY-WP-0019-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8105030b-c91f-5a40-a221-4a38c8531e1c"
|
||||
```
|
||||
|
||||
Before trusting any claim from the upstream ID token: verify the RS256 signature
|
||||
against Authelia's published keys, require the issuer Authelia advertises, require
|
||||
KeyCape's own client ID in the audience, and reject expired or not-yet-valid
|
||||
tokens. Fail closed — an unavailable or unparseable key set denies the login
|
||||
rather than falling back to unverified claims. Resolve Authelia's metadata and
|
||||
key set from the server-side token base URL so a split-horizon deployment, where
|
||||
the advertised public URL is not reachable from KeyCape, still verifies; allow
|
||||
explicit issuer and JWKS overrides for deployments where that inference is wrong.
|
||||
Refresh the key set once on an unknown key id so provider key rotation does not
|
||||
require a KeyCape restart. Replace the comment claiming the TLS boundary as
|
||||
justification.
|
||||
|
||||
`idTokenVerifier` in the adapter resolves the issuer and JWKS path from the
|
||||
provider's discovery document, rebasing the advertised `jwks_uri` path onto the
|
||||
server-side token base URL so split-horizon deployments resolve, with
|
||||
`issuer`/`jwksUrl` config overrides where that inference is wrong. Key sets are
|
||||
cached and refetched once on an unknown key id. `parseIDTokenClaims` remains for
|
||||
diagnostics but is documented as outside the authentication path.
|
||||
|
||||
## Prove the rejections
|
||||
|
||||
```task
|
||||
id: KEY-WP-0019-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "163f64dd-aa87-5ce3-b3be-d134fa993eb6"
|
||||
```
|
||||
|
||||
Negative tests per condition: forged signature, `alg: none` and other algorithms,
|
||||
unknown key id, wrong issuer, missing and wrong audience, expired token, future
|
||||
`iat`/`nbf`, malformed key set, and an unreachable JWKS endpoint. A positive case
|
||||
must confirm a genuine token still authenticates, and a rotation case that a new
|
||||
key id is picked up after one refresh.
|
||||
|
||||
Thirteen rejection cases in `verify_test.go` plus the algorithm and rotation
|
||||
cases. Confirmed they catch the original defect rather than merely passing: with
|
||||
the unverified parse restored, all fifteen fail. The existing adapter tests now
|
||||
build genuinely signed tokens against a fixture provider, so they exercise the
|
||||
real verification path instead of being routed around it.
|
||||
|
||||
## Reconcile the records
|
||||
|
||||
```task
|
||||
id: KEY-WP-0019-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "8a208092-7e85-5ad9-a8c4-0dddc66aaaee"
|
||||
```
|
||||
|
||||
Update `SCOPE.md` and G01's status in the assessment to state that upstream
|
||||
provider tokens are now verified independently of transport, and record the
|
||||
operator decision and its reasoning so the absence of transport enforcement reads
|
||||
as a choice rather than an oversight. G01 closes with this; complete profile
|
||||
conformance is still not claimed.
|
||||
|
||||
SCOPE.md and G01's status record the verification and the operator decision,
|
||||
including why no transport enforcement exists, so its absence reads as a choice.
|
||||
|
||||
## Consolidate the caller verifier
|
||||
|
||||
```task
|
||||
id: KEY-WP-0019-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "0dc909fe-1772-5816-9e11-dbe7664a4e1f"
|
||||
```
|
||||
|
||||
Move `internal/authclient`'s inline verification onto `internal/jose` so one
|
||||
implementation serves both paths. Kept separate from T01/T02 deliberately: the
|
||||
caller verifier is a tested security path, and destabilising it in the same
|
||||
change that introduces upstream verification would confuse the evidence for both.
|
||||
|
||||
`Client.Verify` now fetches the raw key set and delegates parsing and signature
|
||||
checking to `internal/jose`, keeping its own claim policy — audience and nonce
|
||||
bindings are the caller's and cannot live in a shared verifier. Its existing
|
||||
tests pass unchanged, which is the point: the migration preserved behaviour.
|
||||
|
||||
One deliberate strictness increase: a key set containing any malformed RSA
|
||||
signing key is now refused outright, where the previous code would have used a
|
||||
good key alongside a bad one. KeyCape's own `/jwks` publishes a single key, so
|
||||
this affects no current deployment.
|
||||
|
||||
Also added direct tests for `internal/jose` (17 cases). It is now the single
|
||||
verifier behind both paths, so testing it only through its callers would leave
|
||||
its edges — duplicate key ids, `crit`, even exponents, undersized moduli — to be
|
||||
covered by accident.
|
||||
|
||||
Follow-on evidence for the same task: "existing tests pass unchanged" shows the
|
||||
move preserved behaviour, not that the behaviour is checked, so the check was
|
||||
made explicit. Disabling the RSA comparison in `jose.Verify` fails both callers'
|
||||
suites, which establishes the shared verifier is load-bearing on each path.
|
||||
Added two caller-side cases the existing tamper case cannot reach, since it
|
||||
alters signature bytes for a key that is legitimately published: a structurally
|
||||
valid token signed by an unpublished key under a published kid, which fails only
|
||||
if key selection is bound to the key set, and an undersized modulus in the
|
||||
published set, which must deny rather than fall through to the claims.
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
id: KEY-WP-0020
|
||||
type: workplan
|
||||
title: "Make the Keycloak transform preserve or name every policy field"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: migration-contract-preservation
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "803845de-98ad-5a5b-89ec-11bd9e0fafec"
|
||||
---
|
||||
|
||||
Closes the first half of gap G03 of
|
||||
`history/2026-09-05-011726-scope-intent-assessment.md` — semantic preservation.
|
||||
The second half, proof against a running Keycloak, is G04 and stays open.
|
||||
|
||||
`keycape-to-keycloak` calls `Transform`, which passes no clients, so the realm it
|
||||
writes has an empty `clients` array and nothing says the service-identity
|
||||
contract was not migrated. `TransformWithClients` exists but no caller reaches
|
||||
it. Where clients are supplied, `mapClient` always enables the standard flow
|
||||
regardless of grant types, and drops `Audience`, `ServiceSubject`, `Tenant`,
|
||||
`Roles`, `TokenLifetime`, `MFARequired`, `SecretRef` and the handoff URLs.
|
||||
`mapUser` drops the canonical ID, tenant and roles. Realm roles and client scopes
|
||||
are written as empty containers with a comment saying they "can be extended".
|
||||
|
||||
The failure mode is not the missing mapping — it is that a dropped field and an
|
||||
inapplicable one look identical in the output. An operator diffing the realm JSON
|
||||
against the KeyCape config has no way to tell what this tool decided not to carry
|
||||
across. Preserve what Keycloak can express, and name the rest.
|
||||
|
||||
## Give the CLI the client registrations
|
||||
|
||||
```task
|
||||
id: KEY-WP-0020-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6f6bcfd7-16c5-5311-9568-ad9aa9c3b60a"
|
||||
```
|
||||
|
||||
Add a `-clients` flag reading the KeyCape config's client registrations and pass
|
||||
them through `TransformWithClients`. When no client file is given, say so on
|
||||
stderr and in the report rather than emitting an empty `clients` array that reads
|
||||
like a realm with no clients.
|
||||
|
||||
Added `-clients`, reading registrations through a new `config.Registrations()`
|
||||
that converts without resolving secrets — migration tooling needs the contract,
|
||||
never the material, so `ClientSecret` is left empty by construction rather than
|
||||
by remembering not to use it. An unparseable `tokenLifetime` is an error there,
|
||||
since silently dropping a per-client lifetime is the defect class this closes.
|
||||
Run against `config/dev-config.yaml`, all five registrations now reach the realm.
|
||||
|
||||
## Preserve what Keycloak can express
|
||||
|
||||
```task
|
||||
id: KEY-WP-0020-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "88c0126c-fd73-5178-af1f-a018708ff8fb"
|
||||
```
|
||||
|
||||
Derive `standardFlowEnabled` and `serviceAccountsEnabled` from the declared grant
|
||||
types instead of hardcoding the standard flow on. Carry the audience, tenant,
|
||||
service subject, roles and principal type as protocol mappers, per-client
|
||||
lifetime as the Keycloak client attribute that expresses it, and the handoff URLs
|
||||
and secret reference as attributes — the reference, never a secret value. Derive
|
||||
realm roles and client scopes from the users and clients actually present rather
|
||||
than emitting empty containers. Carry the user's canonical ID, tenant and roles.
|
||||
|
||||
Flows now follow the grants: the old mapping set `standardFlowEnabled` on every
|
||||
client, so migrating a `client_credentials` service registration silently gave it
|
||||
the browser flow. Audience, tenant, service subject and roles ride as protocol
|
||||
mappers, since Keycloak has no native concept for the profile's claims and would
|
||||
otherwise issue tokens the profile rejects. Lifetime, handoff URLs and the secret
|
||||
*reference* are attributes; a test marshals the realm and fails if a resolved
|
||||
secret value appears, because a realm import file is not a custody boundary.
|
||||
Realm roles and client scopes are derived and sorted.
|
||||
|
||||
## Name everything not preserved
|
||||
|
||||
```task
|
||||
id: KEY-WP-0020-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5b8127b2-c01c-5d3c-afbf-adbb368941e6"
|
||||
```
|
||||
|
||||
Report each field the transform cannot carry, at the point it is dropped, so the
|
||||
output distinguishes a deliberate omission from a gap: passwords and MFA
|
||||
credentials, MFA policy enforcement, and subject continuity — LLDAP's canonical
|
||||
ID is a DN, and Keycloak will mint its own `sub`, so tokens after a migration
|
||||
will not carry the same subject unless something downstream maps it. Fail the
|
||||
realm-level validation when a supplied client's contract is not fully
|
||||
represented. Test that each unpreserved field appears in the report.
|
||||
|
||||
`UnpreservedReport()` is separate from `ValidationReport()`. Folding them
|
||||
together was the first attempt and it broke an existing test asserting a clean
|
||||
export reports nothing — correctly so: consistency with the snapshot and
|
||||
completeness of the migration are different questions, and one list cannot answer
|
||||
both. Keeping them apart preserves the existing contract and makes the
|
||||
distinction the point rather than a side effect.
|
||||
|
||||
Six tests cover the flows, the mappers and lifetime, the secret-value exclusion,
|
||||
the derived roles and scopes, every named limit, and the separation of the two
|
||||
reports. Mutation-checked: restoring the hardcoded `standardFlowEnabled` fails
|
||||
the flow test. An incomplete transform now emits `partial` telemetry.
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
id: KEY-WP-0021
|
||||
type: workplan
|
||||
title: "Make snapshot attribute validation enforce a real rule"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: snapshot-attribute-validation
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "733ad1a5-c4d8-5c2f-8608-c51bf1d5b17e"
|
||||
---
|
||||
|
||||
Closes gap G06 of `history/2026-09-05-011726-scope-intent-assessment.md`:
|
||||
`checkNoUnknownAttributes` is named for a rule it does not implement. It rejects
|
||||
blank keys, which is not an allow-list, so a snapshot carrying arbitrary or
|
||||
malformed LDAP attribute names passes a check whose name says otherwise. A rule
|
||||
that passes for the wrong reason is worse than an absent one, because a report
|
||||
listing `no_unknown_attributes: passed` is read as evidence.
|
||||
|
||||
One part of the assessment's description needs correcting rather than
|
||||
implementing: it says group membership validation checks nonempty member IDs
|
||||
rather than complete referenced identity existence. `checkValidGroupMemberships`
|
||||
does only check emptiness, but the semantic rule `checkReferencedUsersExist`
|
||||
already resolves every member against the user set and fails on an unknown one.
|
||||
The reference constraint exists; it lives in a different rule than the one the
|
||||
assessment looked at.
|
||||
|
||||
## Enforce real attribute constraints
|
||||
|
||||
```task
|
||||
id: KEY-WP-0021-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "e2921983-853c-501b-8dfc-158751dea093"
|
||||
```
|
||||
|
||||
`ldapAttributes` is defined by the canonical model as raw attributes *not covered
|
||||
by* the model, so an allow-list of permitted names cannot be derived from the
|
||||
schema — the field exists precisely to carry what the schema does not name. Two
|
||||
constraints are derivable and worth enforcing:
|
||||
|
||||
- the attribute name must be a syntactically valid LDAP attribute descriptor
|
||||
(RFC 4512: a letter followed by letters, digits or hyphens, or a numeric OID),
|
||||
since a name that cannot be written to a directory cannot round-trip; and
|
||||
- it must not shadow a canonical field's documented LDAP mapping (`uid`, `cn`,
|
||||
`mail`), because two sources for one value is how an export and a directory
|
||||
come to disagree, and the model already owns those.
|
||||
|
||||
Attribute descriptors are case-insensitive in LDAP, so keys differing only by
|
||||
case are a collision, not two attributes. Keep the blank-key check. Rename the
|
||||
rule to describe what it actually enforces rather than leaving a name that
|
||||
overpromises.
|
||||
|
||||
Implemented as `raw_attributes_well_formed`, replacing `no_unknown_attributes`
|
||||
in both `validator.go` and `spec/ldap-schema.yaml`, with the schema entry stating
|
||||
plainly that this is not an allow-list and why one cannot be derived.
|
||||
|
||||
## Prove each rule with an invalid snapshot
|
||||
|
||||
```task
|
||||
id: KEY-WP-0021-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "de4caf0a-39c6-5148-a986-db18a1308505"
|
||||
```
|
||||
|
||||
The assessment asks for invalid-snapshot cases proving each stated rule. Add one
|
||||
per constraint — malformed descriptor, shadowed canonical mapping, case-only
|
||||
duplicate, blank key — plus passing cases for legitimate raw attributes and a
|
||||
valid numeric OID, so the rule is shown to be selective rather than merely
|
||||
strict.
|
||||
|
||||
Eleven malformed-descriptor cases, six canonical-mapping cases across casings, a
|
||||
case-only duplicate, a message-content case, and five accepted-attribute cases.
|
||||
Confirmed they test the rule rather than merely passing: making the check return
|
||||
early fails four of the five tests.
|
||||
|
||||
## Reconcile the records
|
||||
|
||||
```task
|
||||
id: KEY-WP-0021-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "8a48c6cc-1e7e-5fd4-abfa-2b893c3d400d"
|
||||
```
|
||||
|
||||
Update `SCOPE.md` and G06's status. State what the rule now enforces and, just as
|
||||
importantly, what it still does not: this is not schema-derived validation of
|
||||
every attribute against a directory schema, and the canonical model cannot
|
||||
provide one for a field defined as everything the model does not cover. Correct
|
||||
the assessment's reference-constraint premise rather than restating it.
|
||||
|
||||
`history/…-scope-intent-assessment.md` was being edited by a concurrent session
|
||||
when this workplan was written; make this edit only against a clean tree.
|
||||
|
||||
Done against a clean tree after the other session committed. G06's status records
|
||||
both corrections: no allow-list is derivable for a field defined as what the
|
||||
model does not cover, and the reference constraint was already implemented in
|
||||
`checkReferencedUsersExist` rather than missing.
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
---
|
||||
id: KEY-WP-0022
|
||||
type: workplan
|
||||
title: "Make the replacement harness runnable and target conformance externally"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: replacement-harness-and-external-conformance
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "94d74b17-1b2f-57dd-b6c3-50f10a7dccd9"
|
||||
---
|
||||
|
||||
Closes the runnable half of gap G04 of
|
||||
`history/2026-09-05-011726-scope-intent-assessment.md`. The Scenario B and C
|
||||
scripts cannot execute: they reference compose files that do not exist, call
|
||||
binaries at `src/bin/` where the Makefile builds to root `bin/`, pass `--base-dn`
|
||||
to `lldap-to-ldap` whose flag is `--basedn`, and invoke a hardcoded workstation Go
|
||||
path from outside the Go module.
|
||||
|
||||
The deeper problem is that repairing the shell would not prove anything. Both
|
||||
scripts set `KEYCAPE_TEST_ISSUER`, but the profile suite builds its own `httptest`
|
||||
server and never reads it, so the tests pass identically whether or not the
|
||||
external provider is running. A harness that cannot fail for the reason it exists
|
||||
is worse than a missing one.
|
||||
|
||||
## Repair the harness prerequisites
|
||||
|
||||
```task
|
||||
id: KEY-WP-0022-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "1d552328-e75b-57fd-83bc-e469ae46b397"
|
||||
```
|
||||
|
||||
Fix the binary paths, the `--basedn` flag, and the Go invocation so the scripts
|
||||
run from a clean checkout with `make build`: use the module directory rather than
|
||||
a workstation-specific Go path, and fail with a clear message when a prerequisite
|
||||
binary or a required environment value is absent instead of part-way through.
|
||||
|
||||
Scenario B rewritten: root `bin/`, `go -C src` so the module root is right,
|
||||
prerequisite checks before anything starts, and a cleanup trap so a failure does
|
||||
not leave a stack running. Scenario C repaired in place — `bin/` paths, `--basedn`
|
||||
for `lldap-to-ldap` (`lldap-export` correctly takes `--base-dn`; the two flags
|
||||
genuinely differ), and the same module-relative test invocation.
|
||||
|
||||
## Add the missing stacks
|
||||
|
||||
```task
|
||||
id: KEY-WP-0022-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "06f34e8b-f3d9-53ed-95c5-02e5063774a0"
|
||||
```
|
||||
|
||||
Provide `docker-compose.scenario-b.yml` (Keycloak over the migrated realm) and
|
||||
`docker-compose.scenario-c.yml` (OpenLDAP seeded from generated LDIF, plus
|
||||
Keycloak). Validate them with `docker compose config` rather than assuming they
|
||||
parse. Whether a full scenario passes end to end is a separate claim from whether
|
||||
the stack definition is valid, and only the second is established here.
|
||||
|
||||
Both files added and validated with `docker compose config`. Scenario B was also
|
||||
started for real; Scenario C's stack has not been run.
|
||||
|
||||
## Target conformance at an external issuer
|
||||
|
||||
```task
|
||||
id: KEY-WP-0022-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d837730d-e9d0-5adb-9dda-ffc47661fca3"
|
||||
```
|
||||
|
||||
Add a suite that reads `KEYCAPE_TEST_ISSUER` and exercises that issuer over HTTP:
|
||||
discovery metadata, the JWKS parsing under the same strict rules the runtime
|
||||
applies, and — when client credentials are supplied — a real token exchange whose
|
||||
claims are verified against the published keys. It must skip cleanly when the
|
||||
variable is unset so ordinary `make test` is unaffected, and it must fail when
|
||||
pointed at an issuer that does not meet the profile, which is the property the
|
||||
current harness lacks.
|
||||
|
||||
`src/tests/conformance` checks that the issuer advertises itself, the profile
|
||||
authorization surface (code, authorization_code, S256, RS256), that published
|
||||
keys parse under `internal/jose` — the same rules the runtime applies, so a
|
||||
provider publishing keys KeyCape would refuse fails here rather than at first
|
||||
login — that excluded grants are absent, and, with credentials, that an issued
|
||||
token verifies against the published keys and carries a matching issuer.
|
||||
|
||||
## Prove the suite against a real provider
|
||||
|
||||
```task
|
||||
id: KEY-WP-0022-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "cb4d8af7-76a4-50b8-bee5-eb1c52c2f894"
|
||||
```
|
||||
|
||||
Run the new suite against an actual Keycloak instance, not only against KeyCape,
|
||||
since the point of the scenario is a replacement provider. Record what genuinely
|
||||
passed and what remains unproven. Do not describe a partially exercised scenario
|
||||
as a demonstrated live swap.
|
||||
|
||||
Run against Keycloak 26.0. Discovery, authorization surface and published-key
|
||||
checks passed. A `client_credentials` exchange against a service client created
|
||||
through the admin API returned a token that verified against Keycloak's JWKS via
|
||||
`internal/jose`, with a matching issuer claim — the end-to-end property a relying
|
||||
party depends on, proved against a replacement provider rather than against
|
||||
KeyCape's own handlers.
|
||||
|
||||
The suite also failed, correctly. Stock Keycloak advertises `implicit` and
|
||||
`password` in its discovery document. In Keycloak these are server capabilities,
|
||||
not per-client toggles, so no realm the transformer emits can remove them: a
|
||||
migrated Keycloak presents a wider grant surface than KeyCape. Scenario B
|
||||
therefore reports a real failure today, which is the harness working. KeyCape
|
||||
itself was not targeted in this run — the dev stack needs key material absent
|
||||
from the checkout — so the suite is proved against the replacement provider, not
|
||||
yet against both sides of the swap.
|
||||
|
||||
## Reconcile the records
|
||||
|
||||
```task
|
||||
id: KEY-WP-0022-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "2cb63801-8182-5c71-ab8d-9a1c377f418c"
|
||||
```
|
||||
|
||||
Update `SCOPE.md` and G04's status with what the harness now does and what
|
||||
replacement claims remain unproven — in particular that directory migration, MFA
|
||||
and unchanged relying-party behaviour against a live Keycloak are not established
|
||||
by this work. G04 does not fully close here.
|
||||
|
||||
SCOPE.md and G04's status record both the working harness and the grant-surface
|
||||
finding, which substantiates with evidence what SCOPE previously asserted without
|
||||
it. Scenario C has still never been run end to end.
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
---
|
||||
id: KEY-WP-0023
|
||||
type: workplan
|
||||
title: "Prove the migration against live directories and fix what that surfaced"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: live-migration-proof
|
||||
created: "2026-09-08"
|
||||
updated: "2026-09-08"
|
||||
state_hub_workstream_id: "9f19bfa0-78d1-5b5c-9b64-e251fa4838d6"
|
||||
---
|
||||
|
||||
Finishes the unproven half of gap G04. KEY-WP-0022 made the harness runnable and
|
||||
targeted conformance at a live issuer; what remained was directory migration
|
||||
against a real directory, both sides of the swap measured, and the explicit
|
||||
limits recorded.
|
||||
|
||||
Running it found four defects that no amount of reading would have. That is the
|
||||
result worth keeping: every one of them passed the full unit suite.
|
||||
|
||||
## Run the export against a live LLDAP
|
||||
|
||||
```task
|
||||
id: KEY-WP-0023-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b7b2ea88-8457-5fae-a500-a2d0f75754de"
|
||||
```
|
||||
|
||||
**Defect: every LLDAP user search pointed at a branch that does not exist.**
|
||||
`Config.userOU()` defaulted to `ou=users`; LLDAP stores users under `ou=people`,
|
||||
and nothing in this repository set `UserOU` to correct it. `LookupUser`,
|
||||
`ListUsers` and `ValidatePassword` all derive their search base from it, so all
|
||||
three silently found nothing against a stock LLDAP — human login included, not
|
||||
just the export.
|
||||
|
||||
Symptom that exposed it: exporting from a live LLDAP returned **zero users while
|
||||
still emitting a membership referencing `uid=admin`**, a snapshot the repository's
|
||||
own validator rejects with `referenced_users_exist`. Default corrected to
|
||||
`ou=people` and pinned by a test; the re-run exports one user and validates
|
||||
clean.
|
||||
|
||||
## Correct the rule I broke in KEY-WP-0021
|
||||
|
||||
```task
|
||||
id: KEY-WP-0023-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2f988626-ed08-52ce-919a-cd45d48e712d"
|
||||
```
|
||||
|
||||
**Defect of my own making.** The LLDAP adapter records `_validation_warning`
|
||||
inside `LDAPAttributes`. The `raw_attributes_well_formed` rule added in
|
||||
KEY-WP-0021 rejects underscores as invalid attribute descriptors, so the
|
||||
exporter's own annotation made its own output fail validation — a false positive
|
||||
shipped one workplan earlier and caught only by validating a real export.
|
||||
|
||||
Keys prefixed with `_` are now recognised as tooling annotations and skipped,
|
||||
documented as such. A test proves the exemption does not weaken the rule: a
|
||||
malformed real attribute alongside an annotation still fails.
|
||||
|
||||
## Prove the LDIF loads into a real OpenLDAP
|
||||
|
||||
```task
|
||||
id: KEY-WP-0023-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0f503342-0ca8-55ad-b7e4-e2316d4d7bad"
|
||||
```
|
||||
|
||||
**Defect: migrated group memberships were dangling.** `resolveMemberDN` returned
|
||||
a member that already looked like a DN unchanged, so members kept their *source*
|
||||
DNs (`uid=admin,ou=people,…`) while `writeUser` wrote entries to the *target*
|
||||
branch (`ou=users`). Groups named entries the migrated directory does not
|
||||
contain. Only the naming value is carried across now; branch and naming
|
||||
attribute come from the target.
|
||||
|
||||
**Defect: empty groups could not be loaded at all.** `groupOfNames` makes
|
||||
`member` a MUST, so OpenLDAP rejected every memberless group with an object class
|
||||
violation and aborted the load. Since KEY-WP-0018 the export deliberately
|
||||
preserves empty groups, so this was reachable by any real migration. Empty groups
|
||||
now reference a placeholder entry the same LDIF creates — an `organizationalRole`,
|
||||
not a person, so it cannot be mistaken for a migrated user — emitted only when
|
||||
some group needs it.
|
||||
|
||||
**Defect: the compose file could not start.** `bitnami/openldap:2.6` does not
|
||||
exist in the registry. It passed `docker compose config` in KEY-WP-0022, which is
|
||||
exactly the gap between a file that parses and a stack that runs. Pinned to
|
||||
`osixia/openldap:1.5.0`, the image the scenario was actually proved against.
|
||||
|
||||
Proof: LLDAP → export → validate → LDIF → `ldapadd` into OpenLDAP 1.5.0, all
|
||||
seven entries added with no errors, and every `member` value in the loaded
|
||||
directory resolves to an entry that exists.
|
||||
|
||||
## Measure both sides of the swap
|
||||
|
||||
```task
|
||||
id: KEY-WP-0023-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8a7fe95e-d463-5e74-81fa-5fa60a879774"
|
||||
```
|
||||
|
||||
KeyCape was run locally against a generated key and targeted with the
|
||||
conformance suite: all five checks pass, including the excluded-grant check
|
||||
Keycloak fails. The Keycloak side was exercised twice — the stock realm in
|
||||
KEY-WP-0022, and here the *migrated* realm produced from the live export, which
|
||||
imported cleanly and served discovery. The migrated user arrived carrying
|
||||
`keycape.canonicalId`.
|
||||
|
||||
The divergence is now measured rather than asserted: KeyCape passes 5/5, a
|
||||
migrated Keycloak passes 4/5 and fails on advertising the excluded `implicit` and
|
||||
`password` grants.
|
||||
|
||||
## Record what is proved and what is not
|
||||
|
||||
```task
|
||||
id: KEY-WP-0023-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "acef6ceb-6dea-56e1-94bc-e5d589ef3b6f"
|
||||
```
|
||||
|
||||
G04 is substantially closed: stacks are reproducible and were run, conformance is
|
||||
externally targeted and was run against three issuers, and directory migration is
|
||||
proved end to end into a real directory.
|
||||
|
||||
Not closed, and stated as such: no browser login was performed against the
|
||||
migrated realm, so unchanged relying-party behaviour is not established, and MFA
|
||||
was not exercised. Credential and MFA migration is a **scope exclusion** rather
|
||||
than a test gap — SCOPE says it is not supplied, so no harness can demonstrate
|
||||
it. `keycape-to-keycloak` already reports that subject continuity is not
|
||||
preserved: the canonical ID survives as an attribute while Keycloak mints its own
|
||||
`sub`, so relying parties keyed on `sub` will not recognise migrated users.
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
id: KEY-WP-0024
|
||||
type: workplan
|
||||
title: "Wire tenant_roles as explicit opt-in configuration"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: tenant-roles-opt-in-wiring
|
||||
created: "2026-09-08"
|
||||
updated: "2026-09-08"
|
||||
state_hub_workstream_id: "d75f52f2-df0d-5a56-8394-892feeec4b4d"
|
||||
---
|
||||
|
||||
Closes gap G07. The tenant-engine client and `TokenHandler.TenantEngine` were
|
||||
implemented and tested, but `main.go` supplied no client and exposed no
|
||||
configuration, so the stock executable left the field nil and never emitted
|
||||
`tenant_roles`. The capability existed at library level only.
|
||||
|
||||
Wired rather than documented away as library-only: the claim is cheap, the
|
||||
adapter already fails open, and leaving a tested capability unreachable from the
|
||||
binary invites the same gap being rediscovered later.
|
||||
|
||||
## Add opt-in configuration and wire the client
|
||||
|
||||
```task
|
||||
id: KEY-WP-0024-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "0e53485b-7e94-5dd8-8e02-1d18f5413a78"
|
||||
```
|
||||
|
||||
Added a `tenantEngine` config block with `baseURL` and an optional `timeout`.
|
||||
An empty `baseURL` disables the claim, which keeps the stock server's behaviour
|
||||
exactly as it was — enabling it is a deliberate act.
|
||||
|
||||
Validation treats a configured source as one that must work: the URL must be
|
||||
http/https with a host, and the timeout must parse and fall in (0, 10s], since it
|
||||
sits on the synchronous token-issuance path. A `timeout` set without a `baseURL`
|
||||
is rejected rather than ignored — it means someone expected the claim to be on
|
||||
and it silently would not have been.
|
||||
|
||||
## Verify it in the built executable
|
||||
|
||||
```task
|
||||
id: KEY-WP-0024-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "c4279ce0-f6f8-5612-a48a-c1d2fd3da3b6"
|
||||
```
|
||||
|
||||
G07's closure criterion is the built executable, not the wiring, so all three
|
||||
behaviours were checked by running `bin/keycape` and inspecting real tokens
|
||||
rather than by reading the code:
|
||||
|
||||
- configured against a stub tenant-engine, a `client_credentials` token carries
|
||||
`tenant_roles: [capability:approve, capability:observe]`;
|
||||
- with no `tenantEngine` block, the claim is absent;
|
||||
- configured but with the source down, issuance still succeeds and the claim is
|
||||
absent — the fail-open behaviour the adapter documents, confirmed end to end.
|
||||
|
||||
Nine validation cases cover the accepted and rejected configurations.
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
id: KEY-WP-0025
|
||||
type: workplan
|
||||
title: "Give the runtime real readiness, graceful shutdown and stated operational limits"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: runtime-lifecycle-and-readiness
|
||||
created: "2026-09-08"
|
||||
updated: "2026-09-08"
|
||||
state_hub_workstream_id: "70f74bbd-6748-5bbe-b261-a310b6f24e0c"
|
||||
---
|
||||
|
||||
Closes gap G08. `/healthz` returned a constant without probing anything, the
|
||||
server called `ListenAndServe` with no signal handling, and the operational
|
||||
limits of in-memory state, startup-loaded keys and local-only logout were spread
|
||||
across code comments rather than stated anywhere an operator would look.
|
||||
|
||||
G08 explicitly does not require shared storage or refresh tokens. The work is
|
||||
therefore to make readiness real, make restarts survivable for in-flight
|
||||
requests, and write the limits down honestly.
|
||||
|
||||
## Separate readiness from liveness
|
||||
|
||||
```task
|
||||
id: KEY-WP-0025-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "0e0d3349-efaf-546c-a58d-2a9b736278a9"
|
||||
```
|
||||
|
||||
Added `internal/server/readiness` and a `/readyz` endpoint probing LLDAP,
|
||||
Authelia and privacyIDEA. `/healthz` stays liveness and deliberately probes
|
||||
nothing: wiring liveness to dependency health means an orchestrator restarts
|
||||
KeyCape when a dependency blinks, and since a restart also discards every
|
||||
in-flight login, the reaction is worse than the condition.
|
||||
|
||||
Design decisions worth keeping:
|
||||
|
||||
- **LLDAP is probed with a bind, not a dial.** A rotated or revoked service
|
||||
password leaves the port open and every lookup failing — precisely the state
|
||||
readiness exists to catch, and precisely what a TCP dial would miss.
|
||||
- **The body names the failing check but never the reason.** The endpoint is
|
||||
unauthenticated, and upstream error text carries hostnames and sometimes
|
||||
credentials-in-URLs. Detail goes to the log. A test asserts a probe error
|
||||
containing a credentialed URL does not reach the response.
|
||||
- **Results are cached for 2s.** Not an optimisation: without it, anyone able to
|
||||
reach an unauthenticated endpoint could drive one upstream request per
|
||||
dependency per call.
|
||||
- **Probes run concurrently, each bounded at 3s**, so readiness costs the slowest
|
||||
probe rather than their sum, and a hung dependency makes the endpoint answer
|
||||
rather than hang alongside it.
|
||||
|
||||
## Shut down without dropping requests
|
||||
|
||||
```task
|
||||
id: KEY-WP-0025-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "0bea0780-523a-587e-a157-d429df15307b"
|
||||
```
|
||||
|
||||
`SIGTERM`/`SIGINT` now drain in-flight requests for up to 15 seconds. The grace
|
||||
period sits under the 30s read/write timeouts so a stuck request cannot outlive
|
||||
the window an orchestrator allows before `SIGKILL`.
|
||||
|
||||
In-memory login state is deliberately not preserved: a browser mid-login must
|
||||
start again after a restart. That is a property of the single-replica topology,
|
||||
documented rather than papered over.
|
||||
|
||||
## State the operational limits
|
||||
|
||||
```task
|
||||
id: KEY-WP-0025-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "97eba4bb-f8a0-5941-bdc8-6d59b0fa2942"
|
||||
```
|
||||
|
||||
`docs/operations.md` states the supported topology — exactly one replica, because
|
||||
authorization codes, login sessions and handoffs are process-local and no sticky
|
||||
configuration makes a second replica safe — and the consequences: restarts drop
|
||||
in-flight logins while issued tokens keep working, and rolling deploys have a
|
||||
window where new logins fail.
|
||||
|
||||
It also records three limits that are easy to get wrong in operation:
|
||||
|
||||
- the key ID is the constant `key-1`, so rotating the key without changing it can
|
||||
leave consumers caching JWKS by `kid` rejecting freshly issued tokens;
|
||||
- removing a client registration does not revoke its issued tokens, since there
|
||||
is no revocation or introspection endpoint — they stay valid until expiry;
|
||||
- `/logout` clears the local session only, not the Authelia session or any token.
|
||||
|
||||
No throughput or resource figures are given: nothing here benchmarks KeyCape, so
|
||||
any number would be invented.
|
||||
|
||||
## Verify in the running server
|
||||
|
||||
```task
|
||||
id: KEY-WP-0025-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "bc64fdc2-0a31-5dab-afd1-5e7b6d11d2b3"
|
||||
```
|
||||
|
||||
Checked against the built executable rather than the handlers, following the
|
||||
lesson from KEY-WP-0023:
|
||||
|
||||
- with every dependency down, `/readyz` returns 503 naming all three as failed
|
||||
while `/healthz` still returns 200 — the split doing its job;
|
||||
- starting LLDAP flips its check to `ok` while the others stay failed, so the
|
||||
probes are measuring what they claim;
|
||||
- 40 requests issued across a `SIGTERM` all returned 200, and the log shows the
|
||||
signal handled and shutdown completed.
|
||||
|
||||
Seven unit tests cover the reporter, including the disclosure, caching, timeout
|
||||
and concurrency properties above.
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
id: KEY-WP-0026
|
||||
type: workplan
|
||||
title: "Reconcile packaging, bootstrap and migration credential handling"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: packaging-bootstrap-and-credential-handling
|
||||
created: "2026-09-08"
|
||||
updated: "2026-09-08"
|
||||
state_hub_workstream_id: "d2b04d59-41b1-531b-8cab-a980328611b4"
|
||||
---
|
||||
|
||||
Closes gap G09. Five separate defects, only loosely related: the dev stack could
|
||||
not start from a clean checkout, the image shipped one binary of five, the
|
||||
publish workflow named a different registry than the one the cluster pulls from,
|
||||
the exporter took a password on argv, and every migration artifact was written
|
||||
world-readable.
|
||||
|
||||
## Take the bind password off argv
|
||||
|
||||
```task
|
||||
id: KEY-WP-0026-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "510a4603-337f-5ceb-b6b7-b9126d8605f0"
|
||||
```
|
||||
|
||||
`lldap-export` took `--bind-pw`, so the service account password was visible to
|
||||
any local user through `ps`, and captured by shell history and process
|
||||
accounting. It now prefers `KEYCAPE_LLDAP_BIND_PW`, accepts `--bind-pw-file`, and
|
||||
keeps `--bind-pw` working with a warning — deprecated rather than removed,
|
||||
because existing runbooks use it and silently breaking them would be worse than
|
||||
the exposure for one more cycle.
|
||||
|
||||
Conflicting sources are rejected rather than silently ranked: with two supplied,
|
||||
an operator cannot tell which bind was attempted. A password file has its
|
||||
trailing newline trimmed, since editors and heredocs add one and binding with it
|
||||
fails confusingly. Both migration scripts now pass the password by environment.
|
||||
|
||||
## Stop writing migration artifacts world-readable
|
||||
|
||||
```task
|
||||
id: KEY-WP-0026-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "679e21a1-e37b-574b-bc25-b9fd46bdfd16"
|
||||
```
|
||||
|
||||
The canonical export, the generated LDIF and the Keycloak realm were all written
|
||||
`0644`. None carries credential material, but the snapshot is a directory dump —
|
||||
every username, display name, email and group membership in the estate — and it
|
||||
tended to land in `/tmp`. All three are `0600` now.
|
||||
|
||||
## Ship every binary in the image
|
||||
|
||||
```task
|
||||
id: KEY-WP-0026-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "5252a13c-cd5d-5686-a7f5-04587d7d9b1d"
|
||||
```
|
||||
|
||||
The image packaged `keycape` alone, so the validator and the three migration
|
||||
binaries needed a Go toolchain on the host — which defeats shipping an image for
|
||||
the cutover work they exist to support. All five are built and copied; the issuer
|
||||
remains the entrypoint and the rest are reachable by overriding it. Verified by
|
||||
building the image and running each binary inside it.
|
||||
|
||||
## Reconcile the publish target
|
||||
|
||||
```task
|
||||
id: KEY-WP-0026-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "45b8c386-41bd-514c-9247-5eba9663d8fb"
|
||||
```
|
||||
|
||||
The workflow published to `92.205.130.254:32166/coulomb/key-cape` while the
|
||||
cluster runs `forgejo.coulomb.social/coulomb/key-cape` (KEY-WP-0013). Whether
|
||||
those are one registry behind two names or two registries is not determinable
|
||||
from this repository, which is exactly the problem: a push could not be assumed
|
||||
to have deployed the current source. Now defaults to the recorded Forgejo name
|
||||
and stays overridable through a repository variable.
|
||||
|
||||
**Unverified, and needs an operator's eye:** this repository cannot test that the
|
||||
runner resolves that hostname or that `REGISTRY_USER`/`REGISTRY_TOKEN` are valid
|
||||
for it. If the next publish fails, set the `REGISTRY` repository variable back to
|
||||
the address.
|
||||
|
||||
## Make the dev stack bootstrappable
|
||||
|
||||
```task
|
||||
id: KEY-WP-0026-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "a750ac2f-cad2-5e7f-a505-2ca6b94f1a5a"
|
||||
```
|
||||
|
||||
`docker-compose.dev.yml` mounts `config/dev-key.pem` and `config/authelia/`,
|
||||
neither in the checkout — correctly, since one is a private key and the other
|
||||
carries a password hash. `scripts/bootstrap-dev.sh` generates both locally: an
|
||||
RSA key created under `umask 077` rather than chmodded afterwards, so it is never
|
||||
briefly world-readable, plus an Authelia configuration, its OIDC signing key and
|
||||
a user fixture. Everything it writes is git-ignored, and re-running it leaves
|
||||
existing material alone.
|
||||
|
||||
The fixture password is published in the script deliberately: a reader can see
|
||||
exactly what it unlocks, which is nothing deployed.
|
||||
|
||||
## Reconcile the records
|
||||
|
||||
```task
|
||||
id: KEY-WP-0026-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "cc7c86ae-9c78-5422-8f3c-19441ceeb513"
|
||||
```
|
||||
|
||||
`SCOPE.md` and G09's status record what is fixed and what is not: the registry
|
||||
change is unverified from here, and a documented external bootstrap remains the
|
||||
route for anything beyond development.
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
id: KEY-WP-0027
|
||||
type: workplan
|
||||
title: "Establish the live state and the rollout gap for G10"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: rollout-readiness-and-live-state
|
||||
created: "2026-09-08"
|
||||
updated: "2026-09-08"
|
||||
state_hub_workstream_id: "2ab60558-ee14-542c-9f19-bb487de8e668"
|
||||
---
|
||||
|
||||
G10 cannot be closed from this repository: it waits on named custody and platform
|
||||
owners. What was doable was to stop guessing about the live state, verify the
|
||||
handoffs actually went out, and find out whether main is safe to roll out. The
|
||||
last question turned up a defect in this repository's own recent work.
|
||||
|
||||
## Verify the handoff receipts independently
|
||||
|
||||
```task
|
||||
id: KEY-WP-0027-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "00de2bad-04da-5b58-8cde-aaa144272541"
|
||||
```
|
||||
|
||||
KEY-WP-0009 was reopened once because delivery was claimed without receipts, so
|
||||
the claim that it has now been sent deserved the same scrutiny rather than being
|
||||
taken on trust. `GET /messages/?from_agent=key-cape` shows all seven present and
|
||||
dated 2026-09-08T08:21Z: the four KEY-WP-0009 handoffs (netkingdom,
|
||||
secrets-engine, ops-warden, railiance-platform) and the three admission requests
|
||||
(railiance-platform custody, approval-engine human callback, ops-warden token
|
||||
split). No replies had arrived at the time of writing, which is expected hours
|
||||
after sending and is not itself a blocker to record.
|
||||
|
||||
## Record the live state as observed, not as remembered
|
||||
|
||||
```task
|
||||
id: KEY-WP-0027-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "2d513229-1dbd-500e-9100-c0173be7b4de"
|
||||
```
|
||||
|
||||
Read-only cluster inspection, `sso` namespace, 2026-09-08:
|
||||
|
||||
- image `forgejo.coulomb.social/coulomb/key-cape:main-153258b`, predating every
|
||||
change from KEY-WP-0016 onward;
|
||||
- environment carries `KEYCAPE_RAPP_QONTO_CLIENT_SECRET` only, so the two
|
||||
approval clients are still not materialized — KEY-WP-0013-T02 remains correctly
|
||||
blocked;
|
||||
- four registered clients: `demo-app`, `netkingdom-bootstrap-console` and
|
||||
`openbao-admin` (public, authorization_code) and `rapp-qonto-client`
|
||||
(confidential, client_credentials);
|
||||
- no `tenantEngine` block, so KEY-WP-0024 is inert on rollout.
|
||||
|
||||
**Correction to an earlier claim of mine.** I reported the `ou=people` fix
|
||||
(KEY-WP-0023) as a production-relevant correction to human login. The deployed
|
||||
config sets `userOU: "ou=people"` explicitly, so production was never affected.
|
||||
The fix matters for anyone relying on the default — the dev stack, the scenario
|
||||
scripts, a fresh deployment — not for this one.
|
||||
|
||||
## Find out whether main is safe to roll out
|
||||
|
||||
```task
|
||||
id: KEY-WP-0027-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "23c8d04b-c2b7-5347-80a0-6388c1c41c53"
|
||||
```
|
||||
|
||||
**Defect found in KEY-WP-0019, before it shipped.** Upstream verification
|
||||
discovers the expected issuer server-side from `authelia.tokenBaseURL`. The
|
||||
deployed Authelia derives its advertised issuer from the request Host, confirmed
|
||||
directly:
|
||||
|
||||
| Host presented | issuer advertised |
|
||||
| --- | --- |
|
||||
| `authelia.sso.svc.cluster.local:9091` | `http://authelia.sso.svc.cluster.local:9091` |
|
||||
| `auth.coulomb.social` | `http://auth.coulomb.social` |
|
||||
|
||||
KeyCape fetches over the in-cluster address, so it would expect the first value
|
||||
while tokens minted for the browser-facing host carry the second. Verification
|
||||
fails closed, so if they differ **every human login fails after rollout**, and it
|
||||
presents as a broken login rather than a configuration error.
|
||||
|
||||
Which value the ID token actually carries could not be settled from here without
|
||||
performing a real login against production Authelia, which was not done. The risk
|
||||
is therefore recorded as a rollout precondition, not as a confirmed breakage.
|
||||
|
||||
Two mitigations, both in this repository:
|
||||
|
||||
- `docs/operations.md` documents pinning `authelia.issuer` and `authelia.jwksUrl`
|
||||
explicitly, with the one-line `curl` that reveals the value the provider will
|
||||
advertise for a given Host. The overrides already existed; nothing said when
|
||||
they were needed.
|
||||
- The failure is now diagnosable. The authentication failure event carries a
|
||||
specific reason — `id_token_issuer_mismatch`, distinct from
|
||||
`id_token_signature`, `provider_keys_unavailable` and the rest — so an operator
|
||||
can tell a misconfiguration from an attack. Five cases cover the mapping. The
|
||||
browser still learns nothing.
|
||||
|
||||
## Record what each owner owes
|
||||
|
||||
```task
|
||||
id: KEY-WP-0027-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "7ba84b61-0458-5820-8ded-c6f59d820303"
|
||||
```
|
||||
|
||||
G10's status now names the live state, the outstanding requests with their
|
||||
receipt ids, and the issuer precondition that must be checked before main is
|
||||
deployed. The precondition was also sent to railiance-platform as the deployment
|
||||
owner (message c8b1ad10), with the exact `curl` to settle it, the config keys to
|
||||
pin, and the `error_type` to look for if it bites — plus a note that the two
|
||||
approval clients remain unprovisioned, confirmed by inspection rather than
|
||||
asserted. G10 stays open: nothing here admits custody, registers the human
|
||||
callback, or reconciles the OpenBao-token boundary, and no repo-local change can.
|
||||
|
||||
|
||||
2026-09-08 live return: the actual signed-token uncertainty in T03 is now
|
||||
resolved by KEY-WP-0013-T02's admitted probe. The issuer is exactly
|
||||
`https://auth.coulomb.social`; signature/audience/time/nonce checks passed and
|
||||
the Job exited 0. The temporary route, policies, service and Job/Pod were removed.
|
||||
`docs/evidence/2026-09-08-upstream-issuer-live-proof.json` is the source receipt.
|
||||
The configuration owner still ensures the exact pin before main rollout; no
|
||||
normal config or Deployment changed during this proof.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
id: KEY-WP-0028
|
||||
type: workplan
|
||||
title: "Reject service-identity fields that a browser client silently ignores"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: browser-client-field-validation
|
||||
created: "2026-09-09"
|
||||
updated: "2026-09-09"
|
||||
state_hub_workstream_id: "6d0cb4fd-17da-5466-8011-5ade1ac46171"
|
||||
---
|
||||
|
||||
`serviceSubject` and `roles` are read only on the `client_credentials` path. On a
|
||||
browser client they are accepted at startup and then ignored: the subject and
|
||||
roles come from the directory user. A registration that looks effective and is
|
||||
not fails later, somewhere else, as a downstream rejection rather than as a
|
||||
registration defect.
|
||||
|
||||
`tokenLifetime` was already rejected this way for non-service clients, so the
|
||||
rule exists — it was just incomplete.
|
||||
|
||||
## Reject the ignored fields
|
||||
|
||||
```task
|
||||
id: KEY-WP-0028-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "e4bce8c7-0e60-55e8-ad32-a3a0c4e46044"
|
||||
```
|
||||
|
||||
Config validation now rejects `serviceSubject` and `roles` on any client that
|
||||
does not use `client_credentials`, naming where the value actually comes from.
|
||||
Nothing in `config/dev-config.yaml`, the example fixture or the live deployment
|
||||
sets either field on a browser client, so this breaks no existing configuration —
|
||||
checked against all three rather than assumed.
|
||||
|
||||
Re-verified against `sso/keycape-config` on 2026-09-09, after a peer raised that
|
||||
this validation fails closed at startup and a rollout is imminent, so a stray
|
||||
field would surface as KeyCape failing to boot. The deployed config now holds six
|
||||
clients: `demo-app`, `netkingdom-bootstrap-console` and `openbao-admin` are
|
||||
browser clients carrying neither field, while `rapp-qonto-client`,
|
||||
`secrets-engine-approval` and `approval-engine-operator` carry both legitimately
|
||||
on the `client_credentials` path. The rule rejects none of them. Kept as errors
|
||||
rather than downgraded to warnings on that evidence: a warning for a field that
|
||||
does nothing is the state this change exists to end.
|
||||
|
||||
**`tenant` is deliberately excluded from the rule**, and a test pins that. A
|
||||
browser client may declare a tenant; `humanTenant` resolves it against the
|
||||
directory and refuses issuance when the two disagree (KEY-WP-0013-T05). An
|
||||
earlier version of this change rejected `tenant` too, which would have made that
|
||||
feature unusable.
|
||||
|
||||
## Correction
|
||||
|
||||
```task
|
||||
id: KEY-WP-0028-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "5ab951f8-2f9c-522c-b6ea-7f6d572cd618"
|
||||
```
|
||||
|
||||
This work started from KEY-WP-0013-T05's blocker paragraph, which described a
|
||||
human token as unable to carry `tenant:platform`. That was true when written and
|
||||
had already been fixed in a concurrent session by the time this task began; the
|
||||
paragraph was read as current state rather than as a dated record.
|
||||
|
||||
Two consequences, both corrected within the session:
|
||||
|
||||
- the `tenant` rejection was removed before it was committed, and a test now
|
||||
asserts a browser client carrying `tenant: tenant:platform` validates cleanly;
|
||||
- a hub decision (`0145ab57`) recording the tenant question as open and
|
||||
unimplemented was withdrawn with a rationale pointing at the implementation.
|
||||
Nothing was built on it.
|
||||
|
||||
A third instance followed the same day, outside this task: a message to
|
||||
railiance-platform (d361bf28) listed the upstream issuer pin as outstanding when
|
||||
`docs/evidence/2026-09-09-upstream-issuer-pin.json` showed it done ten hours
|
||||
earlier — `check_before.issuer_matches: false`, then pinned to
|
||||
`https://auth.coulomb.social`. Corrected in dd827b9f. The concern itself was
|
||||
sound; only the claim that it was still owed was wrong, and the two are worth
|
||||
separating rather than letting the correct half excuse the other.
|
||||
|
||||
The lesson worth keeping: in a repository where several sessions work at once,
|
||||
prose ages faster than the code and the receipts it describes. `docs/evidence/`
|
||||
is the authoritative artifact for anything claiming a live proof here. Read it
|
||||
before writing "outstanding", "unproven" or "not done" — that check is cheaper
|
||||
than any of the corrections it would have prevented.
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
id: KEY-WP-0029
|
||||
type: workplan
|
||||
title: "Reassess SCOPE against the current revision"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: scope-reassessment
|
||||
created: "2026-09-09"
|
||||
updated: "2026-09-09"
|
||||
state_hub_workstream_id: "cebe226b-5418-5559-bc48-379e843bca78"
|
||||
state_hub_workstream_id: "cebe226b-5418-5559-bc48-379e843bca78"
|
||||
---
|
||||
|
||||
SCOPE.md declared itself reviewed against `b989de4`, 63 commits behind. Its
|
||||
limits section had been kept current by appending to bullets, which works until
|
||||
the header asserts a review date that no longer describes what was reviewed.
|
||||
|
||||
## Recheck every claim against source
|
||||
|
||||
```task
|
||||
id: KEY-WP-0029-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "b12dfe7d-f4b6-5c44-89c6-9d3c02435d4c"
|
||||
```
|
||||
|
||||
Routes read from the mux registrations, subcommands from the dispatch in
|
||||
`main.go` and `internal/authclient/cli.go`, flags from the flag sets, claim
|
||||
behaviour from `token.go` and `userinfo.go`. Where the existing prose and the
|
||||
code disagreed, the code won.
|
||||
|
||||
Three claims were false and are corrected: caller commands omitted
|
||||
`verify-client`; the container was described as packaging only `keycape`, untrue
|
||||
since KEY-WP-0026; and the issuer API row omitted `/readyz`. Capabilities that
|
||||
had only reached SCOPE piecemeal — the authorization-code bindings, upstream
|
||||
token verification, readiness and drain, the human tenant rules,
|
||||
authentication-time assurance, the shared verifier — are now stated in the
|
||||
capability table rather than only in the limits prose.
|
||||
|
||||
## Separate source behaviour from deployed behaviour
|
||||
|
||||
```task
|
||||
id: KEY-WP-0029-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "28ae58c0-9bb9-573f-9cf3-51230d053e3c"
|
||||
```
|
||||
|
||||
The document now says plainly that it describes source, and that the two are
|
||||
different claims with different evidence: the test suite and harness runs for
|
||||
one, committed receipts in `docs/evidence/` for the other.
|
||||
|
||||
What is proven live is narrower and is stated exactly — the issuer pin, whose
|
||||
pre-pin state did *not* match, so that risk was real rather than hypothetical;
|
||||
and the two approval clients verified in an attended owner process. Both receipts
|
||||
decline real predecessor rotation and observed wall-clock expiry, and SCOPE
|
||||
declines them too. The deployed image predates this revision, so several
|
||||
behaviours SCOPE describes are running nowhere yet, two of them fail-closed at
|
||||
startup.
|
||||
|
||||
This is the correction that matters. Four times in two days a statement about
|
||||
current state was written from a workplan paragraph, a partial `git status`, or
|
||||
an inference about what was admitted, without reading the authoritative artifact.
|
||||
A SCOPE that does not distinguish "the code does this" from "this is running"
|
||||
invites exactly that error from the next reader.
|
||||
|
||||
## Record the reassessment
|
||||
|
||||
```task
|
||||
id: KEY-WP-0029-T03
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "7ef92d4f-eb56-5208-8b85-ed7bb2656c46"
|
||||
```
|
||||
|
||||
`history/2026-09-09-scope-reassessment.md` records the method, what changed since
|
||||
2026-09-05, the corrections, and what remains. It supersedes rather than replaces
|
||||
the 2026-09-05 assessment, which stays as the record of the ten-gap backlog and
|
||||
its closures; both are linked from SCOPE.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
id: KEY-WP-0030
|
||||
type: workplan
|
||||
title: "Make the static-registration precondition a checked condition"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: tenant-precondition-guard
|
||||
created: "2026-09-09"
|
||||
updated: "2026-09-09"
|
||||
state_hub_workstream_id: "9ed8f851-080a-5d15-8642-f799c2e3295c"
|
||||
---
|
||||
|
||||
`informed-decision` replied on KEY-WP-0013-T05 and asked for one thing that is
|
||||
ours to do: the caveat under which a registration-bound human tenant is safe
|
||||
should be "a condition of the capability, so a future change to registration
|
||||
policy has to confront it", not reasoning left in a message.
|
||||
|
||||
They are right that it was only prose. `docs/tenant-claim-contract.md` states it,
|
||||
and a separate test asserted `registration_endpoint` is absent — but nothing
|
||||
connected the two, so a future session adding dynamic registration would see a
|
||||
test about discovery metadata, not a warning about relabelling users.
|
||||
|
||||
## Tie the exclusion to the capability it protects
|
||||
|
||||
```task
|
||||
id: KEY-WP-0030-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "4729d591-39d4-5f33-bef8-db8057090913"
|
||||
```
|
||||
|
||||
`TestRegistrationBoundTenantRequiresStaticRegistration` asserts both halves
|
||||
together: that a client-declared tenant is issued, and that dynamic client
|
||||
registration is not advertised. Whichever is removed first, the failure points at
|
||||
the other.
|
||||
|
||||
The failure message carries the reasoning rather than the observation. Adding
|
||||
dynamic registration while a registration may declare its users' tenant means
|
||||
anyone able to register a client can relabel the users who log in through it into
|
||||
a zone of their choosing; the message says that and names the two ways out, drop
|
||||
the capability or gate it to statically configured registrations.
|
||||
|
||||
Verified in both directions rather than assumed: advertising a
|
||||
`registration_endpoint` fails it with the escalation message, and neutering
|
||||
`humanTenant` fails it with the message saying to remove the guard along with the
|
||||
capability it protects.
|
||||
|
||||
This is deliberately not a vote on the tenant question. It makes the precondition
|
||||
of one option checkable; it does not choose between them, and if option 1 lands
|
||||
the capability and this guard are removed together.
|
||||
|
||||
## What stays with the owners
|
||||
|
||||
```task
|
||||
id: KEY-WP-0030-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "32b10327-f55d-5a0e-a4bc-be92929469dc"
|
||||
```
|
||||
|
||||
`informed-decision` prefers option 2 and explicitly declines to treat that as the
|
||||
answer, flagging it to gate-house on GH-DEC-2026-012 and naming approval-engine's
|
||||
stake. KeyCape leans the same way, which is a reason for more care rather than
|
||||
less: the code already implements option 2 (`329e48f`), so the doctrine question
|
||||
is being decided against a working implementation. That is worth saying out loud
|
||||
to everyone waiting on it, and was.
|
||||
|
||||
Their scope set `[openid, approval:read, approval:approve]` is confirmed and
|
||||
already published. `client_id` and callback follow at their T07, deliberately not
|
||||
before the tenant question resolves — registering a client that fails closed at
|
||||
first use is the failure this exchange existed to avoid.
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
id: KEY-WP-0031
|
||||
type: workplan
|
||||
title: "Implement the gate-house tenant ruling"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: gate-house-tenant-ruling
|
||||
created: "2026-09-10"
|
||||
updated: "2026-09-10"
|
||||
state_hub_workstream_id: "2bc4d085-9b10-5160-bc8a-4fccc5d41ba9"
|
||||
---
|
||||
|
||||
gate-house ruled GH-DEC-2026-013 (`gate-house@16c1d46`) on the human-tenant
|
||||
question KeyCape declined to settle for itself. Directory-sourced is the terminal
|
||||
state; the registration-bound shape is granted as a declared bounded gap under
|
||||
three conditions, on the general property that *a transitional shape is
|
||||
admissible where it fails closed on exactly the case distinguishing it from the
|
||||
correct resolution*. Ours refuses issuance where registration and directory
|
||||
disagree, which is that case.
|
||||
|
||||
Conditions (a) and (c) needed nothing from this repository — (a) is already the
|
||||
implemented behaviour, and its strengthening, that a future change preferring the
|
||||
**directory** is equally void because picking any winner is a silent
|
||||
cross-tenant assertion, is recorded in the contract. This workplan covers the two
|
||||
items that were implementation.
|
||||
|
||||
## Carry the tenant claim's provenance
|
||||
|
||||
```task
|
||||
id: KEY-WP-0031-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "46d42120-eca5-5b7f-8445-2211b3b8eab0"
|
||||
```
|
||||
|
||||
gate-house ruled on GH-DEC-2026-013 while this was in flight. Section 5 is a
|
||||
finding nobody asked for and is ours to implement: `tenant` is a bare string, so
|
||||
a consumer cannot tell a zone the directory asserted about the person from one a
|
||||
registration supplied about the client they came through. approval-engine
|
||||
exact-matches that string while its contract reads as though it relies on the
|
||||
first — the check is sound and the property a reader infers from it is absent.
|
||||
|
||||
Every token now carries `tenant_source` beside `tenant`: `directory`,
|
||||
`registration`, or `default`. It is advertised in `claims_supported` and asserted
|
||||
on both grants at the token level, not only in the resolution function, because
|
||||
the claim a consumer reads is the thing under obligation.
|
||||
|
||||
Three values where the ruling names two, and that is the substantive judgement
|
||||
here. Labelling an unasserted profile default as `directory` would reproduce the
|
||||
same defect one level down — a consumer reading an assertion the identity layer
|
||||
never made. The ruling cites GH-DEC-2026-011 §3 on unknown versus absent for the
|
||||
case it examined; the same rule applies to our own fallback, so the fallback is
|
||||
named rather than folded into the strongest neighbouring value.
|
||||
|
||||
The agreement case resolves to `directory` deliberately: if a registration
|
||||
declares the zone the directory also assigned, the directory did assert it, and
|
||||
reporting the weaker source would understate what is known.
|
||||
|
||||
## Condition (b) corrected a message I had just shipped
|
||||
|
||||
```task
|
||||
id: KEY-WP-0031-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b21aeac8-8370-5572-ab00-6e9812fa48e7"
|
||||
```
|
||||
|
||||
The guard committed in `5f516a0` told a future reader that adding dynamic
|
||||
registration could be resolved by dropping the capability **or gating it to
|
||||
statically configured registrations**. Condition (b) voids the second: admitting
|
||||
dynamic registration voids the registration-bound shape that day, and the
|
||||
directory becomes the only source whatever state the adapter is in. gate-house
|
||||
strengthened our own "must be revisited" on the grounds that *revisited* implies
|
||||
the answer might survive review, and it would not.
|
||||
|
||||
So the message offered a way out the ruling forbids, in the exact place someone
|
||||
would read it while making that change. Corrected the same day. Worth recording
|
||||
rather than quietly editing: the guard was written to force a confrontation, and
|
||||
a guard that suggests an inadmissible resolution is worse than none.
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
id: KEY-WP-0032
|
||||
type: workplan
|
||||
title: "Pin the single route by which a principal becomes human"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: principal-type-provenance-guard
|
||||
created: "2026-09-10"
|
||||
updated: "2026-09-10"
|
||||
state_hub_workstream_id: "150b892c-f112-5fb9-b0f0-8072a52b85d5"
|
||||
---
|
||||
|
||||
GH-DEC-2026-016 §5 applies A-16 to what makes a principal *human*: if `human` is
|
||||
reachable by two routes — asserted by the identity layer about the person, or
|
||||
supplied by a registration about the client they came through — the record must
|
||||
say which, and a human-in-the-loop control must not be discharged on the
|
||||
registration-supplied one. Refusing a service principal while accepting an
|
||||
unverified assertion of humanity moves the defect rather than closing it.
|
||||
|
||||
gate-house said explicitly this was not a request. It is built because the
|
||||
property it protects is true *today* and is the kind a plausible change erases
|
||||
quietly.
|
||||
|
||||
## Pin the property, not the behaviour
|
||||
|
||||
```task
|
||||
id: KEY-WP-0032-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "2f42a72e-6292-5c02-9223-835db17b0a63"
|
||||
```
|
||||
|
||||
`principal_type` has exactly one route to `human`: a string literal on the
|
||||
authorization-code path, reached only after an upstream login resolved to a
|
||||
directory user. No configuration field can assert it — verified against source
|
||||
rather than assumed, and it is why the tenant provenance work has no counterpart
|
||||
here and why A-16 does not yet bite.
|
||||
|
||||
Asserting the *behaviour* would not have protected that. A test checking "a human
|
||||
token says human" passes just as happily when the value starts coming from a
|
||||
registration. So the guard parses the package and requires every assignment to
|
||||
`principal_type` to be a string literal, with the literal set exactly
|
||||
`{human, service}` — one route each. `claims["principal_type"] =
|
||||
client.PrincipalType` would look like a feature in review and fails the build
|
||||
instead, with a message saying that if it is deliberate the claim must carry its
|
||||
provenance the way `tenant_source` does.
|
||||
|
||||
Both assignment shapes are covered. The browser path assigns into a map; the
|
||||
service path uses a key-value pair inside a map literal. Checking only
|
||||
assignments found one of two routes and passed — a guard proving less than it
|
||||
claimed, caught because the expected literal set was pinned rather than merely
|
||||
counted.
|
||||
|
||||
Verified by mutation: replacing the literal with a function of the client
|
||||
registration fails it at the exact line, and restoring it passes.
|
||||
|
||||
## What this deliberately does not do
|
||||
|
||||
```task
|
||||
id: KEY-WP-0032-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "13199970-75d1-5c38-aa8c-27d6e38dcf65"
|
||||
```
|
||||
|
||||
No `principal_type_source` claim was added. There is one route, so a provenance
|
||||
marker would encode a distinction that does not exist and invite consumers to
|
||||
branch on it — the opposite of the tenant case, where two routes existed and the
|
||||
claim could not say which had been taken.
|
||||
|
||||
The guard is the honest response to "not a request today": it makes the absence
|
||||
of a second route a checked fact rather than a remembered one, and hands the
|
||||
decision to whoever creates the second route, at the moment they create it.
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
id: KEY-WP-0033
|
||||
type: workplan
|
||||
title: "Preserve fresh-user authentication for the Vergabe company handoff"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
flavor: implementation
|
||||
owner: codex
|
||||
topic_slug: netkingdom
|
||||
created: "2026-09-12"
|
||||
updated: "2026-09-25"
|
||||
related: [VERGABE-WP-0019, NK-WP-0037, USER-WP-0025]
|
||||
state_hub_workstream_id: "144dd430-1a09-51e1-9aad-e799ee86c338"
|
||||
---
|
||||
|
||||
## Forward login freshness to the actual authentication provider
|
||||
|
||||
```task
|
||||
id: KEY-WP-0033-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "fe05486b-f079-503b-ade1-fca65bf64b34"
|
||||
```
|
||||
|
||||
KeyCape parsed prompt=login and max_age but dropped them before Authelia.
|
||||
Forward these through the provider-neutral AuthRequest and the Authelia adapter.
|
||||
Absent values retain ordinary SSO behavior. Handler and adapter regressions
|
||||
cover forced login and zero/nonzero maximum age. Full Go suite passes after
|
||||
correcting the existing example-count regression: the example file already
|
||||
contains four service clients and the admitted human approver client.
|
||||
|
||||
## Publish, preflight and prove the fresh recipient boundary
|
||||
|
||||
```task
|
||||
id: KEY-WP-0033-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "7b58f08c-6063-554a-8700-a1edbc805ca4"
|
||||
```
|
||||
|
||||
Publish the exact source and validate current live config before replacing the
|
||||
single issuer instance. The live image is dcebd46/digest 7ff54c54; current main
|
||||
also contains startup validation and tenant provenance changes documented in
|
||||
docs/operations.md. Preserve all unrelated client, credential and MFA policy
|
||||
configuration. NK-WP-0037 adds the exact public Vergabe callback with no tenant
|
||||
assertion or MFA downgrade. Confirm prompt=login reaches Authelia through the
|
||||
live redirect, wrong callbacks and missing PKCE fail, and the actual recipient
|
||||
uses their own identity. Provider restart invalidates pending in-memory logins;
|
||||
USER-WP-0025-T03 still owns full provider sign-out coordination.
|
||||
|
||||
2026-09-12 release prepared: source 8d4336e, Forgejo image run 45 succeeded,
|
||||
digest sha256:5f10f36a5da23ce1aaf3df9b84a8ff98d7926f34ceaa19e63bd3356adb68e01a.
|
||||
The exact client-registration and deployment server dry runs pass. The runtime
|
||||
is unchanged. Await the attended window required by docs/operations.md; the
|
||||
complete packet is railiance-apps/docs/vergabe-demo-company-sso-rollout.md.
|
||||
|
||||
2026-09-12 attended rollout executed after explicit operator approval. KeyCape
|
||||
and password setup are Ready on the prepared digests; exact public client
|
||||
registration was CAS-applied (config resourceVersion 60123977) with unrelated
|
||||
config bytes/Secret data preserved. Existing portal and product client both
|
||||
pass fresh-login forwarding, wrong-callback and missing-PKCE checks (6 checks).
|
||||
Vergabe Helm revision 2 is Ready; identity migration completed, both PVCs remain,
|
||||
and requests remain 60m CPU/256Mi memory. Eleven live product checks pass:
|
||||
company welcome, anonymous gate, no-store, secure scoped CSRF, POST/CSRF-only
|
||||
login start, native issuer redirect, private company/media protection and
|
||||
invalid callback/confirmation rejection. Initial readback showed zero accounts,
|
||||
identity mappings and staff accounts. Native invited-user sign-in/MFA and
|
||||
confirmation are now requested from the operator; no user credential was used
|
||||
by the agent. Recovery and two-user acceptance remain their existing tasks.
|
||||
Evidence: railiance-apps/docs/evidence/2026-09-12-demo-company-sso-live.md.
|
||||
|
||||
2026-09-23 regression from the attended recipient sign-in (net-kingdom message
|
||||
ee4808e2, NK-WP-0037-T02): Authelia 4.38 registers the authorization request
|
||||
only after the login, so forwarding prompt=login fails every real login ("auth_time
|
||||
happened before the authorization request was registered"). The 2026-09-12
|
||||
forwarding checks were redirect-only and never completed a login. Fix (owner
|
||||
option a): KeyCape no longer sends prompt to Authelia. prompt=login and
|
||||
max_age=0 travel as max_age=10, a shorter max_age is raised to 10, a longer one
|
||||
passes through. KeyCape then enforces the exact requirement itself against the
|
||||
verified upstream auth_time: prompt=login needs an authentication no earlier
|
||||
than the KeyCape request, max_age needs one within that age, and a missing
|
||||
auth_time fails closed (event `stale_upstream_authentication`). Known edge: a
|
||||
user whose Authelia login is under 10s old at request time is not re-prompted
|
||||
and is refused rather than admitted. Also fixed the stale example-count test left
|
||||
by 651625c/1620ce2 (6 service + 1 human). Full Go suite passes.
|
||||
|
||||
The image was already the live issuer
|
||||
(`sha256:8dc35801`, Forgejo run 69, source `11ce29a`). No second rollout.
|
||||
2026-09-24 23:46 UTC the founder completed the Vergabe journey: one
|
||||
`token_exchange_error`, then `auth_success` and `token_issued` for
|
||||
`vergabe-demo-company`. No `stale_upstream_authentication`. Receipt:
|
||||
`docs/evidence/2026-09-24-fresh-login-and-account-switch.md`.
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
id: KEY-WP-0034
|
||||
type: workplan
|
||||
title: "Browser authentication recovery and confirmed shared sign-out"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
flavor: implementation
|
||||
owner: codex
|
||||
topic_slug: key-cape
|
||||
created: "2026-09-12"
|
||||
updated: "2026-09-25"
|
||||
state_hub_workstream_id: "49b0cd1d-fb9d-5351-99eb-9be9143a092d"
|
||||
---
|
||||
|
||||
The operator reports a dead-end authentication error after using an account
|
||||
outside the product tenant. Recent issuer telemetry indicates token exchange
|
||||
failure; tenant rejection and provider failure must not be conflated.
|
||||
|
||||
## Implement and validate recovery
|
||||
|
||||
```task
|
||||
id: KEY-WP-0034-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ba285335-4ead-5478-8fb7-89a2ec1ba695"
|
||||
```
|
||||
|
||||
Route failed browser login to the public account recovery surface without codes,
|
||||
state or unverified identity. Show verified portal identity, tenant memberships,
|
||||
and recorded workload memberships; preserve operator/customer separation.
|
||||
Provide CSRF-protected portal logout and confirmed shared provider sign-out with
|
||||
fixed owner-configured return locations. No automatic reauthentication loops,
|
||||
MFA downgrade, global JWT revocation claim or inferred workload entitlements.
|
||||
|
||||
## Publish and verify the recovery flow
|
||||
|
||||
```task
|
||||
id: KEY-WP-0034-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "4c836ddf-2eb8-528a-b63f-8499f3a6cbf2"
|
||||
```
|
||||
|
||||
Publish immutable images, update canonical runtime pins, verify anonymous
|
||||
recovery and sign-out confirmation live, and record actual account switching
|
||||
only after browser evidence. Existing application sessions may outlive provider
|
||||
logout. Related: USER-WP-0025-T03 and VERGABE-WP-0019-T06.
|
||||
|
||||
Source verification: Full Go suite passed; final OIDC/config tests passed after recovery expansion. Immutable publication and live checks are in progress.
|
||||
|
||||
### Live recovery rollout — 2026-09-12
|
||||
|
||||
Recovery is deployed in KeyCape 4d8b8fe, User Engine e54b6ee and Vergabe c067993
|
||||
(Helm revision 3). All three are Ready. Six provider checks, eleven product
|
||||
checks and six fresh anonymous Chromium checks pass, including actual provider
|
||||
logout POST and return to the portal without test overrides. Detailed receipt: railiance-apps/docs/evidence/2026-09-12-account-recovery-live.md.
|
||||
|
||||
2026-09-24 23:42–23:45 UTC the founder completed the account switch. The issuer
|
||||
recorded one `mfa_failed` for `user-engine-portal`, then three separate
|
||||
`auth_start` → `auth_success` → `token_issued` sequences. A silent reuse of
|
||||
the previous portal session would not have produced those new successes.
|
||||
Provider logout still does not revoke already issued application JWTs.
|
||||
Receipt: `docs/evidence/2026-09-24-fresh-login-and-account-switch.md`.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
id: KEY-WP-0035
|
||||
type: workplan
|
||||
title: "Opt-in MFA for demo-company login"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: infotech
|
||||
created: "2026-09-13"
|
||||
updated: "2026-09-14"
|
||||
state_hub_workstream_id: "b27601d8-d0a5-5c79-a1ff-c2a4cedafa74"
|
||||
---
|
||||
|
||||
Requested behavior: password-only login before OTP activation; require OTP after
|
||||
activation. Do not lower assurance of unrelated applications.
|
||||
|
||||
## Implement enrollment-dependent browser policy
|
||||
|
||||
```task
|
||||
id: KEY-WP-0035-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "c1ed06c2-c689-53d1-afc7-e10d79e9e94f"
|
||||
```
|
||||
|
||||
Add mfaOptional, preserve explicit AAL2 and existing client policy, fail closed on
|
||||
provider lookup errors and malformed/incomplete responses. Preserve the setting
|
||||
in config/runtime/canonical model and flag manual migration requirements.
|
||||
Validation: `go test ./...` and `git diff --check` passed on 2026-09-13. See docs/optional-mfa.md.
|
||||
|
||||
## Restore authoritative factor lookup and enable the reviewed client
|
||||
|
||||
```task
|
||||
id: KEY-WP-0035-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b066a273-f91c-50ee-b497-a39eace03b3a"
|
||||
```
|
||||
|
||||
Factor lookup restored on 2026-09-13 through the new dedicated service lane
|
||||
RPF-WP-0040 / CCR-2026-0023. Native ESO delivery, scoped provider user lookup,
|
||||
renewal and mounted replacement passed. KeyCape now uses adminTokenFile; other
|
||||
configuration and current MFA policy were preserved. Historical NK-WP-0033
|
||||
resolver lanes remain separate. The remaining T02 work is the exact scoped
|
||||
client migration and no-factor/enrolled/error/step-up acceptance before enabling
|
||||
optional policy. See railiance-platform/docs/evidence/2026-09-13-keycape-factor-custody.md.
|
||||
|
||||
## Verify optional enrollment and account management access
|
||||
|
||||
```task
|
||||
id: KEY-WP-0035-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "93ce2e97-6c5a-58a7-8f3c-1c4f0eb6f1b5"
|
||||
```
|
||||
|
||||
Verify provider self-service login, possession-confirmed activation, cancellation
|
||||
and removal/recovery. Resolve shared portal assurance scope before surfacing the
|
||||
verified OTP setup link. Actual user login acceptance remains open under
|
||||
KEY-WP-0034 and VERGABE-WP-0019; this work does not finish either workplan.
|
||||
|
||||
## Support bounded provider credential renewal without issuer restart
|
||||
|
||||
```task
|
||||
id: KEY-WP-0035-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "494f554d-6542-5797-804f-6df4a05dcadb"
|
||||
```
|
||||
|
||||
Supports platform journey P05 and USER-WP-0030-T03. Add an exclusive mounted adminTokenFile credential source, fresh reads for lookup and validation, no stale fallback, bounded input, sanitized failures and atomic replacement acceptance. Preserve inline configuration compatibility. Require provider success and true validation value before AAL2; bound default request time and reject credential-bearing redirects. Test renewal, rejection, recovery and policy regressions. This implements the consumer delivery contract; owner credential issuance/custody and live P04/P05/P06 acceptance remain T02/T03.
|
||||
|
||||
All Go regression/conformance suites pass, including five new renewal/validation tests with invalid-source subcases. Added exact-commit authentication acceptance CI. Provider-mounted credential delivery and effective optional policy remain gated separately.
|
||||
|
||||
Consumer source 632b1f1 deployed and Ready 1/1; CI, four provider HTTP checks and twelve live browser checks passed. See docs/credential-renewal-release-2026-09-13.md. T02/T03 retain actual credential delivery and live recovery/policy acceptance.
|
||||
|
||||
2026-09-13 custody activation supersedes the earlier owner-handoff gate: the new factor service is live and renewable. T02/T03 now track effective policy/onboarding acceptance, not missing credential ownership.
|
||||
|
||||
## Require confirmed enrollment and actual OTP evidence
|
||||
|
||||
```task
|
||||
id: KEY-WP-0035-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "94f579d1-a7af-564c-ab0c-ae76fce343aa"
|
||||
```
|
||||
|
||||
Live provider source inspection found active tokens can remain in verification
|
||||
state; live policy inspection found password passthrough enabled for users without
|
||||
factors. Distinguish pending enrollment from completed enrollment and require
|
||||
TOTP/HOTP serial/type evidence before AAL2. Cover pending/confirmed/cancelled,
|
||||
existing-factor plus pending enrollment, missing/unknown state, incomplete pages,
|
||||
password passthrough and static-password token rejection. All Go suites pass
|
||||
locally; source 113f3a6 was published, CI acceptance/build passed, and the guarded issuer replacement is live. See docs/enrollment-assurance-release-2026-09-13.md.
|
||||
|
||||
Provider recovery testing additionally proved that withdrawing reader policy
|
||||
returns HTTP 200 with an empty list. Added and deployed a same-realm disabled,
|
||||
unassigned visibility probe before no-factor decisions, with missing visibility
|
||||
failing closed. Native scope proof passed. Isolated installed-provider tests
|
||||
passed possession confirmation/cancellation, permission withdrawal/recovery and
|
||||
provider-issued JWT expiry; these are now included in CI. T02/T03 remain the
|
||||
actual optional-client and self-service browser activation gates.
|
||||
|
||||
2026-09-14: T02/T03 completed through USER-WP-0033 P06. Installed-provider
|
||||
native and five browser checks passed, including confirmed activation,
|
||||
cancellation, old-session MFA and denied active-factor replacement. Portal
|
||||
guards, provider restrictions and both scoped optional-client policies are live;
|
||||
PVC readback confirms effective state. Full release evidence and rollback:
|
||||
`../user-engine/docs/evidence/2026-09-13-p06-authentication-policy.md`.
|
||||
Residual actual-user and workload sign-in acceptance remains live under
|
||||
KEY-WP-0034, USER-WP-0028 and VERGABE-WP-0019 before this closure.
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
id: KEY-WP-0036
|
||||
type: workplan
|
||||
title: "Audited platform administrator lost-factor recovery"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: infotech
|
||||
created: "2026-09-13"
|
||||
updated: "2026-09-13"
|
||||
state_hub_workstream_id: "f9ba00a6-5758-5677-9a49-da1e35875f7a"
|
||||
---
|
||||
|
||||
Implements the provider part of P04 under USER-WP-0030-T03. The existing factor
|
||||
read credential is deliberately not elevated to recovery authority. Recovery
|
||||
now has a private signed-OIDC service and platform-only portal browser flow.
|
||||
|
||||
## Implement bounded and audited provider recovery
|
||||
|
||||
```task
|
||||
id: KEY-WP-0036-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f3287544-8412-5b1f-9d16-9f7b9e895ceb"
|
||||
```
|
||||
|
||||
`scripts/factor_recovery.py` previews exact ownership and shared-identity scope,
|
||||
requires an operator verification attestation and matching metadata version,
|
||||
disables one factor with a row lock, requires pre-mutation audit durability,
|
||||
reads back state, records completion and reconciles retries. Reject changed or
|
||||
replaced factors and reference conflicts. Nine isolated unit tests pass, including replacement between disable and readback.
|
||||
The installed-provider Job provider-recovery-contract-02 passed real database
|
||||
and audit persistence, disable/readback and replay assertions in an isolated
|
||||
fixture. Acceptance CI includes both suites. No real user's factor was changed.
|
||||
|
||||
## Validate the attended owner entry point
|
||||
|
||||
```task
|
||||
id: KEY-WP-0036-T02
|
||||
status: cancel
|
||||
priority: high
|
||||
state_hub_task_id: "2d59da13-782e-5aeb-8957-69e41a15ddb1"
|
||||
```
|
||||
|
||||
railiance-platform/scripts/keycape_factor_recovery.py derives actor from an
|
||||
OpenBao platform-admin entity, rejects root/workload substitutes, checks cluster
|
||||
identity, and emits only a private metadata receipt. Runbook:
|
||||
railiance-platform/docs/keycape-factor-recovery.md. Offline owner tests pass.
|
||||
Superseded: routine recovery now independently verifies the platform operator's
|
||||
signed KeyCape session through the private service. The extra OpenBao/Kubernetes
|
||||
operator front door is not part of the supported P04 journey; its native acceptance
|
||||
is cancelled rather than falsely marked passed. No credential handoff is pending.
|
||||
|
||||
## Integrate platform support recovery with the browser journey
|
||||
|
||||
```task
|
||||
id: KEY-WP-0036-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f7248d5e-6f68-5637-ada5-2527436f5f9e"
|
||||
```
|
||||
|
||||
Implement an authenticated bounded recovery service and platform-only portal
|
||||
flow with fresh assurance, server-derived actor, CSRF, ownership-verification
|
||||
process, exact target/scope confirmation, audit/reference readback and safe
|
||||
retry. Never grant Kubernetes access to the portal. Test unauthorized/tenant
|
||||
admin denial, stale confirmation, provider failure, retry and replacement OTP
|
||||
enrollment using disposable identities. Mandatory-AAL2 access is restored only
|
||||
after possession of a replacement factor is proven. Keep P04 incomplete until
|
||||
browser success, failure and recovery paths pass; CLI tests alone do not close it.
|
||||
|
||||
Completion evidence: user-engine/docs/evidence/2026-09-13-p04-recovery.md.
|
||||
KeyCape 63b3070 and portal c298cd9 are released. Fifteen recovery tests, 219 portal
|
||||
regressions with PostgreSQL and 19 Chromium checks pass. Installed-provider
|
||||
provider-p04-contract-03 verifies signature/role/assurance denials, confirmation,
|
||||
multi-owner refusal, ownership locking, audited mutation/retry/reference lookup and replacement OTP possession/login.
|
||||
Production health, anonymous/invalid-token denials, signing-key connectivity,
|
||||
rollout and new-enrollment policy readback pass. Positive mutation used disposable
|
||||
fixtures; no real account was changed. Provider and portal implement the same
|
||||
supported recovery contract; no outstanding P04 implementation remains.
|
||||
Broader policy/self-service and notification operations remain live under
|
||||
USER-WP-0030-T03 and KEY-WP-0035, not residuals of this bounded recovery plan.
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
---
|
||||
id: KEY-WP-0037
|
||||
type: workplan
|
||||
title: "Secrets Engine OpenBao live registration and protected custody"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-07"
|
||||
state_hub_workstream_id: "62800ecf-064b-5546-a1e4-4521b5932973"
|
||||
---
|
||||
|
||||
KeyCape's accepted provider contract (KEY-WP-0009) does not establish actual
|
||||
registration or custody. This plan names those missing owner gates for
|
||||
RPF-WP-0035-T02. The founder authorized workers to attend the review requests,
|
||||
not live registration, credential generation, rotation, fetching or deployment.
|
||||
|
||||
## Establish the exact registration and deployment authority
|
||||
|
||||
```task
|
||||
id: KEY-WP-0037-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0409f656-65fb-5ee2-a934-a687a3e139f1"
|
||||
```
|
||||
|
||||
Reviewed KeyCape base 672e1faf4b3401ff7bf68bb0b29f8dc50ca51ac6:
|
||||
`config/service-clients.example.yaml`, `docs/openbao-service-auth-contract.md`,
|
||||
`src/internal/config/config.go`, `src/cmd/keycape/clients_test.go`.
|
||||
The exact service registration remains accepted: secrets-engine-openbao,
|
||||
service:secrets-engine, tenant:platform, role secrets-engine, only openbao:login,
|
||||
client_credentials, confidential, tokenLifetime 15m and issuer
|
||||
https://kc.coulomb.social. Audience defaults to clientId, with no resource
|
||||
audience override. See the owner packet for concrete deployment/custody proposals.
|
||||
NetKingdom owns the live sso/keycape deployment and config, not this example.
|
||||
Existing approved approval-client rollout demonstrates the owner procedure;
|
||||
it does not approve or provision this different service client.
|
||||
|
||||
## Agree and prepare the separately approved protected custody operation
|
||||
|
||||
```task
|
||||
id: KEY-WP-0037-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ac798fb4-ed77-5e45-83ab-f56eea92be34"
|
||||
```
|
||||
|
||||
Platform owns the authoritative OpenBao client-secret custody and least-privilege
|
||||
issuer-side delivery; secrets-engine owns its explicitly selected mode-0600
|
||||
client-side file outside every worktree. The proposal in
|
||||
`docs/secrets-engine-openbao-owner-packet.md` provides candidate coordinates,
|
||||
not a live custody claim or allocated CCR. Complete the platform CCR(s), delivery
|
||||
policy/roles, two target bindings, exact attended child and rollback before
|
||||
seeking the founder's separate concrete approval. Values stay out of Hub/Git/chat,
|
||||
argv and logs. Existing verifier/requester values must not be reused.
|
||||
|
||||
**Done 2026-10-05.** The founder resolved `632a8893-0d6a-4591-a50b-40bbdb798f32`.
|
||||
The custody boundary is approved: platform owns OpenBao client-secret custody
|
||||
and issuer-side delivery, and secrets-engine owns the mode-0600 file outside
|
||||
every worktree. The owner packet is still coordinates. This approval does not
|
||||
register the client, generate a secret, or deploy.
|
||||
|
||||
## Prepare and approve the deployment-owned registration operation
|
||||
|
||||
```task
|
||||
id: KEY-WP-0037-T03
|
||||
status: done
|
||||
priority: high
|
||||
needs_human: true
|
||||
decision_id: caafe4be-4f68-47b4-9567-316d47b94992
|
||||
blocking_reason: "Custody boundary 632a8893 is approved. Registration merge and rollout still need an exact reviewed revision and a separate live go. Opening caafe4be does not deploy."
|
||||
state_hub_task_id: "52f03e39-d05b-5da5-85df-e28c059430e4"
|
||||
```
|
||||
|
||||
Coordinate source registration merge and the single new env secretKeyRef with
|
||||
NetKingdom's `sso-mfa/k8s/keycape/deployment.yaml` and owner rollout implementation.
|
||||
An exact reviewed source revision, image digest, config preservation test,
|
||||
synthetic issuance/denial verification and founder live go are required.
|
||||
Use an idempotent exact-match merge: absence permits adding only this client;
|
||||
matching client permits noop; duplicates or drift fail closed. Preserve signing
|
||||
key, unrelated clients, existing policy and confidential config bytes.
|
||||
No deployment changes are authorized by this source plan.
|
||||
|
||||
**Done 2026-10-07 under live go `caafe4be`.**
|
||||
- Custody (decision `2ab717e1`): KV v1 written with cas=0.
|
||||
- ESO amendment `1fdd993b`; the sso Secret is synced.
|
||||
- NK-WP-0046 (`1fbb8db`): env binding, then the exact-match config add
|
||||
(rv 71471711 to 71655453, unrelated bytes preserved), then a restart on image
|
||||
6f79a2af.
|
||||
|
||||
**Prepared 2026-10-06 (source only).** NetKingdom NK-WP-0046 (`a1e7302`) holds
|
||||
the exact-match single-client merge helper, its rollback and the proposed env
|
||||
patch. secrets-engine SECRETS-WP-0012 (`a3897c2`) names the consumer file.
|
||||
Platform CCR-2026-0033 (`df24a77`, still proposed) holds the KV path
|
||||
`platform/workloads/keycape/secrets-engine-openbao`, the sso-only ESO and the
|
||||
attended custody script; its approval is RPF-WP-0035-T09. The live order is
|
||||
custody run, then issuer Secret, env patch, this merge and a restart. Nothing
|
||||
is live. The founder keeps the deployed image (NK-WP-0046-T02, 2026-10-06).
|
||||
Revised the same day: the value lives only in OpenBao (ops-mason MASON-WP-0009)
|
||||
and has no consumer file. The custody approvals A1-A3 are decision
|
||||
`2ab717e1-7881-491e-aed4-28946550c289`.
|
||||
|
||||
## Verify actual issuance and publish non-secret owner receipt
|
||||
|
||||
```task
|
||||
id: KEY-WP-0037-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on:
|
||||
- KEY-WP-0037-T03
|
||||
blocking_reason: "Live issuance proof waits on the registration operation (T03)."
|
||||
state_hub_task_id: "40b80bcd-243d-5bf8-ba18-b992e3c5f8f7"
|
||||
```
|
||||
|
||||
After authorized registration and custody, verify issuer/JWKS signature, subject,
|
||||
audience, principal_type, tenant, roles, scope and 900-second lifetime; refuse
|
||||
wrong credential/excess scope and preserve existing human login. Record only
|
||||
booleans and identities, immutable image/source coordinates and custody binding
|
||||
metadata. Hand off to RPF-WP-0035-T02 for isolated OpenBao metadata provisioning,
|
||||
claim/expiry/use-limit denial and self-revocation proof. Never report an example
|
||||
registration, discovery metadata or source test as live acceptance.
|
||||
|
||||
**Done 2026-10-07.** `keycape verify-client` passed all 10 checks:
|
||||
- issuer, JWKS signature and claims (`service:secrets-engine`, tenant:platform,
|
||||
role secrets-engine);
|
||||
- no excess scope;
|
||||
- `openbao:admin` and a wrong secret refused.
|
||||
|
||||
The founder's KeyCape login after the restart also proved the CCR-2026-0034
|
||||
operator reader (net-kingdom-admins, aal2). Receipt:
|
||||
`docs/evidence/2026-10-07-secrets-engine-openbao-issuance.json`. The 900s lifetime
|
||||
was not measured. Handed to RPF-WP-0035-T02 for the OpenBao JWT role and login
|
||||
proof.
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
---
|
||||
id: KEY-WP-0038
|
||||
type: workplan
|
||||
title: "Forgejo as a NetKingdom OIDC client, so the forge can close"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: railiance
|
||||
created: "2026-10-08"
|
||||
updated: "2026-10-16"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- prj-forgejo-org-refactor
|
||||
- railiance-apps
|
||||
- railiance-platform
|
||||
state_hub_workstream_id: "098e3e2c-a138-560e-b2d3-3a15eb49d8a9"
|
||||
---
|
||||
|
||||
# KEY-WP-0038 - Forgejo as a NetKingdom OIDC client, so the forge can close
|
||||
|
||||
## Why
|
||||
|
||||
`forgejo.coulomb.social` is closing: private by default, no anonymous access
|
||||
(prj-forgejo-org-refactor ORGREF-WP-0002, decision
|
||||
`decisions/forge-visibility-policy.md`). The founder's condition is that Forgejo
|
||||
sign-in runs through NetKingdom **before** anything closes, so that every human,
|
||||
agent and service reader has a governed identity rather than an ad-hoc local
|
||||
account. This plan is ORGREF-WP-0002-T04. Its Forgejo-side counterpart is
|
||||
railiance-apps `RAPPS-WP-0016` (ORGREF-WP-0002-T05), which owns the Forgejo
|
||||
Helm release.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Registration follows the existing human browser-client pattern
|
||||
(`docs/approval-engine-provisioning-request.yaml`, `human_registration`) and the
|
||||
client fields validated in KEY-WP-0028. Redirect URIs are exact, never
|
||||
wildcards.
|
||||
- The client secret goes to OpenBao through railiance-platform custody. No secret
|
||||
value in git, State Hub, logs or chat.
|
||||
- Founder guardrail for the whole closure: tighten gradually, never lock anyone
|
||||
out, never risk assets. Nothing in this plan changes who can read the forge
|
||||
today. It only makes the NetKingdom path available.
|
||||
|
||||
## T01 — Define what Forgejo receives
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5f019d13-1fa3-5b88-a27a-75a3339ca96e"
|
||||
```
|
||||
|
||||
Write the claim contract Forgejo consumes, as a non-secret request packet under
|
||||
`docs/`:
|
||||
|
||||
- issuer `https://kc.coulomb.social`; confidential client `forgejo`; authorization
|
||||
code with PKCE; scopes `openid profile email groups`
|
||||
- redirect URI
|
||||
`https://forgejo.coulomb.social/user/oauth2/netkingdom/callback`. The
|
||||
authentication-source name `netkingdom` must match railiance-apps'
|
||||
configuration exactly.
|
||||
- subject and username: canonical directory subject, plus a stable
|
||||
`preferred_username` that Forgejo can match to the existing local accounts
|
||||
(founder and agent accounts) for linking
|
||||
- `groups` naming the Forgejo organisations and teams to map to (for example
|
||||
`forge:coulomb:owners`, `forge:coulomb:readers`), and `principal_type` so that
|
||||
human and service principals stay distinguishable
|
||||
- MFA: forge administrators and owners require assurance with a fresh second
|
||||
factor; plain readers follow the client-aware MFA policy (KEY-WP-0008)
|
||||
|
||||
Acceptance: the founder accepts the packet. The group names are agreed with
|
||||
railiance-apps.
|
||||
|
||||
2026-10-08: packet drafted at `docs/forgejo-oidc-client-request.yaml`. It covers a
|
||||
confidential client `forgejo` with PKCE and `mfaRequired: true`, the exact
|
||||
callback, scopes, a custody path, the claims as emitted at this revision, new
|
||||
LLDAP groups `forge-admins`, `forge-coulomb-owners` and `forge-coulomb-readers`
|
||||
with a required-claim gate, no automatic account creation, and linking of local
|
||||
accounts. It also proposes that agents and services use scoped tokens bound to
|
||||
service identities, not browser sign-in.
|
||||
|
||||
2026-10-08: accepted by the founder (in session). railiance-apps' RAPPS-WP-0016
|
||||
uses the same authentication-source name `netkingdom` and the same group names,
|
||||
so the agreement condition holds. Before T02, the LLDAP groups `forge-admins`,
|
||||
`forge-coulomb-owners` and `forge-coulomb-readers` must exist, with the founder
|
||||
in the first two.
|
||||
|
||||
## T02 — Register the client and hand over its secret
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0038-T01, NK-WP-0048-T06]
|
||||
blocking_reason: "Registration runs as the first KnownSecurityCase of type oidc-browser-relying-party (NK-WP-0048-T06), not as a hand-built rollout."
|
||||
priority: high
|
||||
state_hub_task_id: "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
|
||||
```
|
||||
|
||||
**Changed 2026-10-08 (founder direction):** no case-specific helper script.
|
||||
Registration is the first case of the SecurityCaseType `oidc-browser-relying-party`
|
||||
(net-kingdom NK-WP-0048, canon `security-case-types_v0.1`). The parameters
|
||||
accepted in T01 become the case; ops-mason materializes it (MASON-WP-0010) and
|
||||
ops-warden follows through (WARDEN-WP-0044). The registration capability is
|
||||
net-kingdom's (`net-kingdom.keycape-client-registration`); key-cape's part is its
|
||||
field rules and reviewing the type's composition.
|
||||
|
||||
The original intent below still describes what must result.
|
||||
|
||||
Register `forgejo` from the packet. Mint the client secret straight into custody
|
||||
(OpenBao path `platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, derived
|
||||
by the case type since net-kingdom 074b3a0; custody owner railiance-platform,
|
||||
consumers KeyCape via ESO and Forgejo). Record a receipt with no
|
||||
secret value.
|
||||
|
||||
Acceptance: discovery metadata lists the client's redirect URI. The secret is
|
||||
readable by Forgejo's custody path only.
|
||||
|
||||
2026-10-10: key-cape reviewed the type's composition
|
||||
(`docs/oidc-browser-relying-party-type-review.md`). net-kingdom fixed the findings
|
||||
in `61ab6bc`, a 1.0 patch: redirects with `?` or `#` refused, the groups scope
|
||||
required, and descriptions corrected. A new 1.1 (`e2174ce`, founder go) adds the
|
||||
`consumer-enforces-groups` verification. For Forgejo, that is the required-claim
|
||||
setting railiance-apps configures. `security-cases/forgejo-netkingdom-oidc.yaml`
|
||||
is re-pinned to @1.1, with parameters unchanged. KeyCape added `keycape
|
||||
check-config` (`c528daa`), which register-client runs before the restart. T02 still
|
||||
waits on NK-WP-0048-T06, which needs both executors' light paths (MASON-WP-0010-T03,
|
||||
WARDEN-WP-0044-T03).
|
||||
|
||||
### Exact offline checker owner review — 2026-10-16
|
||||
|
||||
KeyCape owner review under KEY-WP-0038-T02 endorses only the offline config
|
||||
validation behavior of source `3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`,
|
||||
`src` tree `f0010b9cab17dbc3a787628a29657d98b900320f`, compiled artifact
|
||||
`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
|
||||
The exact source/archive, 108 source files, 9876 cached Go 1.23.0 toolchain
|
||||
files and all ten selected module trees matched the candidate closure. Fourteen
|
||||
actual artifact checks and the cmd/config/policy Go tests passed using only
|
||||
synthetic inputs. The check dispatches before private-key loading, upstream
|
||||
adapter construction or server startup and uses placeholder client secrets.
|
||||
|
||||
[Owner review](../docs/offline-checker-owner-review-2026-10-16.md) records the
|
||||
exact evidence and limits: non-secret portal/logout URL environment overlays,
|
||||
optional/nonempty env-name assertion and policy-store assertion, permissive YAML
|
||||
loading, and no key/custody/readiness or authentication proof. It does not endorse
|
||||
the caller's inherited environment, executable storage/custody, the NetKingdom
|
||||
descriptor/type, Warden admission or any browser/native identity. Parent review
|
||||
identified caller environment inheritance; NetKingdom owns its correction and
|
||||
regression checks. T02 remains `wait`; T03 and all live acceptance gates remain
|
||||
unchanged. No live config, credentials, cluster objects or paid calls were used.
|
||||
|
||||
## T03 — Prove the claims for each kind of identity
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T03
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0038-T02]
|
||||
blocking_reason: "Claims are verified against the registered client."
|
||||
priority: high
|
||||
state_hub_task_id: "40ddaa29-7245-508c-ad48-ddc063e25115"
|
||||
```
|
||||
|
||||
Before Forgejo uses the client, verify the issued ID token and UserInfo for one
|
||||
human (the founder, with MFA), one agent identity and one service identity:
|
||||
subject, `preferred_username`, `groups`, `principal_type`, assurance. Record the
|
||||
claims, minus tokens, as evidence.
|
||||
|
||||
Decide with railiance-apps whether agents and services sign in to Forgejo at
|
||||
all, or receive scoped Forgejo tokens bound to a NetKingdom service identity
|
||||
(ORGREF-WP-0002-T06). Record the decision.
|
||||
|
||||
Acceptance: evidence for all three principals; the agent and service decision is
|
||||
recorded. Then hand over to RAPPS-WP-0016.
|
||||
|
||||
## T04 — Implement the `keycape-client` entry point
|
||||
|
||||
```task
|
||||
id: KEY-WP-0038-T04
|
||||
status: cancel
|
||||
priority: high
|
||||
state_hub_task_id: "583cf1f1-16dc-515f-83bc-1efc45024a89"
|
||||
```
|
||||
|
||||
**Cancelled 2026-10-08: moved to net-kingdom NK-WP-0048-T09 (done).** The live
|
||||
registration machinery (contained kubectl, cluster and issuer pins) already lives
|
||||
in net-kingdom `sso-mfa/k8s/keycape/`, where KeyCape is deployed; a copy here
|
||||
would drift. The entry point is `net-kingdom/sso-mfa/k8s/keycape/keycape-client.py`
|
||||
and the declaration is `net-kingdom.keycape-client-registration`; key-cape's draft
|
||||
declaration is removed. key-cape still owns the client field rules (canonical
|
||||
model) and their enforcement at authorization time.
|
||||
|
||||
`capabilities/playbooks/key-cape.oidc-client-registration.yaml` (contract PASS,
|
||||
`maturity: planned`) declares client registration as a playbook capability, so
|
||||
that the SecurityCaseType `oidc-browser-relying-party` (net-kingdom NK-WP-0048)
|
||||
can compose it. Implement the entry point it names, `tools/keycape-client/`:
|
||||
|
||||
- `keycape-client register --client-file <rendered-client.yaml>` with modes
|
||||
`inspect`, `dry-run`, `apply` and `remove`; adds, updates or removes exactly one
|
||||
client in the live registry; idempotent; never prints or writes secret values;
|
||||
a confidential client's secret is read from its custody reference
|
||||
- one implementation replacing the per-client helpers in net-kingdom
|
||||
`sso-mfa/k8s/keycape/` (register-coulomb-social, portal-client-rollout,
|
||||
vergabe-client-rollout, secrets-engine-openbao-client-rollout), keeping their
|
||||
safeguards (byte-preserving merge, compare-and-swap, cluster pin, safe receipts)
|
||||
and their tests as regression cases
|
||||
- client field validation by the KEY-WP-0028 rules
|
||||
- then set the declaration's `maturity` to `implemented`
|
||||
|
||||
Longer term, loading clients from a non-secret registry with secrets referenced
|
||||
from custody would remove the type's only attended step (see the type README,
|
||||
"Attended steps").
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
---
|
||||
id: KEY-WP-0039
|
||||
type: workplan
|
||||
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-10"
|
||||
updated: "2026-10-17"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- railiance-platform
|
||||
- secrets-engine
|
||||
state_hub_workstream_id: "09c3bddf-6058-5603-8488-cccdb7be4f86"
|
||||
---
|
||||
|
||||
# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role
|
||||
|
||||
## Why
|
||||
|
||||
CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role
|
||||
`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong
|
||||
`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT
|
||||
and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine
|
||||
`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked
|
||||
whether KeyCape can issue the negative tokens without touching the
|
||||
`secrets-engine` registration.
|
||||
|
||||
KeyCape signs only what a registration declares. It has no way to mint a token
|
||||
with arbitrary claims, and adding one would create a forging path in the issuer.
|
||||
So the negative tokens must come from real, separate registrations that differ
|
||||
from `secrets-engine` in exactly one property each.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- The `secrets-engine` registration is not changed.
|
||||
- Probe clients are confidential `client_credentials` clients with the minimum
|
||||
60-second lifetime, scopes that grant nothing, and secrets minted into custody
|
||||
like any other client. They are removed after the proof.
|
||||
- The probe script prints only accept or refuse booleans per case, never tokens or
|
||||
secrets. It runs attended.
|
||||
|
||||
## T01 — Define the probe registrations
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"
|
||||
```
|
||||
|
||||
Write a non-secret request packet under `docs/` with one probe client per claim
|
||||
mismatch, each identical to `secrets-engine` except for:
|
||||
|
||||
- `aud`: a different access-token resource audience
|
||||
- `sub`: a different service subject
|
||||
- `tenant`: a tenant other than `platform`
|
||||
- `roles`: a role other than `secrets-engine`
|
||||
- `scope`: a scope outside the role's bound scope
|
||||
|
||||
Two cases need no probe client. A service token always carries
|
||||
`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong
|
||||
`principal_type` case uses a human access token from an attended login. The
|
||||
coding-agent case uses the existing `codex-railiance-platform` registration (role
|
||||
`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live;
|
||||
T01 confirms that.
|
||||
|
||||
The expired-JWT case reuses any probe client: request a token, wait past its
|
||||
60-second lifetime, then present it.
|
||||
|
||||
Acceptance: railiance-platform agrees the packet covers every refusal that
|
||||
CCR-2026-0038 names.
|
||||
|
||||
2026-10-10: railiance-platform agreed that the case list covers every
|
||||
CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no
|
||||
provider fallback) are theirs and secrets-engine's. Packet written at
|
||||
`docs/openbao-negative-probe-clients.yaml`. It departs from the agreed list in two
|
||||
ways, both sent back for re-agreement:
|
||||
|
||||
- **Expired case.** A token from another probe already fails a claim, so its
|
||||
refusal does not show that expiry was checked. It needs a sixth client,
|
||||
`openbao-probe-expiry`, identical to `secrets-engine-openbao` in every claim. That
|
||||
makes it a second credential able to log in as secrets-engine for the session.
|
||||
OpenBao's `expiration_leeway` (150s by default) also extends the wait.
|
||||
- **Audience pin.** A service token's `aud` is its `clientId` unless `audience` is
|
||||
set. Every probe except the aud probe therefore pins
|
||||
`audience: secrets-engine-openbao`, so it fails only on its own claim.
|
||||
|
||||
Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's
|
||||
environment, because KeyCape resolves `secretRef` at startup. The packet uses a
|
||||
transient Kubernetes Secret that is deleted in the same session.
|
||||
|
||||
## T02 — Register the probe clients, run the attended probe, then remove them
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
|
||||
blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established."
|
||||
priority: medium
|
||||
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
|
||||
```
|
||||
|
||||
2026-10-11: requalified — live registration go caafe4be was already resolved; owner authorization present. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.
|
||||
|
||||
Write the attended script, which prints booleans only.
|
||||
|
||||
2026-10-10: script written at `scripts/openbao_negative_probe.py`, with tests in
|
||||
`scripts/test_openbao_negative_probe.py`. It meets condition (b): one refused
|
||||
boolean per case plus the refusal class (`claim_mismatch`, `expired`,
|
||||
`not_service_principal`), and no token, secret, claim value or OpenBao error text.
|
||||
A transport failure or an unexpected response is reported as `inconclusive`, never
|
||||
as a refusal. A token that OpenBao accepts is revoked at once and fails the run.
|
||||
Still waiting: railiance-platform's re-agreement on the two departures above, and
|
||||
an attended registry session with net-kingdom. Register the clients
|
||||
through the normal registration path, hand the run to railiance-platform, record
|
||||
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.
|
||||
|
||||
Acceptance: every case is refused, the receipt is committed, and the probe clients
|
||||
are gone.
|
||||
|
||||
|
||||
2026-10-11 source preparation: T01 remains progress because the exact expiry
|
||||
client and audience-pin amendments require platform-owner re-agreement; the
|
||||
prior agreement covered the earlier case list, not this revised packet. The
|
||||
prepared runner revision adds a valid expiry positive control and immediate
|
||||
revoke, exact decoded claim-isolation preflight (not signature evidence),
|
||||
semantic refusal classes, bounded expiry TTL and redirect refusal. Seventeen
|
||||
local tests pass; original-source semantic regressions demonstrate two false
|
||||
successes. See `docs/openbao-negative-probe-session-proposal.md` for exact owner
|
||||
returns, live authority, effective leeway, custody boundaries and cleanup. No
|
||||
live token, registry or OpenBao action occurred. T02 continues to wait for owner
|
||||
re-agreement plus separate attended live authorization. MASON-WP-0009-T05
|
||||
operator/ESO capability and nonmember checks remain independent acceptance.
|
||||
|
||||
2026-10-11: Platform independently re-agreed the final amended design (session proposal SHA25623f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09). Original conditional review and narrow confirmation are retained in railiance-platform/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review*. Source acceptance T01 is complete; actual registry/leeway/readiness, founder live go, negative/cleanup and separate capability/no-fallback obligations remain T02 and owner plans.
|
||||
|
||||
|
||||
2026-10-17 Custodian coordinator finalization review (not a KeyCape formal owner
|
||||
acknowledgment): the [infrastructure assessment](../../the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/ASSESSMENT.md)
|
||||
confirms all 14 executable/test/producer-decision pins and three Platform source
|
||||
pins still match. Source reviews remain complete: T01/T03 done, T02 wait. Existing
|
||||
source acceptance remains valid. Resolved registration go caafe4be covers only
|
||||
the original secrets-engine-openbao client (KEY-WP-0037-T03, deployed 6f79a2af,
|
||||
NK-WP-0046 exact-match helper); it does not authorize new probe clients or
|
||||
coding-client custody, and no renewed original-client approval is requested.
|
||||
The standing attended rule remains separate, for reviewed commands within
|
||||
existing scope; no new protocol or blanket approval request is introduced. The
|
||||
remaining sequence is KEY-WP-0038-T02/T03 first-case registration/claims proof,
|
||||
admission of the existing coding-client proposal with sanitized owner readiness
|
||||
returns, then one attended negative/revoke/cleanup receipt committed with probe
|
||||
clients removed. No native acceptance or credential/registration action is
|
||||
inferred from this coordinator review.
|
||||
|
||||
|
||||
## KeyCape issuer-source compatibility inspection for lifecycle v2
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "54d37ad8-2352-51c2-bb2e-af6070dbbd73"
|
||||
```
|
||||
|
||||
2026-10-11: Completed KeyCape's own issuer-source inspection for
|
||||
KEY-WP-0039-T02/attended-lifecycle-v2, exact manifest SHA256
|
||||
6fc0fe47f99a97c11c6e7538f5ea02054335df7d46055d3f84bed4e194aeaaed.
|
||||
Read AGENTS.md, governing INTENT.md, current client-credentials claim source,
|
||||
registration fragments, native caller-authentication commands and frozen v2
|
||||
issuer transport/probe wiring. Seventeen unchanged runner tests pass locally.
|
||||
This task completes only issuer-source inspection; the resulting narrow
|
||||
source acknowledgment is in docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json (retained frozen prior acknowledgment).
|
||||
T01 stays done and T02 stays open for actual native registration, token input,
|
||||
refusal and cleanup proof. No Secret, token, credential or native environment was accessed; no owner
|
||||
checkout was changed during this source inspection. Owner source files were
|
||||
read for the bounded issuer compatibility check.
|
||||
|
||||
Current token.go:306-309 returns HTTP 401 invalid_profile_usage with
|
||||
feature=client_id for a removed confidential client; wrong-secret
|
||||
feature=Authorization cannot count as removed-registration evidence. The
|
||||
proposal and packet comments are narrowly corrected against that current
|
||||
issuer contract. Prior T01/source re-agreement history is preserved; this source
|
||||
acknowledgment supplies no NetKingdom runtime-owner review or founder live go.
|
||||
|
||||
The private token-producer handoff remains a real execution gap under T02.
|
||||
Existing keycape login/service-token commands require a new private output
|
||||
file; login also needs a concrete admitted public PKCE client/callback and
|
||||
attended browser/MFA. The lifecycle expects an in-memory private stdin object
|
||||
containing human_token and coding_agent_token, but no reviewed file-to-process
|
||||
or process-to-process producer, admitted human client/callback, custody-injected
|
||||
coding-agent source, native placement or producer cleanup receipt has been
|
||||
provided. No unattended token producer or admitted secret route is invented.
|
||||
Resolve those exact owner contracts before founder native approval; existing
|
||||
static registration is not live token custody or producer admission.
|
||||
|
||||
|
||||
2026-10-11 same T03 source-inspection refinement for lifecycle V4: completed
|
||||
KeyCape issuer-facing inspection before the new formal acknowledgment in
|
||||
docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json. Exact V4
|
||||
manifest SHA256 d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8,
|
||||
executor b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d.
|
||||
NetKingdom's independent V4 source review is approved, receipt SHA256
|
||||
4f4498203a31bd46dbc3715c0df1cbb9eee63bed5b2c1baf430ff3c4a37c13cb.
|
||||
The V2/V3 unsuccessful reviews and earlier KeyCape acknowledgment remain
|
||||
retained as one correction trial; they are not extra scored successes.
|
||||
|
||||
V4 retains the unchanged issuer runner and six-client claim design, and fixes
|
||||
finite/nonboolean windows, true total request deadlines, late-known OpenBao
|
||||
session private revoke/refusal and guarded bounded final Secret deletion.
|
||||
The issuer-facing acknowledgement covers only stopping late token exchanges,
|
||||
claim source/shape and preserving token cleanup failure/unknown outcomes; it
|
||||
does not replace NetKingdom runtime or Platform backend acceptance. Tests: 41
|
||||
supplied lifecycle, 17 unchanged runner and 17 independent owner checks pass
|
||||
locally. No native tokens, credentials, issuer mutations or live cleanup were
|
||||
performed. T01 stays done; T02 stays open and the genuine private token-producer
|
||||
handoff remains its next source prerequisite before actual founder native go.
|
||||
|
||||
|
||||
2026-10-11 same T03 source-inspection refinement for lifecycle V5: completed
|
||||
KeyCape's own current issuer-source/claim/cleanup-contract inspection before
|
||||
any new formal acknowledgment. Current token.go:306-309 still distinguishes
|
||||
removed-client invalid_profile_usage feature=client_id from wrong-secret
|
||||
feature=Authorization. The six-client design, audience source and expiry
|
||||
positive control remain unchanged, and the original runner's 17 tests pass.
|
||||
Exact V5 manifest SHA256
|
||||
d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285;
|
||||
executor 3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360.
|
||||
V5 has 46 passing supplied lifecycle checks. It adds a single exceptional
|
||||
response-close abort under a fixed20ms cap clipped to nonresettable aggregate
|
||||
cleanup, and to positive original request time remaining, with expired aggregate
|
||||
zero calls, fixed private errors and truthful known-session revoke/unknown
|
||||
outcomes. This inspection covers issuer-facing token lifetime and claim
|
||||
interfaces only; NetKingdom and Platform independently own their runtime and
|
||||
backend review decisions. Previous V2/V3 Net source refusals and Platform V4
|
||||
exceptional-close refusal remain retained; KeyCape's V2/V4 acknowledgments are
|
||||
historical narrow source inspections, never aggregate native approval.
|
||||
T01 remains done, T02 open. No native actions or credentials occurred. The real
|
||||
private token-producer/client/callback/coding-agent custody/cleanup handoff gap
|
||||
stays the next prerequisite; no unattended admission is inferred.
|
||||
|
||||
Exact independent V5 source decisions were then verified: NetKingdom approved
|
||||
receipt SHA256 32a62bd1ca1bd7311965d016d4d6a6e4d9177f3423c63add436d9d309865eacc
|
||||
(46 supplied +17 runner +21 independent checks); Platform approved receipt
|
||||
SHA256 ba872dc53061b1dcca9dea1e8897597c394c1f9365e0fc49ee6f2095f57f11e0
|
||||
(46 supplied +5 independent +17 previously verified runner checks). Those are
|
||||
foreign owners' own source decisions, not KeyCape runtime/backend reviews or
|
||||
founder native authority. Formal KeyCape bounded issuer acknowledgment follows
|
||||
in docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json.
|
||||
|
||||
|
||||
2026-10-11 T02 continuing source preparation: private producer decision packet
|
||||
`KEY-WP-0039-T02/private-producer-handoff-v1` stages supported existing CLI
|
||||
flows, selected existing netkingdom-bootstrap-console literal-loopback human
|
||||
client subject to owner purpose/MFA/listener admission, and the existing
|
||||
codex-railiance-platform source identity subject to actual live/custody/consumer
|
||||
admission. No existing coding-agent delivery receipt was found; AppRole tokens
|
||||
do not substitute for issuer JWTs. A bounded pipe-only adapter for the current
|
||||
CLI's two caller-owned private files has 18 passing synthetic checks, with
|
||||
identity-guarded explicit file consumption and fixed value-free failure output.
|
||||
Decoded claim shape does not prove signatures or authenticated admission.
|
||||
See docs/openbao-negative-token-producer-decision.md and .json for exact owner
|
||||
choices and missing receipts. The exact trusted workstation-to-node launcher,
|
||||
private-file allowance and railiance01 Bao route/public-health guard still need
|
||||
source review and founder disposition before any token/temporary credential/
|
||||
mutation/restart. No registration, custody, login, native action or credential
|
||||
request occurred. V5 and failed review histories retained; T01 stays done and
|
||||
T02 open. No new work record or native acceptance is inferred.
|
||||
|
||||
|
||||
2026-10-11 same T02 source-preparation correction for
|
||||
KEY-WP-0039-T02/private-producer-handoff-v2: NetKingdom's exact V1 consumer
|
||||
refusal remains recorded (no child-ready gate, stdio alias, non-atomic unlink).
|
||||
V2 has source-pinned private launch/ready handshake compatible with exact
|
||||
Net V9 manifest84542ddc: fresh nonce/source/envelope/health/window marker before
|
||||
ANY caller-token file load/consume/write; separate local data/control pipes
|
||||
reject kernel identity aliases of stdin/stdout/stderr. Adapter read/write budget
|
||||
only clips downward after readiness. 31 distinct producer/launcher synthetic
|
||||
checks and 74 compatible consumer supplied checks pass. No SSH/native/login or
|
||||
credential access was performed. Stat/unlink remains non-atomic and requires
|
||||
continuously exclusive sameUID custody; the retained race test is a custody
|
||||
violation counterexample, not a claim of repaired filesystem atomicity. Receipt
|
||||
keeps atomic delete/copy cleanup unverified and possible copies true. Exact
|
||||
trusted launcher/directSSH profile/private tmpfs allowance, selected human
|
||||
client/MFA purpose reuse and coding-agent registration/custody/delivery remain
|
||||
unadmitted owner choices. Native node route, role/leeway/window/founder decision
|
||||
and actual negative/cleanup acceptance remain pending. T01done and T02open; no
|
||||
new task or plan, and V1/V5-V8/current failure histories are not overwritten.
|
||||
|
||||
|
||||
2026-10-11 same T02 producer-preparation correction V3: Platform V2 pair
|
||||
source refusal RPF-KEY39-PAIR-V2-F01 is retained. Private write uncertainty
|
||||
now propagates into sticky remote consumer uncertainty before launcher early
|
||||
return; local SSH termination never proves remote no-effects/recovery. Actual
|
||||
full-payload then interrupted confirmation and4096-byte partialpipe failures
|
||||
are retained regressions. 33 distinct source checks pass. Exact Net V9 source
|
||||
bytes unchanged; new source reviews required, not implicit repin. Native
|
||||
client/MFA/coding-agent custody/transport/tmpfsfile and actualroute/session
|
||||
authority remain unadmitted. T01done/T02open; no new task/plan or native action.
|
||||
|
||||
|
||||
2026-10-11 same T02 producer-preparation V4: exact V3 Net consumer refusal
|
||||
NET-PRODUCER-V3-F01 retained. Actual CLI contract acquisition now bounded
|
||||
nofollow/nonblocking regular stable reads and one ORIGINAL5-second preparation
|
||||
budget across contract/source/envelope/SSHspawn/ready/file/delivery phases, not
|
||||
a renewed launcher allowance. 40 own source tests plus9 retained paired
|
||||
wrapper/child handshake regressions pass with synthetic/no native transport.
|
||||
V3stickyremoteunknown/full+partialdelivery regressions retained; Net V9 exact
|
||||
bytes unchanged. Prior issuer/custody/transport/file/route/readiness-Argo/role/
|
||||
maintenanceexception/founder choices remain unadmitted. No SSH/network/Secrets/
|
||||
credentials/native/owner checkout changes; no task/plan duplication. T01done
|
||||
and T02open; source correction is not native acceptance.
|
||||
|
||||
|
||||
2026-10-11 T02 source-design disposition checkpoint: founder accepted exact
|
||||
source memo `infd-20261011-key39-source-design-r1`, review
|
||||
`review-62626f54-0212-4684-9b45-0a7222d61957` version1 at `2026-10-11T17:19:01Z`.
|
||||
Disposition `disp-870f9c1b-0444-4216-ae7f-f5463d590e36`, approval
|
||||
`ca118afc-d746-4454-a5b9-9b387b120cd9`; authenticated person subject
|
||||
`uid=platform-root,ou=people,dc=netkingdom,dc=local`. Exact canonical public review URL:
|
||||
`https://decisions.coulomb.social/review?memo_id=review-62626f54-0212-4684-9b45-0a7222d61957`.
|
||||
Root's authenticated confirmed readback is
|
||||
`the-custodian/docs/evidence/2026-10-10-security-consolidation/key39-source-design-publication/founder-source-disposition-readback-r1.json`,
|
||||
SHA256 `453213194d605336e9c424976f1ac27691def39e225026f0a99628d1cfc97d1d`; observed
|
||||
`2026-10-11T17:30:47.080081+00:00`. Source commands SHA256
|
||||
`36e20813596f3ddbb86e01fd0b838867ece2f4916c5880305cf12cd61b567f89` and source inventory SHA256
|
||||
`aea196db9e513900d5e7165455d6e9c3dc68749a606dc219ec340ac595642cc0` bind the accepted source direction.
|
||||
|
||||
Acceptance covers recommended source preparation for existing bootstrap-console
|
||||
human-client purpose reuse, returning existing coding-agent custody admission or
|
||||
preparing a narrow owner proposal, proposed founder directSSH source profile,
|
||||
and conditional exclusive-owner tmpfs files. It does not establish actual
|
||||
registration/MFA/listener, coding-agent custody/delivery, transport/file admission,
|
||||
node-local Bao route, issuer/OpenBao role/leeway/maintenance readiness or fresh
|
||||
attended native go. The published choice documents remain historical exact
|
||||
pending proposals; this appended receipt records their later source disposition.
|
||||
T02 remains wait on external owner custody/admission and separate attended
|
||||
execution authorization. Source acceptance permits further source preparation;
|
||||
actual registry, token exchange, negative probe, revoke and cleanup remain pending. T01 and T03
|
||||
remain done. No native or credential action is authorized by this source receipt.
|
||||
See docs/proposals/2026-10-11-key39-next-execution-prerequisites.json for exact
|
||||
owner returns and safe metadata evidence needed before preparing a runnable
|
||||
attended execution proposal.
|
||||
|
||||
2026-10-11 ordered-goal qualification (T02 only): the final source runner already
|
||||
exists and its 17 local semantic tests pass. No new helper or source protocol is
|
||||
needed to repeat accepted preparation. Native execution follows the first
|
||||
Forgejo SecurityCase registration and claims proof (KEY-WP-0038-T02/T03);
|
||||
only those task metadata were checked, not wait-task bodies. NetKingdom owns the
|
||||
registry/env/restart session, KeyCape owns issuance/isolation, and Platform owns
|
||||
leeway/refusal/capability/no-fallback/cleanup proof. The live registration go is
|
||||
already recorded resolved; do not ask again. Attended human authentication and
|
||||
owner-held existing coding-agent custody go on the single attended checklist.
|
||||
No credentials, live registry/API changes or KEY-WP-0040 recovery work occurred.
|
||||
|
||||
2026-10-17 Platform owner returns (source-prepared, not admitted):
|
||||
railiance-platform `docs/proposals/2026-10-17-key39-t02-owner-returns.md`
|
||||
(commit `0a9617a`). The coding client `codex-railiance-platform` has no
|
||||
admission, custody or delivery record. The existing narrow proposal
|
||||
`docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json` (`9460f6f`)
|
||||
remains the one to admit, so the coding-agent case stays unrunnable until owners
|
||||
admit it. The role source (`openbao/auth/secrets-engine-jwt-role.json`) matches
|
||||
the packet's claim design. It declares no `expiration_leeway`, so the expiry
|
||||
probe must assume OpenBao's 150s default until an attended sanitized read
|
||||
confirms the live value. The node-local Bao origin is not declared in source and
|
||||
also needs that attended read. T02 stays wait.
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
id: KEY-WP-0040
|
||||
type: workplan
|
||||
title: "Fix findings from the first attended MFA run (factor recovery preview, OTP prompt help)"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-11"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "a67f9d60-9b8f-5111-9fe5-9f64e631b1c3"
|
||||
---
|
||||
|
||||
From the attended run of 2026-10-11 (user-engine `docs/evidence/2026-10-11-mfa-attended-u05-u08.md`).
|
||||
U05–U07 passed. U08 is blocked by T01.
|
||||
|
||||
## Recovery preview rejects valid input (F6)
|
||||
|
||||
```task
|
||||
id: KEY-WP-0040-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b385602d-714a-5fdb-b40b-bd870420f28e"
|
||||
```
|
||||
|
||||
Operator `platform-root` (holds platform-operator, fresh AAL2) previewed login `tegwick`,
|
||||
reference `U08-2026-10-11` at `/platform/factor-recovery`. Every attempt returned `invalid_request`
|
||||
("Enter a valid login and support reference."). Both inputs match `[A-Za-z0-9_.@:/-]{1,150}`.
|
||||
The `factor-recovery` container in `mfa/privacyidea` logged nothing.
|
||||
|
||||
Candidate causes in `scripts/recovery_service.py` / `scripts/factor_recovery.py`:
|
||||
- the actor `sub` (KeyCape `user.ID`) failing `_recover`'s character rule;
|
||||
- a privacyIDEA serial failing it;
|
||||
- the request body differing from what is validated, e.g. form encoding through the portal.
|
||||
|
||||
Done when:
|
||||
- the failing check is identified and fixed with a regression test;
|
||||
- the sidecar logs the failing check (field name and error code, never values);
|
||||
- a preview for `tegwick` lists the active TOTP (serial suffix …E1D) without applying anything.
|
||||
|
||||
Then hand back to user-engine, which re-runs U08 (USER-WP-0028-T03). The fix and roll-out
|
||||
of the sidecar are blue lane under the build-mode change rule.
|
||||
|
||||
2026-10-12 source repair: LLDAP adapter returns entry.DN as user.ID and the token
|
||||
issuer emits that exact value as sub. Recovery incorrectly applied the login
|
||||
regex to actor, rejecting comma/equal characters. Two DN-shaped subject tests
|
||||
reproduce invalid_request against the old source. The repair validates the
|
||||
bounded printable ASCII actor separately, preserves exact ticket/audit binding,
|
||||
and keeps target/serial/realm/reference validation strict. Validation diagnostics
|
||||
emit only closed field names and error codes to stderr; provider logging stays
|
||||
disabled. Twenty recovery tests pass, including identity switching, malformed
|
||||
input, provider field failures, replay and mutation/audit guards. Go OIDC and
|
||||
LLDAP suites pass. No live factor was read or changed. T01 stays progress until
|
||||
the deployed authenticated preview succeeds and user-engine can rerun U08.
|
||||
|
||||
## Link the OTP prompt to lost-authenticator help (F3)
|
||||
|
||||
```task
|
||||
id: KEY-WP-0040-T02
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "3ad155dd-15ef-5522-bdf9-c78832728553"
|
||||
```
|
||||
|
||||
The OTP input offers no way out for a user whose code is rejected or whose device is lost. Add
|
||||
"Code rejected or lost your authenticator?" linking to
|
||||
`https://users.coulomb.social/security` (the lost-authenticator section). The same link serves users
|
||||
who have no factor yet (the dead end noted in `mfa-attended-run.md`).
|
||||
|
||||
2026-10-12: source link added to the MFA challenge with noreferrer, preserving
|
||||
challenge state and requiring no query/token in the destination. Go OIDC tests
|
||||
pass. Runtime publication and attended journey verification remain open; T02
|
||||
is not represented as deployed.
|
||||
|
||||
2026-10-12 blue-lane runtime repair completed: immutable ConfigMap
|
||||
factor-recovery-01119187d90d7b76 replaced only the recovery-code volume through
|
||||
UID/resourceVersion CAS. Server dry-run proved no other pod-template change.
|
||||
privacyIDEA generation 11 is Ready; recovery health returns 200 and anonymous
|
||||
recovery is denied (403). All Go tests pass in the actual checkout. An early
|
||||
ready=false observation was retained while the rollout completed; no apply was
|
||||
replayed. Abort owner: key-cape; rollback restores prior immutable ConfigMap
|
||||
factor-recovery-79e2964c112f3688 with the same CAS helper. Receipt and exact
|
||||
proposal: the-custodian/docs/evidence/2026-10-12-netkingdom-security/.
|
||||
T01 remains progress for the founder's authenticated tegwick preview and U08.
|
||||
T02 is a local source edit only; its source/image publication remains pending.
|
||||
|
||||
2026-10-14: the MFA challenge render test now requires the lost-authenticator
|
||||
link after the OTP form, with rel="noreferrer" and no request state in the URL.
|
||||
T02 stays progress until that page is published and an attended journey sees it.
|
||||
|
||||
## Current publication and attended acceptance — 2026-10-16
|
||||
|
||||
The source-only publication statements above describe the October 12–14 state.
|
||||
They are superseded for T02: the October 15 founder-run publication recorded in
|
||||
`docs/operations.md` (Current image) and KEY-WP-0041-T02 delivered image
|
||||
`sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010`
|
||||
from source `8c6c071`. That source includes the OTP help link commit `205a1c0`.
|
||||
The founder verified the image-only diff, server dry-run, Recreate rollout,
|
||||
Ready pod with zero restarts and discovery HTTP200. No additional image rollout
|
||||
is needed to make the existing T02 link available. This reconciliation makes
|
||||
no new live observation and does not infer attended acceptance from publication.
|
||||
|
||||
T01 still needs the actual authenticated preview; T02 still needs the actual
|
||||
OTP-page observation. Both remain progress. Resume the existing owner procedure
|
||||
`user-engine/docs/mfa-attended-run.md`, corrected by its October 11 evidence
|
||||
`user-engine/docs/evidence/2026-10-11-mfa-attended-u05-u08.md`:
|
||||
|
||||
1. In the real user-engine-portal OTP challenge, observe **Code rejected or lost
|
||||
your authenticator?**, follow it to `https://users.coulomb.social/security`,
|
||||
and confirm the rejected-code/lost-device guidance is reachable. Preserve no
|
||||
challenge URL, request state, cookie or OTP value in evidence. Record time,
|
||||
client, link text/destination and outcome. This supplies T02 acceptance.
|
||||
2. With platform-root's own fresh AAL2 browser session, open the existing
|
||||
`/platform/factor-recovery` form. Preview `tegwick` using the existing support
|
||||
reference `U08-2026-10-11`; inspect any existing reference before changing it.
|
||||
Confirm the current factor metadata and shared-identity warning. The October
|
||||
11 suffix was …E1D; a changed factor is a reason to reconcile current state,
|
||||
never to force the old selection. Record only time, preview outcome and serial
|
||||
suffix. This preview alone makes no factor change and supplies the remaining
|
||||
T01 preview evidence.
|
||||
3. Only in the separately attended recovery window, after the operator verifies
|
||||
the person and exact current scope, continue the existing U08 confirmation,
|
||||
audited disable, reference readback, replacement enrollment and fresh login
|
||||
procedure. Preserve the original reference and safe recovery record id.
|
||||
Neither the published bug fix nor a passing preview closes U08. Stop on
|
||||
stale metadata, replaced factor or failed audit/readback; do not reset or
|
||||
re-enroll merely to make an acceptance check pass.
|
||||
|
||||
No authenticated preview, real-factor mutation or attended page observation was
|
||||
performed during this source reconciliation. No passwords, codes, token values,
|
||||
provider keys or production Secret objects were requested or read.
|
||||
|
||||
## T02 attended acceptance — 2026-10-17
|
||||
|
||||
The founder reported in session that the real user-engine-portal OTP challenge
|
||||
shows **Code rejected or lost your authenticator?**, that it leads to
|
||||
`https://users.coulomb.social/security`, and that the lost-device guidance is
|
||||
reachable there ("T02 is fine"). This is the founder's own observation, on the
|
||||
deployed image `cdec9dd7` from source `8c6c071`. No exact time or client was
|
||||
recorded, and no challenge URL, request state, cookie or OTP value was preserved.
|
||||
T02 is done. T01 still waits for the authenticated preview (step 2 above).
|
||||
|
||||
## T01 attended preview acceptance — 2026-10-17
|
||||
|
||||
The founder, signed in as platform-root with its own fresh OTP (AAL2), opened
|
||||
`/platform/factor-recovery` and previewed login `tegwick` with the existing
|
||||
reference `U08-2026-10-11`. The founder reported that the preview listed
|
||||
tegwick's TOTP. The `invalid_request` failure from October 11 (F6) did not recur.
|
||||
No confirm or disable action was taken, and no factor was changed. When asked
|
||||
afterwards, the founder confirmed that the listed TOTP's serial suffix is …E1D,
|
||||
the same active factor as on October 11. The exact time was not recorded.
|
||||
|
||||
All three Done-when conditions now hold: the DN-shaped actor defect was fixed
|
||||
with regression tests (2026-10-12); the sidecar logs only closed field names and
|
||||
error codes; and the deployed preview for tegwick lists the active TOTP …E1D
|
||||
without applying anything. That third condition was met only after the suffix
|
||||
was confirmed, not before. T01 and the workplan are done. U08 itself, meaning
|
||||
the attended disable, re-enrolment and fresh login, belongs to user-engine
|
||||
(USER-WP-0028-T03) and is handed back to it. This workplan does not close U08.
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
id: KEY-WP-0041
|
||||
type: workplan
|
||||
title: "Sign-in diagnosis page and auth log uid (NK-WP-0051-T02)"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-14"
|
||||
updated: "2026-10-15"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "092c7590-3700-524d-bbe0-a934e063be6b"
|
||||
---
|
||||
|
||||
Demand: NetKingdom `NK-WP-0051-T02`. The field names (`uid`, `groups`,
|
||||
`assurance`, `client_id`) and the never-show/never-log list come only from
|
||||
`net-kingdom/docs/sign-in-diagnosis.md`; this workplan does not restate them.
|
||||
|
||||
## Opt-in pre-redirect page and auth log fields
|
||||
|
||||
```task
|
||||
id: KEY-WP-0041-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5947be07-bc84-5672-b807-608070a39d10"
|
||||
```
|
||||
|
||||
- Client registration flag `signInDiagnosis` (default false) in the YAML
|
||||
config, `domain.Client` and `spec/canonical-model.yaml`.
|
||||
- With the flag on, `/authorize/callback` renders a page before the redirect
|
||||
with `uid`, sorted `groups`, `assurance` and `client_id`, one continue link
|
||||
to the unchanged redirect (code + state), and a sign-out hint. html/template
|
||||
escaping, `no-store`, `Referrer-Policy: no-referrer`, restrictive CSP.
|
||||
- `assurance` is `aal2` only when this request verified a one-time code; a
|
||||
reused aal2 session is `aal1` (contract wording, stricter than the session
|
||||
satisfied `MFAVerified`).
|
||||
- The `auth_success` log line gains `uid` and `assurance` beside `client_id`.
|
||||
- Enabled for `openbao-admin` in `config/dev-config.yaml`; the live flag is set
|
||||
in `sso/keycape-config` (see `docs/operations.md`).
|
||||
- Tests: `src/internal/server/oidc/signin_diagnosis_test.go`.
|
||||
|
||||
## Deploy the image and record its digest
|
||||
|
||||
```task
|
||||
id: KEY-WP-0041-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "bd5db26c-b71c-5291-bdc3-7c0f718651db"
|
||||
```
|
||||
|
||||
Build and push with `make push IMAGE_TAG=<sha>`, set `signInDiagnosis: true`
|
||||
on the OpenBao browser client in `sso/keycape-config`, roll the issuer under
|
||||
the usual live-change approval, and send the digest to net-kingdom for the
|
||||
evidence table in `docs/sign-in-diagnosis.md`. Not done by the implementing
|
||||
session: deployment needs operator approval.
|
||||
|
||||
2026-10-15: image `sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010`
|
||||
(source `8c6c071`) is live; see `docs/operations.md`. 2026-10-15: `signInDiagnosis: true` set on `openbao-admin` with
|
||||
net-kingdom `openbao-signin-diagnosis-rollout.py` (rv 71655453 → 75372162,
|
||||
unrelated bytes preserved) and the issuer restarted; pod ready, discovery 200.
|
||||
Accepted 2026-10-15: an attended OpenBao login showed the page and completed.
|
||||
|
|
@ -0,0 +1 @@
|
|||
WARN: git commit failed for writeback: Generated-only commits rejected: staged
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"repo_slug": "key-cape",
|
||||
"repo_path": "/home/worsch/key-cape",
|
||||
"source_revision": "d803e9282a47b49daf930fb905312fa5104c0ace",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-24",
|
||||
"message": "Repo classification advisory: capability_tag 'security' is not a recommended family tag (allowed, check for synonym)",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-11",
|
||||
"message": "Task 'ADHOC-2026-09-27-T02' has no state_hub_task_id",
|
||||
"file_path": "workplans/ADHOC-2026-09-27.md#ADHOC-2026-09-27-T02",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
|
||||
"file_path": "workplans/KEY-WP-0007-user-engine-portal-oidc-client.md",
|
||||
"db_id": "",
|
||||
"file_value": "USER-WP-0020",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'NK-WP-0025' is not linked to State Hub",
|
||||
"file_path": "workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md",
|
||||
"db_id": "",
|
||||
"file_value": "NK-WP-0025",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'NK-WP-0030' is not linked to State Hub",
|
||||
"file_path": "workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md",
|
||||
"db_id": "",
|
||||
"file_value": "NK-WP-0030",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0027' is not linked to State Hub",
|
||||
"file_path": "workplans/KEY-WP-0010-openbao-operator-loopback-callback.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0027",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-41",
|
||||
"message": "Task wait-qualifier drift 'KEY-WP-0039-T02': file={'needs_human': False, 'blocking_reason': 'Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established.', 'decision_id': None, 'blocked_on': None, 'wait_until': None} db={'needs_human': False, 'blocking_reason': 'Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved.', 'decision_id': None, 'blocked_on': None, 'wait_until': None} (file wins)",
|
||||
"file_path": "workplans/KEY-WP-0039-openbao-negative-probe-tokens.md#KEY-WP-0039-T02",
|
||||
"db_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"file_value": "{\"blocked_on\": null, \"blocking_reason\": \"Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established.\", \"decision_id\": null, \"needs_human\": false, \"wait_until\": null}",
|
||||
"db_value": "{\"blocked_on\": null, \"blocking_reason\": \"Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved.\", \"decision_id\": null, \"needs_human\": false, \"wait_until\": null}",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'keycape-implementation@retired-20260826' (id=2c9caad8\u2026, status=archived) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "2c9caad8-2ced-492d-9d63-376387b4b9b0",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "2 unread inbox message(s) older than 3 day(s): the-custodian:23172cb2, the-custodian:51e9eace",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "2 unread message(s) lack thread_id for supersession tracking: the-custodian:23172cb2, the-custodian:51e9eace",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
}
|
||||
],
|
||||
"fixes_applied": [
|
||||
"bindings: synced 44/44",
|
||||
"C-17 cleared: pushed 1 backlogged commit(s)",
|
||||
"C-11 skipped: task 'ADHOC-2026-09-27-T02' in finished workstream \u2014 not created",
|
||||
"C-41 fixed: task 03da4c38\u2026 wait qualifiers synced",
|
||||
"brief: .custodian-brief.md updated",
|
||||
"push: pushed"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 0,
|
||||
"automation_error": 0,
|
||||
"warn": 9,
|
||||
"info": 1
|
||||
},
|
||||
"result": "warn"
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
|
||||
"receiver_status": "applied",
|
||||
"acknowledge_retirements": false,
|
||||
"instance_role": "primary",
|
||||
"instance_label": "railiance01",
|
||||
"workplan": {
|
||||
"id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"status": "active",
|
||||
"record_id": "KEY-WP-0039"
|
||||
},
|
||||
"task_bindings": [
|
||||
{
|
||||
"record_id": "KEY-WP-0039-T01",
|
||||
"state_hub_task_id": "4d06b059-3768-53bb-83a4-a9a71c0850b8",
|
||||
"status": "done",
|
||||
"workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"depends_on_source": []
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0039-T02",
|
||||
"state_hub_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"status": "wait",
|
||||
"workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"depends_on_source": [
|
||||
"KEY-WP-0039-T01",
|
||||
"KEY-WP-0038-T02",
|
||||
"KEY-WP-0038-T03"
|
||||
]
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0039-T03",
|
||||
"state_hub_task_id": "54d37ad8-2352-51c2-bb2e-af6070dbbd73",
|
||||
"status": "done",
|
||||
"workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"depends_on_source": []
|
||||
}
|
||||
],
|
||||
"all_other_task_fields_unchanged": true,
|
||||
"only_task_field_delta": {
|
||||
"task": "KEY-WP-0039-T02",
|
||||
"field": "blocking_reason",
|
||||
"old": "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved.",
|
||||
"new": "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established."
|
||||
},
|
||||
"needs_human": false,
|
||||
"primary_task_blocking_reason": "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established.",
|
||||
"frontmatter_unchanged": true,
|
||||
"only_committed_path": "workplans/KEY-WP-0039-openbao-negative-probe-tokens.md",
|
||||
"remote_forge_matches": true,
|
||||
"owner_worktree_and_index_clean": true,
|
||||
"normal_sync_refusal": "unrelated grandfathered ADHOC-2026-09-27-T02 UUID; not modified",
|
||||
"native_acceptance_inferred": false
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"scope": "only authorized KEY39 T02 blocking_reason suffix correction; all other task fields/frontmatter unchanged; no native action",
|
||||
"commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
|
||||
"files": {
|
||||
"KEY39.preimage.md": "sha256:618600946f6dd4128206db7ab23da9cc96e22cb84e0fd301b1b2eacd5c9bdba4",
|
||||
"KEY39.proposed.md": "sha256:517f9526d574ce3faafad18837e0039db8f3f7990eac3e34a1678cdd7307a9e1",
|
||||
"apply.py": "sha256:1c4997372c3c43e78ffcce533e33b0c38ececaf252cd4bf62db69cf43f11967a",
|
||||
"baseline.json": "sha256:7415b840f97b217a29004cd9b3a4a0737dbc31c9c03b821d326442bc793c95ab",
|
||||
"canonical-receiver.json": "sha256:31a225513ccee3266fd59476950875c78bcbe57d13558b20940a096e95536e80",
|
||||
"canonical-reconcile.py": "sha256:dca4dcfc37db7f68e3059fe99b910ff7f207e2ef3c4b69f926ebaf46317d078a",
|
||||
"commit.json": "sha256:9e534c5ab7bd5ebb728774c58d363eaa52bcbb3f561118df75ad07eef9699974",
|
||||
"consistency-backup/.custodian-brief.md": "sha256:96d84539b3a92786bff37fcc70161adec4648b9547287e52e6b817bc080a080a",
|
||||
"consistency-backup/WORK-RECORDS.md": "sha256:431842547bd82750f284f8c1c9f519892f1bb1c1a684b9b1e38adc6fdfdc299d",
|
||||
"consistency-backup/originals.json": "sha256:0d7851a532b1b451b20eb00a1eb98727b004da073173263251cf3fae40a34e52",
|
||||
"consistency-backup/workplans/ADHOC-2026-09-05.md": "sha256:76123d22d0c1efc51212f8914e386f078b3c6d908959af6978ae73874e492b04",
|
||||
"consistency-backup/workplans/ADHOC-2026-09-07.md": "sha256:c335ebfe91dad291277e8f17034ac8f785b3bc767f281f205c446c08f03d7978",
|
||||
"consistency-backup/workplans/ADHOC-2026-09-27.md": "sha256:2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314",
|
||||
"consistency-backup/workplans/KEY-WP-0001-keycape-implementation.md": "sha256:29a2bf4892b7185e28e9126a3d8311d0fed34a876f416a3e75c605915fde0969",
|
||||
"consistency-backup/workplans/KEY-WP-0002-container-image-gitea.md": "sha256:76fee187185b30219bc257ccc9b587661df4489574a2bb57703a365a5d803cec",
|
||||
"consistency-backup/workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md": "sha256:5e949c696e587540d305b682eb1f8390ec544bbec6a566be8d04e73fb1b254ca",
|
||||
"consistency-backup/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md": "sha256:260ba7d3116db3bc5a72e5cd8b9984137e7e372c8128d21073614dbd948c10c2",
|
||||
"consistency-backup/workplans/KEY-WP-0005-iam-profile-core-claims.md": "sha256:c6702b644cdd046c0d3bba8ba8837050826e4dc3d855bd73327ae94218ba5b57",
|
||||
"consistency-backup/workplans/KEY-WP-0006-client-credentials-service-tokens.md": "sha256:b3227849978c95cad89e02afb74bf2217b6188ae65c5b0af6a21703d65477701",
|
||||
"consistency-backup/workplans/KEY-WP-0007-user-engine-portal-oidc-client.md": "sha256:5408ff6dfeb58a8a7756b48fe4f54f7c7b98d7940cb9aaa5c7864f1247e58150",
|
||||
"consistency-backup/workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md": "sha256:bfc9b2118f0712e92a86bbb0f14a2857fafc3a42a3c14c0e2535002aaa57a2f5",
|
||||
"consistency-backup/workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md": "sha256:d1daac9815930104a392ea96c14922e34f947720a31d1b0ac45a770997e0f476",
|
||||
"consistency-backup/workplans/KEY-WP-0010-openbao-operator-loopback-callback.md": "sha256:41d28a16bef9313b776c5c6305316a9e7d4f5528b7fb7427b8384a0ed0ad35c4",
|
||||
"consistency-backup/workplans/KEY-WP-0011-live-secret-exposure-recovery.md": "sha256:3c5458180fe81a04e357f8db737e4287f79640b5ab02682bcadc595b73f846cd",
|
||||
"consistency-backup/workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md": "sha256:68d34d75c914e7d2d8177000e800d6372cccda2b850c03ab85da42b90a66b91c",
|
||||
"consistency-backup/workplans/KEY-WP-0013-approval-engine-resource-audience.md": "sha256:55c88bf4ed9a456db59b319bbaf960dfe6f4147437e339f950e0583d7582057a",
|
||||
"consistency-backup/workplans/KEY-WP-0014-native-credential-lane-handoff.md": "sha256:22aac43ae5039aebd11f9f0aa57a882ad009ad6ed119ec08eb85b32a5af48cd7",
|
||||
"consistency-backup/workplans/KEY-WP-0015-scope-intent-assessment.md": "sha256:60b9f2265eff880d916c3ee8dba36aec9984ec310804de776cebf89cc98fdb95",
|
||||
"consistency-backup/workplans/KEY-WP-0016-authorization-code-protocol-hardening.md": "sha256:ed48128a1cb51cbe3d154727bafd5a5ffb3b5b618c20649b2a0e7db154cf3a0c",
|
||||
"consistency-backup/workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md": "sha256:8700e0e5aa07dcd7f651e792cca12a0330b7ee9d2f6064b4cc3fcf4c7d509340",
|
||||
"consistency-backup/workplans/KEY-WP-0018-export-completeness-evidence.md": "sha256:15026b708d116f803b0b2fa43f30a4dbced76e4d8137b5fcb5b4275e593d7c7b",
|
||||
"consistency-backup/workplans/KEY-WP-0019-upstream-provider-token-verification.md": "sha256:0d2d8b5bda1e0d333935812d8d3749cb7b0c5523a3987d1b26185f9f6d0f0fa2",
|
||||
"consistency-backup/workplans/KEY-WP-0020-migration-contract-preservation.md": "sha256:cc842d15bf42ddc4d0f4056bc19b3176cab2c5f3c1a2fe6d648e5dbd077be8b3",
|
||||
"consistency-backup/workplans/KEY-WP-0021-snapshot-attribute-validation.md": "sha256:b603248693073a9b9b25b4d0a4147d8d88c16a07dc9936e48fa1f3c76fe8bab6",
|
||||
"consistency-backup/workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md": "sha256:8c135e84be81de31b4949f52ac28dccd404f2e4c3eaa576d733bd50160427a53",
|
||||
"consistency-backup/workplans/KEY-WP-0023-live-migration-proof.md": "sha256:e581cd11f3905805b5e8ba8e2ede85092577c7d086e1761f2a2d033fdead4e47",
|
||||
"consistency-backup/workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md": "sha256:af7842fa97263bfd46a4b5ae84c5c1ecddb06ab9623586e0292dcc93099fea35",
|
||||
"consistency-backup/workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md": "sha256:0addb34e6d85e376c6a5db7afba597b6f268afa98b86c8c60add9be174dd1b26",
|
||||
"consistency-backup/workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md": "sha256:c1bc0de47511ae49102009a172c84d9d5d64e829a18c8d40564fe817935563b3",
|
||||
"consistency-backup/workplans/KEY-WP-0027-rollout-readiness-and-live-state.md": "sha256:f559eae61d433d451f80ea575254bb14a47f2485267b412e42d524b434bb1b59",
|
||||
"consistency-backup/workplans/KEY-WP-0028-browser-client-field-validation.md": "sha256:fbd51422fa5e6449000e4520e6639f6135c7393165a5ae02b1e92892edba0480",
|
||||
"consistency-backup/workplans/KEY-WP-0029-scope-reassessment.md": "sha256:3241f3b94db9cad5e3770cf1fedc5c211288357e4f69b5ba9da57065094bdf0e",
|
||||
"consistency-backup/workplans/KEY-WP-0030-tenant-precondition-guard.md": "sha256:c94c3afc7fb4270dfc18f3fe8a32946c524e4a6ba2d02a3d8fda69c9b8441af0",
|
||||
"consistency-backup/workplans/KEY-WP-0031-gate-house-tenant-ruling.md": "sha256:0abb135367f0366aa071769c4bd76fdeb321d810ec75ad3b58d718734f78382b",
|
||||
"consistency-backup/workplans/KEY-WP-0032-principal-type-provenance-guard.md": "sha256:6a5cd483a40b9477227b02156c42b3469094dac145b33727b336118898aa782a",
|
||||
"consistency-backup/workplans/KEY-WP-0033-vergabe-fresh-login.md": "sha256:c01bb51cf2dcad06ea846040dddef09845041fa7271cd7015e5a34a49e60bb8f",
|
||||
"consistency-backup/workplans/KEY-WP-0034-account-recovery.md": "sha256:7db9adcd418ccabfbce672aec283249a6e374212492f0349e8c47a568f130eda",
|
||||
"consistency-backup/workplans/KEY-WP-0035-optional-mfa.md": "sha256:c60ea8d0c195686c0d2317f657aad2de27c9b9432d311627c0e1d06124cabb49",
|
||||
"consistency-backup/workplans/KEY-WP-0036-platform-factor-recovery.md": "sha256:5ceb08fe5789b4f02550bb04fcaf8eeb25a43ac3f506dfc62d60a513bcd0013f",
|
||||
"consistency-backup/workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md": "sha256:3a90a0c460fc439570820b96d1ddc3b7f8646c557ca3a8aa27c6d546bb1a845a",
|
||||
"consistency-backup/workplans/KEY-WP-0038-forgejo-oidc-client.md": "sha256:dc8d88d462799e7e6e44da759a973d52e44102a271969a5daaa9d30ec015b064",
|
||||
"consistency-backup/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md": "sha256:517f9526d574ce3faafad18837e0039db8f3f7990eac3e34a1678cdd7307a9e1",
|
||||
"consistency-backup/workplans/KEY-WP-0040-mfa-attended-run-findings.md": "sha256:98b23d8810bff047067169232213cb313ecc14ff79751224842723afe808fbd2",
|
||||
"consistency-backup/workplans/KEY-WP-0041-sign-in-diagnosis-page.md": "sha256:7e44f46acd6b3942c1852b49e0d64b205dfd7d9bb378aed5241b1bf22d7ce88a",
|
||||
"consistency.stderr.txt": "sha256:f4a6aa1609a13600a0b97cbb08bbc03b3e247b619ff37912d37f7e109ce798ba",
|
||||
"consistency.stdout.json": "sha256:a77c1056e9cbf8fb5ecced6ef316b7bde9646d258452f875575c4e039fea7709",
|
||||
"final-verification.json": "sha256:43e0489c538788921be2f9ea2dec89451730a46ea6ace5b5d35b6e9d812db7a6",
|
||||
"normal-publication.json": "sha256:724f185f729f8224fc85a257cb9c564d77585e81f672e11eb2c77f4de56c20ce",
|
||||
"normal-sync.stderr.txt": "sha256:832bd22063c46072d0d91381f7d1356be933b4d09ded52fa19282da13c2855b1",
|
||||
"normal-sync.stdout.json": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"one-path.patch": "sha256:3dd74d482fe512683731cde8b57d3e3259f0fb523272ee88e6838a17ceb35611",
|
||||
"progress-receipt.json": "sha256:ae2bc1eb3c069753e269bb176b0660ba732a3ab966853a5d6a0d69613b1bf835",
|
||||
"progress-request.json": "sha256:df686f30b8ca8704745bd3317a87611a292409719f0e5a996d267fb087da63f7",
|
||||
"sync.py": "sha256:f9714d9ddd0b47e9e359edabc2b3f7edf84c2c9b4d55952215f2df65750415d0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"published_candidate_commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
|
||||
"normal_commands": [
|
||||
{
|
||||
"command": [
|
||||
"statehub",
|
||||
"fix-consistency",
|
||||
"--repo",
|
||||
"key-cape",
|
||||
"--json"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"label": "consistency"
|
||||
},
|
||||
{
|
||||
"command": [
|
||||
"/home/worsch/repo-manager/.venv/bin/rmgr",
|
||||
"sync",
|
||||
"--path",
|
||||
"/home/worsch/key-cape",
|
||||
"--push"
|
||||
],
|
||||
"exit_code": 1,
|
||||
"label": "normal-sync"
|
||||
}
|
||||
],
|
||||
"unrelated_writeback_restored": [
|
||||
".custodian-brief.md"
|
||||
],
|
||||
"all_original_index_worktree_fields_preserved": true,
|
||||
"one_path_commit": true,
|
||||
"normal_hooks": "changed-staged-blobs scanner8.30.1 clean",
|
||||
"native_or_credential_actions": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
Traceback (most recent call last):
|
||||
File "/home/worsch/repo-manager/.venv/bin/rmgr", line 8, in <module>
|
||||
sys.exit(main())
|
||||
^^^^^^
|
||||
File "/home/worsch/repo-manager/src/repo_manager/cli.py", line 853, in main
|
||||
result = sync_repository_projection(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/worsch/repo-manager/src/repo_manager/projection_sync.py", line 98, in sync_repository_projection
|
||||
assignments = ensure_missing_work_record_identifiers(repo_root)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/worsch/repo-manager/src/repo_manager/identifiers.py", line 962, in ensure_missing_work_record_identifiers
|
||||
raise ValueError(
|
||||
ValueError: missing UUIDs on grandfathered or noncanonical records require an explicit identity decision: [{'kind': 'task', 'record_id': 'ADHOC-2026-09-27-T02', 'path': 'workplans/ADHOC-2026-09-27.md'}]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md b/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md
|
||||
index b22aa6a..4398517 100644
|
||||
--- a/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md
|
||||
+++ b/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md
|
||||
@@ -99,7 +99,7 @@ transient Kubernetes Secret that is deleted in the same session.
|
||||
id: KEY-WP-0039-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
|
||||
-blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved."
|
||||
+blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established."
|
||||
priority: medium
|
||||
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"id": "777d28ea-7ebc-4f2a-814f-adad63d72dd5",
|
||||
"topic_id": null,
|
||||
"task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"decision_id": null,
|
||||
"event_type": "note",
|
||||
"summary": "Published KEY39 current narrative approval-scope clarification in commit 26ec9bb9b195ad377448570d6b161fd682311a0b, then the separately authorized T02 blocking_reason suffix correction in final commit d803e9282a47b49daf930fb905312fa5104c0ace. Decision caafe4be remains valid only for original secrets-engine-openbao registration under KEY-WP-0037-T03 / deployed 6f79a2af / NK-WP-0046 exact helper; it does not authorize new probe clients or coding-client custody. Standing attended rule remains separate within reviewed existing scope; no renewed original-client approval or new protocol requested. Both ordinary one-file commits passed normal hooks, consistency passed, generated brief writeback restored, unrelated historical ADHOC UUID gap preserved. Canonical primary receiving applied each exact published commit with retirement acknowledgment false. Primary task 03da4c38-1dac-53b1-8b0b-e697df48e9d3 blocking_reason equals the entire corrected source value and needs_human remains false. T01 done, T02 wait, T03 done; UUIDs, dependencies, priorities and frontmatter unchanged. No native, credential or registration action. New work stopped after source/metadata verification.",
|
||||
"detail": null,
|
||||
"author": "codex",
|
||||
"session_id": null,
|
||||
"created_at": "2026-10-17T11:42:42.743840Z",
|
||||
"workstream_id": "09c3bddf-6058-5603-8488-cccdb7be4f86"
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"summary": "Published KEY39 current narrative approval-scope clarification in commit 26ec9bb9b195ad377448570d6b161fd682311a0b, then the separately authorized T02 blocking_reason suffix correction in final commit d803e9282a47b49daf930fb905312fa5104c0ace. Decision caafe4be remains valid only for original secrets-engine-openbao registration under KEY-WP-0037-T03 / deployed 6f79a2af / NK-WP-0046 exact helper; it does not authorize new probe clients or coding-client custody. Standing attended rule remains separate within reviewed existing scope; no renewed original-client approval or new protocol requested. Both ordinary one-file commits passed normal hooks, consistency passed, generated brief writeback restored, unrelated historical ADHOC UUID gap preserved. Canonical primary receiving applied each exact published commit with retirement acknowledgment false. Primary task 03da4c38-1dac-53b1-8b0b-e697df48e9d3 blocking_reason equals the entire corrected source value and needs_human remains false. T01 done, T02 wait, T03 done; UUIDs, dependencies, priorities and frontmatter unchanged. No native, credential or registration action. New work stopped after source/metadata verification.",
|
||||
"event_type": "note",
|
||||
"author": "codex",
|
||||
"workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from pathlib import Path
|
||||
import subprocess,json,hashlib,stat
|
||||
root=Path('/home/worsch/key-cape');out=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-finalization-blocking-reason-r1');backup=out/'consistency-backup';backup.mkdir()
|
||||
def git(*args):return subprocess.check_output(['git','-C',str(root),*args]).decode()
|
||||
head=git('rev-parse','HEAD').strip();assert head==json.loads((out/'commit.json').read_text())['commit']
|
||||
assert git('diff-tree','--no-commit-id','--name-only','-r',head).splitlines()==['workplans/KEY-WP-0039-openbao-negative-probe-tokens.md']
|
||||
assert not git('status','--porcelain=v1')
|
||||
paths=sorted({p.relative_to(root).as_posix() for p in (root/'workplans').rglob('*.md')}|{n for n in ('.custodian-brief.md','WORK-RECORDS.md') if (root/n).exists()})
|
||||
original={}
|
||||
for rel in paths:
|
||||
p=root/rel;assert p.is_file() and not p.is_symlink()
|
||||
dest=backup/rel;dest.parent.mkdir(parents=True,exist_ok=True);dest.write_bytes(p.read_bytes())
|
||||
original[rel]={'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'mode':stat.S_IMODE(p.stat().st_mode),'index':git('ls-files','--stage','--',rel)}
|
||||
(backup/'originals.json').write_text(json.dumps(original,indent=2)+'\n')
|
||||
results=[];changed=[]
|
||||
try:
|
||||
for label,argv in [('consistency',['statehub','fix-consistency','--repo','key-cape','--json']),('normal-sync',['/home/worsch/repo-manager/.venv/bin/rmgr','sync','--path',str(root),'--push'])]:
|
||||
cp=subprocess.run(argv,cwd=root,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=240)
|
||||
(out/(label+'.stdout.json')).write_bytes(cp.stdout);(out/(label+'.stderr.txt')).write_bytes(cp.stderr)
|
||||
results.append({'command':argv,'exit_code':cp.returncode,'label':label})
|
||||
finally:
|
||||
assert git('rev-parse','HEAD').strip()==head,'unexpected commit; stop for review'
|
||||
for rel,b in original.items():
|
||||
p=root/rel
|
||||
if not p.exists() or hashlib.sha256(p.read_bytes()).hexdigest()!=b['sha256']:
|
||||
changed.append(rel);p.write_bytes((backup/rel).read_bytes());p.chmod(b['mode'])
|
||||
current=git('ls-files','--stage','--',rel)
|
||||
if current!=b['index']:
|
||||
assert b['index'];mode,oid,stage,path=b['index'].split();assert stage=='0'
|
||||
git('update-index','--cacheinfo',mode,oid,path)
|
||||
assert git('ls-files','--stage','--',rel)==b['index']
|
||||
assert hashlib.sha256(p.read_bytes()).hexdigest()==b['sha256']
|
||||
assert not git('status','--porcelain=v1'),git('status','--porcelain=v1')
|
||||
receipt={'published_candidate_commit':head,'normal_commands':results,'unrelated_writeback_restored':changed,'all_original_index_worktree_fields_preserved':True,'one_path_commit':True,'normal_hooks':'changed-staged-blobs scanner8.30.1 clean','native_or_credential_actions':0}
|
||||
(out/'normal-publication.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps(receipt))
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"id": "32796349-c902-456c-97e6-e4de38467aac",
|
||||
"topic_id": null,
|
||||
"task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"decision_id": null,
|
||||
"event_type": "note",
|
||||
"summary": "Reviewed KEY-WP-0039 and infrastructure owner reviews for finalization. Revalidated17 executable/producer/Platform source pins plus exact source-only infrastructure approval and seven KEY38/KEY39 source/Hub task bindings. T01/T03 complete; T02 remains wait for KEY38 first-case claims proof, existing coding-client custody admission/readiness returns, and actual attended negative/expiry/revoke/client-removal receipt. Kept parent active and existing approvals intact; no native or credential action. Evidence: docs/changes/netkingdom-target-2026-10-17/key39-finalization-current-r2/close-verification-r3.json.",
|
||||
"detail": null,
|
||||
"author": "codex",
|
||||
"session_id": null,
|
||||
"created_at": "2026-10-17T21:58:46.203045Z",
|
||||
"workstream_id": "09c3bddf-6058-5603-8488-cccdb7be4f86"
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"observed_at": "2026-10-17T21:58:43.188615+00:00",
|
||||
"review_files_and_source_pins_verified": 18,
|
||||
"plans": {
|
||||
"KEY-WP-0039": {
|
||||
"source_sha256": "sha256:517f9526d574ce3faafad18837e0039db8f3f7990eac3e34a1678cdd7307a9e1",
|
||||
"parent_status": "active",
|
||||
"tasks": [
|
||||
{
|
||||
"record_id": "KEY-WP-0039-T01",
|
||||
"uuid": "4d06b059-3768-53bb-83a4-a9a71c0850b8",
|
||||
"source_status": "done"
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0039-T02",
|
||||
"uuid": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"source_status": "wait"
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0039-T03",
|
||||
"uuid": "54d37ad8-2352-51c2-bb2e-af6070dbbd73",
|
||||
"source_status": "done"
|
||||
}
|
||||
]
|
||||
},
|
||||
"KEY-WP-0038": {
|
||||
"source_sha256": "sha256:dc8d88d462799e7e6e44da759a973d52e44102a271969a5daaa9d30ec015b064",
|
||||
"parent_status": "active",
|
||||
"tasks": [
|
||||
{
|
||||
"record_id": "KEY-WP-0038-T01",
|
||||
"uuid": "5f019d13-1fa3-5b88-a27a-75a3339ca96e",
|
||||
"source_status": "done"
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0038-T02",
|
||||
"uuid": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30",
|
||||
"source_status": "wait"
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0038-T03",
|
||||
"uuid": "40ddaa29-7245-508c-ad48-ddc063e25115",
|
||||
"source_status": "wait"
|
||||
},
|
||||
{
|
||||
"record_id": "KEY-WP-0038-T04",
|
||||
"uuid": "583cf1f1-16dc-515f-83bc-1efc45024a89",
|
||||
"source_status": "cancel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"disposition": "Source work complete; implementation remains active with native T02 wait. No new approval requested; existing source acceptance preserved.",
|
||||
"native_actions": false,
|
||||
"credentials_accessed": false,
|
||||
"task_status_changes": false,
|
||||
"assessment_reference": "../key39-infra-finalization-r1/ASSESSMENT.md"
|
||||
}
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
---
|
||||
id: KEY-WP-0039
|
||||
type: workplan
|
||||
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-10"
|
||||
updated: "2026-10-17"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- railiance-platform
|
||||
- secrets-engine
|
||||
state_hub_workstream_id: "09c3bddf-6058-5603-8488-cccdb7be4f86"
|
||||
---
|
||||
|
||||
# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role
|
||||
|
||||
## Why
|
||||
|
||||
CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role
|
||||
`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong
|
||||
`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT
|
||||
and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine
|
||||
`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked
|
||||
whether KeyCape can issue the negative tokens without touching the
|
||||
`secrets-engine` registration.
|
||||
|
||||
KeyCape signs only what a registration declares. It has no way to mint a token
|
||||
with arbitrary claims, and adding one would create a forging path in the issuer.
|
||||
So the negative tokens must come from real, separate registrations that differ
|
||||
from `secrets-engine` in exactly one property each.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- The `secrets-engine` registration is not changed.
|
||||
- Probe clients are confidential `client_credentials` clients with the minimum
|
||||
60-second lifetime, scopes that grant nothing, and secrets minted into custody
|
||||
like any other client. They are removed after the proof.
|
||||
- The probe script prints only accept or refuse booleans per case, never tokens or
|
||||
secrets. It runs attended.
|
||||
|
||||
## T01 — Define the probe registrations
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"
|
||||
```
|
||||
|
||||
Write a non-secret request packet under `docs/` with one probe client per claim
|
||||
mismatch, each identical to `secrets-engine` except for:
|
||||
|
||||
- `aud`: a different access-token resource audience
|
||||
- `sub`: a different service subject
|
||||
- `tenant`: a tenant other than `platform`
|
||||
- `roles`: a role other than `secrets-engine`
|
||||
- `scope`: a scope outside the role's bound scope
|
||||
|
||||
Two cases need no probe client. A service token always carries
|
||||
`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong
|
||||
`principal_type` case uses a human access token from an attended login. The
|
||||
coding-agent case uses the existing `codex-railiance-platform` registration (role
|
||||
`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live;
|
||||
T01 confirms that.
|
||||
|
||||
The expired-JWT case reuses any probe client: request a token, wait past its
|
||||
60-second lifetime, then present it.
|
||||
|
||||
Acceptance: railiance-platform agrees the packet covers every refusal that
|
||||
CCR-2026-0038 names.
|
||||
|
||||
2026-10-10: railiance-platform agreed that the case list covers every
|
||||
CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no
|
||||
provider fallback) are theirs and secrets-engine's. Packet written at
|
||||
`docs/openbao-negative-probe-clients.yaml`. It departs from the agreed list in two
|
||||
ways, both sent back for re-agreement:
|
||||
|
||||
- **Expired case.** A token from another probe already fails a claim, so its
|
||||
refusal does not show that expiry was checked. It needs a sixth client,
|
||||
`openbao-probe-expiry`, identical to `secrets-engine-openbao` in every claim. That
|
||||
makes it a second credential able to log in as secrets-engine for the session.
|
||||
OpenBao's `expiration_leeway` (150s by default) also extends the wait.
|
||||
- **Audience pin.** A service token's `aud` is its `clientId` unless `audience` is
|
||||
set. Every probe except the aud probe therefore pins
|
||||
`audience: secrets-engine-openbao`, so it fails only on its own claim.
|
||||
|
||||
Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's
|
||||
environment, because KeyCape resolves `secretRef` at startup. The packet uses a
|
||||
transient Kubernetes Secret that is deleted in the same session.
|
||||
|
||||
## T02 — Register the probe clients, run the attended probe, then remove them
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
|
||||
blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved."
|
||||
priority: medium
|
||||
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
|
||||
```
|
||||
|
||||
2026-10-11: requalified — live registration go caafe4be was already resolved; owner authorization present. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.
|
||||
|
||||
Write the attended script, which prints booleans only.
|
||||
|
||||
2026-10-10: script written at `scripts/openbao_negative_probe.py`, with tests in
|
||||
`scripts/test_openbao_negative_probe.py`. It meets condition (b): one refused
|
||||
boolean per case plus the refusal class (`claim_mismatch`, `expired`,
|
||||
`not_service_principal`), and no token, secret, claim value or OpenBao error text.
|
||||
A transport failure or an unexpected response is reported as `inconclusive`, never
|
||||
as a refusal. A token that OpenBao accepts is revoked at once and fails the run.
|
||||
Still waiting: railiance-platform's re-agreement on the two departures above, and
|
||||
an attended registry session with net-kingdom. Register the clients
|
||||
through the normal registration path, hand the run to railiance-platform, record
|
||||
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.
|
||||
|
||||
Acceptance: every case is refused, the receipt is committed, and the probe clients
|
||||
are gone.
|
||||
|
||||
|
||||
2026-10-11 source preparation: T01 remains progress because the exact expiry
|
||||
client and audience-pin amendments require platform-owner re-agreement; the
|
||||
prior agreement covered the earlier case list, not this revised packet. The
|
||||
prepared runner revision adds a valid expiry positive control and immediate
|
||||
revoke, exact decoded claim-isolation preflight (not signature evidence),
|
||||
semantic refusal classes, bounded expiry TTL and redirect refusal. Seventeen
|
||||
local tests pass; original-source semantic regressions demonstrate two false
|
||||
successes. See `docs/openbao-negative-probe-session-proposal.md` for exact owner
|
||||
returns, live authority, effective leeway, custody boundaries and cleanup. No
|
||||
live token, registry or OpenBao action occurred. T02 continues to wait for owner
|
||||
re-agreement plus separate attended live authorization. MASON-WP-0009-T05
|
||||
operator/ESO capability and nonmember checks remain independent acceptance.
|
||||
|
||||
2026-10-11: Platform independently re-agreed the final amended design (session proposal SHA25623f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09). Original conditional review and narrow confirmation are retained in railiance-platform/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review*. Source acceptance T01 is complete; actual registry/leeway/readiness, founder live go, negative/cleanup and separate capability/no-fallback obligations remain T02 and owner plans.
|
||||
|
||||
|
||||
## KeyCape issuer-source compatibility inspection for lifecycle v2
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "54d37ad8-2352-51c2-bb2e-af6070dbbd73"
|
||||
```
|
||||
|
||||
2026-10-11: Completed KeyCape's own issuer-source inspection for
|
||||
KEY-WP-0039-T02/attended-lifecycle-v2, exact manifest SHA256
|
||||
6fc0fe47f99a97c11c6e7538f5ea02054335df7d46055d3f84bed4e194aeaaed.
|
||||
Read AGENTS.md, governing INTENT.md, current client-credentials claim source,
|
||||
registration fragments, native caller-authentication commands and frozen v2
|
||||
issuer transport/probe wiring. Seventeen unchanged runner tests pass locally.
|
||||
This task completes only issuer-source inspection; the resulting narrow
|
||||
source acknowledgment is in docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json (retained frozen prior acknowledgment).
|
||||
T01 stays done and T02 stays open for actual native registration, token input,
|
||||
refusal and cleanup proof. No Secret, token, credential or native environment was accessed; no owner
|
||||
checkout was changed during this source inspection. Owner source files were
|
||||
read for the bounded issuer compatibility check.
|
||||
|
||||
Current token.go:306-309 returns HTTP 401 invalid_profile_usage with
|
||||
feature=client_id for a removed confidential client; wrong-secret
|
||||
feature=Authorization cannot count as removed-registration evidence. The
|
||||
proposal and packet comments are narrowly corrected against that current
|
||||
issuer contract. Prior T01/source re-agreement history is preserved; this source
|
||||
acknowledgment supplies no NetKingdom runtime-owner review or founder live go.
|
||||
|
||||
The private token-producer handoff remains a real execution gap under T02.
|
||||
Existing keycape login/service-token commands require a new private output
|
||||
file; login also needs a concrete admitted public PKCE client/callback and
|
||||
attended browser/MFA. The lifecycle expects an in-memory private stdin object
|
||||
containing human_token and coding_agent_token, but no reviewed file-to-process
|
||||
or process-to-process producer, admitted human client/callback, custody-injected
|
||||
coding-agent source, native placement or producer cleanup receipt has been
|
||||
provided. No unattended token producer or admitted secret route is invented.
|
||||
Resolve those exact owner contracts before founder native approval; existing
|
||||
static registration is not live token custody or producer admission.
|
||||
|
||||
|
||||
2026-10-11 same T03 source-inspection refinement for lifecycle V4: completed
|
||||
KeyCape issuer-facing inspection before the new formal acknowledgment in
|
||||
docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json. Exact V4
|
||||
manifest SHA256 d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8,
|
||||
executor b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d.
|
||||
NetKingdom's independent V4 source review is approved, receipt SHA256
|
||||
4f4498203a31bd46dbc3715c0df1cbb9eee63bed5b2c1baf430ff3c4a37c13cb.
|
||||
The V2/V3 unsuccessful reviews and earlier KeyCape acknowledgment remain
|
||||
retained as one correction trial; they are not extra scored successes.
|
||||
|
||||
V4 retains the unchanged issuer runner and six-client claim design, and fixes
|
||||
finite/nonboolean windows, true total request deadlines, late-known OpenBao
|
||||
session private revoke/refusal and guarded bounded final Secret deletion.
|
||||
The issuer-facing acknowledgement covers only stopping late token exchanges,
|
||||
claim source/shape and preserving token cleanup failure/unknown outcomes; it
|
||||
does not replace NetKingdom runtime or Platform backend acceptance. Tests: 41
|
||||
supplied lifecycle, 17 unchanged runner and 17 independent owner checks pass
|
||||
locally. No native tokens, credentials, issuer mutations or live cleanup were
|
||||
performed. T01 stays done; T02 stays open and the genuine private token-producer
|
||||
handoff remains its next source prerequisite before actual founder native go.
|
||||
|
||||
|
||||
2026-10-11 same T03 source-inspection refinement for lifecycle V5: completed
|
||||
KeyCape's own current issuer-source/claim/cleanup-contract inspection before
|
||||
any new formal acknowledgment. Current token.go:306-309 still distinguishes
|
||||
removed-client invalid_profile_usage feature=client_id from wrong-secret
|
||||
feature=Authorization. The six-client design, audience source and expiry
|
||||
positive control remain unchanged, and the original runner's 17 tests pass.
|
||||
Exact V5 manifest SHA256
|
||||
d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285;
|
||||
executor 3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360.
|
||||
V5 has 46 passing supplied lifecycle checks. It adds a single exceptional
|
||||
response-close abort under a fixed20ms cap clipped to nonresettable aggregate
|
||||
cleanup, and to positive original request time remaining, with expired aggregate
|
||||
zero calls, fixed private errors and truthful known-session revoke/unknown
|
||||
outcomes. This inspection covers issuer-facing token lifetime and claim
|
||||
interfaces only; NetKingdom and Platform independently own their runtime and
|
||||
backend review decisions. Previous V2/V3 Net source refusals and Platform V4
|
||||
exceptional-close refusal remain retained; KeyCape's V2/V4 acknowledgments are
|
||||
historical narrow source inspections, never aggregate native approval.
|
||||
T01 remains done, T02 open. No native actions or credentials occurred. The real
|
||||
private token-producer/client/callback/coding-agent custody/cleanup handoff gap
|
||||
stays the next prerequisite; no unattended admission is inferred.
|
||||
|
||||
Exact independent V5 source decisions were then verified: NetKingdom approved
|
||||
receipt SHA256 32a62bd1ca1bd7311965d016d4d6a6e4d9177f3423c63add436d9d309865eacc
|
||||
(46 supplied +17 runner +21 independent checks); Platform approved receipt
|
||||
SHA256 ba872dc53061b1dcca9dea1e8897597c394c1f9365e0fc49ee6f2095f57f11e0
|
||||
(46 supplied +5 independent +17 previously verified runner checks). Those are
|
||||
foreign owners' own source decisions, not KeyCape runtime/backend reviews or
|
||||
founder native authority. Formal KeyCape bounded issuer acknowledgment follows
|
||||
in docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json.
|
||||
|
||||
|
||||
2026-10-11 T02 continuing source preparation: private producer decision packet
|
||||
`KEY-WP-0039-T02/private-producer-handoff-v1` stages supported existing CLI
|
||||
flows, selected existing netkingdom-bootstrap-console literal-loopback human
|
||||
client subject to owner purpose/MFA/listener admission, and the existing
|
||||
codex-railiance-platform source identity subject to actual live/custody/consumer
|
||||
admission. No existing coding-agent delivery receipt was found; AppRole tokens
|
||||
do not substitute for issuer JWTs. A bounded pipe-only adapter for the current
|
||||
CLI's two caller-owned private files has 18 passing synthetic checks, with
|
||||
identity-guarded explicit file consumption and fixed value-free failure output.
|
||||
Decoded claim shape does not prove signatures or authenticated admission.
|
||||
See docs/openbao-negative-token-producer-decision.md and .json for exact owner
|
||||
choices and missing receipts. The exact trusted workstation-to-node launcher,
|
||||
private-file allowance and railiance01 Bao route/public-health guard still need
|
||||
source review and founder disposition before any token/temporary credential/
|
||||
mutation/restart. No registration, custody, login, native action or credential
|
||||
request occurred. V5 and failed review histories retained; T01 stays done and
|
||||
T02 open. No new work record or native acceptance is inferred.
|
||||
|
||||
|
||||
2026-10-11 same T02 source-preparation correction for
|
||||
KEY-WP-0039-T02/private-producer-handoff-v2: NetKingdom's exact V1 consumer
|
||||
refusal remains recorded (no child-ready gate, stdio alias, non-atomic unlink).
|
||||
V2 has source-pinned private launch/ready handshake compatible with exact
|
||||
Net V9 manifest84542ddc: fresh nonce/source/envelope/health/window marker before
|
||||
ANY caller-token file load/consume/write; separate local data/control pipes
|
||||
reject kernel identity aliases of stdin/stdout/stderr. Adapter read/write budget
|
||||
only clips downward after readiness. 31 distinct producer/launcher synthetic
|
||||
checks and 74 compatible consumer supplied checks pass. No SSH/native/login or
|
||||
credential access was performed. Stat/unlink remains non-atomic and requires
|
||||
continuously exclusive sameUID custody; the retained race test is a custody
|
||||
violation counterexample, not a claim of repaired filesystem atomicity. Receipt
|
||||
keeps atomic delete/copy cleanup unverified and possible copies true. Exact
|
||||
trusted launcher/directSSH profile/private tmpfs allowance, selected human
|
||||
client/MFA purpose reuse and coding-agent registration/custody/delivery remain
|
||||
unadmitted owner choices. Native node route, role/leeway/window/founder decision
|
||||
and actual negative/cleanup acceptance remain pending. T01done and T02open; no
|
||||
new task or plan, and V1/V5-V8/current failure histories are not overwritten.
|
||||
|
||||
|
||||
2026-10-11 same T02 producer-preparation correction V3: Platform V2 pair
|
||||
source refusal RPF-KEY39-PAIR-V2-F01 is retained. Private write uncertainty
|
||||
now propagates into sticky remote consumer uncertainty before launcher early
|
||||
return; local SSH termination never proves remote no-effects/recovery. Actual
|
||||
full-payload then interrupted confirmation and4096-byte partialpipe failures
|
||||
are retained regressions. 33 distinct source checks pass. Exact Net V9 source
|
||||
bytes unchanged; new source reviews required, not implicit repin. Native
|
||||
client/MFA/coding-agent custody/transport/tmpfsfile and actualroute/session
|
||||
authority remain unadmitted. T01done/T02open; no new task/plan or native action.
|
||||
|
||||
|
||||
2026-10-11 same T02 producer-preparation V4: exact V3 Net consumer refusal
|
||||
NET-PRODUCER-V3-F01 retained. Actual CLI contract acquisition now bounded
|
||||
nofollow/nonblocking regular stable reads and one ORIGINAL5-second preparation
|
||||
budget across contract/source/envelope/SSHspawn/ready/file/delivery phases, not
|
||||
a renewed launcher allowance. 40 own source tests plus9 retained paired
|
||||
wrapper/child handshake regressions pass with synthetic/no native transport.
|
||||
V3stickyremoteunknown/full+partialdelivery regressions retained; Net V9 exact
|
||||
bytes unchanged. Prior issuer/custody/transport/file/route/readiness-Argo/role/
|
||||
maintenanceexception/founder choices remain unadmitted. No SSH/network/Secrets/
|
||||
credentials/native/owner checkout changes; no task/plan duplication. T01done
|
||||
and T02open; source correction is not native acceptance.
|
||||
|
||||
|
||||
2026-10-11 T02 source-design disposition checkpoint: founder accepted exact
|
||||
source memo `infd-20261011-key39-source-design-r1`, review
|
||||
`review-62626f54-0212-4684-9b45-0a7222d61957` version1 at `2026-10-11T17:19:01Z`.
|
||||
Disposition `disp-870f9c1b-0444-4216-ae7f-f5463d590e36`, approval
|
||||
`ca118afc-d746-4454-a5b9-9b387b120cd9`; authenticated person subject
|
||||
`uid=platform-root,ou=people,dc=netkingdom,dc=local`. Exact canonical public review URL:
|
||||
`https://decisions.coulomb.social/review?memo_id=review-62626f54-0212-4684-9b45-0a7222d61957`.
|
||||
Root's authenticated confirmed readback is
|
||||
`the-custodian/docs/evidence/2026-10-10-security-consolidation/key39-source-design-publication/founder-source-disposition-readback-r1.json`,
|
||||
SHA256 `453213194d605336e9c424976f1ac27691def39e225026f0a99628d1cfc97d1d`; observed
|
||||
`2026-10-11T17:30:47.080081+00:00`. Source commands SHA256
|
||||
`36e20813596f3ddbb86e01fd0b838867ece2f4916c5880305cf12cd61b567f89` and source inventory SHA256
|
||||
`aea196db9e513900d5e7165455d6e9c3dc68749a606dc219ec340ac595642cc0` bind the accepted source direction.
|
||||
|
||||
Acceptance covers recommended source preparation for existing bootstrap-console
|
||||
human-client purpose reuse, returning existing coding-agent custody admission or
|
||||
preparing a narrow owner proposal, proposed founder directSSH source profile,
|
||||
and conditional exclusive-owner tmpfs files. It does not establish actual
|
||||
registration/MFA/listener, coding-agent custody/delivery, transport/file admission,
|
||||
node-local Bao route, issuer/OpenBao role/leeway/maintenance readiness or fresh
|
||||
attended native go. The published choice documents remain historical exact
|
||||
pending proposals; this appended receipt records their later source disposition.
|
||||
T02 remains wait on external owner custody/admission and separate attended
|
||||
execution authorization. Source acceptance permits further source preparation;
|
||||
actual registry, token exchange, negative probe, revoke and cleanup remain pending. T01 and T03
|
||||
remain done. No native or credential action is authorized by this source receipt.
|
||||
See docs/proposals/2026-10-11-key39-next-execution-prerequisites.json for exact
|
||||
owner returns and safe metadata evidence needed before preparing a runnable
|
||||
attended execution proposal.
|
||||
|
||||
2026-10-11 ordered-goal qualification (T02 only): the final source runner already
|
||||
exists and its 17 local semantic tests pass. No new helper or source protocol is
|
||||
needed to repeat accepted preparation. Native execution follows the first
|
||||
Forgejo SecurityCase registration and claims proof (KEY-WP-0038-T02/T03);
|
||||
only those task metadata were checked, not wait-task bodies. NetKingdom owns the
|
||||
registry/env/restart session, KeyCape owns issuance/isolation, and Platform owns
|
||||
leeway/refusal/capability/no-fallback/cleanup proof. The live registration go is
|
||||
already recorded resolved; do not ask again. Attended human authentication and
|
||||
owner-held existing coding-agent custody go on the single attended checklist.
|
||||
No credentials, live registry/API changes or KEY-WP-0040 recovery work occurred.
|
||||
|
||||
2026-10-17 Platform owner returns (source-prepared, not admitted):
|
||||
railiance-platform `docs/proposals/2026-10-17-key39-t02-owner-returns.md`
|
||||
(commit `0a9617a`). The coding client `codex-railiance-platform` has no
|
||||
admission, custody or delivery record. The existing narrow proposal
|
||||
`docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json` (`9460f6f`)
|
||||
remains the one to admit, so the coding-agent case stays unrunnable until owners
|
||||
admit it. The role source (`openbao/auth/secrets-engine-jwt-role.json`) matches
|
||||
the packet's claim design. It declares no `expiration_leeway`, so the expiry
|
||||
probe must assume OpenBao's 150s default until an attended sanitized read
|
||||
confirms the live value. The node-local Bao origin is not declared in source and
|
||||
also needs that attended read. T02 stays wait.
|
||||
|
|
@ -0,0 +1,364 @@
|
|||
---
|
||||
id: KEY-WP-0039
|
||||
type: workplan
|
||||
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-10"
|
||||
updated: "2026-10-17"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- railiance-platform
|
||||
- secrets-engine
|
||||
state_hub_workstream_id: "09c3bddf-6058-5603-8488-cccdb7be4f86"
|
||||
---
|
||||
|
||||
# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role
|
||||
|
||||
## Why
|
||||
|
||||
CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role
|
||||
`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong
|
||||
`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT
|
||||
and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine
|
||||
`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked
|
||||
whether KeyCape can issue the negative tokens without touching the
|
||||
`secrets-engine` registration.
|
||||
|
||||
KeyCape signs only what a registration declares. It has no way to mint a token
|
||||
with arbitrary claims, and adding one would create a forging path in the issuer.
|
||||
So the negative tokens must come from real, separate registrations that differ
|
||||
from `secrets-engine` in exactly one property each.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- The `secrets-engine` registration is not changed.
|
||||
- Probe clients are confidential `client_credentials` clients with the minimum
|
||||
60-second lifetime, scopes that grant nothing, and secrets minted into custody
|
||||
like any other client. They are removed after the proof.
|
||||
- The probe script prints only accept or refuse booleans per case, never tokens or
|
||||
secrets. It runs attended.
|
||||
|
||||
## T01 — Define the probe registrations
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"
|
||||
```
|
||||
|
||||
Write a non-secret request packet under `docs/` with one probe client per claim
|
||||
mismatch, each identical to `secrets-engine` except for:
|
||||
|
||||
- `aud`: a different access-token resource audience
|
||||
- `sub`: a different service subject
|
||||
- `tenant`: a tenant other than `platform`
|
||||
- `roles`: a role other than `secrets-engine`
|
||||
- `scope`: a scope outside the role's bound scope
|
||||
|
||||
Two cases need no probe client. A service token always carries
|
||||
`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong
|
||||
`principal_type` case uses a human access token from an attended login. The
|
||||
coding-agent case uses the existing `codex-railiance-platform` registration (role
|
||||
`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live;
|
||||
T01 confirms that.
|
||||
|
||||
The expired-JWT case reuses any probe client: request a token, wait past its
|
||||
60-second lifetime, then present it.
|
||||
|
||||
Acceptance: railiance-platform agrees the packet covers every refusal that
|
||||
CCR-2026-0038 names.
|
||||
|
||||
2026-10-10: railiance-platform agreed that the case list covers every
|
||||
CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no
|
||||
provider fallback) are theirs and secrets-engine's. Packet written at
|
||||
`docs/openbao-negative-probe-clients.yaml`. It departs from the agreed list in two
|
||||
ways, both sent back for re-agreement:
|
||||
|
||||
- **Expired case.** A token from another probe already fails a claim, so its
|
||||
refusal does not show that expiry was checked. It needs a sixth client,
|
||||
`openbao-probe-expiry`, identical to `secrets-engine-openbao` in every claim. That
|
||||
makes it a second credential able to log in as secrets-engine for the session.
|
||||
OpenBao's `expiration_leeway` (150s by default) also extends the wait.
|
||||
- **Audience pin.** A service token's `aud` is its `clientId` unless `audience` is
|
||||
set. Every probe except the aud probe therefore pins
|
||||
`audience: secrets-engine-openbao`, so it fails only on its own claim.
|
||||
|
||||
Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's
|
||||
environment, because KeyCape resolves `secretRef` at startup. The packet uses a
|
||||
transient Kubernetes Secret that is deleted in the same session.
|
||||
|
||||
## T02 — Register the probe clients, run the attended probe, then remove them
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T02
|
||||
status: wait
|
||||
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
|
||||
blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved."
|
||||
priority: medium
|
||||
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
|
||||
```
|
||||
|
||||
2026-10-11: requalified — live registration go caafe4be was already resolved; owner authorization present. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.
|
||||
|
||||
Write the attended script, which prints booleans only.
|
||||
|
||||
2026-10-10: script written at `scripts/openbao_negative_probe.py`, with tests in
|
||||
`scripts/test_openbao_negative_probe.py`. It meets condition (b): one refused
|
||||
boolean per case plus the refusal class (`claim_mismatch`, `expired`,
|
||||
`not_service_principal`), and no token, secret, claim value or OpenBao error text.
|
||||
A transport failure or an unexpected response is reported as `inconclusive`, never
|
||||
as a refusal. A token that OpenBao accepts is revoked at once and fails the run.
|
||||
Still waiting: railiance-platform's re-agreement on the two departures above, and
|
||||
an attended registry session with net-kingdom. Register the clients
|
||||
through the normal registration path, hand the run to railiance-platform, record
|
||||
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.
|
||||
|
||||
Acceptance: every case is refused, the receipt is committed, and the probe clients
|
||||
are gone.
|
||||
|
||||
|
||||
2026-10-11 source preparation: T01 remains progress because the exact expiry
|
||||
client and audience-pin amendments require platform-owner re-agreement; the
|
||||
prior agreement covered the earlier case list, not this revised packet. The
|
||||
prepared runner revision adds a valid expiry positive control and immediate
|
||||
revoke, exact decoded claim-isolation preflight (not signature evidence),
|
||||
semantic refusal classes, bounded expiry TTL and redirect refusal. Seventeen
|
||||
local tests pass; original-source semantic regressions demonstrate two false
|
||||
successes. See `docs/openbao-negative-probe-session-proposal.md` for exact owner
|
||||
returns, live authority, effective leeway, custody boundaries and cleanup. No
|
||||
live token, registry or OpenBao action occurred. T02 continues to wait for owner
|
||||
re-agreement plus separate attended live authorization. MASON-WP-0009-T05
|
||||
operator/ESO capability and nonmember checks remain independent acceptance.
|
||||
|
||||
2026-10-11: Platform independently re-agreed the final amended design (session proposal SHA25623f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09). Original conditional review and narrow confirmation are retained in railiance-platform/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review*. Source acceptance T01 is complete; actual registry/leeway/readiness, founder live go, negative/cleanup and separate capability/no-fallback obligations remain T02 and owner plans.
|
||||
|
||||
|
||||
2026-10-17 Custodian coordinator finalization review (not a KeyCape formal owner
|
||||
acknowledgment): the [infrastructure assessment](../../the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/ASSESSMENT.md)
|
||||
confirms all 14 executable/test/producer-decision pins and three Platform source
|
||||
pins still match. Source reviews remain complete: T01/T03 done, T02 wait. Existing
|
||||
source acceptance, resolved registration go caafe4be and the standing attended
|
||||
rule remain valid; no new protocol or blanket approval request is needed. The
|
||||
remaining sequence is KEY-WP-0038-T02/T03 first-case registration/claims proof,
|
||||
admission of the existing coding-client proposal with sanitized owner readiness
|
||||
returns, then one attended negative/revoke/cleanup receipt committed with probe
|
||||
clients removed. No native acceptance or credential/registration action is
|
||||
inferred from this coordinator review.
|
||||
|
||||
|
||||
## KeyCape issuer-source compatibility inspection for lifecycle v2
|
||||
|
||||
```task
|
||||
id: KEY-WP-0039-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "54d37ad8-2352-51c2-bb2e-af6070dbbd73"
|
||||
```
|
||||
|
||||
2026-10-11: Completed KeyCape's own issuer-source inspection for
|
||||
KEY-WP-0039-T02/attended-lifecycle-v2, exact manifest SHA256
|
||||
6fc0fe47f99a97c11c6e7538f5ea02054335df7d46055d3f84bed4e194aeaaed.
|
||||
Read AGENTS.md, governing INTENT.md, current client-credentials claim source,
|
||||
registration fragments, native caller-authentication commands and frozen v2
|
||||
issuer transport/probe wiring. Seventeen unchanged runner tests pass locally.
|
||||
This task completes only issuer-source inspection; the resulting narrow
|
||||
source acknowledgment is in docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json (retained frozen prior acknowledgment).
|
||||
T01 stays done and T02 stays open for actual native registration, token input,
|
||||
refusal and cleanup proof. No Secret, token, credential or native environment was accessed; no owner
|
||||
checkout was changed during this source inspection. Owner source files were
|
||||
read for the bounded issuer compatibility check.
|
||||
|
||||
Current token.go:306-309 returns HTTP 401 invalid_profile_usage with
|
||||
feature=client_id for a removed confidential client; wrong-secret
|
||||
feature=Authorization cannot count as removed-registration evidence. The
|
||||
proposal and packet comments are narrowly corrected against that current
|
||||
issuer contract. Prior T01/source re-agreement history is preserved; this source
|
||||
acknowledgment supplies no NetKingdom runtime-owner review or founder live go.
|
||||
|
||||
The private token-producer handoff remains a real execution gap under T02.
|
||||
Existing keycape login/service-token commands require a new private output
|
||||
file; login also needs a concrete admitted public PKCE client/callback and
|
||||
attended browser/MFA. The lifecycle expects an in-memory private stdin object
|
||||
containing human_token and coding_agent_token, but no reviewed file-to-process
|
||||
or process-to-process producer, admitted human client/callback, custody-injected
|
||||
coding-agent source, native placement or producer cleanup receipt has been
|
||||
provided. No unattended token producer or admitted secret route is invented.
|
||||
Resolve those exact owner contracts before founder native approval; existing
|
||||
static registration is not live token custody or producer admission.
|
||||
|
||||
|
||||
2026-10-11 same T03 source-inspection refinement for lifecycle V4: completed
|
||||
KeyCape issuer-facing inspection before the new formal acknowledgment in
|
||||
docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json. Exact V4
|
||||
manifest SHA256 d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8,
|
||||
executor b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d.
|
||||
NetKingdom's independent V4 source review is approved, receipt SHA256
|
||||
4f4498203a31bd46dbc3715c0df1cbb9eee63bed5b2c1baf430ff3c4a37c13cb.
|
||||
The V2/V3 unsuccessful reviews and earlier KeyCape acknowledgment remain
|
||||
retained as one correction trial; they are not extra scored successes.
|
||||
|
||||
V4 retains the unchanged issuer runner and six-client claim design, and fixes
|
||||
finite/nonboolean windows, true total request deadlines, late-known OpenBao
|
||||
session private revoke/refusal and guarded bounded final Secret deletion.
|
||||
The issuer-facing acknowledgement covers only stopping late token exchanges,
|
||||
claim source/shape and preserving token cleanup failure/unknown outcomes; it
|
||||
does not replace NetKingdom runtime or Platform backend acceptance. Tests: 41
|
||||
supplied lifecycle, 17 unchanged runner and 17 independent owner checks pass
|
||||
locally. No native tokens, credentials, issuer mutations or live cleanup were
|
||||
performed. T01 stays done; T02 stays open and the genuine private token-producer
|
||||
handoff remains its next source prerequisite before actual founder native go.
|
||||
|
||||
|
||||
2026-10-11 same T03 source-inspection refinement for lifecycle V5: completed
|
||||
KeyCape's own current issuer-source/claim/cleanup-contract inspection before
|
||||
any new formal acknowledgment. Current token.go:306-309 still distinguishes
|
||||
removed-client invalid_profile_usage feature=client_id from wrong-secret
|
||||
feature=Authorization. The six-client design, audience source and expiry
|
||||
positive control remain unchanged, and the original runner's 17 tests pass.
|
||||
Exact V5 manifest SHA256
|
||||
d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285;
|
||||
executor 3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360.
|
||||
V5 has 46 passing supplied lifecycle checks. It adds a single exceptional
|
||||
response-close abort under a fixed20ms cap clipped to nonresettable aggregate
|
||||
cleanup, and to positive original request time remaining, with expired aggregate
|
||||
zero calls, fixed private errors and truthful known-session revoke/unknown
|
||||
outcomes. This inspection covers issuer-facing token lifetime and claim
|
||||
interfaces only; NetKingdom and Platform independently own their runtime and
|
||||
backend review decisions. Previous V2/V3 Net source refusals and Platform V4
|
||||
exceptional-close refusal remain retained; KeyCape's V2/V4 acknowledgments are
|
||||
historical narrow source inspections, never aggregate native approval.
|
||||
T01 remains done, T02 open. No native actions or credentials occurred. The real
|
||||
private token-producer/client/callback/coding-agent custody/cleanup handoff gap
|
||||
stays the next prerequisite; no unattended admission is inferred.
|
||||
|
||||
Exact independent V5 source decisions were then verified: NetKingdom approved
|
||||
receipt SHA256 32a62bd1ca1bd7311965d016d4d6a6e4d9177f3423c63add436d9d309865eacc
|
||||
(46 supplied +17 runner +21 independent checks); Platform approved receipt
|
||||
SHA256 ba872dc53061b1dcca9dea1e8897597c394c1f9365e0fc49ee6f2095f57f11e0
|
||||
(46 supplied +5 independent +17 previously verified runner checks). Those are
|
||||
foreign owners' own source decisions, not KeyCape runtime/backend reviews or
|
||||
founder native authority. Formal KeyCape bounded issuer acknowledgment follows
|
||||
in docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json.
|
||||
|
||||
|
||||
2026-10-11 T02 continuing source preparation: private producer decision packet
|
||||
`KEY-WP-0039-T02/private-producer-handoff-v1` stages supported existing CLI
|
||||
flows, selected existing netkingdom-bootstrap-console literal-loopback human
|
||||
client subject to owner purpose/MFA/listener admission, and the existing
|
||||
codex-railiance-platform source identity subject to actual live/custody/consumer
|
||||
admission. No existing coding-agent delivery receipt was found; AppRole tokens
|
||||
do not substitute for issuer JWTs. A bounded pipe-only adapter for the current
|
||||
CLI's two caller-owned private files has 18 passing synthetic checks, with
|
||||
identity-guarded explicit file consumption and fixed value-free failure output.
|
||||
Decoded claim shape does not prove signatures or authenticated admission.
|
||||
See docs/openbao-negative-token-producer-decision.md and .json for exact owner
|
||||
choices and missing receipts. The exact trusted workstation-to-node launcher,
|
||||
private-file allowance and railiance01 Bao route/public-health guard still need
|
||||
source review and founder disposition before any token/temporary credential/
|
||||
mutation/restart. No registration, custody, login, native action or credential
|
||||
request occurred. V5 and failed review histories retained; T01 stays done and
|
||||
T02 open. No new work record or native acceptance is inferred.
|
||||
|
||||
|
||||
2026-10-11 same T02 source-preparation correction for
|
||||
KEY-WP-0039-T02/private-producer-handoff-v2: NetKingdom's exact V1 consumer
|
||||
refusal remains recorded (no child-ready gate, stdio alias, non-atomic unlink).
|
||||
V2 has source-pinned private launch/ready handshake compatible with exact
|
||||
Net V9 manifest84542ddc: fresh nonce/source/envelope/health/window marker before
|
||||
ANY caller-token file load/consume/write; separate local data/control pipes
|
||||
reject kernel identity aliases of stdin/stdout/stderr. Adapter read/write budget
|
||||
only clips downward after readiness. 31 distinct producer/launcher synthetic
|
||||
checks and 74 compatible consumer supplied checks pass. No SSH/native/login or
|
||||
credential access was performed. Stat/unlink remains non-atomic and requires
|
||||
continuously exclusive sameUID custody; the retained race test is a custody
|
||||
violation counterexample, not a claim of repaired filesystem atomicity. Receipt
|
||||
keeps atomic delete/copy cleanup unverified and possible copies true. Exact
|
||||
trusted launcher/directSSH profile/private tmpfs allowance, selected human
|
||||
client/MFA purpose reuse and coding-agent registration/custody/delivery remain
|
||||
unadmitted owner choices. Native node route, role/leeway/window/founder decision
|
||||
and actual negative/cleanup acceptance remain pending. T01done and T02open; no
|
||||
new task or plan, and V1/V5-V8/current failure histories are not overwritten.
|
||||
|
||||
|
||||
2026-10-11 same T02 producer-preparation correction V3: Platform V2 pair
|
||||
source refusal RPF-KEY39-PAIR-V2-F01 is retained. Private write uncertainty
|
||||
now propagates into sticky remote consumer uncertainty before launcher early
|
||||
return; local SSH termination never proves remote no-effects/recovery. Actual
|
||||
full-payload then interrupted confirmation and4096-byte partialpipe failures
|
||||
are retained regressions. 33 distinct source checks pass. Exact Net V9 source
|
||||
bytes unchanged; new source reviews required, not implicit repin. Native
|
||||
client/MFA/coding-agent custody/transport/tmpfsfile and actualroute/session
|
||||
authority remain unadmitted. T01done/T02open; no new task/plan or native action.
|
||||
|
||||
|
||||
2026-10-11 same T02 producer-preparation V4: exact V3 Net consumer refusal
|
||||
NET-PRODUCER-V3-F01 retained. Actual CLI contract acquisition now bounded
|
||||
nofollow/nonblocking regular stable reads and one ORIGINAL5-second preparation
|
||||
budget across contract/source/envelope/SSHspawn/ready/file/delivery phases, not
|
||||
a renewed launcher allowance. 40 own source tests plus9 retained paired
|
||||
wrapper/child handshake regressions pass with synthetic/no native transport.
|
||||
V3stickyremoteunknown/full+partialdelivery regressions retained; Net V9 exact
|
||||
bytes unchanged. Prior issuer/custody/transport/file/route/readiness-Argo/role/
|
||||
maintenanceexception/founder choices remain unadmitted. No SSH/network/Secrets/
|
||||
credentials/native/owner checkout changes; no task/plan duplication. T01done
|
||||
and T02open; source correction is not native acceptance.
|
||||
|
||||
|
||||
2026-10-11 T02 source-design disposition checkpoint: founder accepted exact
|
||||
source memo `infd-20261011-key39-source-design-r1`, review
|
||||
`review-62626f54-0212-4684-9b45-0a7222d61957` version1 at `2026-10-11T17:19:01Z`.
|
||||
Disposition `disp-870f9c1b-0444-4216-ae7f-f5463d590e36`, approval
|
||||
`ca118afc-d746-4454-a5b9-9b387b120cd9`; authenticated person subject
|
||||
`uid=platform-root,ou=people,dc=netkingdom,dc=local`. Exact canonical public review URL:
|
||||
`https://decisions.coulomb.social/review?memo_id=review-62626f54-0212-4684-9b45-0a7222d61957`.
|
||||
Root's authenticated confirmed readback is
|
||||
`the-custodian/docs/evidence/2026-10-10-security-consolidation/key39-source-design-publication/founder-source-disposition-readback-r1.json`,
|
||||
SHA256 `453213194d605336e9c424976f1ac27691def39e225026f0a99628d1cfc97d1d`; observed
|
||||
`2026-10-11T17:30:47.080081+00:00`. Source commands SHA256
|
||||
`36e20813596f3ddbb86e01fd0b838867ece2f4916c5880305cf12cd61b567f89` and source inventory SHA256
|
||||
`aea196db9e513900d5e7165455d6e9c3dc68749a606dc219ec340ac595642cc0` bind the accepted source direction.
|
||||
|
||||
Acceptance covers recommended source preparation for existing bootstrap-console
|
||||
human-client purpose reuse, returning existing coding-agent custody admission or
|
||||
preparing a narrow owner proposal, proposed founder directSSH source profile,
|
||||
and conditional exclusive-owner tmpfs files. It does not establish actual
|
||||
registration/MFA/listener, coding-agent custody/delivery, transport/file admission,
|
||||
node-local Bao route, issuer/OpenBao role/leeway/maintenance readiness or fresh
|
||||
attended native go. The published choice documents remain historical exact
|
||||
pending proposals; this appended receipt records their later source disposition.
|
||||
T02 remains wait on external owner custody/admission and separate attended
|
||||
execution authorization. Source acceptance permits further source preparation;
|
||||
actual registry, token exchange, negative probe, revoke and cleanup remain pending. T01 and T03
|
||||
remain done. No native or credential action is authorized by this source receipt.
|
||||
See docs/proposals/2026-10-11-key39-next-execution-prerequisites.json for exact
|
||||
owner returns and safe metadata evidence needed before preparing a runnable
|
||||
attended execution proposal.
|
||||
|
||||
2026-10-11 ordered-goal qualification (T02 only): the final source runner already
|
||||
exists and its 17 local semantic tests pass. No new helper or source protocol is
|
||||
needed to repeat accepted preparation. Native execution follows the first
|
||||
Forgejo SecurityCase registration and claims proof (KEY-WP-0038-T02/T03);
|
||||
only those task metadata were checked, not wait-task bodies. NetKingdom owns the
|
||||
registry/env/restart session, KeyCape owns issuance/isolation, and Platform owns
|
||||
leeway/refusal/capability/no-fallback/cleanup proof. The live registration go is
|
||||
already recorded resolved; do not ask again. Attended human authentication and
|
||||
owner-held existing coding-agent custody go on the single attended checklist.
|
||||
No credentials, live registry/API changes or KEY-WP-0040 recovery work occurred.
|
||||
|
||||
2026-10-17 Platform owner returns (source-prepared, not admitted):
|
||||
railiance-platform `docs/proposals/2026-10-17-key39-t02-owner-returns.md`
|
||||
(commit `0a9617a`). The coding client `codex-railiance-platform` has no
|
||||
admission, custody or delivery record. The existing narrow proposal
|
||||
`docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json` (`9460f6f`)
|
||||
remains the one to admit, so the coding-agent case stays unrunnable until owners
|
||||
admit it. The role source (`openbao/auth/secrets-engine-jwt-role.json`) matches
|
||||
the packet's claim design. It declares no `expiration_leeway`, so the expiry
|
||||
probe must assume OpenBao's 150s default until an attended sanitized read
|
||||
confirms the live value. The node-local Bao origin is not declared in source and
|
||||
also needs that attended read. T02 stays wait.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from pathlib import Path
|
||||
import hashlib,json,re,subprocess,yaml
|
||||
root=Path('/home/worsch/key-cape');out=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-finalization-owner-followthrough-r1');out.mkdir()
|
||||
rel='workplans/KEY-WP-0039-openbao-negative-probe-tokens.md';path=root/rel
|
||||
|
||||
def git(*args):return subprocess.check_output(['git','-C',str(root),*args]).decode()
|
||||
assert git('rev-parse','HEAD').strip()=='e76d4130f31a02554048f30bf838043cea7a5b80'
|
||||
assert not git('status','--porcelain=v1')
|
||||
assert not git('diff','--cached','--name-only')
|
||||
before=path.read_text();anchor='## KeyCape issuer-source compatibility inspection for lifecycle v2'
|
||||
assert before.count(anchor)==1
|
||||
paragraph='''2026-10-17 Custodian coordinator finalization review (not a KeyCape formal owner
|
||||
acknowledgment): the [infrastructure assessment](../../the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/ASSESSMENT.md)
|
||||
confirms all 14 executable/test/producer-decision pins and three Platform source
|
||||
pins still match. Source reviews remain complete: T01/T03 done, T02 wait. Existing
|
||||
source acceptance, resolved registration go caafe4be and the standing attended
|
||||
rule remain valid; no new protocol or blanket approval request is needed. The
|
||||
remaining sequence is KEY-WP-0038-T02/T03 first-case registration/claims proof,
|
||||
admission of the existing coding-client proposal with sanitized owner readiness
|
||||
returns, then one attended negative/revoke/cleanup receipt committed with probe
|
||||
clients removed. No native acceptance or credential/registration action is
|
||||
inferred from this coordinator review.
|
||||
|
||||
|
||||
'''
|
||||
after=before.replace(anchor,paragraph+anchor)
|
||||
blocks=lambda s:re.findall(r'^```task\n.*?^```',s,re.M|re.S)
|
||||
assert blocks(before)==blocks(after)
|
||||
assert before.split('---',2)[1]==after.split('---',2)[1]
|
||||
(out/'KEY39.preimage.md').write_text(before);(out/'KEY39.proposed.md').write_text(after)
|
||||
baseline={'base_commit':git('rev-parse','HEAD').strip(),'branch':git('branch','--show-current').strip(),'status':git('status','--porcelain=v1'),'index':git('ls-files','--stage'),'path':rel,'before_sha256':hashlib.sha256(before.encode()).hexdigest(),'after_sha256':hashlib.sha256(after.encode()).hexdigest(),'frontmatter':yaml.safe_load(before.split('---',2)[1]),'task_blocks':[yaml.safe_load(t.split('\n',1)[1].rsplit('```',1)[0]) for t in blocks(before)]}
|
||||
(out/'baseline.json').write_text(json.dumps(baseline,indent=2)+'\n')
|
||||
path.write_text(after)
|
||||
assert git('diff','--name-only').splitlines()==[rel]
|
||||
(out/'one-path.patch').write_text(git('diff','--',rel))
|
||||
print(json.dumps({'status':'one T02 narrative paragraph applied','task_blocks_unchanged':True,'frontmatter_unchanged':True,'base':baseline['base_commit'],'task_bindings':baseline['task_blocks']},indent=2))
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"schema": "state-hub.repository-projection-reconcile.v1",
|
||||
"instance_role": "primary",
|
||||
"instance_label": "railiance01",
|
||||
"expected_commit": "40de616e078906e04c692edf7cb7a8bfbc07b5ea",
|
||||
"derived_commit": "40de616e078906e04c692edf7cb7a8bfbc07b5ea",
|
||||
"outcome": {
|
||||
"schema": "state-hub.projection-reset.v1",
|
||||
"repo_slug": "key-cape",
|
||||
"commit": "40de616e078906e04c692edf7cb7a8bfbc07b5ea",
|
||||
"status": "applied",
|
||||
"counts": {
|
||||
"created": 0,
|
||||
"updated": 44,
|
||||
"retired": 0,
|
||||
"refused": 0,
|
||||
"released": 0,
|
||||
"created_tasks": 0,
|
||||
"updated_tasks": 0,
|
||||
"cancelled_tasks": 0,
|
||||
"created_edges": 0,
|
||||
"removed_edges": 0,
|
||||
"unresolved_edges": 1,
|
||||
"external_edges": 8
|
||||
},
|
||||
"created": [],
|
||||
"updated": [
|
||||
"ADHOC-2026-09-05",
|
||||
"ADHOC-2026-09-07",
|
||||
"ADHOC-2026-09-27",
|
||||
"KEY-WP-0001",
|
||||
"KEY-WP-0002",
|
||||
"KEY-WP-0003",
|
||||
"KEY-WP-0004",
|
||||
"KEY-WP-0005",
|
||||
"KEY-WP-0006",
|
||||
"KEY-WP-0007",
|
||||
"KEY-WP-0008",
|
||||
"KEY-WP-0009",
|
||||
"KEY-WP-0010",
|
||||
"KEY-WP-0011",
|
||||
"KEY-WP-0012",
|
||||
"KEY-WP-0013",
|
||||
"KEY-WP-0014",
|
||||
"KEY-WP-0015",
|
||||
"KEY-WP-0016",
|
||||
"KEY-WP-0017",
|
||||
"KEY-WP-0018",
|
||||
"KEY-WP-0019",
|
||||
"KEY-WP-0020",
|
||||
"KEY-WP-0021",
|
||||
"KEY-WP-0022",
|
||||
"KEY-WP-0023",
|
||||
"KEY-WP-0024",
|
||||
"KEY-WP-0025",
|
||||
"KEY-WP-0026",
|
||||
"KEY-WP-0027",
|
||||
"KEY-WP-0028",
|
||||
"KEY-WP-0029",
|
||||
"KEY-WP-0030",
|
||||
"KEY-WP-0031",
|
||||
"KEY-WP-0032",
|
||||
"KEY-WP-0033",
|
||||
"KEY-WP-0034",
|
||||
"KEY-WP-0035",
|
||||
"KEY-WP-0036",
|
||||
"KEY-WP-0037",
|
||||
"KEY-WP-0038",
|
||||
"KEY-WP-0039",
|
||||
"KEY-WP-0040",
|
||||
"KEY-WP-0041"
|
||||
],
|
||||
"retired": [],
|
||||
"released": [],
|
||||
"created_tasks": [],
|
||||
"updated_tasks": [],
|
||||
"cancelled_tasks": [],
|
||||
"created_edges": [],
|
||||
"removed_edges": [],
|
||||
"unresolved_edges": [
|
||||
{
|
||||
"workplan": "KEY-WP-0010",
|
||||
"from": "KEY-WP-0010",
|
||||
"target": "RAILIANCE-WP-0027",
|
||||
"reason": "missing"
|
||||
}
|
||||
],
|
||||
"external_edges": [
|
||||
{
|
||||
"workplan": "KEY-WP-0038",
|
||||
"from": "KEY-WP-0038-T03",
|
||||
"target": "KEY-WP-0038-T02",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
|
||||
"from_task_id": "40ddaa29-7245-508c-ad48-ddc063e25115",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0038",
|
||||
"from": "KEY-WP-0038-T02",
|
||||
"target": "KEY-WP-0038-T01",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
|
||||
"from_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "5f019d13-1fa3-5b88-a27a-75a3339ca96e"
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0038",
|
||||
"from": "KEY-WP-0038-T02",
|
||||
"target": "NK-WP-0048-T06",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
|
||||
"from_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0039",
|
||||
"from": "KEY-WP-0039-T02",
|
||||
"target": "KEY-WP-0038-T03",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "40ddaa29-7245-508c-ad48-ddc063e25115"
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0039",
|
||||
"from": "KEY-WP-0039-T02",
|
||||
"target": "KEY-WP-0039-T01",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "4d06b059-3768-53bb-83a4-a9a71c0850b8"
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0039",
|
||||
"from": "KEY-WP-0039-T02",
|
||||
"target": "KEY-WP-0038-T02",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
|
||||
"from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0007",
|
||||
"from": "KEY-WP-0007",
|
||||
"target": "KEY-WP-0005",
|
||||
"relationship_type": "blocks",
|
||||
"from_workplan_id": "24db4178-705c-5b09-b7db-ee7a345d71a0",
|
||||
"from_task_id": null,
|
||||
"to_workplan_id": "711a8303-69aa-5db6-9043-92530c6dc81b",
|
||||
"to_task_id": null
|
||||
},
|
||||
{
|
||||
"workplan": "KEY-WP-0009",
|
||||
"from": "KEY-WP-0009",
|
||||
"target": "KEY-WP-0006",
|
||||
"relationship_type": "blocks",
|
||||
"from_workplan_id": "c1a9b1cc-2ff0-566a-b544-1a2ef967fc0d",
|
||||
"from_task_id": null,
|
||||
"to_workplan_id": "d8d20d8f-61e0-54e2-9a88-a3958f0b6f2d",
|
||||
"to_task_id": null
|
||||
}
|
||||
],
|
||||
"refused": [],
|
||||
"notes": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
from pathlib import Path
|
||||
import hashlib,json,subprocess,urllib.request,urllib.error,yaml,re
|
||||
root=Path('/home/worsch/key-cape');out=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-finalization-owner-followthrough-r1');base='http://127.0.0.1:8000';head='40de616e078906e04c692edf7cb7a8bfbc07b5ea'
|
||||
def git(*args):return subprocess.check_output(['git','-C',str(root),*args]).decode()
|
||||
assert git('rev-parse','HEAD').strip()==head
|
||||
assert git('ls-remote','origin','refs/heads/main').split()[0]==head
|
||||
assert not git('status','--porcelain=v1')
|
||||
def get(path):
|
||||
with urllib.request.urlopen(base+path,timeout=30) as response:return json.load(response)
|
||||
health=get('/state/health');assert (health['instance_role'],health['instance_label'])==('primary','railiance01')
|
||||
request_path='/repos/key-cape/work-record-projection/reconcile';body={'expected_commit':head,'acknowledge_retirements':False}
|
||||
digest=hashlib.sha256(json.dumps({'method':'POST','path':request_path,'body':body},sort_keys=True,separators=(',',':')).encode()).hexdigest()
|
||||
request=urllib.request.Request(base+request_path,data=json.dumps(body).encode(),method='POST',headers={'Content-Type':'application/json','Idempotency-Key':'rmgr-projection:v2:'+digest,'X-StateHub-Source-Agent':'custodian-codex'})
|
||||
try:
|
||||
with urllib.request.urlopen(request,timeout=120) as response:receipt=json.load(response)
|
||||
except urllib.error.HTTPError as error:
|
||||
raw=error.read();(out/'canonical-receiver-refusal.json').write_bytes(raw);raise
|
||||
(out/'canonical-receiver.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
assert receipt['expected_commit']==receipt['derived_commit']==head
|
||||
assert (receipt['instance_role'],receipt['instance_label'])==('primary','railiance01')
|
||||
assert receipt['outcome']['status'] in ('applied','noop'),receipt['outcome']['status']
|
||||
snapshot=get('/repos/key-cape/work-record-projection/snapshot');baseline=json.loads((out/'baseline.json').read_text())
|
||||
planid=baseline['frontmatter']['state_hub_workstream_id'];plan=next(w for w in snapshot['workplans'] if w['id']==planid)
|
||||
rows=[]
|
||||
for block in baseline['task_blocks']:
|
||||
task=next(t for t in snapshot['tasks'] if t['id']==block['state_hub_task_id'])
|
||||
assert task['status']==block['status'];rows.append({'record_id':block['id'],'state_hub_task_id':task['id'],'status':task['status'],'workplan_id':task.get('workplan_id'),'depends_on_source':block.get('depends_on',[])})
|
||||
current=(root/baseline['path']).read_text();assert current==(out/'KEY39.proposed.md').read_text()
|
||||
assert re.findall(r'^```task\n.*?^```',current,re.M|re.S)==re.findall(r'^```task\n.*?^```',(out/'KEY39.preimage.md').read_text(),re.M|re.S)
|
||||
verification={'commit':head,'receiver_status':receipt['outcome']['status'],'acknowledge_retirements':False,'instance_role':receipt['instance_role'],'instance_label':receipt['instance_label'],'workplan':{'id':planid,'status':plan['status'],'record_id':'KEY-WP-0039'},'task_bindings':rows,'all_three_source_blocks_unchanged':True,'frontmatter_unchanged':True,'only_committed_path':baseline['path'],'remote_forge_matches':True,'owner_worktree_and_index_clean':not git('status','--porcelain=v1'),'normal_sync_refusal':'unrelated grandfathered ADHOC-2026-09-27-T02 UUID; not modified','native_acceptance_inferred':False}
|
||||
assert plan['status']==baseline['frontmatter']['status']
|
||||
(out/'final-verification.json').write_text(json.dumps(verification,indent=2)+'\n');print(json.dumps(verification,indent=2))
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — key-cape
|
||||
|
||||
**Domain:** infotech
|
||||
**Last synced:** 2026-10-17 02:16 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Negative-check tokens for the secrets-engine OpenBao JWT role
|
||||
Progress: 2/3 done | workplan_id: `09c3bddf-6058-5603-8488-cccdb7be4f86`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T01 — Define the probe registrations `03da4c38`
|
||||
*(wait: Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved.)*
|
||||
|
||||
### Forgejo as a NetKingdom OIDC client, so the forge can close
|
||||
Progress: 2/4 done | workplan_id: `098e3e2c-a138-560e-b2d3-3a15eb49d8a9`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T01 — Define what Forgejo receives `ddcfb482`
|
||||
*(wait: Registration runs as the first KnownSecurityCase of type oidc-browser-relying-party (NK-WP-0048-T06), not as a hand-built rollout.)*
|
||||
- ! T02 — Register the client and hand over its secret `40ddaa29`
|
||||
*(wait: Claims are verified against the registered client.)*
|
||||
|
||||
## Inbox Hygiene
|
||||
|
||||
**Stale unread:** 2 message(s) older than 3 day(s) — triage at session start.
|
||||
**Missing thread_id:** 2 unread message(s) lack supersession chains.
|
||||
|
||||
---
|
||||
## MCP Orientation (when available)
|
||||
|
||||
If the state-hub MCP server is reachable, call:
|
||||
`get_domain_summary("infotech")`
|
||||
This provides richer cross-domain context.
|
||||
If the MCP call fails, use this file as your orientation source.
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
# Work Records — key-cape
|
||||
|
||||
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
|
||||
> stage 3). Do not edit by hand — edit the source file/block listed for
|
||||
> each record and re-run fix-consistency to refresh this index. Archived
|
||||
> workplans are omitted; closed decisions/intakes/engagements stay listed
|
||||
> so recently-resolved work is still visible. [auto]
|
||||
|
||||
| Kind | ID | Status | Lane | Source |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| workplan | ADHOC-2026-09-05 | finished | — | workplans/ADHOC-2026-09-05.md |
|
||||
| workplan | ADHOC-2026-09-07 | finished | — | workplans/ADHOC-2026-09-07.md |
|
||||
| workplan | ADHOC-2026-09-27 | finished | — | workplans/ADHOC-2026-09-27.md |
|
||||
| workplan | KEY-WP-0001 | finished | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| workplan | KEY-WP-0002 | finished | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| workplan | KEY-WP-0003 | finished | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
|
||||
| workplan | KEY-WP-0004 | finished | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| workplan | KEY-WP-0005 | finished | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
|
||||
| workplan | KEY-WP-0006 | finished | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
|
||||
| workplan | KEY-WP-0007 | finished | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
|
||||
| workplan | KEY-WP-0008 | finished | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
|
||||
| workplan | KEY-WP-0009 | finished | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
|
||||
| workplan | KEY-WP-0010 | finished | — | workplans/KEY-WP-0010-openbao-operator-loopback-callback.md |
|
||||
| workplan | KEY-WP-0011 | finished | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
|
||||
| workplan | KEY-WP-0012 | finished | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
|
||||
| workplan | KEY-WP-0013 | finished | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| workplan | KEY-WP-0014 | finished | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
|
||||
| workplan | KEY-WP-0015 | finished | — | workplans/KEY-WP-0015-scope-intent-assessment.md |
|
||||
| workplan | KEY-WP-0016 | finished | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
|
||||
| workplan | KEY-WP-0017 | finished | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
|
||||
| workplan | KEY-WP-0018 | finished | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
|
||||
| workplan | KEY-WP-0019 | finished | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
|
||||
| workplan | KEY-WP-0020 | finished | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
|
||||
| workplan | KEY-WP-0021 | finished | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
|
||||
| workplan | KEY-WP-0022 | finished | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
|
||||
| workplan | KEY-WP-0023 | finished | — | workplans/KEY-WP-0023-live-migration-proof.md |
|
||||
| workplan | KEY-WP-0024 | finished | — | workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md |
|
||||
| workplan | KEY-WP-0025 | finished | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
|
||||
| workplan | KEY-WP-0026 | finished | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| workplan | KEY-WP-0027 | finished | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
|
||||
| workplan | KEY-WP-0028 | finished | — | workplans/KEY-WP-0028-browser-client-field-validation.md |
|
||||
| workplan | KEY-WP-0029 | finished | — | workplans/KEY-WP-0029-scope-reassessment.md |
|
||||
| workplan | KEY-WP-0030 | finished | — | workplans/KEY-WP-0030-tenant-precondition-guard.md |
|
||||
| workplan | KEY-WP-0031 | finished | — | workplans/KEY-WP-0031-gate-house-tenant-ruling.md |
|
||||
| workplan | KEY-WP-0032 | finished | — | workplans/KEY-WP-0032-principal-type-provenance-guard.md |
|
||||
| workplan | KEY-WP-0033 | finished | — | workplans/KEY-WP-0033-vergabe-fresh-login.md |
|
||||
| workplan | KEY-WP-0034 | finished | — | workplans/KEY-WP-0034-account-recovery.md |
|
||||
| workplan | KEY-WP-0035 | finished | — | workplans/KEY-WP-0035-optional-mfa.md |
|
||||
| workplan | KEY-WP-0036 | finished | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
|
||||
| workplan | KEY-WP-0037 | finished | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
|
||||
| workplan | KEY-WP-0038 | active | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
|
||||
| workplan | KEY-WP-0039 | active | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
|
||||
| workplan | KEY-WP-0040 | finished | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md |
|
||||
| workplan | KEY-WP-0041 | finished | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md |
|
||||
| task | ADHOC-2026-09-05-T01 | done | — | workplans/ADHOC-2026-09-05.md |
|
||||
| task | ADHOC-2026-09-07-T01 | done | — | workplans/ADHOC-2026-09-07.md |
|
||||
| task | ADHOC-2026-09-27-T01 | done | — | workplans/ADHOC-2026-09-27.md |
|
||||
| task | ADHOC-2026-09-27-T02 | done | — | workplans/ADHOC-2026-09-27.md |
|
||||
| task | KEY-WP-0001-T01 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T02 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T03 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T04 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T05 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T06 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T07 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T08 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T09 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T10 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T11 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T12 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T13 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T14 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T15 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T16 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T17 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T18 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T19 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T20 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T21 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T22 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0001-T23 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
|
||||
| task | KEY-WP-0002-T01 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| task | KEY-WP-0002-T02 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| task | KEY-WP-0002-T03 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| task | KEY-WP-0002-T04 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| task | KEY-WP-0002-T05 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| task | KEY-WP-0002-T06 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
|
||||
| task | KEY-WP-0003-T01 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
|
||||
| task | KEY-WP-0003-T02 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
|
||||
| task | KEY-WP-0003-T03 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
|
||||
| task | KEY-WP-0003-T04 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
|
||||
| task | KEY-WP-0003-T05 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
|
||||
| task | KEY-WP-0004-T01 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0004-T02 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0004-T03 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0004-T04 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0004-T05 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0004-T06 | cancel | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0004-T07 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
|
||||
| task | KEY-WP-0005-T01 | done | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
|
||||
| task | KEY-WP-0005-T02 | done | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
|
||||
| task | KEY-WP-0005-T03 | done | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
|
||||
| task | KEY-WP-0006-T01 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
|
||||
| task | KEY-WP-0006-T02 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
|
||||
| task | KEY-WP-0006-T03 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
|
||||
| task | KEY-WP-0006-T04 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
|
||||
| task | KEY-WP-0006-T05 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
|
||||
| task | KEY-WP-0007-T01 | done | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
|
||||
| task | KEY-WP-0007-T02 | done | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
|
||||
| task | KEY-WP-0007-T03 | done | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
|
||||
| task | KEY-WP-0008-T01 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
|
||||
| task | KEY-WP-0008-T02 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
|
||||
| task | KEY-WP-0008-T03 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
|
||||
| task | KEY-WP-0008-T04 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
|
||||
| task | KEY-WP-0009-T01 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
|
||||
| task | KEY-WP-0009-T02 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
|
||||
| task | KEY-WP-0009-T03 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
|
||||
| task | KEY-WP-0009-T04 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
|
||||
| task | KEY-WP-0010-T01 | done | — | workplans/KEY-WP-0010-openbao-operator-loopback-callback.md |
|
||||
| task | KEY-WP-0011-T01 | done | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
|
||||
| task | KEY-WP-0011-T02 | done | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
|
||||
| task | KEY-WP-0011-T03 | done | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
|
||||
| task | KEY-WP-0012-T01 | done | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
|
||||
| task | KEY-WP-0012-T02 | done | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
|
||||
| task | KEY-WP-0012-T03 | done | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
|
||||
| task | KEY-WP-0013-T01 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0013-T02 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0013-T03 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0013-T04 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0013-T05 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0013-T06 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0013-T07 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
|
||||
| task | KEY-WP-0014-T01 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
|
||||
| task | KEY-WP-0014-T02 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
|
||||
| task | KEY-WP-0014-T03 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
|
||||
| task | KEY-WP-0014-T04 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
|
||||
| task | KEY-WP-0015-T01 | done | — | workplans/KEY-WP-0015-scope-intent-assessment.md |
|
||||
| task | KEY-WP-0016-T01 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
|
||||
| task | KEY-WP-0016-T02 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
|
||||
| task | KEY-WP-0016-T03 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
|
||||
| task | KEY-WP-0016-T04 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
|
||||
| task | KEY-WP-0017-T01 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
|
||||
| task | KEY-WP-0017-T02 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
|
||||
| task | KEY-WP-0017-T03 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
|
||||
| task | KEY-WP-0017-T04 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
|
||||
| task | KEY-WP-0018-T01 | done | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
|
||||
| task | KEY-WP-0018-T02 | done | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
|
||||
| task | KEY-WP-0018-T03 | done | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
|
||||
| task | KEY-WP-0019-T01 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
|
||||
| task | KEY-WP-0019-T02 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
|
||||
| task | KEY-WP-0019-T03 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
|
||||
| task | KEY-WP-0019-T04 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
|
||||
| task | KEY-WP-0019-T05 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
|
||||
| task | KEY-WP-0020-T01 | done | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
|
||||
| task | KEY-WP-0020-T02 | done | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
|
||||
| task | KEY-WP-0020-T03 | done | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
|
||||
| task | KEY-WP-0021-T01 | done | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
|
||||
| task | KEY-WP-0021-T02 | done | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
|
||||
| task | KEY-WP-0021-T03 | done | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
|
||||
| task | KEY-WP-0022-T01 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
|
||||
| task | KEY-WP-0022-T02 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
|
||||
| task | KEY-WP-0022-T03 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
|
||||
| task | KEY-WP-0022-T04 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
|
||||
| task | KEY-WP-0022-T05 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
|
||||
| task | KEY-WP-0023-T01 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
|
||||
| task | KEY-WP-0023-T02 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
|
||||
| task | KEY-WP-0023-T03 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
|
||||
| task | KEY-WP-0023-T04 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
|
||||
| task | KEY-WP-0023-T05 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
|
||||
| task | KEY-WP-0024-T01 | done | — | workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md |
|
||||
| task | KEY-WP-0024-T02 | done | — | workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md |
|
||||
| task | KEY-WP-0025-T01 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
|
||||
| task | KEY-WP-0025-T02 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
|
||||
| task | KEY-WP-0025-T03 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
|
||||
| task | KEY-WP-0025-T04 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
|
||||
| task | KEY-WP-0026-T01 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| task | KEY-WP-0026-T02 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| task | KEY-WP-0026-T03 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| task | KEY-WP-0026-T04 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| task | KEY-WP-0026-T05 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| task | KEY-WP-0026-T06 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
|
||||
| task | KEY-WP-0027-T01 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
|
||||
| task | KEY-WP-0027-T02 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
|
||||
| task | KEY-WP-0027-T03 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
|
||||
| task | KEY-WP-0027-T04 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
|
||||
| task | KEY-WP-0028-T01 | done | — | workplans/KEY-WP-0028-browser-client-field-validation.md |
|
||||
| task | KEY-WP-0028-T02 | done | — | workplans/KEY-WP-0028-browser-client-field-validation.md |
|
||||
| task | KEY-WP-0029-T01 | done | — | workplans/KEY-WP-0029-scope-reassessment.md |
|
||||
| task | KEY-WP-0029-T02 | done | — | workplans/KEY-WP-0029-scope-reassessment.md |
|
||||
| task | KEY-WP-0029-T03 | done | — | workplans/KEY-WP-0029-scope-reassessment.md |
|
||||
| task | KEY-WP-0030-T01 | done | — | workplans/KEY-WP-0030-tenant-precondition-guard.md |
|
||||
| task | KEY-WP-0030-T02 | done | — | workplans/KEY-WP-0030-tenant-precondition-guard.md |
|
||||
| task | KEY-WP-0031-T01 | done | — | workplans/KEY-WP-0031-gate-house-tenant-ruling.md |
|
||||
| task | KEY-WP-0031-T02 | done | — | workplans/KEY-WP-0031-gate-house-tenant-ruling.md |
|
||||
| task | KEY-WP-0032-T01 | done | — | workplans/KEY-WP-0032-principal-type-provenance-guard.md |
|
||||
| task | KEY-WP-0032-T02 | done | — | workplans/KEY-WP-0032-principal-type-provenance-guard.md |
|
||||
| task | KEY-WP-0033-T01 | done | — | workplans/KEY-WP-0033-vergabe-fresh-login.md |
|
||||
| task | KEY-WP-0033-T02 | done | — | workplans/KEY-WP-0033-vergabe-fresh-login.md |
|
||||
| task | KEY-WP-0034-T01 | done | — | workplans/KEY-WP-0034-account-recovery.md |
|
||||
| task | KEY-WP-0034-T02 | done | — | workplans/KEY-WP-0034-account-recovery.md |
|
||||
| task | KEY-WP-0035-T01 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
|
||||
| task | KEY-WP-0035-T02 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
|
||||
| task | KEY-WP-0035-T03 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
|
||||
| task | KEY-WP-0035-T04 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
|
||||
| task | KEY-WP-0035-T05 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
|
||||
| task | KEY-WP-0036-T01 | done | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
|
||||
| task | KEY-WP-0036-T02 | cancel | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
|
||||
| task | KEY-WP-0036-T03 | done | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
|
||||
| task | KEY-WP-0037-T01 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
|
||||
| task | KEY-WP-0037-T02 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
|
||||
| task | KEY-WP-0037-T03 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
|
||||
| task | KEY-WP-0037-T04 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
|
||||
| task | KEY-WP-0038-T01 | done | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
|
||||
| task | KEY-WP-0038-T02 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
|
||||
| task | KEY-WP-0038-T03 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
|
||||
| task | KEY-WP-0038-T04 | cancel | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
|
||||
| task | KEY-WP-0039-T01 | done | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
|
||||
| task | KEY-WP-0039-T02 | wait | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
|
||||
| task | KEY-WP-0039-T03 | done | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
|
||||
| task | KEY-WP-0040-T01 | done | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md |
|
||||
| task | KEY-WP-0040-T02 | done | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md |
|
||||
| task | KEY-WP-0041-T01 | done | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md |
|
||||
| task | KEY-WP-0041-T02 | done | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md |
|
||||
| intake | KEY-IN-0001 | closed | — | intakes/intakes.md |
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
{
|
||||
".custodian-brief.md": {
|
||||
"sha256": "96d84539b3a92786bff37fcc70161adec4648b9547287e52e6b817bc080a080a",
|
||||
"mode": 436,
|
||||
"index": "100644 d3b36c3bffbeb922769a2a788e35b4113fccaa3f 0\t.custodian-brief.md\n"
|
||||
},
|
||||
"WORK-RECORDS.md": {
|
||||
"sha256": "431842547bd82750f284f8c1c9f519892f1bb1c1a684b9b1e38adc6fdfdc299d",
|
||||
"mode": 420,
|
||||
"index": "100644 d86b29bbc480acfa6d12d5052039578d7d4e380c 0\tWORK-RECORDS.md\n"
|
||||
},
|
||||
"workplans/ADHOC-2026-09-05.md": {
|
||||
"sha256": "76123d22d0c1efc51212f8914e386f078b3c6d908959af6978ae73874e492b04",
|
||||
"mode": 420,
|
||||
"index": "100644 f7e78675df62c9b48e29f882fa99298d6d8ce7f1 0\tworkplans/ADHOC-2026-09-05.md\n"
|
||||
},
|
||||
"workplans/ADHOC-2026-09-07.md": {
|
||||
"sha256": "c335ebfe91dad291277e8f17034ac8f785b3bc767f281f205c446c08f03d7978",
|
||||
"mode": 420,
|
||||
"index": "100644 17401073c053c9710e6e680243bbd313b08d1fc1 0\tworkplans/ADHOC-2026-09-07.md\n"
|
||||
},
|
||||
"workplans/ADHOC-2026-09-27.md": {
|
||||
"sha256": "2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314",
|
||||
"mode": 420,
|
||||
"index": "100644 0b755547aa7d5589f0898df2f102a45b6aa24838 0\tworkplans/ADHOC-2026-09-27.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0001-keycape-implementation.md": {
|
||||
"sha256": "29a2bf4892b7185e28e9126a3d8311d0fed34a876f416a3e75c605915fde0969",
|
||||
"mode": 420,
|
||||
"index": "100644 74334411cd056e7c7253e84f1c4b40c42eb13de3 0\tworkplans/KEY-WP-0001-keycape-implementation.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0002-container-image-gitea.md": {
|
||||
"sha256": "76fee187185b30219bc257ccc9b587661df4489574a2bb57703a365a5d803cec",
|
||||
"mode": 420,
|
||||
"index": "100644 418f477c0ede13d7c003d744abf234347f5d5e31 0\tworkplans/KEY-WP-0002-container-image-gitea.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md": {
|
||||
"sha256": "5e949c696e587540d305b682eb1f8390ec544bbec6a566be8d04e73fb1b254ca",
|
||||
"mode": 420,
|
||||
"index": "100644 9896c67d78f0efdd03325535eb41295250be3a19 0\tworkplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md": {
|
||||
"sha256": "260ba7d3116db3bc5a72e5cd8b9984137e7e372c8128d21073614dbd948c10c2",
|
||||
"mode": 420,
|
||||
"index": "100644 a50e910558ee43073c92cb27865a1aceac990dd5 0\tworkplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0005-iam-profile-core-claims.md": {
|
||||
"sha256": "c6702b644cdd046c0d3bba8ba8837050826e4dc3d855bd73327ae94218ba5b57",
|
||||
"mode": 420,
|
||||
"index": "100644 648d2405699dcf25b3121df6c46d871597100d7b 0\tworkplans/KEY-WP-0005-iam-profile-core-claims.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0006-client-credentials-service-tokens.md": {
|
||||
"sha256": "b3227849978c95cad89e02afb74bf2217b6188ae65c5b0af6a21703d65477701",
|
||||
"mode": 420,
|
||||
"index": "100644 632c4c2cad39cbde55c2ae1f2ba5ecf2f445b1a0 0\tworkplans/KEY-WP-0006-client-credentials-service-tokens.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0007-user-engine-portal-oidc-client.md": {
|
||||
"sha256": "5408ff6dfeb58a8a7756b48fe4f54f7c7b98d7940cb9aaa5c7864f1247e58150",
|
||||
"mode": 420,
|
||||
"index": "100644 f1b04e1a61b398426c23d8d9ca655b3937d7bd10 0\tworkplans/KEY-WP-0007-user-engine-portal-oidc-client.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md": {
|
||||
"sha256": "bfc9b2118f0712e92a86bbb0f14a2857fafc3a42a3c14c0e2535002aaa57a2f5",
|
||||
"mode": 420,
|
||||
"index": "100644 438ab5480de3bd66d75b6ec7e09639d1b93e3e5f 0\tworkplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md": {
|
||||
"sha256": "d1daac9815930104a392ea96c14922e34f947720a31d1b0ac45a770997e0f476",
|
||||
"mode": 420,
|
||||
"index": "100644 c9e585db65424c7c4e3b5adf37a68de2bdfbdc1c 0\tworkplans/KEY-WP-0009-provider-capabilities-and-service-identities.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0010-openbao-operator-loopback-callback.md": {
|
||||
"sha256": "41d28a16bef9313b776c5c6305316a9e7d4f5528b7fb7427b8384a0ed0ad35c4",
|
||||
"mode": 420,
|
||||
"index": "100644 81614eeb8c98205d6e5eb3201da10e36b76a21e9 0\tworkplans/KEY-WP-0010-openbao-operator-loopback-callback.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0011-live-secret-exposure-recovery.md": {
|
||||
"sha256": "3c5458180fe81a04e357f8db737e4287f79640b5ab02682bcadc595b73f846cd",
|
||||
"mode": 420,
|
||||
"index": "100644 f7b906f552f6e3279895a90afbe216b6e78ee5ff 0\tworkplans/KEY-WP-0011-live-secret-exposure-recovery.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md": {
|
||||
"sha256": "68d34d75c914e7d2d8177000e800d6372cccda2b850c03ab85da42b90a66b91c",
|
||||
"mode": 420,
|
||||
"index": "100644 0f3deb626e5de9a9f898754ef81d3f2ee5282792 0\tworkplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0013-approval-engine-resource-audience.md": {
|
||||
"sha256": "55c88bf4ed9a456db59b319bbaf960dfe6f4147437e339f950e0583d7582057a",
|
||||
"mode": 420,
|
||||
"index": "100644 7d77aee3caf3d79fe6d6c1369249c6d94e0ae123 0\tworkplans/KEY-WP-0013-approval-engine-resource-audience.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0014-native-credential-lane-handoff.md": {
|
||||
"sha256": "22aac43ae5039aebd11f9f0aa57a882ad009ad6ed119ec08eb85b32a5af48cd7",
|
||||
"mode": 420,
|
||||
"index": "100644 bacf04c07f59125a90c519d3c306a7414cb9a1a3 0\tworkplans/KEY-WP-0014-native-credential-lane-handoff.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0015-scope-intent-assessment.md": {
|
||||
"sha256": "60b9f2265eff880d916c3ee8dba36aec9984ec310804de776cebf89cc98fdb95",
|
||||
"mode": 420,
|
||||
"index": "100644 82b952a216346ffb7f5cab413623fe4a88f7700e 0\tworkplans/KEY-WP-0015-scope-intent-assessment.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0016-authorization-code-protocol-hardening.md": {
|
||||
"sha256": "ed48128a1cb51cbe3d154727bafd5a5ffb3b5b618c20649b2a0e7db154cf3a0c",
|
||||
"mode": 420,
|
||||
"index": "100644 52839bd0f83d97656c8ad97a8ffc338d9019239a 0\tworkplans/KEY-WP-0016-authorization-code-protocol-hardening.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md": {
|
||||
"sha256": "8700e0e5aa07dcd7f651e792cca12a0330b7ee9d2f6064b4cc3fcf4c7d509340",
|
||||
"mode": 420,
|
||||
"index": "100644 00eb03971aae75724afbf7b8a4946ce6b7eda3af 0\tworkplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0018-export-completeness-evidence.md": {
|
||||
"sha256": "15026b708d116f803b0b2fa43f30a4dbced76e4d8137b5fcb5b4275e593d7c7b",
|
||||
"mode": 420,
|
||||
"index": "100644 325fc483d62dbad53b92b986de56112168a92b4a 0\tworkplans/KEY-WP-0018-export-completeness-evidence.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0019-upstream-provider-token-verification.md": {
|
||||
"sha256": "0d2d8b5bda1e0d333935812d8d3749cb7b0c5523a3987d1b26185f9f6d0f0fa2",
|
||||
"mode": 420,
|
||||
"index": "100644 dfeb92296f46d4ce2ac7df1fedcc61e9ccc5d6b3 0\tworkplans/KEY-WP-0019-upstream-provider-token-verification.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0020-migration-contract-preservation.md": {
|
||||
"sha256": "cc842d15bf42ddc4d0f4056bc19b3176cab2c5f3c1a2fe6d648e5dbd077be8b3",
|
||||
"mode": 420,
|
||||
"index": "100644 7b6f3be976293469dee2180c10e16f313876d640 0\tworkplans/KEY-WP-0020-migration-contract-preservation.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0021-snapshot-attribute-validation.md": {
|
||||
"sha256": "b603248693073a9b9b25b4d0a4147d8d88c16a07dc9936e48fa1f3c76fe8bab6",
|
||||
"mode": 420,
|
||||
"index": "100644 1f6efeb32a6eb8fcbc52f4d3b591e2fc6b9e006d 0\tworkplans/KEY-WP-0021-snapshot-attribute-validation.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md": {
|
||||
"sha256": "8c135e84be81de31b4949f52ac28dccd404f2e4c3eaa576d733bd50160427a53",
|
||||
"mode": 420,
|
||||
"index": "100644 7c69629ecd20d0158aa1332d9d1442b796f6a7af 0\tworkplans/KEY-WP-0022-replacement-harness-and-external-conformance.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0023-live-migration-proof.md": {
|
||||
"sha256": "e581cd11f3905805b5e8ba8e2ede85092577c7d086e1761f2a2d033fdead4e47",
|
||||
"mode": 420,
|
||||
"index": "100644 3e7c83cb12e7fc31e682cf3c27f4c4767ded2905 0\tworkplans/KEY-WP-0023-live-migration-proof.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md": {
|
||||
"sha256": "af7842fa97263bfd46a4b5ae84c5c1ecddb06ab9623586e0292dcc93099fea35",
|
||||
"mode": 420,
|
||||
"index": "100644 baa0bbd195a92708f2befd027b938fcd1812ced7 0\tworkplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md": {
|
||||
"sha256": "0addb34e6d85e376c6a5db7afba597b6f268afa98b86c8c60add9be174dd1b26",
|
||||
"mode": 420,
|
||||
"index": "100644 a53e646761178748e8c4b00dcde888698447a948 0\tworkplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md": {
|
||||
"sha256": "c1bc0de47511ae49102009a172c84d9d5d64e829a18c8d40564fe817935563b3",
|
||||
"mode": 420,
|
||||
"index": "100644 1f96a2ac3ac939c4e6e0f66172477cf73db04e51 0\tworkplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0027-rollout-readiness-and-live-state.md": {
|
||||
"sha256": "f559eae61d433d451f80ea575254bb14a47f2485267b412e42d524b434bb1b59",
|
||||
"mode": 420,
|
||||
"index": "100644 57e01b2db033847cc786504b8795e37112d988a7 0\tworkplans/KEY-WP-0027-rollout-readiness-and-live-state.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0028-browser-client-field-validation.md": {
|
||||
"sha256": "fbd51422fa5e6449000e4520e6639f6135c7393165a5ae02b1e92892edba0480",
|
||||
"mode": 420,
|
||||
"index": "100644 46b0672fea90cd7cb672f7d119706c240474f8ca 0\tworkplans/KEY-WP-0028-browser-client-field-validation.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0029-scope-reassessment.md": {
|
||||
"sha256": "3241f3b94db9cad5e3770cf1fedc5c211288357e4f69b5ba9da57065094bdf0e",
|
||||
"mode": 420,
|
||||
"index": "100644 392f97750f6cb8b12868a6fd016c7d396b58477e 0\tworkplans/KEY-WP-0029-scope-reassessment.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0030-tenant-precondition-guard.md": {
|
||||
"sha256": "c94c3afc7fb4270dfc18f3fe8a32946c524e4a6ba2d02a3d8fda69c9b8441af0",
|
||||
"mode": 420,
|
||||
"index": "100644 0f74e0d5a037eec64b11b57c67b02057a3b0bfef 0\tworkplans/KEY-WP-0030-tenant-precondition-guard.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0031-gate-house-tenant-ruling.md": {
|
||||
"sha256": "0abb135367f0366aa071769c4bd76fdeb321d810ec75ad3b58d718734f78382b",
|
||||
"mode": 420,
|
||||
"index": "100644 d2ede9d494927976ccb9ba6a5aeaf1ed21c30837 0\tworkplans/KEY-WP-0031-gate-house-tenant-ruling.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0032-principal-type-provenance-guard.md": {
|
||||
"sha256": "6a5cd483a40b9477227b02156c42b3469094dac145b33727b336118898aa782a",
|
||||
"mode": 420,
|
||||
"index": "100644 a04f52f817a51efc1d0d0ecb99efacea8dc53a17 0\tworkplans/KEY-WP-0032-principal-type-provenance-guard.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0033-vergabe-fresh-login.md": {
|
||||
"sha256": "c01bb51cf2dcad06ea846040dddef09845041fa7271cd7015e5a34a49e60bb8f",
|
||||
"mode": 420,
|
||||
"index": "100644 5b478f953890339aec796c11a536af0ec3238789 0\tworkplans/KEY-WP-0033-vergabe-fresh-login.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0034-account-recovery.md": {
|
||||
"sha256": "7db9adcd418ccabfbce672aec283249a6e374212492f0349e8c47a568f130eda",
|
||||
"mode": 420,
|
||||
"index": "100644 eb58f99bc26e3c3ac33230dfe432c8b1fabe47ce 0\tworkplans/KEY-WP-0034-account-recovery.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0035-optional-mfa.md": {
|
||||
"sha256": "c60ea8d0c195686c0d2317f657aad2de27c9b9432d311627c0e1d06124cabb49",
|
||||
"mode": 420,
|
||||
"index": "100644 29eb711f656c91808e91f3981a9168c3a0ebc253 0\tworkplans/KEY-WP-0035-optional-mfa.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0036-platform-factor-recovery.md": {
|
||||
"sha256": "5ceb08fe5789b4f02550bb04fcaf8eeb25a43ac3f506dfc62d60a513bcd0013f",
|
||||
"mode": 420,
|
||||
"index": "100644 fd296bc05a6e565b0ef6e88a9ae3daa7c9f81786 0\tworkplans/KEY-WP-0036-platform-factor-recovery.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md": {
|
||||
"sha256": "3a90a0c460fc439570820b96d1ddc3b7f8646c557ca3a8aa27c6d546bb1a845a",
|
||||
"mode": 420,
|
||||
"index": "100644 ad7fed77a27aa8487693ad6fcf342502f418427a 0\tworkplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0038-forgejo-oidc-client.md": {
|
||||
"sha256": "dc8d88d462799e7e6e44da759a973d52e44102a271969a5daaa9d30ec015b064",
|
||||
"mode": 420,
|
||||
"index": "100644 8cd5bc66f7b0115eec7813d6eec994a1bb618668 0\tworkplans/KEY-WP-0038-forgejo-oidc-client.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0039-openbao-negative-probe-tokens.md": {
|
||||
"sha256": "c576db4b97ea5de0a50e17f251f8d5c57a744ded557bede963e48608bf3a6ca2",
|
||||
"mode": 420,
|
||||
"index": "100644 fd0fc46e49164312208afcc2c53549973b56e1fd 0\tworkplans/KEY-WP-0039-openbao-negative-probe-tokens.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0040-mfa-attended-run-findings.md": {
|
||||
"sha256": "98b23d8810bff047067169232213cb313ecc14ff79751224842723afe808fbd2",
|
||||
"mode": 420,
|
||||
"index": "100644 8e8730c8e7733b13d6a9f42689a9d8e407769e81 0\tworkplans/KEY-WP-0040-mfa-attended-run-findings.md\n"
|
||||
},
|
||||
"workplans/KEY-WP-0041-sign-in-diagnosis-page.md": {
|
||||
"sha256": "7e44f46acd6b3942c1852b49e0d64b205dfd7d9bb378aed5241b1bf22d7ce88a",
|
||||
"mode": 420,
|
||||
"index": "100644 56fd05819e3acd37b8e6cb468ec3d62307f1ae7a 0\tworkplans/KEY-WP-0041-sign-in-diagnosis-page.md\n"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: ADHOC-2026-09-05
|
||||
type: workplan
|
||||
title: "Repository review and layer declaration"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: codex
|
||||
created: "2026-09-05"
|
||||
updated: "2026-09-05"
|
||||
state_hub_workstream_id: "6f5a12f0-5dac-5e02-bbd9-9ab2263264ba"
|
||||
---
|
||||
|
||||
## Resolve declaration intake and normalize repository records
|
||||
|
||||
```task
|
||||
id: ADHOC-2026-09-05-T01
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "54c89abe-2539-550d-bc3a-649af8d4580a"
|
||||
```
|
||||
|
||||
Reviewed KEY-IN-0001 against local security-layer-model_v0.4. Declared Tooling
|
||||
and the authentication/authorization boundary in INTENT.md and closed the intake.
|
||||
Normalized two legacy completed workplan frontmatter statuses to finished.
|
||||
Ignored the generated .repo-manager workstation cache. Existing task statuses
|
||||
already conform to the State Hub task lifecycle canon.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
id: ADHOC-2026-09-07
|
||||
type: workplan
|
||||
title: "Document the KEY-WP-0016 authorization-code bindings for consumers"
|
||||
domain: infotech
|
||||
repo: key-cape
|
||||
status: finished
|
||||
owner: claude
|
||||
created: "2026-09-07"
|
||||
updated: "2026-09-07"
|
||||
state_hub_workstream_id: "c2fdc5cc-89be-561d-b832-f0b3019ea1c7"
|
||||
---
|
||||
|
||||
## Publish the consumer-facing rollout note
|
||||
|
||||
```task
|
||||
id: ADHOC-2026-09-07-T01
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "2187cef2-07c0-5ac1-89ca-3bb25a160fa4"
|
||||
```
|
||||
|
||||
KEY-WP-0016 changed `/token` and `/userinfo` behaviour without a consumer-facing
|
||||
note; no document in `docs/` mentioned `redirect_uri` at all, so the change would
|
||||
have reached a deployment silently.
|
||||
|
||||
Published `docs/authorization-code-bindings.md` stating what an exchange must
|
||||
send, who is affected and how to roll out. Verified the blast radius rather than
|
||||
assuming it: all five browser registrations in `config/dev-config.yaml`
|
||||
(`openbao-admin`, `demo-app`, `netkingdom-bootstrap-console`,
|
||||
`user-engine-portal`, `coulomb-social`) are `clientType: public` with
|
||||
`grantTypes: ["authorization_code"]`, so the grant-type and confidential-client
|
||||
bindings cannot affect them and only the `redirect_uri` requirement can. Named
|
||||
the realistic failure — a client that sends `redirect_uri` to `/authorize` but
|
||||
omits it at `/token` — and said plainly that it has not been observed against a
|
||||
live consumer. Referenced the note from SCOPE.md.
|
||||
Loading…
Add table
Add a link
Reference in a new issue