Record reviewed case source evidence (4/13)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 8s

This commit is contained in:
codex 2026-10-17 22:32:28 +02:00
parent 935aed2bcb
commit 42e18ef0c8
20 changed files with 2801 additions and 0 deletions

View file

@ -0,0 +1,79 @@
from pathlib import Path
import hashlib,json,subprocess,copy,yaml,re,importlib.util,sys
p=Path(__file__).resolve().parent;r=Path('/home/worsch/net-kingdom');base='342a6f25af5cdc602841948f42759ea52ef1c8f5'
def git(root,*a):return subprocess.check_output(['git','-C',str(root),*a],text=True).strip()
assert git(r,'rev-parse','HEAD')==base
assert git(Path('/home/worsch/railiance-platform'),'rev-parse','HEAD')=='104ed90f42a9a86bf9fda343b49b65a85c9ee4b9'
template=Path('/home/worsch/railiance-platform/manifests/templates/keycape-issuer-eso.yaml')
assert hashlib.sha256(template.read_bytes()).hexdigest()=='e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743'
name='security-case-type-revisions/1.3/oidc-browser-relying-party/type.yaml';old=(r/name).read_bytes();old_doc=yaml.safe_load(old)
assert hashlib.sha256(old).hexdigest()=='e75fe94110ef9fb7fb18072fe2856941b33507c99cb406d36576046481485d0a'
(p/'type.preimage.yaml').write_bytes(old)
patch=p.parent/'nk48-r5-f2-publication-r1/netkingdom-entry-point.conditional.patch'
assert hashlib.sha256(patch.read_bytes()).hexdigest()=='6784a21fbd10bd62712be2e0f4929034601c94629aa74ccaa2e93fc815eecd61'
subprocess.run(['git','apply','--check',str(patch.resolve())],cwd=r,check=True)
subprocess.run(['git','apply',str(patch.resolve())],cwd=r,check=True)
new=(r/name).read_bytes();new_doc=yaml.safe_load(new)
a=copy.deepcopy(old_doc);b=copy.deepcopy(new_doc)
for doc in [a,b]:
step=next(x for x in doc['spec']['follow_through'] if x['id']=='deliver-secret');step['entry_point']='same-entry-removed-for-comparison'
assert a==b,'only entry_point may change'
assert old_doc['spec']['parameters']==new_doc['spec']['parameters']
assert old_doc['spec']['execution_descriptors']==new_doc['spec']['execution_descriptors']
assert new_doc.get('metadata',{}).get('status')==old_doc.get('metadata',{}).get('status')
# Compose by importing the read-only seven-adapter composer, never dispatch.
sys.path.insert(0,'/home/worsch/ops-warden/src');sys.dont_write_bytecode=True
module=r/'sso-mfa/k8s/keycape/compose-1.2-descriptors.py'
spec=importlib.util.spec_from_file_location('nk48_composer',module);c=importlib.util.module_from_spec(spec);spec.loader.exec_module(c)
artifact=r/'sso-mfa/k8s/keycape/descriptor-composition-1.3.candidate.json';before=artifact.read_bytes();doc=json.loads(before)
regenerated=(json.dumps(c.artifact(doc['check_config_digest'],doc['params'],'1.3'),indent=2,sort_keys=True)+'\n').encode()
assert regenerated==before,'Unexpected descriptor/artifact delta; inspect before writing'
(p/'composition-regenerated.json').write_bytes(regenerated)
wp=r/'workplans/NK-WP-0048-security-case-types.md';text=wp.read_text();(p/'workplan.preimage.md').write_text(text)
for task,status in [('NK-WP-0048-T33','done'),('NK-WP-0048-T34','progress')]:
match=re.search(r'```task\nid: '+task+r'\n.*?```',text,re.S);assert match
before_block=match.group();assert 'status: todo\n' in before_block
text=text[:match.start()]+before_block.replace('status: todo\n','status: '+status+'\n')+text[match.end():]
needle='## T34 — Close the consumer configuration inverse finding'
completion="""2026-10-17 completed exact proposed-source reference correction: Platform
published template, metadata helper and eight focused tests in
`104ed90f42a9a86bf9fda343b49b65a85c9ee4b9`; independent source review
`nk48-r5-f2-review-r1` found no blocking source-fit defect. The proposed 1.3
entry now names ClusterSecretStore `openbao-keycape-{client_id}-issuer`, cluster
scope restricted to `sso`, and the exact Platform template digest. Only that
entry text changes; parameters and all seven descriptors are unchanged, and
regeneration produces the identical not-an-admission composition artifact.
This closes the coordinator's vague-reference source finding only. It supplies
no formal owner assent, existing live store, installed ESO compatibility,
real delivery/removal/session capability, grant or native acceptance; existing
RPF-1.3-T4 and T16 keep those obligations. F3 is independent and remains open.
Evidence: Custodian `nk48-r5-f2-publication-r1/` and
`nk48-issuer-reference-integration-r1/` under the existing 17 October packet.
"""
assert text.count(needle)==1;text=text.replace(needle,completion+needle)
needle='## T35 — Align source admission fixtures with the authenticated catalog and attended-session API'
note="""2026-10-17 implementation progress: the fresh independent Apps review
reproduced a missing fresh inverse-session boundary. Three new regressions fail
the original wrapper. Corrected r2 requires a separately authenticated inverse
session before the release-writer lock and revokes it on every exit; 19 tests /
33 subtests pass, and removing the session guard is detected. This remains a
source candidate: direct Apps source inspection found no real owner transaction,
durable journal, all-release-writer exclusion or inverse-session backend to bind.
The four exact missing owner facilities and smallest existing T05 implementation
are retained in Custodian `nk48-r5-f3-implementation-r2/owner-backend-next.md`.
T34 progresses for this correction and investigation; it does not claim the real
backend, accepted type placement, owner assent or F3 closure. The unsafe original
candidate/review remain frozen. Do not integrate its type prose as an implemented
capability or reuse the revoked hand-over session.
"""
assert text.count(needle)==1;text=text.replace(needle,note+needle)
text=text.replace('and `nk48-catalog-fixture-publication-r1/`. Canonical validation/publication
receipts belong in the latter packet when performed.','and `nk48-catalog-fixture-publication-r2/`. Root canonical validation passes
31 tests / 53 subtests against Warden `922c26d` (the same reviewed code plus
one normal allocator-only source UUID commit). Publication receipts belong
in that packet.')
wp.write_text(text)
(p/'verification.json').write_text(json.dumps({'status':'integrated-proposed-source-only','only_type_delta':'deliver-secret.entry_point','type_before_sha256':hashlib.sha256(old).hexdigest(),'type_after_sha256':hashlib.sha256(new).hexdigest(),'parameters_unchanged':True,'seven_descriptors_unchanged':True,'composition_byte_identical':True,'T33':'done: exact source reference only','T34':'progress: real owner backend absent','owner_assent':False,'native_action':False},indent=2)+'\n')
print('Applied exact issuer reference, verified unchanged descriptors/parameters/artifact; recorded bounded T33 completion and T34 progress.')

View file

@ -0,0 +1,495 @@
apiVersion: netkingdom.io/security-case-type/v0.1
kind: SecurityCaseType
spec:
id: oidc-browser-relying-party
version: '1.3'
status: proposed
title: Browser OIDC relying party for an internal service
purpose: 'Let an internal web service sign its human users in through NetKingdom
(KeyCape): an exact-redirect OIDC client, optional MFA pin, the LLDAP groups the
service maps to its own roles, and, for confidential clients, a client secret
minted straight into OpenBao custody. Replaces the per-client rollout scripts
in sso-mfa/k8s/keycape. First case: Forgejo (KEY-WP-0038).'
parameters:
- name: client_id
type: string
case_settable: true
constraints:
pattern: ^[a-z0-9][a-z0-9-]{1,62}$
sensitivity: operational
description: OAuth2 client_id, unique in KeyCape.
- name: display_name
type: string
case_settable: true
constraints:
pattern: ^[^\n\r]{1,80}$
sensitivity: public
description: Name shown on the sign-in page.
- name: redirect_uris
type: string
case_settable: true
constraints:
pattern: ^https://[^*?#\s,]+(,https://[^*?#\s,]+)*$
sensitivity: security_sensitive
description: 'Comma-separated exact https redirect URIs. No wildcards, no plain
http, no query and no fragment: KeyCape refuses ''*'' and ''?'' at startup,
and OAuth 2.0 forbids fragments (key-cape type review R1).'
- name: allowed_scopes
type: string
case_settable: true
constraints:
enum:
- openid,profile,email,groups
- openid,email,groups
- openid,profile,groups
- openid,groups
sensitivity: security_sensitive
description: Scopes the client may request, openid first. Always includes groups,
because access_groups is required and KeyCape's UserInfo returns groups only
for that scope (key-cape type review R2).
- name: client_type
type: string
case_settable: true
constraints:
enum:
- confidential
- public
sensitivity: security_sensitive
description: confidential for server-side services that can hold a secret; public
for SPAs and native apps (PKCE only).
- name: mfa_required
type: string
case_settable: true
constraints:
enum:
- 'true'
- 'false'
sensitivity: security_sensitive
description: '"true" pins MFA on this client; required when the service grants
administrative or owner rights. "false" is not "no MFA": the client then carries
no mfaRequired field and KeyCape applies the per-user privacyIDEA policy. A
renderer must never emit an explicit mfaRequired false, which would skip that
policy (key-cape type review R3).'
- name: access_groups
type: string
case_settable: true
constraints:
pattern: ^[a-z][a-z0-9-]{1,62}(,[a-z][a-z0-9-]{1,62})*$
sensitivity: operational
description: 'Comma-separated LLDAP groups that the service maps to its roles.
Created if absent. KeyCape has no per-client group gate: it issues tokens to
any authenticated user and only carries the groups claim, so the consuming service
enforces the gate itself (for Forgejo, its required-claim setting; key-cape
type review R2).'
- name: initial_members
type: string
required: false
case_settable: true
constraints:
pattern: ^[a-z][a-z0-9-]*=[a-z][a-z0-9._-]*(\+[a-z][a-z0-9._-]*)*(;[a-z][a-z0-9-]*=[a-z][a-z0-9._-]*(\+[a-z][a-z0-9._-]*)*)*$
sensitivity: operational
description: Initial memberships as group=user+user;group=user. Usernames are
directory usernames, never secrets.
- name: consuming_service
type: string
case_settable: true
constraints:
pattern: ^[a-z0-9][a-z0-9-]{0,62}$
sensitivity: operational
description: The service that signs users in with this client.
- name: consumer_owner_repo
type: string
case_settable: true
constraints:
pattern: ^[a-z0-9][a-z0-9-]*$
sensitivity: operational
description: The repository that owns the consuming service's configuration.
- name: consumer_namespace
type: string
case_settable: true
constraints:
pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
sensitivity: security_sensitive
description: Consumer namespace for namespaced ESO TokenRequest reader; actual
workload owner must confirm; never infer from service name.
- name: issuer
type: string
case_settable: false
default: https://kc.coulomb.social
sensitivity: security_sensitive
description: The NetKingdom issuer; fixed by the type.
composition:
authority: netkingdom
initial_trust:
- runtime_secret_trust
- runtime_identity_trust
capabilities:
- c1.lightweight-sso
providers:
c1.lightweight-sso: net-kingdom.keycape-client-registration
parameter_bindings:
net-kingdom.keycape-client-registration.client_id: client_id
net-kingdom.keycape-client-registration.display_name: display_name
net-kingdom.keycape-client-registration.redirect_uris: redirect_uris
net-kingdom.keycape-client-registration.allowed_scopes: allowed_scopes
net-kingdom.keycape-client-registration.client_type: client_type
net-kingdom.keycape-client-registration.mfa_required: mfa_required
materialization:
- id: client-secret-path
kind: kv_path
when: client_type == confidential
template: platform/workloads/keycape/{client_id}
body: field CLIENT_SECRET; minted by the credential broker, never written by hand
- id: keycape-reader-policy
kind: openbao_policy
when: client_type == confidential
template: workload-kv-read-keycape-{client_id}-eso
body: "path \"platform/data/workloads/keycape/{client_id}\" {{\n capabilities\
\ = [\"read\"]\n}}\n"
description: KeyCape reads its client secret only through ESO. No metadata, list,
parent or sibling access.
- id: consumer-reader-policy
kind: openbao_policy
when: client_type == confidential
template: workload-kv-read-keycape-{client_id}-{consuming_service}
body: "path \"platform/data/workloads/keycape/{client_id}\" {{\n capabilities\
\ = [\"read\"]\n}}\n"
description: The consuming service reads the same secret. No metadata, list, parent
or sibling access.
- id: eso-role
kind: openbao_role
when: client_type == confidential
template: external-secrets-keycape-{client_id}
body: 'auth: kubernetes
bound_service_account_names: [external-secrets]
bound_service_account_namespaces: [external-secrets]
token_policies: [workload-kv-read-keycape-{client_id}-eso]
token_no_default_policy: true
token_ttl: 15m
token_max_ttl: 15m
'
description: The Kubernetes-auth role deliver-secret uses; bound to the ESO service
account only.
- id: consumer-eso-role
kind: openbao_role
when: client_type == confidential
template: external-secrets-keycape-{client_id}-{consuming_service}
body: 'auth: kubernetes
bound_service_account_names: [{consuming_service}-keycape-{client_id}-eso]
bound_service_account_namespaces: [{consumer_namespace}]
audience: openbao
token_policies: [workload-kv-read-keycape-{client_id}-{consuming_service}]
token_no_default_policy: true
token_ttl: 15m
token_max_ttl: 15m
'
description: Consumer ESO requests a token for this separate reference identity.
Common ESO controller remains trusted and can request tokens; no cryptographic
controller isolation is claimed.
- id: catalog-entry
kind: catalog_entry
template: '{consuming_service}-keycape-client'
body: 'id: {consuming_service}-keycape-client
owner_repo: key-cape
warden_executes: false
risk: high
status: active once verification passes
workload_ref: applicable, name {consuming_service}, declaration_ref of the consumer''s
published declaration or the explicit unknown form, never inferred
path_template: platform/workloads/keycape/{client_id}
field: CLIENT_SECRET
delegation: native, intended_owner key-cape
rotation: mint-secret then verification; automatable false until a run has been
proven
'
description: One entry per case (ops-mason item 5, ops-warden R4'). It carries
the consumer's workload reference, which ADR-0009 needs for zone selection.
follow_through:
- id: ensure-groups
owner: net-kingdom
entry_point: reviewed adapter net-kingdom/keycape-ensure-groups, dispatched by Warden's
case kernel inside the attended session; it calls identity-provisioner
ensure_service_groups in the provisioner pod through the session's contained
cluster access (groups {access_groups}; members exactly {initial_members} as
recorded in the accepted case, nothing else)
auth: the attended human session on lane keycape-attended-case-change (actor human,
parent-session revoked per attempt); the in-pod call uses the provisioner's own
directory binding, and no provisioner or user-engine token is read or sent
lane: keycape-attended-case-change
warden_executes: false
attended: true
value_handling: none
attended_justification: The provisioner's only HTTP credential is user-engine's
runtime token, and its NetworkPolicy admits only user-engine pods. A proxy lane
would act as user-engine (ops-warden review R8; canon section 3; ops-warden
ADR-0002). Running it in the session register-client already needs adds no attended
session. It can become unattended once the provisioner has an operator credential
of its own and an admitted path. Warden's case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.
- id: mint-secret
owner: railiance-platform
when: client_type == confidential
entry_point: 'railiance-platform credential broker: generate a client secret inside
custody at bao:platform/workloads/keycape/{client_id}, through reviewed adapter
railiance-platform/keycape-mint-secret'
auth: a human caller (net-kingdom-admins, aal2) starts it; Warden runs it unattended
through the broker lane under the caller's session, with the caller's broker grant
for the case's KV path; agents and root are refused, and the value is generated
in custody and never returned
lane: keycape-client-secret-generate
warden_executes: true
attended: false
value_handling: generated_in_custody
- id: deliver-secret
owner: railiance-platform
when: client_type == confidential
entry_point: reviewed adapter railiance-platform/keycape-deliver-secret, dispatched
by Warden's case kernel; Platform's deliver capability applies ExternalSecret
sso/keycape-{client_id}-client (key client-secret) through the ESO store for
role external-secrets-keycape-{client_id}, from bao:platform/workloads/keycape/{client_id}
auth: Platform's own delivery path on lane platform-secret-delivery, inside the human
aal2 session; ESO reads custody through role external-secrets-keycape-{client_id};
Warden holds no Platform credential, and no value leaves custody
lane: platform-secret-delivery
warden_executes: false
attended: false
value_handling: none
ready_check: the delivery for sso/keycape-{client_id}-client reports Ready=True
(status only, no value read)
- id: bind-env
owner: net-kingdom
when: client_type == confidential
entry_point: 'reviewed adapter net-kingdom/keycape-bind-env, dispatched by Warden''s
case kernel inside the attended session: patch Deployment sso/keycape env
KEYCAPE_{client_id:env}_CLIENT_SECRET from sso/keycape-{client_id}-client, by
reference only'
auth: the attended human session on lane keycape-attended-case-change, with operator
kube credentials for namespace sso (actor human, parent-session revoked per attempt)
lane: keycape-attended-case-change
warden_executes: false
attended: true
value_handling: none
attended_justification: A change to the live identity provider's Deployment. It
runs in the same attended operator session as register-client, before it, because
KeyCape resolves a confidential client's secret at start and fails closed if
the binding is missing. Warden's case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.
- id: register-client
owner: net-kingdom
entry_point: reviewed adapter net-kingdom/keycape-register-client, dispatched by Warden's
case kernel inside the attended session; it runs sso-mfa/k8s/keycape/keycape-client.py
apply --client-file <client-from-case> --case-acceptance <record> --check-config
<keycape binary> (net-kingdom.keycape-client-registration)
auth: the attended human session on lane keycape-attended-case-change, with operator
write access to the live KeyCape client registry in namespace sso (actor human,
parent-session revoked per attempt)
lane: keycape-attended-case-change
warden_executes: false
attended: true
value_handling: none
attended_justification: 'The live KeyCape client registry is a Kubernetes Secret,
and agents do not read or write Secrets. Until KeyCape loads clients from a
non-secret registry with secrets referenced from custody, one attended operator
session applies the rendered registration. ops-warden groups all attended steps
of a run into that one session. KeyCape loads clients at startup and keeps login,
authorization and handoff state in memory, so the restart drops in-flight logins
and sessions for every client; runs go in a quiet window (key-cape type review
R4). The rendered registry must pass KeyCape''s own startup checks (keycape
check-config, the pinned checker artifact sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5
built from key-cape 3c71226, which keycape-client.py runs before it patches)
before the restart: a refused entry stops the single replica and every client
loses sign-in (R1). Warden''s case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.'
- id: deliver-consumer-secret
owner: '{consumer_owner_repo}'
when: client_type == confidential
entry_point: Reviewed adapter net-kingdom/keycape-deliver-consumer-secret, dispatched
by Warden's case kernel inside the attended consumer-owner session; the owner's
apply capability creates dedicated ServiceAccount {consumer_namespace}/{consuming_service}-keycape-{client_id}-eso
(automount=false, no legacy tokens), namespace SecretStore openbao-keycape-{client_id}-consumer
with serviceAccountRef and audiences [openbao], and ExternalSecret {consuming_service}-netkingdom-oidc-client
with Owner target, Retain remote deletion and only CLIENT_SECRET from workloads/keycape/{client_id}.
Admission validates actual installed ESO TokenRequest support and common controller
RBAC before any owner-approved apply.
auth: the attended consumer-owner session on lane consumer-owner-deployment (actor
human, the consumer owner's operator deployment authority, on the founder's go
for the change); ESO authenticates to OpenBao role external-secrets-keycape-{client_id}-{consuming_service}
with a TokenRequest for the dedicated namespaced serviceAccountRef; controller
remains trusted
lane: consumer-owner-deployment
warden_executes: false
attended: true
value_handling: none
attended_justification: The consumer owner's only deployment authority in the consumer
namespace is its operator credential, and production applies need the founder's
go per change. No contained agent deployer identity exists, and the owner does
not want a standing credential an agent could trigger. It runs in one attended
consumer-owner session together with hand-over. Warden's case kernel dispatches
the reviewed adapter only inside that session (actor human, parent-session
revoked per attempt); the human session is the authority and the kernel is only
the dispatcher.
ready_check: Namespace SecretStore and ExternalSecret both report Ready=True;
dedicated SA has no legacy token refs; status and fixed metadata only, never
Secret values.
- id: hand-over
owner: '{consumer_owner_repo}'
entry_point: The consumer owner first makes its own Helm change configuring {consuming_service}
with issuer {issuer}, client {client_id} and custody bao:platform/workloads/keycape/{client_id},
requiring one of {access_groups} in the groups claim before sign-in completes.
Then reviewed adapter net-kingdom/keycape-hand-over, dispatched by Warden's case
kernel inside the attended consumer-owner session, verifies the consumer with
four value-free probes (member-login, non-member-refused, membership-add-grants,
membership-remove-revokes) and records the hand-over receipt.
auth: the attended consumer-owner session on lane apps-consumer-handover (actor human,
parent-session revoked per attempt); the consumer configuration itself is the
owner's own deployment path
lane: apps-consumer-handover
warden_executes: false
attended: true
value_handling: none
attended_justification: The verification needs a real member login and a refused
non-member login, which only an attended session can perform, and it follows
the owner's own Helm change in the same consumer-owner session as
deliver-consumer-secret. Warden's case kernel dispatches the reviewed adapter
only inside that session (actor human, parent-session revoked per attempt); the
human session is the authority and the kernel is only the dispatcher.
verification:
- id: authorize-accepts-redirects
check: GET {issuer}/authorize with client {client_id} and each of {redirect_uris}
returns the login page
expect: present
reads_secret_value: false
- id: authorize-refuses-other-redirect
check: GET {issuer}/authorize with client {client_id} and an unregistered redirect
returns an OAuth error
expect: deny
reads_secret_value: false
- id: custody-denied-to-default
when: client_type == confidential
check: a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/{client_id}
expect: deny
reads_secret_value: false
- id: custody-allowed-to-consumer
when: client_type == confidential
check: policy workload-kv-read-keycape-{client_id}-{consuming_service} grants
read on bao:platform/workloads/keycape/{client_id} (capabilities check only)
expect: allow
reads_secret_value: false
- id: groups-present
check: LLDAP groups {access_groups} exist, with exactly the memberships of the
accepted case added
expect: present
reads_secret_value: false
- id: consumer-enforces-groups
check: '{consuming_service}''s OIDC source for {client_id} (owner {consumer_owner_repo})
requires one of {access_groups} in the groups claim (configuration read, no
secret value)'
expect: present
reads_secret_value: false
- id: secret-delivered
when: client_type == confidential
check: the delivery for sso/keycape-{client_id}-client reports Ready=True (status
only)
expect: present
reads_secret_value: false
- id: mfa-pinned
when: mfa_required == true
check: keycape-client.py inspect for {client_id} shows the MFA pin on the registered
client (non-secret client view)
expect: present
reads_secret_value: false
- id: consumer-secret-delivered
when: client_type == confidential
check: '{consumer_namespace} SecretStore openbao-keycape-{client_id}-consumer
and ExternalSecret {consuming_service}-netkingdom-oidc-client Ready=True, exact
dedicated SA/audience role binding, fixed CLIENT_SECRET mapping; no Secret value
read'
expect: present
reads_secret_value: false
rollback:
- reverses: hand-over
action: the adapter removes only its hand-over receipt. Removing the OIDC source
for {client_id} from {consuming_service} is the owner's Helm rollback (owner
{consumer_owner_repo}), done inside deliver-consumer-secret's owner_remove
before any consumer object is removed
- reverses: deliver-consumer-secret
when: client_type == confidential
action: After reverting consumer secret reference, delete only authenticated created
ExternalSecret/owned target by UID and ownerReference, then SecretStore and
dedicated SA; preserve preexisting-matched resources and never delete a shared
target. Retain audit and ownership receipts; removing SecretStore/SA alone is
not immediate OpenBao token revocation.
- reverses: register-client
action: keycape-client.py apply --operation remove for {client_id}
- reverses: bind-env
when: client_type == confidential
action: remove env KEYCAPE_{client_id:env}_CLIENT_SECRET from Deployment sso/keycape,
after register-client is reversed
- reverses: deliver-secret
when: client_type == confidential
action: delete ExternalSecret sso/keycape-{client_id}-client and its target
- reverses: mint-secret
when: client_type == confidential
action: 'covered by client-secret-path: one metadata delete, after deliver-secret
is reversed'
- reverses: ensure-groups
action: identity-provisioner revoke_service_groups inside the provisioner pod
(attended, as ensure-groups) with the case's memberships; delete_if_empty only
if no other case uses {access_groups}
- reverses: client-secret-path
when: client_type == confidential
action: delete metadata and all versions at platform/workloads/keycape/{client_id},
after the ExternalSecret is gone (this also reverses mint-secret)
- reverses: eso-role
when: client_type == confidential
action: delete Kubernetes-auth role external-secrets-keycape-{client_id}
- reverses: keycape-reader-policy
when: client_type == confidential
action: delete policy workload-kv-read-keycape-{client_id}-eso, after eso-role
- reverses: consumer-eso-role
when: client_type == confidential
action: Delete only this case-created role external-secrets-keycape-{client_id}-{consuming_service},
before deleting consumer-reader-policy; revoke case-issued OpenBao tokens or
prove expiration before declaring deactivation.
- reverses: consumer-reader-policy
when: client_type == confidential
action: delete policy workload-kv-read-keycape-{client_id}-{consuming_service}
- reverses: catalog-entry
action: remove catalog entry {consuming_service}-keycape-client
acceptance:
accepted_by:
- owner
- founder
risk_class: medium
# Reviewed machine descriptors (NK-WP-0048-T16 successor candidate). Digests are
# generated by sso-mfa/k8s/keycape/compose-1.2-descriptors.py --version 1.3.
execution_descriptors:
bind-env: sha256:d5fcb20cf15bf7454890d2529180c582218e3df12584a99162e4b664b899caa0
deliver-consumer-secret: sha256:36281df8139fb1ec64b226d94f80eb592de5addab7063ff54c691c641e275fc7
deliver-secret: sha256:75aaa7346aebdea2a0eb0ba993995515b5248ffefc34caf8e713525e9b58da85
ensure-groups: sha256:ebc9d5b9167d18e6015f4bbeb9699e8d396d64fbfaf195142781f84f4f47a70a
hand-over: sha256:1bce07a3229b7d0c7c730f35ce5b40fe471009a82de8dd53788ed970a9be1224
mint-secret: sha256:c92957227235db04953809d92b260d32c8b947472a8e8fd17a258ade3ae8da70
register-client: sha256:4ecc769f75433b66c7161f13c0fe7e5599633cf466d46cb6d8031f0456f426e7

View file

@ -0,0 +1,13 @@
{
"status": "integrated-proposed-source-only",
"only_type_delta": "deliver-secret.entry_point",
"type_before_sha256": "e75fe94110ef9fb7fb18072fe2856941b33507c99cb406d36576046481485d0a",
"type_after_sha256": "ef02ddae3167bdd1558193a5f6db0379fb8cc84207531706d5ef35caabc3684f",
"parameters_unchanged": true,
"seven_descriptors_unchanged": true,
"composition_byte_identical": true,
"T33": "done: exact source reference only",
"T34": "progress: real owner backend absent",
"owner_assent": false,
"native_action": false
}

View file

@ -0,0 +1,11 @@
# NK48-R5-F1 successor coverage, prepared only
The patch changes only `sso-mfa/k8s/keycape/test_admit_1_3_type.py`; it has not been applied to the owner checkout, committed or published. Source base: `3236d5000e52e0643f33906a26a7825aa0b67465`. Kernel dependency: frozen `117fec9241e7b985dfb187b992e173d0a5dc3700`.
The committed-map refusal now explicitly includes an agent-actor mint descriptor. A separate fixture-map test acknowledges precisely that changed descriptor digest, allowing admission to reach the independent `agent mint is forbidden` guard. The human descriptor is also tested with an explicit fixture `WARDEN_AGENT_ID`; the helper sets it for that call instead of clearing it. Existing changed-caller coverage remains.
Focused results: original tests pass against both frozen and scratch guard-removed kernels (8 tests / 31 subtests each). Successor tests pass against the real frozen kernel (10 tests / 32 subtests). Removing only its admission agent-mint guard in a separate scratch kernel makes exactly both new guard tests fail (2 failed, 8 passed / 32 subtests). The dispatch-time guard remains present in the mutant; no adapter is dispatched.
`test-admit-1-3-agent-mint-coverage.patch` is the proposed owner patch. `scratch-kernel-mutation.patch` is negative-control evidence only and MUST NOT be applied to any canonical owner source. Preimage, successor, exact commands/results and hashes are retained beside this note. The runner requires the exported scratch trees named in scratch.json; it refuses a mismatched guard and modifies only those scratch trees.
These are fixture admission checks. Type acknowledgment, the 1.3 parameter pin and the changed descriptor-map entry exist only in memory inside tests. No actual type acknowledgment, catalog/pin change, kernel acceptance, native execution or grant is established. R5 F2/F3 and owner/kernel follow-through are unchanged. Root review/integration is the next step; no canonical task refinement is authored in this preparation-only packet.

View file

@ -0,0 +1,8 @@
{
"status": "applied_test_only",
"path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/test_admit_1_3_type.py",
"preimage_sha256": "sha256:f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5",
"after_sha256": "sha256:c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"native_actions": 0,
"kernel_or_type_pin_changes": 0
}

View file

@ -0,0 +1,6 @@
M .custodian-brief.md
M WORK-RECORDS.md
?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-capture/
?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-inventory.jsonl
?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-monitor.json
?? intakes/NET-IN-0005-single-use-setup-link-mail-handoff.md

View file

@ -0,0 +1,8 @@
{
"command": "env PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/home/worsch/ops-warden/src /home/worsch/ops-warden/.venv/bin/python -B -m pytest -q -p no:cacheprovider test_admit_1_3_type.py",
"working_directory": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape",
"result": "10 passed, 32 subtests passed in 5.05s",
"exit_code": 0,
"source_sha256": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"publication_status": "not committed; remaining source implementation work retained"
}

View file

@ -0,0 +1,112 @@
{
"schema": "custodian.nk48-r5-f1-source-implementation.v1",
"status": "prepared-tested-not-applied",
"finding": "NK48-R5-F1",
"source_base": "3236d5000e52e0643f33906a26a7825aa0b67465",
"reviewed_code_base": "001a38ca2aa85155ce4840c4da22aca6e2346171",
"kernel_base": "117fec9241e7b985dfb187b992e173d0a5dc3700",
"target": "sso-mfa/k8s/keycape/test_admit_1_3_type.py",
"preimage_sha256": "f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5",
"successor_sha256": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"frozen_r5_review_sha256": "e3016352c58178894ccbfc600b960d3f9e2b5d534a05b8df2c5b79737cb0127a",
"canonical_test_still_matches_preimage": true,
"canonical_edits": false,
"canonical_commit_or_publish": false,
"kernel_mutation_scratch_only": true,
"mutant_changed_files": [
"src/warden/case_run.py"
],
"native_actions": false,
"grants": false,
"knowledge_orientation": {
"package": "0.2.6",
"digest": "8d1d6a9d4d07fec7b619618de7dfe2254c1790f85f4198c7fe19db9751c02",
"cards": {
"start": "withheld-source-changed",
"identity": "withheld-source-changed"
},
"direct_source_review": [
"net-kingdom/AGENTS.md",
"net-kingdom/INTENT.md",
"net-kingdom/SECURITY-COMPANION.md",
"ops-warden/AGENTS.md",
"ops-warden/INTENT.md",
"ops-warden frozen case_run admission guard"
]
},
"scope": "Only successor test_admit_1_3_type.py coverage. Fixture type status, map and parameter pin are memory-only test setup. No acknowledgment, pin, catalog, canonical kernel, native capability or owner acceptance change. F2/F3 and independent kernel/catalog/owner reviews remain open.",
"results": [
{
"name": "preimage-frozen",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-frozen/src",
"test_sha256": "f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5",
"kernel_case_run_sha256": "77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768",
"exit_code": 0,
"expected_exit_code": 0
},
{
"name": "preimage-mutant",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-admission-guard-removed/src",
"test_sha256": "f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5",
"kernel_case_run_sha256": "6b0d61fb0da4737f305bb959524540752fbcf0f2993a7969bd7e368bdd03ea5a",
"exit_code": 0,
"expected_exit_code": 0
},
{
"name": "successor-frozen",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-frozen/src",
"test_sha256": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"kernel_case_run_sha256": "77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768",
"exit_code": 0,
"expected_exit_code": 0
},
{
"name": "successor-mutant",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-admission-guard-removed/src",
"test_sha256": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"kernel_case_run_sha256": "6b0d61fb0da4737f305bb959524540752fbcf0f2993a7969bd7e368bdd03ea5a",
"exit_code": 1,
"expected_exit_code": 1
}
]
}

View file

@ -0,0 +1,19 @@
{
"status": "prepared-tested-not-applied",
"files": {
"implementation.json": "33d51784fe3ab7d9307292a265b0e684a8bc2fc77564ba58099833e41c849306",
"reproduce-scratch-checks.py": "8f0b01e606c03b3b27b178df60eab664a758df6fa3f9b1bc62ef8b58122c2cd8",
"successor-mutant.txt": "9f03083de5863b3855e533b94671cfbab6fd0dd565e0a0b8c385d51d9510d97c",
"README.md": "78a4dbef0088f18db2b9c1dbe701b3e8a2c20516ae6d2039420a82b8d6b1fe66",
"preimage-frozen.txt": "63dd3d754896c0e8578d82f98ae6f8e460a6ac43f3937a95fa61f65f8aa4accf",
"test_admit_1_3_type.after.py": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"scratch.json": "7e329154c5c60528d9f4387f0bbc9cfb4167c79decd49b14497e67d59e21e469",
"successor-frozen.txt": "275324dcf33eb852eb467832ff18fe9dd09f09d64d6ac37b43bf01766ea745d3",
"canonical-net-kingdom-status-after.txt": "354b3dd65c711df0855c22ba053a4ca295d5491941c5a64c1157a0b7d43cf4cb",
"scratch-kernel-mutation.patch": "dd12c63bd331b8f127149c402077e49fc66d62a452f48044486f58e79e0e29f7",
"test-results.json": "2aab986ed96fb9fa683992051ffe6ec7b2a9327d816542b908dcc66f9dd8df23",
"preimage-mutant.txt": "6c823ed279c355a34169d7753817256890d56fe4a1996ea59aa01fb19e5a5e1f",
"test-admit-1-3-agent-mint-coverage.patch": "5bc1da176836eea65195cfe5fae965284c1f38809a502a0be58c231b60111a75",
"test_admit_1_3_type.before.py": "f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5"
}
}

View file

@ -0,0 +1,2 @@
........ [100%]
8 passed, 31 subtests passed in 4.98s

View file

@ -0,0 +1,2 @@
........ [100%]
8 passed, 31 subtests passed in 4.90s

View file

@ -0,0 +1,30 @@
from pathlib import Path
import os,json,subprocess,shutil,hashlib,difflib
packet=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1')
s=json.loads((packet/'scratch.json').read_text());scratch=Path(s['scratch_root']);nk=scratch/'net-kingdom';frozen=scratch/'warden-frozen';mutant=scratch/'warden-admission-guard-removed';shutil.copytree(frozen,mutant)
p=mutant/'src/warden/case_run.py';before=p.read_text()
guard=''' if descriptor.effect == "mint" and (
actor != "human" or os.getenv("WARDEN_AGENT_ID")
):
raise Refused("agent mint is forbidden")
'''
assert before.count(guard)==1
p.write_text(before.replace(guard,''))
(packet/'scratch-kernel-mutation.patch').write_text(''.join(difflib.unified_diff(before.splitlines(True),p.read_text().splitlines(True),fromfile='frozen/src/warden/case_run.py',tofile='scratch-mutant/src/warden/case_run.py')))
test=nk/s['target'];successor=test.read_bytes();original=(packet/'test_admit_1_3_type.before.py').read_bytes()
results=[]
for name,kernel,source,expected in [('preimage-frozen',frozen,original,0),('preimage-mutant',mutant,original,0),('successor-frozen',frozen,successor,0),('successor-mutant',mutant,successor,1)]:
test.write_bytes(source)
env=os.environ.copy();env['PYTHONDONTWRITEBYTECODE']='1';env['PYTHONPATH']=str(kernel/'src')
cmd=['/home/worsch/ops-warden/.venv/bin/python','-m','pytest','-q','-p','no:cacheprovider',test.name]
run=subprocess.run(cmd,cwd=test.parent,env=env,capture_output=True,text=True)
(packet/(name+'.txt')).write_text(run.stdout+run.stderr)
results.append({'name':name,'command':cmd,'cwd':str(test.parent),'pythonpath':env['PYTHONPATH'],'test_sha256':hashlib.sha256(source).hexdigest(),'kernel_case_run_sha256':hashlib.sha256((kernel/'src/warden/case_run.py').read_bytes()).hexdigest(),'exit_code':run.returncode,'expected_exit_code':expected})
print(name,run.returncode,run.stdout.splitlines()[-1],flush=True)
assert run.returncode==expected
if name=='successor-mutant':
assert 'test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor' in run.stdout
assert 'test_human_mint_is_refused_with_agent_runtime_identity' in run.stdout
assert '2 failed, 8 passed' in run.stdout
test.write_bytes(successor)
(packet/'test-results.json').write_text(json.dumps(results,indent=2)+'\n')

View file

@ -0,0 +1,13 @@
--- frozen/src/warden/case_run.py
+++ scratch-mutant/src/warden/case_run.py
@@ -646,10 +646,6 @@
or lane != descriptor.lane
):
raise Refused("principal actor or lane mismatch")
- if descriptor.effect == "mint" and (
- actor != "human" or os.getenv("WARDEN_AGENT_ID")
- ):
- raise Refused("agent mint is forbidden")
parameters = case.get("parameters", {})
declared = {
item["name"]: item

View file

@ -0,0 +1,6 @@
{
"scratch_root": "/tmp/nk48-r5-f1-ahudwokt",
"target": "sso-mfa/k8s/keycape/test_admit_1_3_type.py",
"net_kingdom_commit": "3236d5000e52e0643f33906a26a7825aa0b67465",
"warden_commit": "117fec9241e7b985dfb187b992e173d0a5dc3700"
}

View file

@ -0,0 +1,2 @@
.......... [100%]
10 passed, 32 subtests passed in 5.33s

View file

@ -0,0 +1,30 @@
F....F.... [100%]
=================================== FAILURES ===================================
_ Admit13TypeTests.test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor _
self = <test_admit_1_3_type.Admit13TypeTests testMethod=test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor>
def test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor(self):
# A committed-map mismatch cannot exercise the independent mint guard.
mint = replace(self.descriptors['mint-secret'], actor='agent')
typ = yaml.safe_load(with_status('acknowledged'))
typ['spec']['execution_descriptors']['mint-secret'] = digest(c.encode(mint))
> with pinned_13(), self.assertRaisesRegex(Refused, 'agent mint is forbidden'):
E AssertionError: Refused not raised
test_admit_1_3_type.py:150: AssertionError
___ Admit13TypeTests.test_human_mint_is_refused_with_agent_runtime_identity ____
self = <test_admit_1_3_type.Admit13TypeTests testMethod=test_human_mint_is_refused_with_agent_runtime_identity>
def test_human_mint_is_refused_with_agent_runtime_identity(self):
mint = self.descriptors['mint-secret']
self.assertEqual(mint.actor, 'human')
> with pinned_13(), self.assertRaisesRegex(Refused, 'agent mint is forbidden'):
E AssertionError: Refused not raised
test_admit_1_3_type.py:156: AssertionError
=========================== short test summary info ============================
FAILED test_admit_1_3_type.py::Admit13TypeTests::test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor
FAILED test_admit_1_3_type.py::Admit13TypeTests::test_human_mint_is_refused_with_agent_runtime_identity
2 failed, 8 passed, 32 subtests passed in 5.72s

View file

@ -0,0 +1,68 @@
--- a/sso-mfa/k8s/keycape/test_admit_1_3_type.py
+++ b/sso-mfa/k8s/keycape/test_admit_1_3_type.py
@@ -1,9 +1,9 @@
"""Kernel admission against the real 1.3 successor type (NK-WP-0048-T16 candidate).
-Unlike test_admit_1_2_adapter_descriptors.py, nothing is patched into the type's
-execution_descriptors: the map tested here is the one committed in
-security-case-type-revisions/1.3. The type is `proposed`, so the kernel must refuse
-it as it stands. Warden's BrowserInputs also pins each type version's parameter
+The committed-map tests use security-case-type-revisions/1.3 unchanged. One
+separate fixture-map test acknowledges only an agent-actor mint descriptor digest
+to reach the independent kernel mint prohibition after map validation. The type
+is `proposed`, so the kernel must refuse it as it stands. Warden's BrowserInputs also pins each type version's parameter
fingerprint and knows only 1.1 and 1.2 (ops-warden case_inputs.PARAMETER_PINS), so an
acknowledged 1.3 is still refused until ops-warden adds a 1.3 pin. That is an
ops-warden owner change, and it is not made here. The tests show that 1.3's
@@ -74,7 +74,7 @@
ARTIFACT['params']['consumer_owner_repo'],
version='1.3'))
- def admit(self, desc, type_source, *, actor=None, lane=None):
+ def admit(self, desc, type_source, *, actor=None, lane=None, agent_id=None):
desc_bytes = c.encode(desc)
adapter = Adapter(desc.adapter, desc.adapter_source, unused, unused)
executor = Executor(Journal(Path(self.temp.name) / desc.step),
@@ -82,7 +82,10 @@
lambda purpose, source, evidence: evidence in EVIDENCE,
browser_inputs=inputs.BrowserInputs(REPO))
with mock.patch.dict(os.environ):
- os.environ.pop('WARDEN_AGENT_ID', None)
+ if agent_id is None:
+ os.environ.pop('WARDEN_AGENT_ID', None)
+ else:
+ os.environ['WARDEN_AGENT_ID'] = agent_id
return executor.admit(self.case_source, type_source, desc_bytes,
b'fixture-only-catalog', principal='founder', actor=actor or desc.actor,
lane=lane or desc.lane,
@@ -125,7 +128,8 @@
def test_descriptor_outside_the_map_is_refused(self):
acknowledged = with_status('acknowledged')
mint = self.descriptors['mint-secret']
- for changed in (replace(mint, lane='other-lane'), replace(mint, retry_safe=True)):
+ for changed in (replace(mint, lane='other-lane'), replace(mint, retry_safe=True),
+ replace(mint, actor='agent')):
with self.subTest(changed=changed), pinned_13(), self.assertRaisesRegex(
Refused, 'no exact acknowledged machine descriptor'):
self.admit(changed, acknowledged)
@@ -137,6 +141,20 @@
self.assertEqual((mint.effect, mint.actor), ('mint', 'human'))
with pinned_13(), self.assertRaisesRegex(Refused, 'principal actor or lane mismatch'):
self.admit(mint, acknowledged, actor='agent')
+
+ def test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor(self):
+ # A committed-map mismatch cannot exercise the independent mint guard.
+ mint = replace(self.descriptors['mint-secret'], actor='agent')
+ typ = yaml.safe_load(with_status('acknowledged'))
+ typ['spec']['execution_descriptors']['mint-secret'] = digest(c.encode(mint))
+ with pinned_13(), self.assertRaisesRegex(Refused, 'agent mint is forbidden'):
+ self.admit(mint, yaml.safe_dump(typ).encode())
+
+ def test_human_mint_is_refused_with_agent_runtime_identity(self):
+ mint = self.descriptors['mint-secret']
+ self.assertEqual(mint.actor, 'human')
+ with pinned_13(), self.assertRaisesRegex(Refused, 'agent mint is forbidden'):
+ self.admit(mint, with_status('acknowledged'), agent_id='fixture-agent')
def test_wrong_lane_is_refused(self):
# Moved from test_admit_1_2_adapter_descriptors.py in r5.

View file

@ -0,0 +1,74 @@
[
{
"name": "preimage-frozen",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-frozen/src",
"test_sha256": "f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5",
"kernel_case_run_sha256": "77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768",
"exit_code": 0,
"expected_exit_code": 0
},
{
"name": "preimage-mutant",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-admission-guard-removed/src",
"test_sha256": "f4f6a35cfbada462c939fe357e51fa5d03efc9d20c17a511f224c1359ca0faa5",
"kernel_case_run_sha256": "6b0d61fb0da4737f305bb959524540752fbcf0f2993a7969bd7e368bdd03ea5a",
"exit_code": 0,
"expected_exit_code": 0
},
{
"name": "successor-frozen",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-frozen/src",
"test_sha256": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"kernel_case_run_sha256": "77e8117a1fb4e039815b555797778f7180f11e30f64cebe51b350b3eb7e46768",
"exit_code": 0,
"expected_exit_code": 0
},
{
"name": "successor-mutant",
"command": [
"/home/worsch/ops-warden/.venv/bin/python",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"test_admit_1_3_type.py"
],
"cwd": "/tmp/nk48-r5-f1-ahudwokt/net-kingdom/sso-mfa/k8s/keycape",
"pythonpath": "/tmp/nk48-r5-f1-ahudwokt/warden-admission-guard-removed/src",
"test_sha256": "c512ba00513d066fd1ca8af08fd6bf106fbd7a132981b1dcc5af80a745ab03f0",
"kernel_case_run_sha256": "6b0d61fb0da4737f305bb959524540752fbcf0f2993a7969bd7e368bdd03ea5a",
"exit_code": 1,
"expected_exit_code": 1
}
]

View file

@ -0,0 +1,175 @@
"""Kernel admission against the real 1.3 successor type (NK-WP-0048-T16 candidate).
The committed-map tests use security-case-type-revisions/1.3 unchanged. One
separate fixture-map test acknowledges only an agent-actor mint descriptor digest
to reach the independent kernel mint prohibition after map validation. The type
is `proposed`, so the kernel must refuse it as it stands. Warden's BrowserInputs also pins each type version's parameter
fingerprint and knows only 1.1 and 1.2 (ops-warden case_inputs.PARAMETER_PINS), so an
acknowledged 1.3 is still refused until ops-warden adds a 1.3 pin. That is an
ops-warden owner change, and it is not made here. The tests show that 1.3's
parameters carry 1.2's pinned fingerprint, and that with only that one pin added in
memory the committed map admits exactly the composed descriptors and nothing else.
Trust is fixture-only, nothing is dispatched, and none of this is owner admission
or an acknowledgment.
"""
from dataclasses import replace
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import tempfile
import unittest
from unittest import mock
import yaml
from warden.case_run import Adapter, Executor, Journal, digest
from warden.case_receipt_state import Refused
import warden.case_inputs as inputs
ROOT = Path(__file__).parent
REPO = ROOT.parents[2]
spec = importlib.util.spec_from_file_location('compose_13_admit', ROOT / 'compose-1.2-descriptors.py')
c = importlib.util.module_from_spec(spec)
spec.loader.exec_module(c)
TYPE_DIR = REPO / 'security-case-type-revisions/1.3/oidc-browser-relying-party'
TYPE_BYTES = (TYPE_DIR / 'type.yaml').read_bytes()
ARTIFACT = json.loads((ROOT / 'descriptor-composition-1.3.candidate.json').read_text())
EVIDENCE = {'fixture-acceptance', 'fixture-materialization', 'fixture-descriptor', 'fixture-session'}
def unused(*_):
raise AssertionError('admission must not dispatch')
def parameter_fingerprint(type_bytes):
params = yaml.safe_load(type_bytes)['spec']['parameters']
return hashlib.sha256(json.dumps(params, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
def pinned_13():
"""Warden's parameter pins plus the one 1.3 entry ops-warden would have to add."""
return mock.patch.dict(inputs.PARAMETER_PINS, {'1.3': parameter_fingerprint(TYPE_BYTES)})
def with_status(status):
"""The committed 1.3 type with only its status changed."""
typ = yaml.safe_load(TYPE_BYTES)
typ['spec']['status'] = status
return yaml.safe_dump(typ).encode()
class Admit13TypeTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
case = yaml.safe_load((TYPE_DIR / 'examples/forgejo.yaml').read_bytes())
case['spec'].update(status='materialized', acceptance={
'at': '2026-10-17', 'record': 'fixture-only acceptance, not an owner record',
'by': [{'name': 'fixture', 'role': 'owner'}, {'name': 'fixture', 'role': 'founder'}]})
self.case_source = yaml.safe_dump(case).encode()
self.descriptors = dict(c.adapter_descriptors(ARTIFACT['check_config_digest'],
ARTIFACT['params']['consumer_owner_repo'],
version='1.3'))
def admit(self, desc, type_source, *, actor=None, lane=None, agent_id=None):
desc_bytes = c.encode(desc)
adapter = Adapter(desc.adapter, desc.adapter_source, unused, unused)
executor = Executor(Journal(Path(self.temp.name) / desc.step),
{adapter.reference: adapter},
lambda purpose, source, evidence: evidence in EVIDENCE,
browser_inputs=inputs.BrowserInputs(REPO))
with mock.patch.dict(os.environ):
if agent_id is None:
os.environ.pop('WARDEN_AGENT_ID', None)
else:
os.environ['WARDEN_AGENT_ID'] = agent_id
return executor.admit(self.case_source, type_source, desc_bytes,
b'fixture-only-catalog', principal='founder', actor=actor or desc.actor,
lane=lane or desc.lane,
acceptance_evidence='fixture-acceptance',
materialization_evidence='fixture-materialization',
descriptor_evidence='fixture-descriptor', session_evidence='fixture-session')
def test_committed_map_is_exactly_the_composed_set(self):
committed = yaml.safe_load(TYPE_BYTES)['spec']['execution_descriptors']
composed = {s: digest(c.encode(d)) for s, d in self.descriptors.items()}
self.assertEqual(len(composed), 7)
self.assertEqual(committed, composed)
self.assertEqual(committed, ARTIFACT['execution_descriptors'])
def test_proposed_type_is_refused_as_it_stands(self):
self.assertEqual(yaml.safe_load(TYPE_BYTES)['spec']['status'], 'proposed')
for step, desc in self.descriptors.items():
with self.subTest(step=step), self.assertRaisesRegex(Refused, 'type is not acknowledged'):
self.admit(desc, TYPE_BYTES)
def test_warden_has_no_1_3_parameter_pin_yet(self):
self.assertNotIn('1.3', inputs.PARAMETER_PINS)
# 1.3 keeps 1.2's parameters, so the missing pin is 1.2's fingerprint.
self.assertEqual(parameter_fingerprint(TYPE_BYTES), inputs.PARAMETER_PINS['1.2'])
acknowledged = with_status('acknowledged')
for step, desc in self.descriptors.items():
with self.subTest(step=step), self.assertRaisesRegex(
Refused, 'failed exact-version validation'):
self.admit(desc, acknowledged)
def test_committed_map_admits_every_descriptor_once_acknowledged_and_pinned(self):
acknowledged = with_status('acknowledged')
for step, desc in self.descriptors.items():
with self.subTest(step=step), pinned_13():
admitted, invocation, _ = self.admit(desc, acknowledged)
self.assertEqual(admitted, desc)
self.assertEqual((invocation.step, invocation.actor, invocation.target),
(desc.step, desc.actor, 'forgejo'))
def test_descriptor_outside_the_map_is_refused(self):
acknowledged = with_status('acknowledged')
mint = self.descriptors['mint-secret']
for changed in (replace(mint, lane='other-lane'), replace(mint, retry_safe=True),
replace(mint, actor='agent')):
with self.subTest(changed=changed), pinned_13(), self.assertRaisesRegex(
Refused, 'no exact acknowledged machine descriptor'):
self.admit(changed, acknowledged)
def test_mint_secret_admits_only_a_human_actor(self):
# Moved from test_admit_1_2_adapter_descriptors.py (NK48-R1-F1) in r5.
acknowledged = with_status('acknowledged')
mint = self.descriptors['mint-secret']
self.assertEqual((mint.effect, mint.actor), ('mint', 'human'))
with pinned_13(), self.assertRaisesRegex(Refused, 'principal actor or lane mismatch'):
self.admit(mint, acknowledged, actor='agent')
def test_agent_mint_is_refused_after_fixture_map_acknowledges_descriptor(self):
# A committed-map mismatch cannot exercise the independent mint guard.
mint = replace(self.descriptors['mint-secret'], actor='agent')
typ = yaml.safe_load(with_status('acknowledged'))
typ['spec']['execution_descriptors']['mint-secret'] = digest(c.encode(mint))
with pinned_13(), self.assertRaisesRegex(Refused, 'agent mint is forbidden'):
self.admit(mint, yaml.safe_dump(typ).encode())
def test_human_mint_is_refused_with_agent_runtime_identity(self):
mint = self.descriptors['mint-secret']
self.assertEqual(mint.actor, 'human')
with pinned_13(), self.assertRaisesRegex(Refused, 'agent mint is forbidden'):
self.admit(mint, with_status('acknowledged'), agent_id='fixture-agent')
def test_wrong_lane_is_refused(self):
# Moved from test_admit_1_2_adapter_descriptors.py in r5.
acknowledged = with_status('acknowledged')
for step, desc in self.descriptors.items():
with self.subTest(step=step), pinned_13(), self.assertRaisesRegex(
Refused, 'principal actor or lane mismatch'):
self.admit(desc, acknowledged, lane='wrong-lane')
def test_earlier_versions_carry_no_execution_map(self):
for version in ('1.2',):
path = REPO / f'security-case-type-revisions/{version}/oidc-browser-relying-party/type.yaml'
with self.subTest(version=version):
self.assertNotIn('execution_descriptors', yaml.safe_load(path.read_bytes())['spec'])
if __name__ == '__main__':
unittest.main()