docs: preserve reviewed NetKingdom session artifacts
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s
Reject generated-only commits / generated-commit-guard (push) Successful in 14s

This commit is contained in:
codex 2026-11-01 22:49:34 +01:00
parent 042ac52ac2
commit 0c82a71aed
46 changed files with 5672 additions and 0 deletions

View file

@ -0,0 +1,20 @@
/tmp/nk-checker-artifact-next-r3/keycape: go1.23.0
path keycape/cmd/keycape
mod keycape (devel)
dep github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
dep github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
dep github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
dep github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
dep github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
dep github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
dep github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
dep golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
dep golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
dep gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
build -buildmode=exe
build -compiler=gc
build -trimpath=true
build CGO_ENABLED=0
build GOARCH=amd64
build GOOS=linux
build GOAMD64=v1

View file

@ -0,0 +1,45 @@
"""Isolated offline source build; never reads runtime configuration/credentials."""
from pathlib import Path
import hashlib,io,json,os,subprocess,tarfile
packet=Path(__file__).resolve().parent;owner=Path('/home/worsch/key-cape');target=Path('/tmp/nk-checker-artifact-next-r3');target.mkdir(exist_ok=False)
go=Path('/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.linux-amd64/bin/go')
sha=lambda data:'sha256:'+hashlib.sha256(data).hexdigest()
git=lambda *args:subprocess.check_output(['git','-C',str(owner),*args])
revision=git('rev-parse','HEAD').decode().strip()
if git('diff','--name-only','HEAD','--','src'):raise SystemExit('canonical source dirty')
origin=git('rev-parse','origin/main').decode().strip()
if origin!=revision:raise SystemExit('source not equal published origin')
archive=git('archive','--format=tar',revision,'src');(target/'source.tar').write_bytes(archive)
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:tar.extractall(target,filter='data')
source=target/'src';binary=target/'keycape';toolchain=go.parent.parent
buildenv={'PATH':str(go.parent)+':/usr/bin:/bin','HOME':str(target),'TMPDIR':str(target),'GOTOOLCHAIN':'local','GOPROXY':'off','GOSUMDB':'off','GOMODCACHE':'/home/worsch/go/pkg/mod','GOCACHE':str(target/'go-build-cache'),'CGO_ENABLED':'0','GOOS':'linux','GOARCH':'amd64','GOAMD64':'v1','GOFLAGS':'-mod=readonly'}
version=subprocess.check_output([str(go),'version'],env=buildenv,text=True).strip()
command=[str(go),'build','-trimpath','-buildvcs=false','-o',str(binary),'./cmd/keycape']
result=subprocess.run(command,cwd=source,env=buildenv,capture_output=True,text=True)
(packet/'build.stdout.txt').write_text(result.stdout);(packet/'build.stderr.txt').write_text(result.stderr)
manifest={'schema':'netkingdom.offline-checker-artifact.v1','keycape_revision':revision,'keycape_published_origin':origin,'keycape_source_tree':git('rev-parse',revision+':src').decode().strip(),'archive_sha256':sha(archive),'worktree':str(target),'toolchain':version,'go_binary':str(go),'command':command,'environment':buildenv,'exit_code':result.returncode,'native_admission':False,'owner_endorsement':False,'production_config_access':False}
(packet/'build-receipt.json').write_text(json.dumps(manifest,indent=2)+'\n')
if result.returncode:raise SystemExit(result.returncode)
# Bind the entire archived source and installed toolchain; module dependency
# closure is obtained from the exact same offline module/build configuration.
def files(root):return {str(p.relative_to(root)):sha(p.read_bytes()) for p in sorted(root.rglob('*')) if p.is_file()}
closure={'source':files(source),'toolchain':files(toolchain)}
modules=subprocess.check_output([str(go),'list','-buildvcs=false','-deps','-json','./cmd/keycape'],cwd=source,env=buildenv,text=True)
(target/'packages.json-stream').write_text(modules)
decoder=json.JSONDecoder();remaining=modules;selected=[];seen=set();packages=[]
while remaining.strip():
package,end=decoder.raw_decode(remaining.lstrip());remaining=remaining.lstrip()[end:]
packages.append({'import_path':package['ImportPath'],'go_files':package.get('GoFiles',[]),
'embed_files':package.get('EmbedFiles',[]),'imports':package.get('Imports',[])})
module=package.get('Module')
if module and not module.get('Main') and module['Path'] not in seen:
seen.add(module['Path']);directory=module.get('Dir')
if not directory:raise SystemExit('offline module source missing '+module['Path'])
selected.append({'path':module['Path'],'version':module['Version'],
'go_mod_sum':module.get('GoModSum'),'sum':module.get('Sum'),'files':files(Path(directory))})
closure['built_packages']=packages
closure['modules']=selected
(packet/'source-toolchain-module-closure.json').write_text(json.dumps(closure,sort_keys=True,indent=2)+'\n')
manifest.update(artifact_sha256=sha(binary.read_bytes()),artifact_bytes=binary.stat().st_size,closure_sha256=sha((packet/'source-toolchain-module-closure.json').read_bytes()),module_count=len(selected),toolchain_file_count=len(closure['toolchain']),source_file_count=len(closure['source']))
(packet/'build-receipt.json').write_text(json.dumps(manifest,indent=2)+'\n')
print(json.dumps({k:manifest[k] for k in ('keycape_revision','toolchain','artifact_sha256','artifact_bytes','module_count','toolchain_file_count','source_file_count')}))

View file

@ -0,0 +1,40 @@
"""Isolated offline source build; never reads runtime configuration/credentials."""
from pathlib import Path
import hashlib,io,json,os,subprocess,tarfile
packet=Path(__file__).resolve().parent;owner=Path('/home/worsch/key-cape');target=Path('/tmp/nk-checker-artifact-next');target.mkdir(exist_ok=False)
go=Path('/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.linux-amd64/bin/go')
sha=lambda data:'sha256:'+hashlib.sha256(data).hexdigest()
git=lambda *args:subprocess.check_output(['git','-C',str(owner),*args])
revision=git('rev-parse','HEAD').decode().strip()
if git('diff','--name-only','HEAD','--','src'):raise SystemExit('canonical source dirty')
origin=git('rev-parse','origin/main').decode().strip()
if origin!=revision:raise SystemExit('source not equal published origin')
archive=git('archive','--format=tar',revision,'src');(target/'source.tar').write_bytes(archive)
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:tar.extractall(target,filter='data')
source=target/'src';binary=target/'keycape';toolchain=go.parent.parent
buildenv={'PATH':str(go.parent)+':/usr/bin:/bin','HOME':str(target),'TMPDIR':str(target),'GOTOOLCHAIN':'local','GOPROXY':'off','GOSUMDB':'off','GOMODCACHE':'/home/worsch/go/pkg/mod','GOCACHE':str(target/'go-build-cache'),'CGO_ENABLED':'0','GOOS':'linux','GOARCH':'amd64','GOAMD64':'v1','GOFLAGS':'-mod=readonly'}
version=subprocess.check_output([str(go),'version'],env=buildenv,text=True).strip()
command=[str(go),'build','-trimpath','-buildvcs=false','-o',str(binary),'./cmd/keycape']
result=subprocess.run(command,cwd=source,env=buildenv,capture_output=True,text=True)
(packet/'build.stdout.txt').write_text(result.stdout);(packet/'build.stderr.txt').write_text(result.stderr)
manifest={'schema':'netkingdom.offline-checker-artifact.v1','keycape_revision':revision,'keycape_published_origin':origin,'keycape_source_tree':git('rev-parse',revision+':src').decode().strip(),'archive_sha256':sha(archive),'worktree':str(target),'toolchain':version,'go_binary':str(go),'command':command,'environment':buildenv,'exit_code':result.returncode,'native_admission':False,'owner_endorsement':False,'production_config_access':False}
(packet/'build-receipt.json').write_text(json.dumps(manifest,indent=2)+'\n')
if result.returncode:raise SystemExit(result.returncode)
# Bind the entire archived source and installed toolchain; module dependency
# closure is obtained from the exact same offline module/build configuration.
def files(root):return {str(p.relative_to(root)):sha(p.read_bytes()) for p in sorted(root.rglob('*')) if p.is_file()}
closure={'source':files(source),'toolchain':files(toolchain)}
modules=subprocess.check_output([str(go),'list','-m','-json','all'],cwd=source,env=buildenv,text=True)
(target/'modules.json-stream').write_text(modules)
decoder=json.JSONDecoder();remaining=modules;selected=[]
while remaining.strip():
module,end=decoder.raw_decode(remaining.lstrip());remaining=remaining.lstrip()[end:]
if not module.get('Main'):
directory=module.get('Dir')
if not directory:raise SystemExit('offline module source missing '+module['Path'])
selected.append({'path':module['Path'],'version':module['Version'],'go_mod_sum':module.get('GoModSum'),'sum':module.get('Sum'),'files':files(Path(directory))})
closure['modules']=selected
(packet/'source-toolchain-module-closure.json').write_text(json.dumps(closure,sort_keys=True,indent=2)+'\n')
manifest.update(artifact_sha256=sha(binary.read_bytes()),artifact_bytes=binary.stat().st_size,closure_sha256=sha((packet/'source-toolchain-module-closure.json').read_bytes()),module_count=len(selected),toolchain_file_count=len(closure['toolchain']),source_file_count=len(closure['source']))
(packet/'build-receipt.json').write_text(json.dumps(manifest,indent=2)+'\n')
print(json.dumps({k:manifest[k] for k in ('keycape_revision','toolchain','artifact_sha256','artifact_bytes','module_count','toolchain_file_count','source_file_count')}))

View file

@ -0,0 +1,45 @@
"""Isolated offline source build; never reads runtime configuration/credentials."""
from pathlib import Path
import hashlib,io,json,os,subprocess,tarfile
packet=Path(__file__).resolve().parent;owner=Path('/home/worsch/key-cape');target=Path('/tmp/nk-checker-artifact-next-r2');target.mkdir(exist_ok=False)
go=Path('/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.0.linux-amd64/bin/go')
sha=lambda data:'sha256:'+hashlib.sha256(data).hexdigest()
git=lambda *args:subprocess.check_output(['git','-C',str(owner),*args])
revision=git('rev-parse','HEAD').decode().strip()
if git('diff','--name-only','HEAD','--','src'):raise SystemExit('canonical source dirty')
origin=git('rev-parse','origin/main').decode().strip()
if origin!=revision:raise SystemExit('source not equal published origin')
archive=git('archive','--format=tar',revision,'src');(target/'source.tar').write_bytes(archive)
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:tar.extractall(target,filter='data')
source=target/'src';binary=target/'keycape';toolchain=go.parent.parent
buildenv={'PATH':str(go.parent)+':/usr/bin:/bin','HOME':str(target),'TMPDIR':str(target),'GOTOOLCHAIN':'local','GOPROXY':'off','GOSUMDB':'off','GOMODCACHE':'/home/worsch/go/pkg/mod','GOCACHE':str(target/'go-build-cache'),'CGO_ENABLED':'0','GOOS':'linux','GOARCH':'amd64','GOAMD64':'v1','GOFLAGS':'-mod=readonly'}
version=subprocess.check_output([str(go),'version'],env=buildenv,text=True).strip()
command=[str(go),'build','-trimpath','-buildvcs=false','-o',str(binary),'./cmd/keycape']
result=subprocess.run(command,cwd=source,env=buildenv,capture_output=True,text=True)
(packet/'build.stdout.txt').write_text(result.stdout);(packet/'build.stderr.txt').write_text(result.stderr)
manifest={'schema':'netkingdom.offline-checker-artifact.v1','keycape_revision':revision,'keycape_published_origin':origin,'keycape_source_tree':git('rev-parse',revision+':src').decode().strip(),'archive_sha256':sha(archive),'worktree':str(target),'toolchain':version,'go_binary':str(go),'command':command,'environment':buildenv,'exit_code':result.returncode,'native_admission':False,'owner_endorsement':False,'production_config_access':False}
(packet/'build-receipt.json').write_text(json.dumps(manifest,indent=2)+'\n')
if result.returncode:raise SystemExit(result.returncode)
# Bind the entire archived source and installed toolchain; module dependency
# closure is obtained from the exact same offline module/build configuration.
def files(root):return {str(p.relative_to(root)):sha(p.read_bytes()) for p in sorted(root.rglob('*')) if p.is_file()}
closure={'source':files(source),'toolchain':files(toolchain)}
modules=subprocess.check_output([str(go),'list','-deps','-json','./cmd/keycape'],cwd=source,env=buildenv,text=True)
(target/'packages.json-stream').write_text(modules)
decoder=json.JSONDecoder();remaining=modules;selected=[];seen=set();packages=[]
while remaining.strip():
package,end=decoder.raw_decode(remaining.lstrip());remaining=remaining.lstrip()[end:]
packages.append({'import_path':package['ImportPath'],'go_files':package.get('GoFiles',[]),
'embed_files':package.get('EmbedFiles',[]),'imports':package.get('Imports',[])})
module=package.get('Module')
if module and not module.get('Main') and module['Path'] not in seen:
seen.add(module['Path']);directory=module.get('Dir')
if not directory:raise SystemExit('offline module source missing '+module['Path'])
selected.append({'path':module['Path'],'version':module['Version'],
'go_mod_sum':module.get('GoModSum'),'sum':module.get('Sum'),'files':files(Path(directory))})
closure['built_packages']=packages
closure['modules']=selected
(packet/'source-toolchain-module-closure.json').write_text(json.dumps(closure,sort_keys=True,indent=2)+'\n')
manifest.update(artifact_sha256=sha(binary.read_bytes()),artifact_bytes=binary.stat().st_size,closure_sha256=sha((packet/'source-toolchain-module-closure.json').read_bytes()),module_count=len(selected),toolchain_file_count=len(closure['toolchain']),source_file_count=len(closure['source']))
(packet/'build-receipt.json').write_text(json.dumps(manifest,indent=2)+'\n')
print(json.dumps({k:manifest[k] for k in ('keycape_revision','toolchain','artifact_sha256','artifact_bytes','module_count','toolchain_file_count','source_file_count')}))

View file

@ -0,0 +1,24 @@
{
"schema": "netkingdom.checker-artifact-proposal.v1",
"owner": "net-kingdom",
"native_admission": false,
"canonical_writes": false,
"work_record_preimage": "sha256:ae4293e3b90aa6a58bb31c72ff9391be1a57bb877fca2c067d31b9625a79b74e",
"work_record_postimage": "sha256:fa6fc927552c0b0c3b3131f233a723ec26c0fc02fe326a13bf104dc61b66741b",
"files": {
"work-record-proposal.patch": "sha256:17bb96d302df82f885d6d974018b9c1f8ad756a116cae0233fad2d482954d4e9",
"real-checker-tests.txt": "sha256:ba06bd71319c0469d3d02e53c0186af6e3c2db765a034c67306294ff9ef422dd",
"repeated-build-artifacts.json": "sha256:0ea6d4f7a7e8620f720cd35a7118447c6b7925c444daf27eff80a05165438ddd",
"test_real_checker_adapter.py": "sha256:0b9484fb5e6aab5b3e5f2f05a47d01d09362fd2631708f55938e5e23c09eb396",
"build.stderr.txt": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"README.md": "sha256:0df0fd7f263481ef706433c320af0d908f1b763557c3b0f5e4beca57408d7d5a",
"build_checker.py": "sha256:ea58117252e7cc5328307d3551580dc251780db12e9e8c1600de88633ea24179",
"build-receipt.json": "sha256:cc227c17375cd89edb9a1b0304e2cf277a8c8d46370cd71f1c071da57931d3de",
"verification.json": "sha256:1655501a3124cbb3ebd88cd440d96592ccfd1138e01724b79eaafff69031e464",
"minimal-fixture-refusal.json": "sha256:a2c58e8f1e7275c79fa56476f3464da4e5a55140579a2ec2d9de32d9ec2cc331",
"build.stdout.txt": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"artifact-build-info.txt": "sha256:025391cc0fc013352783eedadd34a9bdaf6dc6cf10d5d2cebec7099598a8300a",
"source-toolchain-module-closure.json": "sha256:cb2f8ce0a3ee1efb7099ce00d139ebe00a23a8b356fba160ab8c4b067f5818f9",
"proposed-descriptor.json": "sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3"
}
}

View file

@ -0,0 +1,10 @@
{
"scope": "existing incomplete synthetic fixture checked by real checker, no production configuration",
"valid": false,
"errors": [
"port: must be between 1 and 65535, got 0",
"clients[1] (secrets-engine-approval): redirect_uri: at least one redirectUri must be registered",
"privateKeyPem: path must not be empty"
],
"disposition": "complete synthetic fixture only; no native defect asserted"
}

View file

@ -0,0 +1,786 @@
---
id: NK-WP-0048
type: workplan
title: "Establish SecurityCaseTypes so that known security cases are light"
domain: infotech
repo: net-kingdom
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-16"
depends_on: []
related_repos:
- ops-mason
- ops-warden
- key-cape
- railiance-platform
- informed-decision
state_hub_workstream_id: "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
---
# NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
## Why
The founder's direction (2026-10-08): setting up a security integration of a
kind that has been done before should be easy, meaning accept the case, then
ops-mason arranges it and ops-warden follows through. Setting up a new kind
needs ample documentation and tests and an informed-decision, because its shape
is part of the security architecture.
Today every integration is hand-built: per-client helper scripts, per-lane
custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC
client), whose next step was going to be yet another hand-written rollout
script.
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
into **SecurityCaseTypes** that ops-mason and ops-warden can execute. Canon:
`canon/standards/security-case-types_v0.1.md` (proposed).
Companion plans: ops-mason `MASON-WP-0010` (materialization) and ops-warden
`WARDEN-WP-0044` (follow-through).
## Tasks
```task
id: NK-WP-0048-T01
status: done
priority: high
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
```
### T01 — Accept the terminology and the canon
Founder review of `canon/standards/security-case-types_v0.1.md`, including its
section 10 open questions. On acceptance the canon status goes from `proposed`
to `accepted`.
2026-10-08: accepted by the founder (in session). Canon status `accepted`;
section 10's open questions are carried forward to the first type (T04, T05).
```task
id: NK-WP-0048-T02
status: done
priority: high
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
```
### T02 — Inventory the de-facto types already built by hand
Read-only. List every security integration built so far and group them by shape.
Known starting points:
- browser OIDC relying parties on key-cape: informed-decision-approver,
coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
- confidential service clients with `client_credentials`: secrets-engine
approval, requester and openbao clients, approval-engine operator
- workload KV read lanes on OpenBao: the railiance-platform credential lane
designs and CCRs, for example `railiance-backup-offsite-lane`
- short-lived SSH certificates through ops-warden (`warden sign`)
- tunnelled operator access (ops-bridge)
For each: the repositories involved, the hand-built steps, attended human steps,
custody paths, verification evidence, and rollback (if any). Output
`docs/security-case-type-inventory.md`, ranking candidate types by how often the
shape recurs and how much manual work each instance costs today.
2026-10-08: done, `docs/security-case-type-inventory.md`. 36 of 37 CCRs are
already `request_type: workload-kv-read`, a de-facto type reviewed each time as
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
rollout script. Confidential service clients need a registration plus two or
three KV-read CCRs each. A forge read identity is needed in bulk by
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
```task
id: NK-WP-0048-T03
status: done
depends_on: [NK-WP-0048-T01]
priority: high
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
```
### T03 — Schema, template and validator
- `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`
- `security-case-types/_template/` with `type.yaml`, `README.md` and tests to copy
- a validator, extending `tools/security-scenario-composer/`, that checks a type,
checks a case against its type, and renders the case into the composer's
deterministic plan (still `execution.permitted: false`)
Acceptance: the validator rejects missing sections, secret-shaped parameter
values, unpinned providers and cases against non-`implemented` types, with tests
for each.
2026-10-08: done. `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`; `tools/security-case-types/security_case_validator.py`
(`validate-type`, `validate-case`, `render`, reusing the scenario composer);
`security-case-types/_template/` (a valid, runnable example type over
`net-kingdom.local-identity`, with README sections, an example case and tests that
travel with every copy). 31 tests pass, including the composer's existing suite.
```task
id: NK-WP-0048-T04
status: done
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
priority: high
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
```
### T04 — Define the first type: browser OIDC relying party
`security-case-types/oidc-browser-relying-party/`, version 1.0:
- parameters: client id, display name, exact redirect URIs, scopes, client type
(confidential or public), MFA pin, the groups the consuming service maps,
custody path for the secret (confidential only), consuming service and its
owner
- composition: key-cape client registration (key-cape declares it as a playbook
capability), secret minting into custody (railiance-platform), LLDAP group
existence and membership (net-kingdom identity-provisioner), handover to the
consuming service
- materialization (ops-mason) and follow-through (ops-warden) sections
- verification: discovery lists the redirect URI; a test sign-in returns the
expected claims for one human; anonymous access to the custody path is denied
- rollback: remove exactly this client and its custody path
- README: threat model, MFA rationale, why redirect URIs are exact, which steps
still need an attended login and how that is minimised
- reference instances: informed-decision-approver, coulomb-social, user-engine
portal, openbao-admin
Acceptance: complete per canon section 3, tests green, status `proposed`.
2026-10-08: done. `security-case-types/oidc-browser-relying-party/` 1.0 (proposed):
`type.yaml` with 12 parameters (the custody reference is required only for
confidential clients), composition over key-cape's new
`key-cape.oidc-client-registration` declaration (contract PASS, `maturity:
planned`), materialization and follow-through, verification and rollback, all
conditional on client type where it matters. One attended step,
`register-client`, justified by the live registry being a Kubernetes Secret.
README with threat model, alternatives, an honest fit table (portal and vergabe
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
(checked against the accepted request) and a public portal. The validator gained
`when` / `required_when` conditions and provider discovery. 43 tests pass.
ops-mason's and ops-warden's sections are drafted in the type. Their owners
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
(T05).
```task
id: NK-WP-0048-T05
status: done
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
priority: high
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
```
### T05 — Acknowledge the type through informed-decision
Prepare the Decision Memo from the type's README and `type.yaml` and submit it
through informed-decision's production intake. On acknowledgment the type
moves to `acknowledged`.
2026-10-08: Decision Memo prepared. Hub decision `e418263e-a71f-4f01-a970-c87c9e187c09`.
`security-case-types/oidc-browser-relying-party/decision/`: `packet.md` (question,
one act, what acknowledging does not do, the shape, alternatives, attended
steps, limits, evidence), `build_intake.py` and the built `intake.json` (memo
class `decision-record`, review group `net-kingdom-admins`, resolves the hub
decision on answer). The envelope passes informed-decision's own
`validate_submission`. Queuing it is an attended operator step, a write inside
the production dispatcher pod:
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
< security-case-types/oidc-browser-relying-party/decision/intake.json
The answer then appears for review in informed-decision.
2026-10-08: queued by the founder (`pending`, `queued`) after one refusal: the
first envelope's idempotency key was not State Hub's stable
`state-hub.<decision>.<memo>` form, and the builder now validates against both
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
delivered). T05 waits for the founder's answer in informed-decision, which
resolves hub decision `e418263e`.
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
1.0 `proposed`: `secret_ref` is no longer a parameter, the type derives
`platform/workloads/keycape/{client_id}`; policies are
`workload-kv-read-keycape-{client_id}-eso` and
`workload-kv-read-keycape-{client_id}-{consuming_service}`, each with a templated
body (`read` on that one data path); new `openbao_role`
`external-secrets-keycape-{client_id}` (Kubernetes auth, ESO service account,
15m, no default policy); catalog entry `{consuming_service}-keycape-client`;
the two rollback steps on the KV path collapse into one metadata delete. The
schema and validator now require a `body` on policy and role items. 105 tests
pass. Two consequences: the delivered memo describes the type before these
changes (a case-set custody reference, two differently named policies, no ESO
role), so the founder answers on a superseded shape unless the memo is
re-sent; and Forgejo's custody path moves from the accepted request's proposed
`platform/workloads/forgejo/netkingdom-oidc-client` to
`platform/workloads/keycape/forgejo`, which key-cape (KEY-WP-0038) and
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
(WARDEN-WP-0044-T01) is still in progress.
2026-10-08: revised memo `nk-wp-0048-t05-oidc-browser-relying-party-r2` built
(`packet.md`, `intake.json`, packet `sha256:af6435e2…`); it states that it
supersedes the first memo and passes informed-decision's and the dispatcher's
validation. It needs a new memo id because the dispatcher refuses new content
under a delivered idempotency key (`idempotency_conflict`). Queuing it is the
same attended step as above. Both memos serve hub decision `e418263e`; the
first answer resolves it, so the first memo must not stay open: an accept on it
would acknowledge the superseded shape, and a decline would resolve the
decision against r2. Close it with `return` (a reviewer verb that requests a
new revision and does not resolve the hub decision; only accept and decline
do), commenting that r2 supersedes it, then answer r2. `withdraw` exists in
the vocabulary but is not offered to reviewers, and lifecycle `revise` only
applies to lifecycle-registered memos, not intake-delivered ones.
2026-10-09: the founder reports r2 enqueued and the first memo returned. At
close, hub decision `e418263e` still showed `open` (not verified whether r2
was answered or the answer is still in transit; the dispatcher ledger was not
read). ops-warden's review (WARDEN-WP-0044-T01, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`, d470f2e) arrived at
21:25, before r2 was built, and is not in r2. Required before WARDEN-WP-0044-T02:
R1 a per-step `lane` (missing: identity-provisioner caller credential,
railiance-platform broker grant), R2 canon meaning of `warden_executes: true`
(caller proxy, caller identity, no standing ops-warden token; ADR-0002), R3 a
per-step `value_handling`. Refinements R4 (one type-level catalog lane instead
of a per-case entry, which conflicts with ops-mason's item 5), R5 (ESO Ready gate
before bind-env), R6 (exact accepted memberships), R7 (mfa-pinned and
secret-delivered checks). Next: if r2 was accepted, fold R1-R3 and R5-R7 in as
a follow-up and settle R4 between ops-warden and ops-mason before T06; if not,
hold r2, fold them in and build r3.
key-cape and railiance-apps were told about Forgejo's new custody
path.
2026-10-09: r2 was not answered (hub decision `e418263e` open; dispatcher 9
delivered), so r2 is held and r3 replaces it. ops-warden withdrew R4 for R4′
(message a66a9204): one catalog entry per case with its body templated in the
type. Folded in:
- Canon section 3: a narrow meaning of `warden_executes: true` (caller proxy,
caller identity, no standing credential; ops-warden ADR-0002), plus per-step
`lane`, `value_handling` and an optional `ready_check`. A catalog entry body is
now required, like policy and role bodies.
- Schema and validator enforce all of these.
- The type: lanes `identity-provisioner-groups` and
`keycape-client-secret-generate` (both new) and `key-cape-oidc-login`;
`mint-secret` is `generated_in_custody`; a Ready gate on `deliver-secret`;
`ensure-groups` adds exactly the accepted case's memberships; new checks
`secret-delivered` and `mfa-pinned`; the catalog-entry body follows R4′.
- 110 tests pass.
Memo r3 (`nk-wp-0048-t05-oidc-browser-relying-party-r3`, packet
`sha256:726dfc01…`) passes informed-decision's and the dispatcher's validation.
The founder queues it with the same command, returns r2 with a comment that r3
supersedes it, then answers r3.
Prerequisites for T06, not for T05:
- the two new lanes, each through its owner;
- a founder-approved rollout of an identity-provisioner image containing
630ebfd (the deployed one is main-6c4fcaf from 2026-09-24, without
`/v1/groups/ensure`).
ops-mason's request to create LLDAP group `fluid-telegram-operators` (message
259ec26d) was answered: not possible from here by that path; the founder creates
it in the LLDAP admin UI.
2026-10-09: ops-warden finished its side of WARDEN-WP-0044-T01 (message
`58d33f40`, commit 0e7ceb8): the revised type conforms to `warden case
check-type`, with no further changes asked of the type. This acknowledges that
review. Its later addendum R8 (message `af274783`, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`) showed that lane
`identity-provisioner-groups` cannot exist as declared: the provisioner's only
HTTP credential is user-engine's runtime token, and its NetworkPolicy admits
only user-engine pods, so a caller-proxy lane would act as user-engine. Taken
option 1 (ops-warden's recommendation): `ensure-groups` and its rollback are
attended, in the `register-client` session on `key-cape-oidc-login`, run inside
the provisioner pod; no token is read. Option 2 (an operator credential of its
own on the provisioner) stays open for a later version. `warden case
check-type` now reports only `keycape-client-secret-generate`
(railiance-platform) as missing. 89 tests pass.
Hub decision `e418263e` was still open, so memo r4
(`nk-wp-0048-t05-oidc-browser-relying-party-r4`, packet `sha256:452ea421…`)
replaces r3; it passes informed-decision's and the dispatcher's validation. The
founder queues it with the same command, returns r2/r3 if still open with a
comment that r4 supersedes them, then answers r4.
Prerequisites for T06 are now: lane `keycape-client-secret-generate`
(railiance-platform), and a founder-approved rollout of an identity-provisioner
image containing 630ebfd.
2026-10-09: done. The founder queued r4 (`pending`, `queued`), returned r3 as
superseded and accepted r4 in informed-decision. informed-decision's hub
write-back is off in production (`INFD_HUB_WRITEBACK` unset), so net-kingdom
resolved hub decision `e418263e` from the founder's report, naming memo r4 in
the rationale. The type is `acknowledged`; it becomes `implemented` with T06.
```task
id: NK-WP-0048-T06
status: wait
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
```
### T06 — First KnownSecurityCase: Forgejo
Write `security-cases/forgejo-netkingdom-oidc.yaml` from the parameters the
founder accepted in KEY-WP-0038-T01 (`key-cape/docs/forgejo-oidc-client-request.yaml`),
so that acceptance carries over as the case acceptance. ops-mason materializes it;
ops-warden follows through and verifies it. The type becomes `implemented` once
this reference case is live.
This replaces the hand-built path proposed for KEY-WP-0038-T02.
2026-10-09: case written, `security-cases/forgejo-netkingdom-oidc.yaml`, status
`accepted`: the example's parameters plus the founder's 2026-10-08 acceptance of
the key-cape request (owner and founder), with the custody-path derivation
covered by the type's acknowledgment (memo r4). It validates and renders (plan
`execution.permitted: false`; three attended steps on `key-cape-oidc-login`);
it needs `--allow-unimplemented` until it is live, since it is the case that
makes the type `implemented`. A test keeps it equal to the type's example.
Still open before it runs: ops-mason renders and builds it (MASON-WP-0010-T02,
T03, unblocked by T05), ops-warden plans and runs it (WARDEN-WP-0044-T02, T03),
railiance-platform's `keycape-client-secret-generate` lane (asked by ops-mason
`789feb39` and ops-warden `d9f889b1`, both unread), and a founder-approved
identity-provisioner rollout containing 630ebfd.
2026-10-09: identity-provisioner rolled out, founder-approved in session:
`main-630ebfd` (`sha256:bafb4860…`, built by the image workflow) replaces
`main-6c4fcaf` (`sha256:ffacd5d7…`; only change: 630ebfd, additive). Applied
with `kubectl set image` on the Deployment only (generation 17 → 18, 1/1 Ready);
the file's Service, NetworkPolicies and Ingress were not reapplied. Verified in
the pod: `ensure_service_groups` and `revoke_service_groups` present, `/readyz`
200; no token used, no group changed. Rollback: `kubectl -n sso set image
deploy/identity-provisioner provisioner=forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ffacd5d790a5b7800e0535f3aa95b7de52c7a68e4f575b9192c3e1622fa672c2`.
The pin in `sso-mfa/k8s/identity-provisioner/deployment.yaml` follows.
2026-10-09: ops-warden closed WARDEN-WP-0044-T01 and shipped `warden case plan`
(T02, message `06303d1d`); the accepted Forgejo case is refused until ops-mason
materializes it, and a materialized copy plans cleanly once
`keycape-client-secret-generate` exists. Its observation that `bind-env` carries
a literal `KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`: the validator now renders a
`{param:env}` format spec (upper case, `-` as `_`, as `keycape-client.py`
derives the name) and rejects unknown specs, and follow-through and rollback
text is now checked for unknown placeholders too (patch-level, canon section 6).
The type keeps the literal until ops-warden's planner (`text.format`) renders the
spec as well; asked of ops-warden. 53 tests pass.
2026-10-10: ops-warden rendered `{param:env}` in `warden case plan`
(23ba584, message `c1df14b7`; other specs and `!conversion` refused). The type
adopts it as a 1.0 patch (canon section 6): `bind-env` and its rollback now read
`KEYCAPE_{client_id:env}_CLIENT_SECRET`. ops-mason reports the Forgejo case
rendered and built (message `1ebddae9`; CCR drafts handed to railiance-platform),
so T06 now waits on the platform apply, the mint-secret lane and the attended
register-client session.
2026-10-10: key-cape adopted the type's custody path (key-cape ab5cccd, message
`88a4559c`): `docs/forgejo-oidc-client-request.yaml` and KEY-WP-0038-T02 name
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, delivered as
`sso/keycape-forgejo-client` through `external-secrets-keycape-forgejo`, and the
`forgejo/netkingdom-oidc-client` proposal is withdrawn. This matches the type and
the Forgejo example; no change needed here.
2026-10-10: key-cape's type-composition review (key-cape 6038483, message
`8e5ed5f6`) found the composition sound with two blocking findings, taken as a
1.0 patch (stricter validation and documentation, canon section 6):
R1, redirect URIs now refuse `?` and `#` in the type pattern and in
`keycape-client.py`'s `REDIRECT` (KeyCape refuses `?` at startup and the single
replica would stop); R2, `allowed_scopes` always includes `groups`, the renderer
refuses `access_groups` without it, and `access_groups` now says the consumer
enforces the gate because KeyCape has none; R3, `mfa_required` "false" means the
per-user privacyIDEA policy; R4, register-client's justification records the
restart for every client, the quiet window, and that the rendered registry must
pass KeyCape's client validation before it. The accepted Forgejo case and both
examples still validate; 116 tests pass.
Open, needs a decision: R2's verification step that the consumer enforces the
group gate is a new verification step, hence 1.1 (minor), and the validator pins
cases to the exact type version, so the accepted 1.0 Forgejo case would need
re-pinning. Also open with key-cape: a KeyCape entry point that validates a
rendered client registry without starting the server; until then
`keycape-client.py` mirrors the redirect rule.
2026-10-10: founder go for 1.1, so no 1.0 run overlaps. The type adds
verification `consumer-enforces-groups`, and hand-over now requires the consumer
to enforce `access_groups` (minor, canon section 6; risk class unchanged, no new
acknowledgment; history in the type README). The accepted Forgejo case and both
examples are re-pinned to `oidc-browser-relying-party@1.1` before any apply; the
acknowledgment intake keeps `@1.0` as the record of what was acknowledged.
ops-mason's build and ops-warden's case-ref digest predate this and need a
re-render.
2026-10-10: ops-mason re-rendered the Forgejo case from `@1.1` (message
`22c6bba6`; plan digest `af1f4f81…`, no collisions) and rebuilt the bundle. The
rendered OpenBao objects are identical to 1.0, so the already applied
CCR-2026-0041 stands; 1.1 changes only consumer-side steps. key-cape shipped
`keycape check-config` (c528daa, message `a7b0aeff`): KeyCape's startup checks
without secret values, including the new duplicate-clientId rule.
`keycape-client.py --check-config <binary>` now runs it on the live and the
proposed registry, fed on stdin, and apply refuses a proposed registry that
fails; register-client's entry point names the option (1.1 patch). The deployed
image predates c528daa, so the binary is built from key-cape source for now.
Before the next KeyCape rollout (Forgejo register-client or the CCR-2026-0018
window), the attended operator runs `inspect --check-config` so the live
registry is checked too.
```task
id: NK-WP-0048-T07
status: wait
depends_on: [NK-WP-0048-T06]
blocking_reason: "The next types follow once the first one is proven end to end."
priority: medium
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
```
### T07 — Queue the next types
From the T02 ranking, open the next type drafts (likely the confidential service
client and the workload KV read lane), each to be acknowledged on its own.
Record which existing hand-built instances get adopted as cases, without
rewriting them in place.
```task
id: NK-WP-0048-T08
status: done
priority: high
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
```
### T08 — identity-provisioner: ensure service-access groups
The type's `ensure-groups` step calls `identity-provisioner groups ensure --groups
<list> --members <group=user+user;...>`. The provisioner already has idempotent
`_ensure_group` and `_add_group`, but exposes only tenant-scoped operations. Add
the general operation (create if absent, add the listed memberships, never
remove others), its rollback (remove only this case's memberships), tests, and
the ops-warden route for its service identity.
2026-10-08: done. `identity-provisioner/provisioner.py` gained
`ensure_service_groups` (`POST /v1/groups/ensure`) and `revoke_service_groups`
(`POST /v1/groups/revoke`), behind the existing service-token check. Names
matching `tenant*`, `netkingdom-*` and `lldap_*` are reserved; members must
already exist; everything is validated before any change; memberships are read
back before success; nothing else is ever removed; groups are deleted only when
asked and empty. 7 new tests (39 in the provisioner suite). The type's
`ensure-groups` step and its rollback now name these endpoints. The ops-warden
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
```task
id: NK-WP-0048-T09
status: done
priority: high
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
```
### T09 — The `keycape-client` entry point and its declaration
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
live registration needs already lives in net-kingdom, where KeyCape is deployed
(`sso-mfa/k8s/keycape/`): the contained kubectl, the cluster-identity pin and the
issuer pin. A copy in key-cape would drift.
2026-10-08: done. `sso-mfa/k8s/keycape/keycape-client.py` derives the KeyCape
client record from a case (`client-from-case`). It adds or removes exactly one
client with the existing safeguards (byte-preserving merge, exact match or
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
requires the Deployment env binding first for confidential clients, and requires
the case acceptance record for `apply` instead of a per-client founder decision
id. 15 tests, including a regression showing that the vergabe case reproduces
its hand-built registration exactly; all 71 helper tests pass. Declaration
`capabilities/playbooks/net-kingdom.keycape-client-registration.yaml` (contract
PASS, `maturity: implemented`) replaces key-cape's draft. The type now pins it,
and for confidential clients adds `deliver-secret` (delivery from custody into
the sso namespace) and `bind-env` (Deployment env), attended in the same session
as `register-client`. The per-client helpers stay until their clients are
adopted as cases.
2026-10-10 — NetKingdom owner review of the execution seam, source only:
ops-warden/docs/security-case-execution-proposal.md SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
was reviewed against INTENT, SecurityCase canon, acknowledged type1.1, current
case and owner source. docs/security-case-execution-owner-review.md supports
the closed adapter/receipt direction with explicit versioned schema, structural
materialization provenance, unchanged caller/session boundaries, safe CAS
reconciliation and exact-created-object rollback requirements. This is owner
review, not approval. Supporting file digests are in the adjacent sources JSON.
Current source exposes a circular wait: ops-mason T03 waits on mint while Warden
and the broker refuse a non-materialized case. Structural acceptance therefore
needs its own native pinned receipt before mint, not a successful mint before
materialized. The platform broker additionally pins type1.0 while Forgejo is
accepted at1.1; platform-owner alignment and tests are required, not an unchecked
version bypass. T06 remains wait. No case/type lifecycle status, grant, authority
or live runtime changed by this review. A proposed informed-decision would
adopt the source contract only; native materialization and execution acceptance
stay distinct.
Final proposal re-review: staged Warden proposal SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
adds only the explicitly local reducer appendix, not an accepted wire protocol
or executor. The exact-final review and sources JSON supersede the earlier
1035f049... proposal review. Platform staged the exact type1.1 broker correction
with ten fake-backend subprocess checks (script SHA256
9576cd211cfc06526ec4cc9bd0a5b06f0f59287a68931bb2d6e3592a951ddc7d).
The next source work is the versioned closed adapter/receipt/materialization
contract, not another relaxation of the broker's status gate. Native work still
requires installed broker provenance, structural reader receipts, exact new
materialized-case bytes, caller grant/session revocation and verified execution.
No installed/native acceptance is inferred from that staged correction.
2026-10-11 — new source-only type1.2 architecture acknowledgment:
Confirmed native founder ACCEPT of infd-20261011-forgejo-type12-source-r1@1
at06:47:39Z, approval8e9332c8-754a-420c-b8b7-445cb4fe6765. Exact frozen packet
0b377d647ca748cd03c92f9792396412048055d4bde9f7bb06ff6d413f7acd68 and submission
585cee731de30d2b37883a57abfd9eb32f023ee694e8d911989109d5caf80569; native receipt
retained in docs/evidence/2026-10-11-forgejo-type12-source-acknowledgment/.
The complete acknowledged1.2 source package is isolated under
security-case-type-revisions/1.2/oidc-browser-relying-party; default loader and
current accepted1.1 case remain untouched because load_type is single-version.
The new proposed1.2 case is NOT accepted/materialized/live and has no acceptance
fields. Actual reference-migration design approval is a separate Apps owner
receipt; no1.1 approval is inherited. Source-recording review/test proof and exact
next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md.
T06 remains wait and T07 remains its existing dependent task; type is NOT
implemented and no first-case/native grant or broader workplan finish is asserted.
2026-10-11 — bounded explicit version selection source preparation for T06:
The validator now offers optional exact version_roots/--version-root VERSION=PATH
selection with duplicate/mismatch/traversal/symlink refusal and no fallback. The
default1.1 source and historicalacceptedcase hashes/ordinaryrefusal/preparation
render stayunchanged; actual1.1 is acknowledged, not implemented. Acknowledged1.2
ordinaryadmission also refuses; optionalpreparation remains nonexecuting.43local
validatorchecks pass and original1.1 CLIrenderbytes match exactly. Makerproof:
docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
FormalNetownerreview/reference-source integration and all existing native adapter,
provenance/broker/materialization/attendedproof gates remain; T06 stayswait, no
new residual or firstcase/typeimplementation/native grant is inferred.
2026-10-16 — isolated source preparation for T06 (canonical integration pending):
Added `check-evidence` to the existing validator CLI. It reuses exact version
selection and ordinary type/case/render admission, checks actual Mason structural
request source/inventory/projection pins and reads actual Warden journal rows
read-only. Successful rows require exact type-declared descriptors and attempt/
revocation fingerprints. Existing acknowledged types without machine descriptors
remain plan-only. The compatibility result never claims authenticated native
completion: Request and journal fingerprints are not portable native evidence.
Owner authentication/status binding and actual native gates remain unresolved;
T06/T07 statuses and both type/case lifecycles remain unchanged. No dispatch,
credential access or authority expansion is introduced. See tool README for the
existing owner protocol boundaries and source integration tests.
Concrete next Warden owner integration: current `Executor.admit` refuses every
case parameter whose sensitivity is not public/operational, so browser
`redirect_uris`, `allowed_scopes`, `client_type`, `mfa_required` (and 1.2
`consumer_namespace`) fail after otherwise prepared admission. The sensitive
classification is a security-change review level, not secret content. The
Warden owner must validate through NetKingdom's existing exact-version case/type
validator, then project only descriptor-declared safe target metadata; do not
remove the credential/value refusal or copy complete parameters into journals.
Actual owner descriptor acknowledgment and independent evidence admission remain
prerequisites, with normal admission still refusing unimplemented types.
## T10 — Source compatibility checker for Mason and Warden evidence
```task
id: NK-WP-0048-T10
status: done
priority: high
state_hub_task_id: "4b06ca97-c1e4-5680-bf22-ab75ab1646a4"
```
Completed 2026-10-16 in published source `c31d21ba`. The read-only
`check-evidence` command checks the actual Mason structural request and Warden
journal source protocols against an exact version/render. Independent source
review passed 103 tests at that checkpoint. It always reports native completion
false and execution prohibited. Fixtures and local journal fingerprints do not
provide authenticated owner/native evidence. This records completed source scope
previously described under T06; it does not duplicate native acceptance credit.
## T11 — Validate the optional machine-descriptor pin contract
```task
id: NK-WP-0048-T11
status: done
priority: high
state_hub_task_id: "ef2a0119-f59e-5e45-9519-115da9df7b85"
```
Completed 2026-10-16. Reproduced the JSON schema rejecting a valid
`execution_descriptors` map while the Python validator accepted malformed maps.
The schema now supports the existing Warden optional exact-byte pin map; semantic
validation binds keys to declared steps, bounds the map and checks digest shape.
The direct evidence API refuses malformed maps before accessing them. Twenty-one
new checks and all 127 affected source checks passed. No current type/case bytes,
status, owner descriptors or native acceptance changed. Source evidence and
publication receipt: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-acceptance-next/`.
## T12 — Prepare NetKingdom-owned attended step adapters
```task
id: NK-WP-0048-T12
status: todo
priority: high
state_hub_task_id: "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd"
```
Owner: NetKingdom. Retained scope: prepare bounded attended Deployment env,
config verification and restart/readiness steps around the existing helpers.
Next action: identify exact current entry points and their revision/readback
contracts, then implement fixed source-pinned callbacks or receipt-only attended
steps. Completion requires positive/refusal checks, exact target/source pins and
uncertainty behavior without acquiring user-engine runtime identity or exposing
registry/client secrets. Existing caller/session, cluster/issuer, CAS/readback
and status-only output boundaries remain mandatory.
2026-10-16 task refinement: the original broad adapter scope is split into T13
(completed registration source candidate), T14 (group creation provenance), T15
(durable registration recovery) and T16 (descriptor/artifact selection and owner
review). This preserves T12's task ID and Hub UUID while giving each remaining
outcome an owner and completion gate. T06 now names every source dependency;
its native/reference lifecycle acceptance scope remains unchanged. Completion
of any source task supplies no Warden, Platform or Apps assent and no native
execution authority.
## T13 — Prepare the fixed registration adapter source candidate
```task
id: NK-WP-0048-T13
status: done
priority: high
state_hub_task_id: "b264c10b-565c-5d29-b788-03cf5fbd2883"
```
Completed 2026-10-16, source implementation and synthetic fixture proof only.
Added an injection-only adapter around actual `keycape-client.run`: fixed
case/principal/human/lane/source/acceptance binding, existing CAS/readback,
metadata-only owner evidence and per-attempt parent-session revocation boundary.
The descriptor source composition pins the exact checker executable digest
alongside adapter/helper source. Creation-only rollback authenticates its
creation receipt and refuses changed postcreation UID/resourceVersion;
pre-existing clients confer no rollback authority. Missing readback, evidence,
revocation or process-local creation provenance cannot become retry-safe success.
Evidence: Custodian `docs/changes/netkingdom-target-2026-10-16/browser-adapter-next/`
R3 candidate, explicit patch and postimages, retained R1 rejected custody finding
and R2 revision, and 36 passing source checks (16 new, 20 existing). Three checks
use actual Warden kernel admission/dispatch/rollback with published Warden
`d97652a3bb5fa733b063d71a1b74068efcccf572` producer pins unchanged. Owner trust,
descriptor/type/case and Kubernetes objects remain synthetic fixtures. This
records a completed source deliverable, not independent review, publication,
type acknowledgment, exercised procedure, identity admission or native browser
acceptance. Independent root source review: Custodian browser-adapter-next/coordinator-review-r3.json. Source integration and normal publication/synchronization receipts: Custodian browser-adapter-next/canonical-integration.json. These receipts supply no other-owner assent or native authority.
Source publication: `0775c2bfa41af236301155bb5a69bbf088bdf823`, independently
reviewed by the Custodian coordinator before integration. The required initial
projection and ordinary UUID writeback both completed with zero failures and
zero automation errors. Generated-only brief commits were refused by the normal
hook; exact pre-existing staged/working brief bytes were restored. The separate
canonical integration receipt retains those refusals and projection evidence.
Native admission and all other-owner acceptance remain open.
## T14 — Preserve created-only provenance for group ensure and revoke
```task
id: NK-WP-0048-T14
status: todo
priority: high
state_hub_task_id: "fce95440-a25b-5f27-8ef3-a3d182099113"
```
Owner: NetKingdom. Next action: extend or wrap the existing identity-provisioner
ensure/revoke contract with independently retained prestate and exact newly
created groups/memberships before exposing reversible owner callbacks. Current
ensure returns no membership-creation inventory; current revoke may remove a
membership that predated this case. Completion requires actual helper-backed
positive/refusal fixtures, all validation before mutation, per-attempt evidence
and revocation, readback, ambiguity handling, and rollback that preserves every
pre-existing/shared group and membership. No existing endpoint or service token
is treated as new authorization.
## T15 — Define authenticated recovery for lost registration process state
```task
id: NK-WP-0048-T15
status: todo
priority: high
state_hub_task_id: "1b825705-4848-5edc-832f-b1622cd9bdb7"
```
Owner: NetKingdom for registration state, with Warden owning kernel evidence
admission. Next action: select the exact existing owner evidence holder/readback
contract and define authenticated lookup for the original creation attempt.
Completion requires restart/lost-receipt fixtures that bind case/source/principal,
original created-only provenance and current native revision before any undo;
uncertain outcomes remain non-replayable. The T13 adapter deliberately refuses
post-restart rollback and installs no reconciliation callback. A new broker or
unverified JSON receipt is not a recovery contract. Actual evidence-holder
acceptance and attended recovery remain separately authorized native work.
## T16 — Select and review the actual machine descriptor composition
```task
id: NK-WP-0048-T16
status: todo
priority: high
state_hub_task_id: "200bf500-5084-5ae7-be26-3033fe1f296e"
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
independently owns global provenance/admission. Next action: obtain review of the
exact checker source/artifact proposed by T17 and identify native owner evidence/
session callback contracts, then assemble the complete step descriptor set and
a proposed explicit type version selection. T13's historical descriptor retains
its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
pins for NetKingdom 015694ee; it does not attest T13's new adapter. Pinning any
machine descriptor into an acknowledged type is a separately reviewed source
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.
## T17 — Build and exercise the exact offline config-checker artifact
```task
id: NK-WP-0048-T17
status: done
priority: high
```
Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
existing `key-cape/src/cmd/keycape` command from published clean source revision
`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
read-only module selection. The full retained closure pins 108 source files,
9876 toolchain files and ten used module source directories. Three isolated
build locations produced identical 13633409-byte artifacts:
`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
Eight disposable checks use the actual executable and maintained helper/kernel,
with complete synthetic configs and a minimal environment: add/created-only
removal, normal pinned producer admission, rollback, lost-readback uncertainty,
stale revisions, substituted checker refusal, invalid startup config and missing
secret-env refusal. No checker stub or producer-pin override remains. The
artifact-bound descriptor proposal is
`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
Evidence: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
The first two build attempts succeeded but their closure collection failed;
both failures are retained, as is refusal of the older incomplete synthetic
fixture. No production config, credentials or cluster mutation was used.
This extracts completed build/fixture scope from T16 without claiming KeyCape
owner endorsement, native checker selection/storage custody, acknowledged type
integration, native evidence/session callbacks or attended browser acceptance.
T16 retains exact owner review and composition/admission scope. The attended
registration procedure remains unexercised.

View file

@ -0,0 +1,748 @@
---
id: NK-WP-0048
type: workplan
title: "Establish SecurityCaseTypes so that known security cases are light"
domain: infotech
repo: net-kingdom
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-16"
depends_on: []
related_repos:
- ops-mason
- ops-warden
- key-cape
- railiance-platform
- informed-decision
state_hub_workstream_id: "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
---
# NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
## Why
The founder's direction (2026-10-08): setting up a security integration of a
kind that has been done before should be easy, meaning accept the case, then
ops-mason arranges it and ops-warden follows through. Setting up a new kind
needs ample documentation and tests and an informed-decision, because its shape
is part of the security architecture.
Today every integration is hand-built: per-client helper scripts, per-lane
custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC
client), whose next step was going to be yet another hand-written rollout
script.
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
into **SecurityCaseTypes** that ops-mason and ops-warden can execute. Canon:
`canon/standards/security-case-types_v0.1.md` (proposed).
Companion plans: ops-mason `MASON-WP-0010` (materialization) and ops-warden
`WARDEN-WP-0044` (follow-through).
## Tasks
```task
id: NK-WP-0048-T01
status: done
priority: high
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
```
### T01 — Accept the terminology and the canon
Founder review of `canon/standards/security-case-types_v0.1.md`, including its
section 10 open questions. On acceptance the canon status goes from `proposed`
to `accepted`.
2026-10-08: accepted by the founder (in session). Canon status `accepted`;
section 10's open questions are carried forward to the first type (T04, T05).
```task
id: NK-WP-0048-T02
status: done
priority: high
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
```
### T02 — Inventory the de-facto types already built by hand
Read-only. List every security integration built so far and group them by shape.
Known starting points:
- browser OIDC relying parties on key-cape: informed-decision-approver,
coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
- confidential service clients with `client_credentials`: secrets-engine
approval, requester and openbao clients, approval-engine operator
- workload KV read lanes on OpenBao: the railiance-platform credential lane
designs and CCRs, for example `railiance-backup-offsite-lane`
- short-lived SSH certificates through ops-warden (`warden sign`)
- tunnelled operator access (ops-bridge)
For each: the repositories involved, the hand-built steps, attended human steps,
custody paths, verification evidence, and rollback (if any). Output
`docs/security-case-type-inventory.md`, ranking candidate types by how often the
shape recurs and how much manual work each instance costs today.
2026-10-08: done, `docs/security-case-type-inventory.md`. 36 of 37 CCRs are
already `request_type: workload-kv-read`, a de-facto type reviewed each time as
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
rollout script. Confidential service clients need a registration plus two or
three KV-read CCRs each. A forge read identity is needed in bulk by
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
```task
id: NK-WP-0048-T03
status: done
depends_on: [NK-WP-0048-T01]
priority: high
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
```
### T03 — Schema, template and validator
- `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`
- `security-case-types/_template/` with `type.yaml`, `README.md` and tests to copy
- a validator, extending `tools/security-scenario-composer/`, that checks a type,
checks a case against its type, and renders the case into the composer's
deterministic plan (still `execution.permitted: false`)
Acceptance: the validator rejects missing sections, secret-shaped parameter
values, unpinned providers and cases against non-`implemented` types, with tests
for each.
2026-10-08: done. `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`; `tools/security-case-types/security_case_validator.py`
(`validate-type`, `validate-case`, `render`, reusing the scenario composer);
`security-case-types/_template/` (a valid, runnable example type over
`net-kingdom.local-identity`, with README sections, an example case and tests that
travel with every copy). 31 tests pass, including the composer's existing suite.
```task
id: NK-WP-0048-T04
status: done
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
priority: high
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
```
### T04 — Define the first type: browser OIDC relying party
`security-case-types/oidc-browser-relying-party/`, version 1.0:
- parameters: client id, display name, exact redirect URIs, scopes, client type
(confidential or public), MFA pin, the groups the consuming service maps,
custody path for the secret (confidential only), consuming service and its
owner
- composition: key-cape client registration (key-cape declares it as a playbook
capability), secret minting into custody (railiance-platform), LLDAP group
existence and membership (net-kingdom identity-provisioner), handover to the
consuming service
- materialization (ops-mason) and follow-through (ops-warden) sections
- verification: discovery lists the redirect URI; a test sign-in returns the
expected claims for one human; anonymous access to the custody path is denied
- rollback: remove exactly this client and its custody path
- README: threat model, MFA rationale, why redirect URIs are exact, which steps
still need an attended login and how that is minimised
- reference instances: informed-decision-approver, coulomb-social, user-engine
portal, openbao-admin
Acceptance: complete per canon section 3, tests green, status `proposed`.
2026-10-08: done. `security-case-types/oidc-browser-relying-party/` 1.0 (proposed):
`type.yaml` with 12 parameters (the custody reference is required only for
confidential clients), composition over key-cape's new
`key-cape.oidc-client-registration` declaration (contract PASS, `maturity:
planned`), materialization and follow-through, verification and rollback, all
conditional on client type where it matters. One attended step,
`register-client`, justified by the live registry being a Kubernetes Secret.
README with threat model, alternatives, an honest fit table (portal and vergabe
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
(checked against the accepted request) and a public portal. The validator gained
`when` / `required_when` conditions and provider discovery. 43 tests pass.
ops-mason's and ops-warden's sections are drafted in the type. Their owners
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
(T05).
```task
id: NK-WP-0048-T05
status: done
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
priority: high
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
```
### T05 — Acknowledge the type through informed-decision
Prepare the Decision Memo from the type's README and `type.yaml` and submit it
through informed-decision's production intake. On acknowledgment the type
moves to `acknowledged`.
2026-10-08: Decision Memo prepared. Hub decision `e418263e-a71f-4f01-a970-c87c9e187c09`.
`security-case-types/oidc-browser-relying-party/decision/`: `packet.md` (question,
one act, what acknowledging does not do, the shape, alternatives, attended
steps, limits, evidence), `build_intake.py` and the built `intake.json` (memo
class `decision-record`, review group `net-kingdom-admins`, resolves the hub
decision on answer). The envelope passes informed-decision's own
`validate_submission`. Queuing it is an attended operator step, a write inside
the production dispatcher pod:
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
< security-case-types/oidc-browser-relying-party/decision/intake.json
The answer then appears for review in informed-decision.
2026-10-08: queued by the founder (`pending`, `queued`) after one refusal: the
first envelope's idempotency key was not State Hub's stable
`state-hub.<decision>.<memo>` form, and the builder now validates against both
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
delivered). T05 waits for the founder's answer in informed-decision, which
resolves hub decision `e418263e`.
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
1.0 `proposed`: `secret_ref` is no longer a parameter, the type derives
`platform/workloads/keycape/{client_id}`; policies are
`workload-kv-read-keycape-{client_id}-eso` and
`workload-kv-read-keycape-{client_id}-{consuming_service}`, each with a templated
body (`read` on that one data path); new `openbao_role`
`external-secrets-keycape-{client_id}` (Kubernetes auth, ESO service account,
15m, no default policy); catalog entry `{consuming_service}-keycape-client`;
the two rollback steps on the KV path collapse into one metadata delete. The
schema and validator now require a `body` on policy and role items. 105 tests
pass. Two consequences: the delivered memo describes the type before these
changes (a case-set custody reference, two differently named policies, no ESO
role), so the founder answers on a superseded shape unless the memo is
re-sent; and Forgejo's custody path moves from the accepted request's proposed
`platform/workloads/forgejo/netkingdom-oidc-client` to
`platform/workloads/keycape/forgejo`, which key-cape (KEY-WP-0038) and
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
(WARDEN-WP-0044-T01) is still in progress.
2026-10-08: revised memo `nk-wp-0048-t05-oidc-browser-relying-party-r2` built
(`packet.md`, `intake.json`, packet `sha256:af6435e2…`); it states that it
supersedes the first memo and passes informed-decision's and the dispatcher's
validation. It needs a new memo id because the dispatcher refuses new content
under a delivered idempotency key (`idempotency_conflict`). Queuing it is the
same attended step as above. Both memos serve hub decision `e418263e`; the
first answer resolves it, so the first memo must not stay open: an accept on it
would acknowledge the superseded shape, and a decline would resolve the
decision against r2. Close it with `return` (a reviewer verb that requests a
new revision and does not resolve the hub decision; only accept and decline
do), commenting that r2 supersedes it, then answer r2. `withdraw` exists in
the vocabulary but is not offered to reviewers, and lifecycle `revise` only
applies to lifecycle-registered memos, not intake-delivered ones.
2026-10-09: the founder reports r2 enqueued and the first memo returned. At
close, hub decision `e418263e` still showed `open` (not verified whether r2
was answered or the answer is still in transit; the dispatcher ledger was not
read). ops-warden's review (WARDEN-WP-0044-T01, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`, d470f2e) arrived at
21:25, before r2 was built, and is not in r2. Required before WARDEN-WP-0044-T02:
R1 a per-step `lane` (missing: identity-provisioner caller credential,
railiance-platform broker grant), R2 canon meaning of `warden_executes: true`
(caller proxy, caller identity, no standing ops-warden token; ADR-0002), R3 a
per-step `value_handling`. Refinements R4 (one type-level catalog lane instead
of a per-case entry, which conflicts with ops-mason's item 5), R5 (ESO Ready gate
before bind-env), R6 (exact accepted memberships), R7 (mfa-pinned and
secret-delivered checks). Next: if r2 was accepted, fold R1-R3 and R5-R7 in as
a follow-up and settle R4 between ops-warden and ops-mason before T06; if not,
hold r2, fold them in and build r3.
key-cape and railiance-apps were told about Forgejo's new custody
path.
2026-10-09: r2 was not answered (hub decision `e418263e` open; dispatcher 9
delivered), so r2 is held and r3 replaces it. ops-warden withdrew R4 for R4′
(message a66a9204): one catalog entry per case with its body templated in the
type. Folded in:
- Canon section 3: a narrow meaning of `warden_executes: true` (caller proxy,
caller identity, no standing credential; ops-warden ADR-0002), plus per-step
`lane`, `value_handling` and an optional `ready_check`. A catalog entry body is
now required, like policy and role bodies.
- Schema and validator enforce all of these.
- The type: lanes `identity-provisioner-groups` and
`keycape-client-secret-generate` (both new) and `key-cape-oidc-login`;
`mint-secret` is `generated_in_custody`; a Ready gate on `deliver-secret`;
`ensure-groups` adds exactly the accepted case's memberships; new checks
`secret-delivered` and `mfa-pinned`; the catalog-entry body follows R4′.
- 110 tests pass.
Memo r3 (`nk-wp-0048-t05-oidc-browser-relying-party-r3`, packet
`sha256:726dfc01…`) passes informed-decision's and the dispatcher's validation.
The founder queues it with the same command, returns r2 with a comment that r3
supersedes it, then answers r3.
Prerequisites for T06, not for T05:
- the two new lanes, each through its owner;
- a founder-approved rollout of an identity-provisioner image containing
630ebfd (the deployed one is main-6c4fcaf from 2026-09-24, without
`/v1/groups/ensure`).
ops-mason's request to create LLDAP group `fluid-telegram-operators` (message
259ec26d) was answered: not possible from here by that path; the founder creates
it in the LLDAP admin UI.
2026-10-09: ops-warden finished its side of WARDEN-WP-0044-T01 (message
`58d33f40`, commit 0e7ceb8): the revised type conforms to `warden case
check-type`, with no further changes asked of the type. This acknowledges that
review. Its later addendum R8 (message `af274783`, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`) showed that lane
`identity-provisioner-groups` cannot exist as declared: the provisioner's only
HTTP credential is user-engine's runtime token, and its NetworkPolicy admits
only user-engine pods, so a caller-proxy lane would act as user-engine. Taken
option 1 (ops-warden's recommendation): `ensure-groups` and its rollback are
attended, in the `register-client` session on `key-cape-oidc-login`, run inside
the provisioner pod; no token is read. Option 2 (an operator credential of its
own on the provisioner) stays open for a later version. `warden case
check-type` now reports only `keycape-client-secret-generate`
(railiance-platform) as missing. 89 tests pass.
Hub decision `e418263e` was still open, so memo r4
(`nk-wp-0048-t05-oidc-browser-relying-party-r4`, packet `sha256:452ea421…`)
replaces r3; it passes informed-decision's and the dispatcher's validation. The
founder queues it with the same command, returns r2/r3 if still open with a
comment that r4 supersedes them, then answers r4.
Prerequisites for T06 are now: lane `keycape-client-secret-generate`
(railiance-platform), and a founder-approved rollout of an identity-provisioner
image containing 630ebfd.
2026-10-09: done. The founder queued r4 (`pending`, `queued`), returned r3 as
superseded and accepted r4 in informed-decision. informed-decision's hub
write-back is off in production (`INFD_HUB_WRITEBACK` unset), so net-kingdom
resolved hub decision `e418263e` from the founder's report, naming memo r4 in
the rationale. The type is `acknowledged`; it becomes `implemented` with T06.
```task
id: NK-WP-0048-T06
status: wait
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
```
### T06 — First KnownSecurityCase: Forgejo
Write `security-cases/forgejo-netkingdom-oidc.yaml` from the parameters the
founder accepted in KEY-WP-0038-T01 (`key-cape/docs/forgejo-oidc-client-request.yaml`),
so that acceptance carries over as the case acceptance. ops-mason materializes it;
ops-warden follows through and verifies it. The type becomes `implemented` once
this reference case is live.
This replaces the hand-built path proposed for KEY-WP-0038-T02.
2026-10-09: case written, `security-cases/forgejo-netkingdom-oidc.yaml`, status
`accepted`: the example's parameters plus the founder's 2026-10-08 acceptance of
the key-cape request (owner and founder), with the custody-path derivation
covered by the type's acknowledgment (memo r4). It validates and renders (plan
`execution.permitted: false`; three attended steps on `key-cape-oidc-login`);
it needs `--allow-unimplemented` until it is live, since it is the case that
makes the type `implemented`. A test keeps it equal to the type's example.
Still open before it runs: ops-mason renders and builds it (MASON-WP-0010-T02,
T03, unblocked by T05), ops-warden plans and runs it (WARDEN-WP-0044-T02, T03),
railiance-platform's `keycape-client-secret-generate` lane (asked by ops-mason
`789feb39` and ops-warden `d9f889b1`, both unread), and a founder-approved
identity-provisioner rollout containing 630ebfd.
2026-10-09: identity-provisioner rolled out, founder-approved in session:
`main-630ebfd` (`sha256:bafb4860…`, built by the image workflow) replaces
`main-6c4fcaf` (`sha256:ffacd5d7…`; only change: 630ebfd, additive). Applied
with `kubectl set image` on the Deployment only (generation 17 → 18, 1/1 Ready);
the file's Service, NetworkPolicies and Ingress were not reapplied. Verified in
the pod: `ensure_service_groups` and `revoke_service_groups` present, `/readyz`
200; no token used, no group changed. Rollback: `kubectl -n sso set image
deploy/identity-provisioner provisioner=forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ffacd5d790a5b7800e0535f3aa95b7de52c7a68e4f575b9192c3e1622fa672c2`.
The pin in `sso-mfa/k8s/identity-provisioner/deployment.yaml` follows.
2026-10-09: ops-warden closed WARDEN-WP-0044-T01 and shipped `warden case plan`
(T02, message `06303d1d`); the accepted Forgejo case is refused until ops-mason
materializes it, and a materialized copy plans cleanly once
`keycape-client-secret-generate` exists. Its observation that `bind-env` carries
a literal `KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`: the validator now renders a
`{param:env}` format spec (upper case, `-` as `_`, as `keycape-client.py`
derives the name) and rejects unknown specs, and follow-through and rollback
text is now checked for unknown placeholders too (patch-level, canon section 6).
The type keeps the literal until ops-warden's planner (`text.format`) renders the
spec as well; asked of ops-warden. 53 tests pass.
2026-10-10: ops-warden rendered `{param:env}` in `warden case plan`
(23ba584, message `c1df14b7`; other specs and `!conversion` refused). The type
adopts it as a 1.0 patch (canon section 6): `bind-env` and its rollback now read
`KEYCAPE_{client_id:env}_CLIENT_SECRET`. ops-mason reports the Forgejo case
rendered and built (message `1ebddae9`; CCR drafts handed to railiance-platform),
so T06 now waits on the platform apply, the mint-secret lane and the attended
register-client session.
2026-10-10: key-cape adopted the type's custody path (key-cape ab5cccd, message
`88a4559c`): `docs/forgejo-oidc-client-request.yaml` and KEY-WP-0038-T02 name
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, delivered as
`sso/keycape-forgejo-client` through `external-secrets-keycape-forgejo`, and the
`forgejo/netkingdom-oidc-client` proposal is withdrawn. This matches the type and
the Forgejo example; no change needed here.
2026-10-10: key-cape's type-composition review (key-cape 6038483, message
`8e5ed5f6`) found the composition sound with two blocking findings, taken as a
1.0 patch (stricter validation and documentation, canon section 6):
R1, redirect URIs now refuse `?` and `#` in the type pattern and in
`keycape-client.py`'s `REDIRECT` (KeyCape refuses `?` at startup and the single
replica would stop); R2, `allowed_scopes` always includes `groups`, the renderer
refuses `access_groups` without it, and `access_groups` now says the consumer
enforces the gate because KeyCape has none; R3, `mfa_required` "false" means the
per-user privacyIDEA policy; R4, register-client's justification records the
restart for every client, the quiet window, and that the rendered registry must
pass KeyCape's client validation before it. The accepted Forgejo case and both
examples still validate; 116 tests pass.
Open, needs a decision: R2's verification step that the consumer enforces the
group gate is a new verification step, hence 1.1 (minor), and the validator pins
cases to the exact type version, so the accepted 1.0 Forgejo case would need
re-pinning. Also open with key-cape: a KeyCape entry point that validates a
rendered client registry without starting the server; until then
`keycape-client.py` mirrors the redirect rule.
2026-10-10: founder go for 1.1, so no 1.0 run overlaps. The type adds
verification `consumer-enforces-groups`, and hand-over now requires the consumer
to enforce `access_groups` (minor, canon section 6; risk class unchanged, no new
acknowledgment; history in the type README). The accepted Forgejo case and both
examples are re-pinned to `oidc-browser-relying-party@1.1` before any apply; the
acknowledgment intake keeps `@1.0` as the record of what was acknowledged.
ops-mason's build and ops-warden's case-ref digest predate this and need a
re-render.
2026-10-10: ops-mason re-rendered the Forgejo case from `@1.1` (message
`22c6bba6`; plan digest `af1f4f81…`, no collisions) and rebuilt the bundle. The
rendered OpenBao objects are identical to 1.0, so the already applied
CCR-2026-0041 stands; 1.1 changes only consumer-side steps. key-cape shipped
`keycape check-config` (c528daa, message `a7b0aeff`): KeyCape's startup checks
without secret values, including the new duplicate-clientId rule.
`keycape-client.py --check-config <binary>` now runs it on the live and the
proposed registry, fed on stdin, and apply refuses a proposed registry that
fails; register-client's entry point names the option (1.1 patch). The deployed
image predates c528daa, so the binary is built from key-cape source for now.
Before the next KeyCape rollout (Forgejo register-client or the CCR-2026-0018
window), the attended operator runs `inspect --check-config` so the live
registry is checked too.
```task
id: NK-WP-0048-T07
status: wait
depends_on: [NK-WP-0048-T06]
blocking_reason: "The next types follow once the first one is proven end to end."
priority: medium
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
```
### T07 — Queue the next types
From the T02 ranking, open the next type drafts (likely the confidential service
client and the workload KV read lane), each to be acknowledged on its own.
Record which existing hand-built instances get adopted as cases, without
rewriting them in place.
```task
id: NK-WP-0048-T08
status: done
priority: high
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
```
### T08 — identity-provisioner: ensure service-access groups
The type's `ensure-groups` step calls `identity-provisioner groups ensure --groups
<list> --members <group=user+user;...>`. The provisioner already has idempotent
`_ensure_group` and `_add_group`, but exposes only tenant-scoped operations. Add
the general operation (create if absent, add the listed memberships, never
remove others), its rollback (remove only this case's memberships), tests, and
the ops-warden route for its service identity.
2026-10-08: done. `identity-provisioner/provisioner.py` gained
`ensure_service_groups` (`POST /v1/groups/ensure`) and `revoke_service_groups`
(`POST /v1/groups/revoke`), behind the existing service-token check. Names
matching `tenant*`, `netkingdom-*` and `lldap_*` are reserved; members must
already exist; everything is validated before any change; memberships are read
back before success; nothing else is ever removed; groups are deleted only when
asked and empty. 7 new tests (39 in the provisioner suite). The type's
`ensure-groups` step and its rollback now name these endpoints. The ops-warden
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
```task
id: NK-WP-0048-T09
status: done
priority: high
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
```
### T09 — The `keycape-client` entry point and its declaration
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
live registration needs already lives in net-kingdom, where KeyCape is deployed
(`sso-mfa/k8s/keycape/`): the contained kubectl, the cluster-identity pin and the
issuer pin. A copy in key-cape would drift.
2026-10-08: done. `sso-mfa/k8s/keycape/keycape-client.py` derives the KeyCape
client record from a case (`client-from-case`). It adds or removes exactly one
client with the existing safeguards (byte-preserving merge, exact match or
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
requires the Deployment env binding first for confidential clients, and requires
the case acceptance record for `apply` instead of a per-client founder decision
id. 15 tests, including a regression showing that the vergabe case reproduces
its hand-built registration exactly; all 71 helper tests pass. Declaration
`capabilities/playbooks/net-kingdom.keycape-client-registration.yaml` (contract
PASS, `maturity: implemented`) replaces key-cape's draft. The type now pins it,
and for confidential clients adds `deliver-secret` (delivery from custody into
the sso namespace) and `bind-env` (Deployment env), attended in the same session
as `register-client`. The per-client helpers stay until their clients are
adopted as cases.
2026-10-10 — NetKingdom owner review of the execution seam, source only:
ops-warden/docs/security-case-execution-proposal.md SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
was reviewed against INTENT, SecurityCase canon, acknowledged type1.1, current
case and owner source. docs/security-case-execution-owner-review.md supports
the closed adapter/receipt direction with explicit versioned schema, structural
materialization provenance, unchanged caller/session boundaries, safe CAS
reconciliation and exact-created-object rollback requirements. This is owner
review, not approval. Supporting file digests are in the adjacent sources JSON.
Current source exposes a circular wait: ops-mason T03 waits on mint while Warden
and the broker refuse a non-materialized case. Structural acceptance therefore
needs its own native pinned receipt before mint, not a successful mint before
materialized. The platform broker additionally pins type1.0 while Forgejo is
accepted at1.1; platform-owner alignment and tests are required, not an unchecked
version bypass. T06 remains wait. No case/type lifecycle status, grant, authority
or live runtime changed by this review. A proposed informed-decision would
adopt the source contract only; native materialization and execution acceptance
stay distinct.
Final proposal re-review: staged Warden proposal SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
adds only the explicitly local reducer appendix, not an accepted wire protocol
or executor. The exact-final review and sources JSON supersede the earlier
1035f049... proposal review. Platform staged the exact type1.1 broker correction
with ten fake-backend subprocess checks (script SHA256
9576cd211cfc06526ec4cc9bd0a5b06f0f59287a68931bb2d6e3592a951ddc7d).
The next source work is the versioned closed adapter/receipt/materialization
contract, not another relaxation of the broker's status gate. Native work still
requires installed broker provenance, structural reader receipts, exact new
materialized-case bytes, caller grant/session revocation and verified execution.
No installed/native acceptance is inferred from that staged correction.
2026-10-11 — new source-only type1.2 architecture acknowledgment:
Confirmed native founder ACCEPT of infd-20261011-forgejo-type12-source-r1@1
at06:47:39Z, approval8e9332c8-754a-420c-b8b7-445cb4fe6765. Exact frozen packet
0b377d647ca748cd03c92f9792396412048055d4bde9f7bb06ff6d413f7acd68 and submission
585cee731de30d2b37883a57abfd9eb32f023ee694e8d911989109d5caf80569; native receipt
retained in docs/evidence/2026-10-11-forgejo-type12-source-acknowledgment/.
The complete acknowledged1.2 source package is isolated under
security-case-type-revisions/1.2/oidc-browser-relying-party; default loader and
current accepted1.1 case remain untouched because load_type is single-version.
The new proposed1.2 case is NOT accepted/materialized/live and has no acceptance
fields. Actual reference-migration design approval is a separate Apps owner
receipt; no1.1 approval is inherited. Source-recording review/test proof and exact
next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md.
T06 remains wait and T07 remains its existing dependent task; type is NOT
implemented and no first-case/native grant or broader workplan finish is asserted.
2026-10-11 — bounded explicit version selection source preparation for T06:
The validator now offers optional exact version_roots/--version-root VERSION=PATH
selection with duplicate/mismatch/traversal/symlink refusal and no fallback. The
default1.1 source and historicalacceptedcase hashes/ordinaryrefusal/preparation
render stayunchanged; actual1.1 is acknowledged, not implemented. Acknowledged1.2
ordinaryadmission also refuses; optionalpreparation remains nonexecuting.43local
validatorchecks pass and original1.1 CLIrenderbytes match exactly. Makerproof:
docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
FormalNetownerreview/reference-source integration and all existing native adapter,
provenance/broker/materialization/attendedproof gates remain; T06 stayswait, no
new residual or firstcase/typeimplementation/native grant is inferred.
2026-10-16 — isolated source preparation for T06 (canonical integration pending):
Added `check-evidence` to the existing validator CLI. It reuses exact version
selection and ordinary type/case/render admission, checks actual Mason structural
request source/inventory/projection pins and reads actual Warden journal rows
read-only. Successful rows require exact type-declared descriptors and attempt/
revocation fingerprints. Existing acknowledged types without machine descriptors
remain plan-only. The compatibility result never claims authenticated native
completion: Request and journal fingerprints are not portable native evidence.
Owner authentication/status binding and actual native gates remain unresolved;
T06/T07 statuses and both type/case lifecycles remain unchanged. No dispatch,
credential access or authority expansion is introduced. See tool README for the
existing owner protocol boundaries and source integration tests.
Concrete next Warden owner integration: current `Executor.admit` refuses every
case parameter whose sensitivity is not public/operational, so browser
`redirect_uris`, `allowed_scopes`, `client_type`, `mfa_required` (and 1.2
`consumer_namespace`) fail after otherwise prepared admission. The sensitive
classification is a security-change review level, not secret content. The
Warden owner must validate through NetKingdom's existing exact-version case/type
validator, then project only descriptor-declared safe target metadata; do not
remove the credential/value refusal or copy complete parameters into journals.
Actual owner descriptor acknowledgment and independent evidence admission remain
prerequisites, with normal admission still refusing unimplemented types.
## T10 — Source compatibility checker for Mason and Warden evidence
```task
id: NK-WP-0048-T10
status: done
priority: high
state_hub_task_id: "4b06ca97-c1e4-5680-bf22-ab75ab1646a4"
```
Completed 2026-10-16 in published source `c31d21ba`. The read-only
`check-evidence` command checks the actual Mason structural request and Warden
journal source protocols against an exact version/render. Independent source
review passed 103 tests at that checkpoint. It always reports native completion
false and execution prohibited. Fixtures and local journal fingerprints do not
provide authenticated owner/native evidence. This records completed source scope
previously described under T06; it does not duplicate native acceptance credit.
## T11 — Validate the optional machine-descriptor pin contract
```task
id: NK-WP-0048-T11
status: done
priority: high
state_hub_task_id: "ef2a0119-f59e-5e45-9519-115da9df7b85"
```
Completed 2026-10-16. Reproduced the JSON schema rejecting a valid
`execution_descriptors` map while the Python validator accepted malformed maps.
The schema now supports the existing Warden optional exact-byte pin map; semantic
validation binds keys to declared steps, bounds the map and checks digest shape.
The direct evidence API refuses malformed maps before accessing them. Twenty-one
new checks and all 127 affected source checks passed. No current type/case bytes,
status, owner descriptors or native acceptance changed. Source evidence and
publication receipt: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-acceptance-next/`.
## T12 — Prepare NetKingdom-owned attended step adapters
```task
id: NK-WP-0048-T12
status: todo
priority: high
state_hub_task_id: "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd"
```
Owner: NetKingdom. Retained scope: prepare bounded attended Deployment env,
config verification and restart/readiness steps around the existing helpers.
Next action: identify exact current entry points and their revision/readback
contracts, then implement fixed source-pinned callbacks or receipt-only attended
steps. Completion requires positive/refusal checks, exact target/source pins and
uncertainty behavior without acquiring user-engine runtime identity or exposing
registry/client secrets. Existing caller/session, cluster/issuer, CAS/readback
and status-only output boundaries remain mandatory.
2026-10-16 task refinement: the original broad adapter scope is split into T13
(completed registration source candidate), T14 (group creation provenance), T15
(durable registration recovery) and T16 (descriptor/artifact selection and owner
review). This preserves T12's task ID and Hub UUID while giving each remaining
outcome an owner and completion gate. T06 now names every source dependency;
its native/reference lifecycle acceptance scope remains unchanged. Completion
of any source task supplies no Warden, Platform or Apps assent and no native
execution authority.
## T13 — Prepare the fixed registration adapter source candidate
```task
id: NK-WP-0048-T13
status: done
priority: high
state_hub_task_id: "b264c10b-565c-5d29-b788-03cf5fbd2883"
```
Completed 2026-10-16, source implementation and synthetic fixture proof only.
Added an injection-only adapter around actual `keycape-client.run`: fixed
case/principal/human/lane/source/acceptance binding, existing CAS/readback,
metadata-only owner evidence and per-attempt parent-session revocation boundary.
The descriptor source composition pins the exact checker executable digest
alongside adapter/helper source. Creation-only rollback authenticates its
creation receipt and refuses changed postcreation UID/resourceVersion;
pre-existing clients confer no rollback authority. Missing readback, evidence,
revocation or process-local creation provenance cannot become retry-safe success.
Evidence: Custodian `docs/changes/netkingdom-target-2026-10-16/browser-adapter-next/`
R3 candidate, explicit patch and postimages, retained R1 rejected custody finding
and R2 revision, and 36 passing source checks (16 new, 20 existing). Three checks
use actual Warden kernel admission/dispatch/rollback with published Warden
`d97652a3bb5fa733b063d71a1b74068efcccf572` producer pins unchanged. Owner trust,
descriptor/type/case and Kubernetes objects remain synthetic fixtures. This
records a completed source deliverable, not independent review, publication,
type acknowledgment, exercised procedure, identity admission or native browser
acceptance. Independent root source review: Custodian browser-adapter-next/coordinator-review-r3.json. Source integration and normal publication/synchronization receipts: Custodian browser-adapter-next/canonical-integration.json. These receipts supply no other-owner assent or native authority.
Source publication: `0775c2bfa41af236301155bb5a69bbf088bdf823`, independently
reviewed by the Custodian coordinator before integration. The required initial
projection and ordinary UUID writeback both completed with zero failures and
zero automation errors. Generated-only brief commits were refused by the normal
hook; exact pre-existing staged/working brief bytes were restored. The separate
canonical integration receipt retains those refusals and projection evidence.
Native admission and all other-owner acceptance remain open.
## T14 — Preserve created-only provenance for group ensure and revoke
```task
id: NK-WP-0048-T14
status: todo
priority: high
state_hub_task_id: "fce95440-a25b-5f27-8ef3-a3d182099113"
```
Owner: NetKingdom. Next action: extend or wrap the existing identity-provisioner
ensure/revoke contract with independently retained prestate and exact newly
created groups/memberships before exposing reversible owner callbacks. Current
ensure returns no membership-creation inventory; current revoke may remove a
membership that predated this case. Completion requires actual helper-backed
positive/refusal fixtures, all validation before mutation, per-attempt evidence
and revocation, readback, ambiguity handling, and rollback that preserves every
pre-existing/shared group and membership. No existing endpoint or service token
is treated as new authorization.
## T15 — Define authenticated recovery for lost registration process state
```task
id: NK-WP-0048-T15
status: todo
priority: high
state_hub_task_id: "1b825705-4848-5edc-832f-b1622cd9bdb7"
```
Owner: NetKingdom for registration state, with Warden owning kernel evidence
admission. Next action: select the exact existing owner evidence holder/readback
contract and define authenticated lookup for the original creation attempt.
Completion requires restart/lost-receipt fixtures that bind case/source/principal,
original created-only provenance and current native revision before any undo;
uncertain outcomes remain non-replayable. The T13 adapter deliberately refuses
post-restart rollback and installs no reconciliation callback. A new broker or
unverified JSON receipt is not a recovery contract. Actual evidence-holder
acceptance and attended recovery remain separately authorized native work.
## T16 — Select and review the actual machine descriptor composition
```task
id: NK-WP-0048-T16
status: todo
priority: high
state_hub_task_id: "200bf500-5084-5ae7-be26-3033fe1f296e"
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
independently owns global provenance/admission. Next action: identify the exact
reviewed checker executable artifact and native owner evidence/session callback
contracts, then assemble the complete step descriptor set and a proposed explicit
type version selection. T13's retained descriptor pins a synthetic checker only.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
pins for NetKingdom 015694ee; it does not attest T13's new adapter. Pinning any
machine descriptor into an acknowledged type is a separately reviewed source
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.

View file

@ -0,0 +1 @@
{"actor":"human","adapter":"net-kingdom/keycape-registration@v1","adapter_source":"sha256:ea63efd04b4b92dc7cf52a775c6e0362b61f57783a5009d42972697e1cfaf2cd","allowed_created":["keycape-client"],"effect":"change","lane":"key-cape-oidc-login","owner":"net-kingdom","retry_safe":false,"revocation_scopes":["parent-session"],"step":"register-client","target_parameter":"client_id","version":"warden.case.execution.v1"}

View file

@ -0,0 +1,13 @@
test_actual_add_and_created_only_rollback_preserve_original_bytes (__main__.ArtifactAdapterChecks.test_actual_add_and_created_only_rollback_preserve_original_bytes) ... ok
test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust (__main__.ArtifactAdapterChecks.test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust) ... ok
test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo (__main__.ArtifactAdapterChecks.test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo) ... ok
test_kernel_external_revision_change_retains_uncertain_rollback (__main__.ArtifactAdapterChecks.test_kernel_external_revision_change_retains_uncertain_rollback) ... ok
test_stale_revision_refuses_mutation (__main__.ArtifactAdapterChecks.test_stale_revision_refuses_mutation) ... ok
test_substituting_caller_selected_checker_under_same_descriptor_is_refused (__main__.ArtifactAdapterChecks.test_substituting_caller_selected_checker_under_same_descriptor_is_refused) ... ok
test_real_checker_rejects_invalid_startup_config_before_cas (__main__.ArtifactAdapterChecks.test_real_checker_rejects_invalid_startup_config_before_cas) ... ok
test_real_checker_rejects_missing_declared_secret_env_before_cas (__main__.ArtifactAdapterChecks.test_real_checker_rejects_missing_declared_secret_env_before_cas) ... ok
----------------------------------------------------------------------
Ran 8 tests in 1.497s
OK

View file

@ -0,0 +1,8 @@
{
"artifacts": {
"/tmp/nk-checker-artifact-next/keycape": "sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5",
"/tmp/nk-checker-artifact-next-r2/keycape": "sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5",
"/tmp/nk-checker-artifact-next-r3/keycape": "sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5"
},
"identical": true
}

View file

@ -0,0 +1,62 @@
"""Disposable adapter fixtures using exact compiled checker, no checker mocks."""
from dataclasses import asdict,replace
import base64,copy,importlib.util,json,os
from pathlib import Path
import unittest
ROOT=Path('/home/worsch/net-kingdom')
spec=importlib.util.spec_from_file_location('owner_registration_tests',ROOT/'sso-mfa/k8s/keycape/test_keycape_registration_adapter.py')
t=importlib.util.module_from_spec(spec);spec.loader.exec_module(t)
BINARY=Path('/tmp/nk-checker-artifact-next-r3/keycape')
class ArtifactAdapterChecks(t.RegistrationTests):
def setUp(self):
super().setUp()
self.check_patch.stop() # The real maintained subprocess checker runs.
self.binary.write_bytes(BINARY.read_bytes())
self.binary.chmod(0o700)
desc=t.a.descriptor(t.digest(self.binary.read_bytes()))
self.desc_bytes=json.dumps(asdict(desc),sort_keys=True,separators=(',',':')).encode()
self.admitted=replace(self.admitted,pins=replace(self.admitted.pins,adapter=t.digest(self.desc_bytes)))
self.inv=replace(self.admitted,attempt_id='attempt-one',operation='run')
self.kw.update(admitted=self.admitted,check_config_digest=t.digest(self.binary.read_bytes()))
self.missing_env=False
clients=copy.deepcopy(t.f.EXISTING)
clients[1].update(displayName='Synthetic legacy approval',
redirectUris=['https://approval.example.invalid/cb'],allowedScopes=['openid'],grantTypes=['authorization_code'])
self.secret,self.raw=t.f.fixture(clients,tail='port: 8080\nprivateKeyPem: /nonexistent/synthetic-key.pem\ntokenLifetime: 15m\n')
def kube(self,args,payload=None):
value=super().kube(args,payload)
if args[:5]==['-n','sso','get','deployment','keycape'] and not self.missing_env:
value['spec']['template']['spec']['containers'][0]['env'].append({
'name':'KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET',
'valueFrom':{'secretKeyRef':{'name':'synthetic-legacy','key':'client-secret'}}})
return value
def test_real_checker_rejects_invalid_startup_config_before_cas(self):
raw=base64.b64decode(self.secret['data']['config.yaml']).decode().replace('port: 8080','port: 0')
self.secret['data']['config.yaml']=base64.b64encode(raw.encode()).decode()
with self.assertRaises(t.Refused):t.a.build(**self.kw).invoke(self.inv)
self.assertFalse(self.effects)
self.assertEqual(self.revocations,[self.inv])
def test_real_checker_rejects_missing_declared_secret_env_before_cas(self):
self.missing_env=True
with self.assertRaises(t.Refused):t.a.build(**self.kw).invoke(self.inv)
self.assertFalse(self.effects)
self.assertEqual(self.revocations,[self.inv])
if __name__=='__main__':
selected=[
'test_actual_add_and_created_only_rollback_preserve_original_bytes',
'test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust',
'test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo',
'test_kernel_external_revision_change_retains_uncertain_rollback',
'test_stale_revision_refuses_mutation',
'test_substituting_caller_selected_checker_under_same_descriptor_is_refused',
'test_real_checker_rejects_invalid_startup_config_before_cas',
'test_real_checker_rejects_missing_declared_secret_env_before_cas']
suite=unittest.TestSuite(ArtifactAdapterChecks(name) for name in selected)
result=unittest.TextTestRunner(verbosity=2).run(suite)
raise SystemExit(not result.wasSuccessful())

View file

@ -0,0 +1,68 @@
--- a/workplans/NK-WP-0048-security-case-types.md
+++ b/workplans/NK-WP-0048-security-case-types.md
@@ -323,7 +323,7 @@
```task
id: NK-WP-0048-T06
status: wait
-depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
+depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
@@ -734,10 +734,11 @@
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
-independently owns global provenance/admission. Next action: identify the exact
-reviewed checker executable artifact and native owner evidence/session callback
-contracts, then assemble the complete step descriptor set and a proposed explicit
-type version selection. T13's retained descriptor pins a synthetic checker only.
+independently owns global provenance/admission. Next action: obtain review of the
+exact checker source/artifact proposed by T17 and identify native owner evidence/
+session callback contracts, then assemble the complete step descriptor set and
+a proposed explicit type version selection. T13's historical descriptor retains
+its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
@@ -746,3 +747,40 @@
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.
+
+## T17 — Build and exercise the exact offline config-checker artifact
+
+```task
+id: NK-WP-0048-T17
+status: done
+priority: high
+```
+
+Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
+existing `key-cape/src/cmd/keycape` command from published clean source revision
+`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
+`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
+CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
+read-only module selection. The full retained closure pins 108 source files,
+9876 toolchain files and ten used module source directories. Three isolated
+build locations produced identical 13633409-byte artifacts:
+`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
+
+Eight disposable checks use the actual executable and maintained helper/kernel,
+with complete synthetic configs and a minimal environment: add/created-only
+removal, normal pinned producer admission, rollback, lost-readback uncertainty,
+stale revisions, substituted checker refusal, invalid startup config and missing
+secret-env refusal. No checker stub or producer-pin override remains. The
+artifact-bound descriptor proposal is
+`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
+Evidence: Custodian
+`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
+The first two build attempts succeeded but their closure collection failed;
+both failures are retained, as is refusal of the older incomplete synthetic
+fixture. No production config, credentials or cluster mutation was used.
+
+This extracts completed build/fixture scope from T16 without claiming KeyCape
+owner endorsement, native checker selection/storage custody, acknowledged type
+integration, native evidence/session callbacks or attended browser acceptance.
+T16 retains exact owner review and composition/admission scope. The attended
+registration procedure remains unexercised.

View file

@ -0,0 +1,44 @@
test_ambient_overrides_cannot_admit_supplied_invalid_config (test_keycape_checker_environment.RealCheckerEnvironmentTests.test_ambient_overrides_cannot_admit_supplied_invalid_config) ... ok
test_ambient_overrides_cannot_reject_supplied_valid_config (test_keycape_checker_environment.RealCheckerEnvironmentTests.test_ambient_overrides_cannot_reject_supplied_valid_config) ... ok
test_relative_selected_artifact_is_not_replaced_by_path_decoy (test_keycape_checker_environment.RealCheckerEnvironmentTests.test_relative_selected_artifact_is_not_replaced_by_path_decoy) ... ok
test_access_groups_require_groups_scope (test_keycape_client.ClientRecordTests.test_access_groups_require_groups_scope) ... ok
test_client_from_case_requires_the_type (test_keycape_client.ClientRecordTests.test_client_from_case_requires_the_type) ... ok
test_confidential_record_binds_env_secret_and_pins_mfa (test_keycape_client.ClientRecordTests.test_confidential_record_binds_env_secret_and_pins_mfa) ... ok
test_invalid_params_refused (test_keycape_client.ClientRecordTests.test_invalid_params_refused) ... ok
test_public_record_has_no_secret (test_keycape_client.ClientRecordTests.test_public_record_has_no_secret) ... ok
test_redirects_keycape_would_refuse_at_startup_are_refused (test_keycape_client.ClientRecordTests.test_redirects_keycape_would_refuse_at_startup_are_refused) ... ok
test_vergabe_case_reproduces_the_hand_built_registration (test_keycape_client.ClientRecordTests.test_vergabe_case_reproduces_the_hand_built_registration) ... ok
test_add_appends_only_this_client_and_preserves_bytes (test_keycape_client.MergeTests.test_add_appends_only_this_client_and_preserves_bytes) ... ok
test_identical_is_noop_and_drift_refused (test_keycape_client.MergeTests.test_identical_is_noop_and_drift_refused) ... ok
test_removal_removes_exactly_this_client (test_keycape_client.MergeTests.test_removal_removes_exactly_this_client) ... ok
test_shared_secret_ref_and_duplicates_refused (test_keycape_client.MergeTests.test_shared_secret_ref_and_duplicates_refused) ... ok
test_apply_needs_case_acceptance (test_keycape_client.RunTests.test_apply_needs_case_acceptance) ... ok
test_apply_needs_env_binding_for_confidential_clients (test_keycape_client.RunTests.test_apply_needs_env_binding_for_confidential_clients) ... ok
test_apply_needs_keycape_check_config (test_keycape_client.RunTests.test_apply_needs_keycape_check_config) ... ok
test_apply_patches_reads_back_and_records_acceptance (test_keycape_client.RunTests.test_apply_patches_reads_back_and_records_acceptance) ... ok
test_apply_refuses_a_changed_revision (test_keycape_client.RunTests.test_apply_refuses_a_changed_revision) ... ok
test_apply_refuses_a_registry_keycape_would_refuse (test_keycape_client.RunTests.test_apply_refuses_a_registry_keycape_would_refuse) ... ok
test_inspect_changes_nothing_and_emits_no_values (test_keycape_client.RunTests.test_inspect_changes_nothing_and_emits_no_values) ... ok
test_inspect_reports_live_and_proposed_checks_with_deployment_env (test_keycape_client.RunTests.test_inspect_reports_live_and_proposed_checks_with_deployment_env) ... ok
test_main_contains_failures (test_keycape_client.RunTests.test_main_contains_failures) ... ok
test_actual_add_and_created_only_rollback_preserve_original_bytes (test_keycape_registration_adapter.RegistrationTests.test_actual_add_and_created_only_rollback_preserve_original_bytes) ... ok
test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust (test_keycape_registration_adapter.RegistrationTests.test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust) ... ok
test_binary_or_source_drift_refused_before_tooling (test_keycape_registration_adapter.RegistrationTests.test_binary_or_source_drift_refused_before_tooling) ... ok
test_existing_drift_refused_and_session_revoked (test_keycape_registration_adapter.RegistrationTests.test_existing_drift_refused_and_session_revoked) ... ok
test_failed_readback_is_uncertain_and_never_provides_creation (test_keycape_registration_adapter.RegistrationTests.test_failed_readback_is_uncertain_and_never_provides_creation) ... ok
test_identical_client_is_no_change_and_cannot_be_removed (test_keycape_registration_adapter.RegistrationTests.test_identical_client_is_no_change_and_cannot_be_removed) ... ok
test_initial_case_descriptor_and_target_pins_are_required (test_keycape_registration_adapter.RegistrationTests.test_initial_case_descriptor_and_target_pins_are_required) ... ok
test_invocation_rebinding_refused_before_tooling (test_keycape_registration_adapter.RegistrationTests.test_invocation_rebinding_refused_before_tooling) ... ok
test_kernel_external_revision_change_retains_uncertain_rollback (test_keycape_registration_adapter.RegistrationTests.test_kernel_external_revision_change_retains_uncertain_rollback) ... ok
test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo (test_keycape_registration_adapter.RegistrationTests.test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo) ... ok
test_missing_or_tampered_evidence_never_verifies_or_authorizes_undo (test_keycape_registration_adapter.RegistrationTests.test_missing_or_tampered_evidence_never_verifies_or_authorizes_undo) ... ok
test_process_loss_does_not_reconstruct_created_authority (test_keycape_registration_adapter.RegistrationTests.test_process_loss_does_not_reconstruct_created_authority) ... ok
test_revocation_or_publication_failure_cannot_produce_verified_success (test_keycape_registration_adapter.RegistrationTests.test_revocation_or_publication_failure_cannot_produce_verified_success) ... ok
test_rollback_requires_exact_provenance_and_postcreation_revision (test_keycape_registration_adapter.RegistrationTests.test_rollback_requires_exact_provenance_and_postcreation_revision) ... ok
test_stale_revision_refuses_mutation (test_keycape_registration_adapter.RegistrationTests.test_stale_revision_refuses_mutation) ... ok
test_substituting_caller_selected_checker_under_same_descriptor_is_refused (test_keycape_registration_adapter.RegistrationTests.test_substituting_caller_selected_checker_under_same_descriptor_is_refused) ... ok
----------------------------------------------------------------------
Ran 39 tests in 1.495s
OK

View file

@ -0,0 +1,4 @@
----------------------------------------------------------------------
Ran 39 tests in 1.261s
OK

View file

@ -0,0 +1,16 @@
test_actual_add_and_created_only_rollback_preserve_original_bytes (__main__.ArtifactAdapterChecks.test_actual_add_and_created_only_rollback_preserve_original_bytes) ... ok
test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust (__main__.ArtifactAdapterChecks.test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust) ... ok
test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo (__main__.ArtifactAdapterChecks.test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo) ... ok
test_kernel_external_revision_change_retains_uncertain_rollback (__main__.ArtifactAdapterChecks.test_kernel_external_revision_change_retains_uncertain_rollback) ... ok
test_stale_revision_refuses_mutation (__main__.ArtifactAdapterChecks.test_stale_revision_refuses_mutation) ... ok
test_substituting_caller_selected_checker_under_same_descriptor_is_refused (__main__.ArtifactAdapterChecks.test_substituting_caller_selected_checker_under_same_descriptor_is_refused) ... ok
test_real_checker_rejects_invalid_startup_config_before_cas (__main__.ArtifactAdapterChecks.test_real_checker_rejects_invalid_startup_config_before_cas) ... ok
test_real_checker_rejects_missing_declared_secret_env_before_cas (__main__.ArtifactAdapterChecks.test_real_checker_rejects_missing_declared_secret_env_before_cas) ... ok
test_ambient_urls_cannot_reject_supplied_valid_config (__main__.ArtifactAdapterChecks.test_ambient_urls_cannot_reject_supplied_valid_config) ... ok
test_ambient_urls_cannot_admit_supplied_invalid_config (__main__.ArtifactAdapterChecks.test_ambient_urls_cannot_admit_supplied_invalid_config) ... ok
test_relative_selected_real_binary_is_not_replaced_by_path_decoy (__main__.ArtifactAdapterChecks.test_relative_selected_real_binary_is_not_replaced_by_path_decoy) ... ok
----------------------------------------------------------------------
Ran 11 tests in 1.849s
OK

View file

@ -0,0 +1,38 @@
from pathlib import Path
from collections import Counter
import datetime,hashlib,json,re,subprocess,urllib.request
import yaml
p=Path(__file__).resolve().parent;owner=Path('/home/worsch/net-kingdom');backup=p/'publication-backup';manifest=json.loads((p/'manifest.json').read_text());wp='e31f3f9d-0748-5137-b0a3-7b3ca46a66eb'
sha=lambda data:'sha256:'+hashlib.sha256(data).hexdigest();git=lambda *args:subprocess.check_output(['git','-C',str(owner),*args]);get=lambda path:json.load(urllib.request.urlopen('http://127.0.0.1:8000'+path,timeout=10))
syncs={}
for name in ('normal-source','normal-final'):
value=json.loads((p/(name+'.stdout.json')).read_text());exit_code=json.loads((p/(name+'.exit.json')).read_text())['exit_code']
if exit_code or value['summary']['fail'] or value['summary']['automation_error']:raise SystemExit('sync failure '+name)
syncs[name]={'exit_code':exit_code,'summary':value['summary'],'result':value['result'],'stderr':(p/(name+'.stderr.txt')).read_text(),'fixes_applied':value.get('fixes_applied')}
rel='workplans/NK-WP-0048-security-case-types.md';source=(owner/rel).read_text();blocks=[yaml.safe_load(t) for t in re.findall(r'```task\n(.*?)```',source,re.S)];by_source={b['id']:b for b in blocks};rows=get('/tasks/?workplan_id='+wp);by_uuid={r['id']:r for r in rows}
if len(blocks)!=18 or len(rows)!=18:raise SystemExit('task counts differ')
for b in blocks:
if b['state_hub_task_id'] not in by_uuid or by_uuid[b['state_hub_task_id']]['status']!=b['status']:raise SystemExit('task projection differs '+b['id'])
old={b['id']:b for b in [yaml.safe_load(t) for t in re.findall(r'```task\n(.*?)```',(p/'preimages'/rel).read_text(),re.S)]}
for name,b in old.items():
if by_source[name]['state_hub_task_id']!=b['state_hub_task_id']:raise SystemExit('prior UUID changed '+name)
deps=get('/workplans/'+wp+'/dependencies/');verified=[]
for b in blocks:
if b['status'] in ('done','cancel'):continue
for target in b.get('depends_on',[]):
expected_uuid=by_source[target]['state_hub_task_id'] if target in by_source else None
if not any(d['from_task_id']==b['state_hub_task_id'] and d['relationship_type']=='starts_after' and (d['to_task_id']==expected_uuid if expected_uuid else d.get('description')==b['id']+' depends_on '+target) for d in deps):raise SystemExit('dependency missing '+target)
verified.append(b['id']+' -> '+target)
if git('show',':.custodian-brief.md')!=(backup/'brief.index').read_bytes() or (owner/'.custodian-brief.md').read_bytes()!=(backup/'brief.worktree').read_bytes():raise SystemExit('brief preservation failed')
for name,h in manifest['postimages'].items():
if not name.startswith('workplans/') and sha((owner/name).read_bytes())!=h:raise SystemExit('reviewed source drift '+name)
current=git('rev-parse','HEAD').decode().strip();published=git('rev-parse','origin/main').decode().strip();source_commit='916ba4b33e03179274d03b78bd898a4843e8653f'
if current!=published or not current.startswith('cbabb6d'):raise SystemExit('publication differs')
if git('diff','--name-only','--',rel).strip():raise SystemExit('workplan left dirty')
(p/'projection-tasks.json').write_text(json.dumps([{'id':b['id'],'uuid':b['state_hub_task_id'],'status':b['status'],'depends_on':b.get('depends_on',[])} for b in blocks],indent=2)+'\n');(p/'projection-dependencies.json').write_text(json.dumps(deps,indent=2)+'\n')
summary='NetKingdom source checker boundary fix published '+source_commit+' and task-publication receipt '+current+'. Checker gets absolute selected path and env={}; predecessor URL/PATH counterexamples retained. 39 affected tests plus11 actual-artifact/helper/kernel scenarios pass; exact be377 artifact reproduced from pinned source/tree. All18task UUID/status projections and all open-task dependency edges verified; original staged/working brief restored. Native callbacks/type/identity admission remain open. Stopping at requested pause point.'
request=urllib.request.Request('http://127.0.0.1:8000/progress/',data=json.dumps({'summary':summary,'event_type':'note','author':'codex','workplan_id':wp,'task_id':by_source['NK-WP-0048-T18']['state_hub_task_id']}).encode(),headers={'Content-Type':'application/json'},method='POST');progress=json.load(urllib.request.urlopen(request,timeout=10));(p/'owner-progress.json').write_text(json.dumps(progress,indent=2)+'\n')
resume={'schema':'netkingdom.checker-boundary-resume.v1','stopped_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'reason':'user requested good pause point; current publication and sync finished','owner_revision':current,'no_running_child_operation':True,'source_tasks_done':['NK-WP-0048-T13','NK-WP-0048-T17','NK-WP-0048-T18'],'remaining_todo':{'NK-WP-0048-T12':'env/config/restart source adapters','NK-WP-0048-T14':'created-only group/membership provenance','NK-WP-0048-T15':'authenticated durable registration recovery','NK-WP-0048-T16':'exact owner/native executable storage and callback/type composition admission'},'native_reference_gate':'NK-WP-0048-T06 remains wait','next_action':'At resume, read current owner records and exact review/native prerequisites before choosing one remaining source task. No current native authority is granted.','historical_packets_preserved':['browser-adapter-next','browser-checker-artifact-next'],'no_production_config_or_credential_access':True}
(p/'resume-points.json').write_text(json.dumps(resume,indent=2)+'\n')
receipt={'schema':'custodian.netkingdom.checker-boundary-source-integration.v1','source_commit':source_commit,'publication_record_commit':current,'published_origin':published,'source_review':'coordinator-review.json','reviewed_postimages':manifest['postimages'],'final_source_hashes':{name:sha((owner/name).read_bytes()) for name in manifest['postimages']},'normal_syncs':syncs,'file_task_count':len(blocks),'projected_task_count':len(rows),'status_counts':dict(Counter(b['status'] for b in blocks)),'all_prior_UUIDs_preserved':True,'dependency_edges_verified':verified,'dependency_audit_scope':'all open task dependencies; completed historical-task edges were outside this current admission audit','retained_verification_refinement':'initial broader audit refused absent completed T03->T01 historical edge; current T06/T07 open acceptance edges are verified individually','exact_original_staged_brief_preserved':True,'exact_original_working_brief_preserved':True,'post_sync_race_observation':'brief-post-sync-observation.json','status_after':git('status','--porcelain=v1').decode(),'tests':{'affected':39,'real_artifact_scenarios':11,'producer_pins_overridden':False,'checker_mocked':False},'checker_artifact':manifest['checker_artifact'],'retained_predecessor_failures':'pre-fix-counterexample.txt','publication_failure':{'action':'initial git commit --only','reason':'new regression test not staged; pathspec refusal before commit','recovery':'staged only four reviewed files; normal scanner and source commit/push passed'},'historical_descriptors_unchanged':True,'native_acceptance':False,'new_credential_or_live_action':False,'progress_id':progress.get('id'),'pause_point':'publication and normal final sync complete; no new work begun'}
(p/'canonical-integration.json').write_text(json.dumps(receipt,indent=2)+'\n');print(json.dumps({'source':source_commit,'final':current,'counts':receipt['status_counts'],'dependencies':len(verified),'progress_id':progress.get('id')}))

View file

@ -0,0 +1,198 @@
--- a/sso-mfa/k8s/keycape/keycape-client.py
+++ b/sso-mfa/k8s/keycape/keycape-client.py
@@ -200,10 +200,10 @@
def check_config(binary, config_text, env_names, policy_store):
"""KeyCape's startup checks on one registry: names and messages only, no values."""
- command = [binary, 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
+ command = [str(Path(binary).resolve()), 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
if policy_store:
command.append('--policy-store')
- result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60)
+ result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60, env={})
report = json.loads(result.stdout)
require(isinstance(report, dict) and isinstance(report.get('valid'), bool)
and report['valid'] == (result.returncode == 0), 'check_config_output_shape')
--- /dev/null
+++ b/sso-mfa/k8s/keycape/test_keycape_checker_environment.py
@@ -0,0 +1,66 @@
+"""Opt-in real-checker boundary regressions; no production inputs or checker stub.
+
+Set KEYCAPE_TEST_CHECKER_BINARY and KEYCAPE_TEST_CHECKER_SHA256 to an independently
+built artifact. Ordinary helper-only runs skip this class when none is supplied.
+"""
+from contextlib import chdir
+import hashlib
+import os
+from pathlib import Path
+import tempfile
+import unittest
+from unittest.mock import patch
+
+from test_keycape_client import kc
+
+VALID = '''issuer: https://issuer.example.invalid
+port: 8080
+privateKeyPem: /nonexistent/synthetic-key.pem
+clients:
+ - clientId: synthetic-client
+ displayName: Synthetic client
+ redirectUris: [https://client.example.invalid/cb]
+ allowedScopes: [openid]
+ grantTypes: [authorization_code]
+ clientType: public
+'''
+
+
+class RealCheckerEnvironmentTests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ source = os.environ.get('KEYCAPE_TEST_CHECKER_BINARY')
+ if not source:
+ raise unittest.SkipTest('exact real checker artifact not supplied')
+ data = Path(source).read_bytes()
+ expected = os.environ.get('KEYCAPE_TEST_CHECKER_SHA256')
+ if expected != 'sha256:' + hashlib.sha256(data).hexdigest():
+ raise ValueError('real checker artifact digest differs')
+ cls.directory = tempfile.TemporaryDirectory()
+ cls.addClassCleanup(cls.directory.cleanup)
+ cls.binary = Path(cls.directory.name) / 'keycape'
+ cls.binary.write_bytes(data)
+ cls.binary.chmod(0o700)
+
+ def check(self, raw, binary=None):
+ return kc.check_config(str(binary or self.binary), raw, [], False)
+
+ def test_ambient_overrides_cannot_reject_supplied_valid_config(self):
+ with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://polluted.example.invalid',
+ 'KEYCAPE_BROWSER_LOGOUT_URL': 'http://polluted.example.invalid/logout'}):
+ self.assertTrue(self.check(VALID)['valid'])
+
+ def test_ambient_overrides_cannot_admit_supplied_invalid_config(self):
+ invalid = VALID + 'accountPortalURL: http://invalid.example.invalid\nbrowserLogoutURL: http://invalid.example.invalid/logout\n'
+ with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'https://polluted.example.invalid',
+ 'KEYCAPE_BROWSER_LOGOUT_URL': 'https://polluted.example.invalid/logout'}):
+ self.assertFalse(self.check(invalid)['valid'])
+
+ def test_relative_selected_artifact_is_not_replaced_by_path_decoy(self):
+ decoy = Path(self.directory.name) / 'decoy'
+ decoy.mkdir()
+ trap = decoy / 'keycape'
+ trap.write_text('#!/bin/sh\nexit 27\n')
+ trap.chmod(0o700)
+ with chdir(self.directory.name), patch.dict(os.environ, {'PATH': str(decoy)}):
+ self.assertTrue(self.check(VALID, binary=Path('keycape'))['valid'])
--- a/sso-mfa/k8s/keycape/registration-adapter.md
+++ b/sso-mfa/k8s/keycape/registration-adapter.md
@@ -13,7 +13,12 @@
and its complete local helper import closure together with the exact reviewed
`check-config` executable digest. Changing that digest changes the descriptor
source pin and descriptor bytes, requiring independently reviewed admission.
-The fixed executable is rehashed before each attempt. The caller constructs the
+The fixed executable is rehashed before each attempt. The helper resolves the
+selected file to an absolute executable path and supplies an empty checker
+environment, so ambient PATH and KeyCape URL overrides cannot select another
+executable or alter validation of supplied bytes. Path resolution does not
+establish immutable storage or eliminate replacement races; executable custody
+and native runtime admission remain separate requirements. The caller constructs the
proposed invocation from exact source inputs, installs the fixed adapter once,
and calls normal `Executor.admit`; independently derived admission must match.
No callable replacement after admission is required or supported by this recipe.
--- a/workplans/NK-WP-0048-security-case-types.md
+++ b/workplans/NK-WP-0048-security-case-types.md
@@ -323,7 +323,7 @@
```task
id: NK-WP-0048-T06
status: wait
-depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
+depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, NK-WP-0048-T18, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
@@ -734,10 +734,11 @@
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
-independently owns global provenance/admission. Next action: identify the exact
-reviewed checker executable artifact and native owner evidence/session callback
-contracts, then assemble the complete step descriptor set and a proposed explicit
-type version selection. T13's retained descriptor pins a synthetic checker only.
+independently owns global provenance/admission. Next action: obtain review of the
+exact checker source/artifact proposed by T17 and identify native owner evidence/
+session callback contracts, then assemble the complete step descriptor set and
+a proposed explicit type version selection. T13's historical descriptor retains
+its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
@@ -746,3 +747,70 @@
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.
+
+## T17 — Build and exercise the exact offline config-checker artifact
+
+```task
+id: NK-WP-0048-T17
+status: done
+priority: high
+```
+
+Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
+existing `key-cape/src/cmd/keycape` command from published clean source revision
+`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
+`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
+CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
+read-only module selection. The full retained closure pins 108 source files,
+9876 toolchain files and ten used module source directories. Three isolated
+build locations produced identical 13633409-byte artifacts:
+`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
+
+Eight disposable checks use the actual executable and maintained helper/kernel,
+with complete synthetic configs and a minimal environment: add/created-only
+removal, normal pinned producer admission, rollback, lost-readback uncertainty,
+stale revisions, substituted checker refusal, invalid startup config and missing
+secret-env refusal. No checker stub or producer-pin override remains. The
+artifact-bound descriptor proposal is
+`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
+Evidence: Custodian
+`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
+The first two build attempts succeeded but their closure collection failed;
+both failures are retained, as is refusal of the older incomplete synthetic
+fixture. No production config, credentials or cluster mutation was used.
+
+This extracts completed build/fixture scope from T16 without claiming KeyCape
+owner endorsement, native checker selection/storage custody, acknowledged type
+integration, native evidence/session callbacks or attended browser acceptance.
+T16 retains exact owner review and composition/admission scope. The attended
+registration procedure remains unexercised.
+
+## T18 — Make checker input and executable selection deterministic
+
+```task
+id: NK-WP-0048-T18
+status: done
+priority: high
+```
+
+Completed 2026-10-16, isolated source correction and real-artifact fixture proof
+only; independent integration/publication remain separately recorded. KeyCape
+source review identified non-secret `KEYCAPE_ACCOUNT_PORTAL_URL` and
+`KEYCAPE_BROWSER_LOGOUT_URL` overrides in config.Load. The original helper
+inherited ambient environment, allowing valid supplied configuration to be
+rejected or invalid supplied URL configuration to be admitted. A bare relative
+checker filename could also be replaced by PATH lookup after the adapter had
+hashed the selected local file. All three counterexamples are retained against
+the unchanged predecessor.
+
+The existing helper now resolves the selected checker file to an absolute path
+and passes an explicit empty subprocess environment. Three opt-in real-checker
+regressions verify both URL-verdict directions and relative-file selection under
+a non-checker PATH decoy. All 39 affected checks pass with the exact supplied
+artifact, as do 11 real-artifact helper/kernel scenarios. Source evidence:
+Custodian `docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/`.
+Historical descriptors remain retained; the helper correction changes successor
+source-composition pins and requires their separate review/admission. An absolute
+path is not immutable custody and does not eliminate file replacement races;
+T16 retains native executable storage/custody and callback/type admission.
+No production configuration, credentials or cluster mutation was used.

View file

@ -0,0 +1,15 @@
from pathlib import Path
import hashlib,json,subprocess
p=Path(__file__).resolve().parent;owner=Path('/home/worsch/net-kingdom');m=json.loads((p/'manifest.json').read_text());r=json.loads((p/'coordinator-review.json').read_text());sha=lambda data:'sha256:'+hashlib.sha256(data).hexdigest()
if r['reviewed_postimages']!=m['postimages']:raise SystemExit('review source map differs')
for rel,expected in m['postimages'].items():
if sha((p/'postimages'/rel).read_bytes())!=expected:raise SystemExit('postimage drift '+rel)
target=owner/rel
if rel in m['preimages']:
if sha(target.read_bytes())!=m['preimages'][rel]:raise SystemExit('preimage race '+rel)
elif target.exists():raise SystemExit('unexpected existing target '+rel)
backup=p/'publication-backup';backup.mkdir(exist_ok=False);git=lambda *args:subprocess.check_output(['git','-C',str(owner),*args])
(backup/'brief.worktree').write_bytes((owner/'.custodian-brief.md').read_bytes());(backup/'brief.index').write_bytes(git('show',':.custodian-brief.md'));(backup/'index-record.txt').write_bytes(git('ls-files','--stage','.custodian-brief.md'));(backup/'status.txt').write_bytes(git('status','--porcelain=v1'));(backup/'work-records.preimage').write_bytes((owner/'WORK-RECORDS.md').read_bytes())
for rel in m['postimages']:
dest=owner/rel;dest.parent.mkdir(parents=True,exist_ok=True);dest.write_bytes((p/'postimages'/rel).read_bytes())
print('Integrated four exact reviewed postimages; original staged/working brief retained in private local backup.')

View file

@ -0,0 +1,17 @@
{
"command": [
"/usr/bin/python3",
"docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/build_checker.py",
"--source-revision",
"HEAD",
"--source-tree",
"f0010b9cab17dbc3a787628a29657d98b900320f",
"--output-dir",
"/tmp/nk-checker-invalid-ref-must-not-exist",
"--expected-artifact-sha256",
"sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5"
],
"exit_code": 2,
"stderr": "usage: build_checker.py [-h] --source-revision SOURCE_REVISION --source-tree\n SOURCE_TREE --output-dir OUTPUT_DIR\n --expected-artifact-sha256 EXPECTED_ARTIFACT_SHA256\n [--owner-root OWNER_ROOT]\nbuild_checker.py: error: source revision/tree must be exact full hashes\n",
"output_directory_created": false
}

View file

@ -0,0 +1 @@
WARN: git commit failed for writeback: Generated-only commits rejected: staged

View file

@ -0,0 +1,343 @@
{
"repo_slug": "net-kingdom",
"repo_path": "/home/worsch/net-kingdom",
"source_revision": "cbabb6dc3611dc9b9a304b175b17e132ae057bac",
"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-35",
"message": "Repo Manager warning: workplans/: repository uses more than one workplan prefix: ['NET-WP', 'NK-WP']",
"file_path": "workplans/",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0011' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0011-enterprise-federation-saml.md",
"db_id": "1075448f-d533-5f9e-94b7-c3adfe151a07",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
"file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md",
"db_id": "",
"file_value": "USER-WP-0020",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub",
"file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md",
"db_id": "",
"file_value": "KEY-WP-0004",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
"file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md",
"db_id": "",
"file_value": "USER-WP-0020",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub",
"file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md",
"db_id": "",
"file_value": "KEY-WP-0004",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0021' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "USER-WP-0021",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'FLEX-WP-0009' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "FLEX-WP-0009",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'AUDIT-WP-0003' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "AUDIT-WP-0003",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'EMAIL-WP-0004' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "EMAIL-WP-0004",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0022' is not linked to State Hub",
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
"db_id": "",
"file_value": "USER-WP-0022",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'KEY-WP-0008' is not linked to State Hub",
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
"db_id": "",
"file_value": "KEY-WP-0008",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'CSOC-WP-0003' is not linked to State Hub",
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
"db_id": "",
"file_value": "CSOC-WP-0003",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0027' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0027-reef-placement-reconciliation.md",
"db_id": "965ad365-6b81-50a1-a2a3-2d0c1fcce0b4",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0031' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0031-deterministic-posture-feedback.md",
"db_id": "9d7b04f9-3803-5613-b7a5-8bd606c77f5a",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0040' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0040-execution-attribution-receipt.md",
"db_id": "e2533f3a-aa43-59b3-bff3-8e64b6149487",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Missing DB dependency edge: task 'NK-WP-0048-T06' starts after task NK-WP-0048-T17",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T06",
"db_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"file_value": "NK-WP-0048-T17",
"db_value": "",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Missing DB dependency edge: task 'NK-WP-0048-T06' starts after task NK-WP-0048-T18",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T06",
"db_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"file_value": "NK-WP-0048-T18",
"db_value": "",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-22",
"message": "Task description drift 'NK-WP-0048-T18': file content differs from DB (file wins)",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T18",
"db_id": "9c45da71-fb4c-5f20-9065-90f448f37c64",
"file_value": "Completed 2026-10-16, isolated source correction and real-artifact fixture proof\nonly; independent integration/publicati",
"db_value": "Completed 2026-10-16, isolated source correction and real-artifact fixture proof\nonly; independent integration/publicati",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'RAIL-PL-WP-0002' is not linked to State Hub",
"file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md",
"db_id": "",
"file_value": "RAIL-PL-WP-0002",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'nk-wp-0035@retired-20260908' (id=064c5e8b\u2026, status=archived) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "064c5e8b-7852-4d7f-a5fa-e26e406ab8e0",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'adhoc-2026-08-14@retired-20260827' (id=a5097121\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "a5097121-1461-59f8-81fa-e6ecef527337",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'adhoc-2026-08-23@retired-20260827' (id=e25ba5fa\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "e25ba5fa-eaae-5039-8d93-a69c6d837fc4",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'net-kingdom-adhoc-2026-06-14@retired-20260827' (id=c6f3d6bf\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "c6f3d6bf-4916-490d-96ce-092d2776d7e7",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'net-kingdom-adhoc-2026-07-02@retired-20260827' (id=67c1c7ac\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "67c1c7ac-d8b1-41dc-a81a-4e43f1afd068",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0020' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md",
"db_id": "",
"file_value": "NET-WP-0020",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0015' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260603-NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md",
"db_id": "",
"file_value": "NET-WP-0015",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0016' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260603-NET-WP-0016-guided-security-bootstrap-experience.md",
"db_id": "",
"file_value": "NET-WP-0016",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0018' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260604-NET-WP-0018-bootstrap-automation-and-rebuild-readiness.md",
"db_id": "",
"file_value": "NET-WP-0018",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0017' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md",
"db_id": "",
"file_value": "NET-WP-0017",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0019' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260702-NET-WP-0019-t06-adjacent-user-lifecycle-dry-run-polish.md",
"db_id": "",
"file_value": "NET-WP-0019",
"db_value": "NK-WP",
"fixable": false
}
],
"fixes_applied": [
"bindings: synced 64/64",
"C-20 fixed: dependency e31f3f9d\u2026 starts_after \u2192 8c3e4baa\u2026",
"C-20 fixed: dependency e31f3f9d\u2026 starts_after \u2192 9c45da71\u2026",
"C-22 fixed: task 9c45da71\u2026 description updated",
"brief: .custodian-brief.md updated",
"push: pushed"
],
"summary": {
"fail": 0,
"automation_error": 0,
"warn": 27,
"info": 5
},
"result": "warn"
}

View file

@ -0,0 +1 @@
WARN: git commit failed for writeback: Generated-only commits rejected: staged

View file

@ -0,0 +1,374 @@
{
"repo_slug": "net-kingdom",
"repo_path": "/home/worsch/net-kingdom",
"source_revision": "916ba4b33e03179274d03b78bd898a4843e8653f",
"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-35",
"message": "Repo Manager warning: workplans/: repository uses more than one workplan prefix: ['NET-WP', 'NK-WP']",
"file_path": "workplans/",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-33",
"message": "WORK-RECORDS.md is missing or stale \u2014 regenerate via fix-consistency (CUST-WP-0061-T04 generated work-record index)",
"file_path": "WORK-RECORDS.md",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0011' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0011-enterprise-federation-saml.md",
"db_id": "1075448f-d533-5f9e-94b7-c3adfe151a07",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
"file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md",
"db_id": "",
"file_value": "USER-WP-0020",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub",
"file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md",
"db_id": "",
"file_value": "KEY-WP-0004",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub",
"file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md",
"db_id": "",
"file_value": "USER-WP-0020",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub",
"file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md",
"db_id": "",
"file_value": "KEY-WP-0004",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0021' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "USER-WP-0021",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'FLEX-WP-0009' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "FLEX-WP-0009",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'AUDIT-WP-0003' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "AUDIT-WP-0003",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'EMAIL-WP-0004' is not linked to State Hub",
"file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md",
"db_id": "",
"file_value": "EMAIL-WP-0004",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'USER-WP-0022' is not linked to State Hub",
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
"db_id": "",
"file_value": "USER-WP-0022",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'KEY-WP-0008' is not linked to State Hub",
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
"db_id": "",
"file_value": "KEY-WP-0008",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'CSOC-WP-0003' is not linked to State Hub",
"file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md",
"db_id": "",
"file_value": "CSOC-WP-0003",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0027' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0027-reef-placement-reconciliation.md",
"db_id": "965ad365-6b81-50a1-a2a3-2d0c1fcce0b4",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0031' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0031-deterministic-posture-feedback.md",
"db_id": "9d7b04f9-3803-5613-b7a5-8bd606c77f5a",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'nk-wp-0040' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/NK-WP-0040-execution-attribution-receipt.md",
"db_id": "e2533f3a-aa43-59b3-bff3-8e64b6149487",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Task dependency target 'NK-WP-0048-T17' of task 'NK-WP-0048-T06' is not linked to State Hub",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T06",
"db_id": "",
"file_value": "NK-WP-0048-T17",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Task dependency target 'NK-WP-0048-T18' of task 'NK-WP-0048-T06' is not linked to State Hub",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T06",
"db_id": "",
"file_value": "NK-WP-0048-T18",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-22",
"message": "Task description drift 'NK-WP-0048-T16': file content differs from DB (file wins)",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T16",
"db_id": "200bf500-5084-5ae7-be26-3033fe1f296e",
"file_value": "Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden\nindependently owns global provenance/adm",
"db_value": "Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden\nindependently owns global provenance/adm",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-11",
"message": "Task 'NK-WP-0048-T17' has no state_hub_task_id",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T17",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-11",
"message": "Task 'NK-WP-0048-T18' has no state_hub_task_id",
"file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T18",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'RAIL-PL-WP-0002' is not linked to State Hub",
"file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md",
"db_id": "",
"file_value": "RAIL-PL-WP-0002",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'nk-wp-0035@retired-20260908' (id=064c5e8b\u2026, status=archived) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "064c5e8b-7852-4d7f-a5fa-e26e406ab8e0",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'adhoc-2026-08-14@retired-20260827' (id=a5097121\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "a5097121-1461-59f8-81fa-e6ecef527337",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'adhoc-2026-08-23@retired-20260827' (id=e25ba5fa\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "e25ba5fa-eaae-5039-8d93-a69c6d837fc4",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'net-kingdom-adhoc-2026-06-14@retired-20260827' (id=c6f3d6bf\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "c6f3d6bf-4916-490d-96ce-092d2776d7e7",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "INFO",
"check_id": "C-08",
"message": "Retired DB workstream 'net-kingdom-adhoc-2026-07-02@retired-20260827' (id=67c1c7ac\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
"file_path": "",
"db_id": "67c1c7ac-d8b1-41dc-a81a-4e43f1afd068",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0020' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md",
"db_id": "",
"file_value": "NET-WP-0020",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0015' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260603-NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md",
"db_id": "",
"file_value": "NET-WP-0015",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0016' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260603-NET-WP-0016-guided-security-bootstrap-experience.md",
"db_id": "",
"file_value": "NET-WP-0016",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0018' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260604-NET-WP-0018-bootstrap-automation-and-rebuild-readiness.md",
"db_id": "",
"file_value": "NET-WP-0018",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0017' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md",
"db_id": "",
"file_value": "NET-WP-0017",
"db_value": "NK-WP",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-26",
"message": "Workplan id 'NET-WP-0019' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform",
"file_path": "workplans/archived/260702-NET-WP-0019-t06-adjacent-user-lifecycle-dry-run-polish.md",
"db_id": "",
"file_value": "NET-WP-0019",
"db_value": "NK-WP",
"fixable": false
}
],
"fixes_applied": [
"bindings: synced 64/64",
"C-33 fixed: regenerated WORK-RECORDS.md",
"C-22 fixed: task 200bf500\u2026 description updated",
"C-11 fixed: task 'NK-WP-0048-T17' \u2192 8c3e4baa\u2026",
"C-11 fixed: task 'NK-WP-0048-T18' \u2192 9c45da71\u2026",
"brief: .custodian-brief.md updated",
"push: pushed"
],
"summary": {
"fail": 0,
"automation_error": 0,
"warn": 30,
"info": 5
},
"result": "warn"
}

View file

@ -0,0 +1,14 @@
{
"workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"id": "d801aba6-6f8c-49ce-b770-005e1fdd18fe",
"topic_id": null,
"task_id": "9c45da71-fb4c-5f20-9065-90f448f37c64",
"decision_id": null,
"event_type": "note",
"summary": "NetKingdom source checker boundary fix published 916ba4b33e03179274d03b78bd898a4843e8653f and task-publication receipt cbabb6dc3611dc9b9a304b175b17e132ae057bac. Checker gets absolute selected path and env={}; predecessor URL/PATH counterexamples retained. 39 affected tests plus11 actual-artifact/helper/kernel scenarios pass; exact be377 artifact reproduced from pinned source/tree. All18task UUID/status projections and all open-task dependency edges verified; original staged/working brief restored. Native callbacks/type/identity admission remain open. Stopping at requested pause point.",
"detail": null,
"author": "codex",
"session_id": null,
"created_at": "2026-10-16T20:32:34.297844Z",
"workstream_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
}

View file

@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""Register or remove exactly one KeyCape client from a SecurityCase, without exporting Secret values.
Entry point of the playbook capability net-kingdom.keycape-client-registration,
composed by the SecurityCaseType oidc-browser-relying-party
(security-case-types/, canon security-case-types_v0.1). It generalizes the
per-client helpers in this directory (portal-client-rollout,
register-coulomb-social, vergabe-client-rollout,
secrets-engine-openbao-client-rollout) and keeps their safeguards:
- contained kubectl, cluster-identity pin and verified upstream issuer pin
- byte-preserving merge: only this client's lines change; every other config
byte, the signing key and unrelated clients are preserved
- exact match: absent -> add; identical -> noop; drift, duplicates or a shared
secretRef -> refuse (update = remove, then add, each accepted)
- compare-and-swap on the Secret's uid and resourceVersion, then read-back
- confidential clients: the KeyCape Deployment must already bind the client's
secret env var (delivered from custody), or the next pod start fails closed
- KeyCape's own startup checks (keycape check-config, key-cape c528daa) on the
live and the proposed registry, fed on stdin and never written to disk; apply
refuses a registry that would stop the single replica at its next start
- no Secret value, parser message or Kubernetes error text leaves the process
Modes: inspect (default), dry-run (server-side), apply. apply requires the
case's acceptance record instead of a per-client founder decision id: the case
was accepted under an acknowledged type.
keycape-client.py client-from-case <case.yaml>
keycape-client.py inspect --client-file client.yaml [--operation remove]
keycape-client.py apply --client-file client.yaml --case-acceptance <record> \\
--check-config <keycape binary> --expected-uid <uid> --expected-resource-version <rv>
"""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
import re
import subprocess
import sys
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
lane = module('portal_rollout', 'portal-client-rollout.py')
pin = lane.pin
Refused = lane.Refused
require = lane.require
CASE_TYPE = re.compile(r'^oidc-browser-relying-party@1\.\d+$')
CLIENT_ID = re.compile(r'^[a-z0-9][a-z0-9-]{1,62}$')
# KeyCape refuses '*' and '?' at startup; OAuth 2.0 forbids fragments.
REDIRECT = re.compile(r'^https://[^*?#\s,]+$')
SCOPES = {'openid', 'profile', 'email', 'groups'}
def env_name(client_id):
return 'KEYCAPE_' + client_id.upper().replace('-', '_') + '_CLIENT_SECRET'
def env_secret_name(client_id):
return f'keycape-{client_id}-client'
def client_from_params(params):
"""The KeyCape client record for a case of oidc-browser-relying-party."""
cid = params['client_id']
require(CLIENT_ID.fullmatch(cid or '') is not None, 'invalid_client_id')
redirects = [r for r in params['redirect_uris'].split(',')]
require(redirects and all(REDIRECT.fullmatch(r) for r in redirects), 'redirect_uris_must_be_exact_https')
scopes = params['allowed_scopes'].split(',')
require(scopes[0] == 'openid' and set(scopes) <= SCOPES and len(set(scopes)) == len(scopes),
'allowed_scopes_invalid')
# KeyCape has no per-client group gate; the consumer reads groups, and UserInfo
# returns them only for the groups scope.
require(not params.get('access_groups') or 'groups' in scopes, 'access_groups_require_groups_scope')
require(params['client_type'] in ('confidential', 'public'), 'client_type_invalid')
# The type derives custody at platform/workloads/keycape/<client_id>; a case cannot choose it.
require('secret_ref' not in params, 'secret_ref_is_derived_by_the_type')
require(params['mfa_required'] in ('true', 'false'), 'mfa_required_invalid')
client = {
'clientId': cid,
'displayName': params['display_name'],
'redirectUris': redirects,
'allowedScopes': scopes,
'grantTypes': ['authorization_code'],
'clientType': params['client_type'],
}
if params['client_type'] == 'confidential':
client['secretRef'] = 'env:' + env_name(cid)
if params['mfa_required'] == 'true':
client['mfaRequired'] = True
return client
def client_from_case(case):
spec = case.get('spec') or {}
require(CASE_TYPE.fullmatch(str(spec.get('type', ''))) is not None, 'case_type_not_oidc_browser_relying_party')
return client_from_params(spec.get('parameters') or {})
def env_binding(client):
return {'name': env_name(client['clientId']), 'valueFrom': {
'secretKeyRef': {'name': env_secret_name(client['clientId']), 'key': 'client-secret'}}}
def _client_node(raw):
root = yaml.compose(raw)
node = next(value for key, value in root.value if key.value == 'clients')
require(isinstance(node, yaml.nodes.SequenceNode) and not node.flow_style,
'block_client_sequence_required')
return node
def _line_start(raw, index):
start = raw.rfind('\n', 0, index) + 1
return start if not raw[start:index].strip() else index
def _inspect(secret, client):
raw, config, _ = pin.issuer_document(secret)
require(config['authelia'].get('issuer') == pin.VERIFIED_UPSTREAM_ISSUER, 'verified_issuer_pin_required')
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
require(all(isinstance(c, dict) and 'clientId' in c for c in clients), 'client_shape_unexpected')
ids = [c['clientId'] for c in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
if 'secretRef' in client:
others = [c for c in clients if c['clientId'] != client['clientId']]
require(all(c.get('secretRef') != client['secretRef'] for c in others), 'secret_ref_already_bound')
return raw, config, ids
def replacement(secret, client):
"""Return (base64 config, changed) with only this client added."""
raw, config, ids = _inspect(secret, client)
if client['clientId'] in ids:
require(config['clients'][ids.index(client['clientId'])] == client,
'existing_registration_differs_requires_reconciliation')
return secret['data']['config.yaml'], False
node = _client_node(raw)
index = _line_start(raw, node.end_mark.index)
indent = ' ' * node.start_mark.column
fragment = yaml.safe_dump([client], sort_keys=False, width=120)
fragment = ''.join(indent + line if line.strip() else line for line in fragment.splitlines(True))
if index and raw[index - 1] != '\n':
fragment = '\n' + fragment
updated = raw[:index] + fragment + raw[index:]
expected = copy.deepcopy(config)
expected['clients'].append(client)
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def removal(secret, client):
"""Rollback: return (base64 config, changed) with only this exact client removed."""
raw, config, ids = _inspect(secret, client)
if client['clientId'] not in ids:
return secret['data']['config.yaml'], False
position = ids.index(client['clientId'])
require(config['clients'][position] == client, 'existing_registration_differs_requires_reconciliation')
require(len(ids) > 1, 'removal_would_empty_client_sequence')
node = _client_node(raw)
item = node.value[position]
start = raw.rfind('\n', 0, item.start_mark.index) + 1
require(raw[start:item.start_mark.index].strip() == '-', 'unexpected_item_layout')
if position + 1 < len(node.value):
end = raw.rfind('\n', 0, node.value[position + 1].start_mark.index) + 1
else:
end = _line_start(raw, node.end_mark.index)
updated = raw[:start] + raw[end:]
expected = copy.deepcopy(config)
del expected['clients'][position]
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def deployment_bound(deployment, client):
"""Whether the live Deployment carries exactly this client's secret binding."""
if 'secretRef' not in client:
return None
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
matches = [e for e in container.get('env', []) if e.get('name') == env_name(client['clientId'])]
require(len(matches) <= 1 and all(e == env_binding(client) for e in matches), 'env_binding_drift')
return bool(matches)
def check_config(binary, config_text, env_names, policy_store):
"""KeyCape's startup checks on one registry: names and messages only, no values."""
command = [str(Path(binary).resolve()), 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
if policy_store:
command.append('--policy-store')
result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60, env={})
report = json.loads(result.stdout)
require(isinstance(report, dict) and isinstance(report.get('valid'), bool)
and report['valid'] == (result.returncode == 0), 'check_config_output_shape')
return {'valid': report['valid'], 'clients': report.get('clients'),
'errors': [str(e) for e in report.get('errors') or []]}
def deployment_env_names(deployment):
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
return sorted(e['name'] for e in container.get('env', []))
def run(args, client):
kubectl = lane.kubectl
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == lane.CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = (replacement if args.operation == 'add' else removal)(before, client)
bound = deployment = None
if 'secretRef' in client or args.check_config:
deployment = kubectl(['-n', 'sso', 'get', 'deployment', 'keycape', '-o', 'json'])
bound = deployment_bound(deployment, client)
checks = None
if args.check_config:
names = deployment_env_names(deployment)
policy_store = 'KEYCAPE_POLICY_PATH' in names
checks = {stage: check_config(args.check_config, base64.b64decode(config).decode(), names, policy_store)
for stage, config in (('live', before['data']['config.yaml']), ('proposed', encoded))}
receipt = {'schema': 'netkingdom.keycape-client.v1', 'target': 'sso/keycape-config',
'before': metadata, 'operation': args.operation, 'client_id': client['clientId'],
'client_type': client['clientType'], 'change_needed': changed, 'mode': args.mode,
'deployment_env_bound': bound, 'check_config': checks, 'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
if args.mode == 'apply':
require(bool(args.case_acceptance), 'case_acceptance_record_required')
receipt['case_acceptance'] = args.case_acceptance
require(checks is not None, 'keycape_check_config_required')
require(checks['proposed']['valid'], 'proposed_registry_fails_keycape_check_config')
if args.operation == 'add' and bound is not None:
require(bound, 'deployment_env_binding_required_first')
require(metadata == {'uid': args.expected_uid, 'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}), 'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid']
and after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after), restart_required=changed)
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('mode', nargs='?', default='inspect',
choices=['inspect', 'dry-run', 'apply', 'client-from-case'])
parser.add_argument('case', nargs='?', help='case file, for client-from-case')
parser.add_argument('--client-file', help='KeyCape client record (output of client-from-case)')
parser.add_argument('--operation', default='add', choices=['add', 'remove'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
parser.add_argument('--case-acceptance', help='the case acceptance record; required for apply')
parser.add_argument('--check-config', help='a keycape binary with check-config (key-cape c528daa or later); required for apply')
args = parser.parse_args(argv)
try:
if args.mode == 'client-from-case':
require(bool(args.case), 'case_file_required')
print(yaml.safe_dump(client_from_case(yaml.safe_load(Path(args.case).read_text())), sort_keys=False), end='')
return 0
require(bool(args.client_file), 'client_file_required')
client = yaml.safe_load(Path(args.client_file).read_text())
require(isinstance(client, dict) and 'clientId' in client, 'client_file_shape')
print(json.dumps(run(args, client), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except pin.IssuerPinError as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,64 @@
# Attended registration adapter candidate
Exercise status: unexercised
This is a source-only, injection-only adapter for the existing Warden closed
kernel. It installs no command, runtime dispatcher, credential provider, evidence
trust root or new service. NetKingdom type/case status and bytes remain unchanged.
The generated descriptor is a proposal; it is not owner acknowledgment.
`keycape-registration-adapter.py` wraps maintained `keycape-client.run` in apply
mode. A fixed invocation binds principal, human actor, lane, case/type/descriptor/
catalog/acceptance/run-sheet digests and target. The descriptor pins this adapter
and its complete local helper import closure together with the exact reviewed
`check-config` executable digest. Changing that digest changes the descriptor
source pin and descriptor bytes, requiring independently reviewed admission.
The fixed executable is rehashed before each attempt. The helper resolves the
selected file to an absolute executable path and supplies an empty checker
environment, so ambient PATH and KeyCape URL overrides cannot select another
executable or alter validation of supplied bytes. Path resolution does not
establish immutable storage or eliminate replacement races; executable custody
and native runtime admission remain separate requirements. The caller constructs the
proposed invocation from exact source inputs, installs the fixed adapter once,
and calls normal `Executor.admit`; independently derived admission must match.
No callable replacement after admission is required or supported by this recipe.
The operation uses existing cluster/issuer checks, byte-preserving merge,
Deployment secret-env prerequisite, config validation, UID/resourceVersion CAS
and whole-data readback. Only closed metadata is passed to the injected durable
owner publisher. The owner authenticator must independently authenticate those
exact bytes. The session-revocation capability must end the same attempt's
parent session; Warden separately verifies its exact revocation evidence.
These capabilities are owner-selected callables, never case-supplied commands.
No implementation of these native capabilities is supplied or implied here.
An identical existing client returns no change and creates no rollback authority.
Removal requires this process's independently authenticated successful creation
receipt, exact created-object label, unchanged postcreation UID/resourceVersion,
and the original exact client record. External drift, a lost readback, receipt
publication/revocation failure or process loss requires owner reconciliation.
No uncertainty is labeled retry-safe. No automatic reconciliation is installed.
Rollback after restart intentionally remains unavailable until an owner supplies
independently authenticated durable creation provenance and reviews recovery.
The source tests execute actual maintained helper CAS/readback against synthetic
Kubernetes objects. Three checks exercise actual Warden admission/run/rollback
and uncertainty transitions with synthetic owner trust. Warden d97652a's exact
published producer source pins apply unchanged through actual BrowserInputs.
This is a source compatibility fixture, not acknowledgment, owner provenance,
normal native admission or deployment acceptance.
Remaining work belongs in live work records before T12 closure:
- Warden owns global evidence admission and exact native evidence/session
capabilities. Published d97652a refreshes BrowserInputs producer pins for
NetKingdom 015694ee; that source assent does not attest this new adapter.
- NetKingdom owns separately reviewed descriptor/type pin selection, the env
binding/config/restart steps, and group ensure/revoke provenance. Current group
ensure does not report newly added memberships; current revoke can remove
memberships that predate the case. Wrapping these as reversible creation would
be unsound. Preserve them until a reviewed created-only contract exists.
- Platform owns structural/mint/broker and secret delivery evidence; Apps owns
actual consumer login and group enforcement. No assent is supplied here.
- Attended use still requires existing owner authorization, current caller
identity, native source/config/image pins, native receipts and cleanup proof.

View file

@ -0,0 +1,66 @@
"""Opt-in real-checker boundary regressions; no production inputs or checker stub.
Set KEYCAPE_TEST_CHECKER_BINARY and KEYCAPE_TEST_CHECKER_SHA256 to an independently
built artifact. Ordinary helper-only runs skip this class when none is supplied.
"""
from contextlib import chdir
import hashlib
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from test_keycape_client import kc
VALID = '''issuer: https://issuer.example.invalid
port: 8080
privateKeyPem: /nonexistent/synthetic-key.pem
clients:
- clientId: synthetic-client
displayName: Synthetic client
redirectUris: [https://client.example.invalid/cb]
allowedScopes: [openid]
grantTypes: [authorization_code]
clientType: public
'''
class RealCheckerEnvironmentTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
source = os.environ.get('KEYCAPE_TEST_CHECKER_BINARY')
if not source:
raise unittest.SkipTest('exact real checker artifact not supplied')
data = Path(source).read_bytes()
expected = os.environ.get('KEYCAPE_TEST_CHECKER_SHA256')
if expected != 'sha256:' + hashlib.sha256(data).hexdigest():
raise ValueError('real checker artifact digest differs')
cls.directory = tempfile.TemporaryDirectory()
cls.addClassCleanup(cls.directory.cleanup)
cls.binary = Path(cls.directory.name) / 'keycape'
cls.binary.write_bytes(data)
cls.binary.chmod(0o700)
def check(self, raw, binary=None):
return kc.check_config(str(binary or self.binary), raw, [], False)
def test_ambient_overrides_cannot_reject_supplied_valid_config(self):
with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://polluted.example.invalid',
'KEYCAPE_BROWSER_LOGOUT_URL': 'http://polluted.example.invalid/logout'}):
self.assertTrue(self.check(VALID)['valid'])
def test_ambient_overrides_cannot_admit_supplied_invalid_config(self):
invalid = VALID + 'accountPortalURL: http://invalid.example.invalid\nbrowserLogoutURL: http://invalid.example.invalid/logout\n'
with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'https://polluted.example.invalid',
'KEYCAPE_BROWSER_LOGOUT_URL': 'https://polluted.example.invalid/logout'}):
self.assertFalse(self.check(invalid)['valid'])
def test_relative_selected_artifact_is_not_replaced_by_path_decoy(self):
decoy = Path(self.directory.name) / 'decoy'
decoy.mkdir()
trap = decoy / 'keycape'
trap.write_text('#!/bin/sh\nexit 27\n')
trap.chmod(0o700)
with chdir(self.directory.name), patch.dict(os.environ, {'PATH': str(decoy)}):
self.assertTrue(self.check(VALID, binary=Path('keycape'))['valid'])

View file

@ -0,0 +1,816 @@
---
id: NK-WP-0048
type: workplan
title: "Establish SecurityCaseTypes so that known security cases are light"
domain: infotech
repo: net-kingdom
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-16"
depends_on: []
related_repos:
- ops-mason
- ops-warden
- key-cape
- railiance-platform
- informed-decision
state_hub_workstream_id: "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
---
# NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
## Why
The founder's direction (2026-10-08): setting up a security integration of a
kind that has been done before should be easy, meaning accept the case, then
ops-mason arranges it and ops-warden follows through. Setting up a new kind
needs ample documentation and tests and an informed-decision, because its shape
is part of the security architecture.
Today every integration is hand-built: per-client helper scripts, per-lane
custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC
client), whose next step was going to be yet another hand-written rollout
script.
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
into **SecurityCaseTypes** that ops-mason and ops-warden can execute. Canon:
`canon/standards/security-case-types_v0.1.md` (proposed).
Companion plans: ops-mason `MASON-WP-0010` (materialization) and ops-warden
`WARDEN-WP-0044` (follow-through).
## Tasks
```task
id: NK-WP-0048-T01
status: done
priority: high
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
```
### T01 — Accept the terminology and the canon
Founder review of `canon/standards/security-case-types_v0.1.md`, including its
section 10 open questions. On acceptance the canon status goes from `proposed`
to `accepted`.
2026-10-08: accepted by the founder (in session). Canon status `accepted`;
section 10's open questions are carried forward to the first type (T04, T05).
```task
id: NK-WP-0048-T02
status: done
priority: high
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
```
### T02 — Inventory the de-facto types already built by hand
Read-only. List every security integration built so far and group them by shape.
Known starting points:
- browser OIDC relying parties on key-cape: informed-decision-approver,
coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
- confidential service clients with `client_credentials`: secrets-engine
approval, requester and openbao clients, approval-engine operator
- workload KV read lanes on OpenBao: the railiance-platform credential lane
designs and CCRs, for example `railiance-backup-offsite-lane`
- short-lived SSH certificates through ops-warden (`warden sign`)
- tunnelled operator access (ops-bridge)
For each: the repositories involved, the hand-built steps, attended human steps,
custody paths, verification evidence, and rollback (if any). Output
`docs/security-case-type-inventory.md`, ranking candidate types by how often the
shape recurs and how much manual work each instance costs today.
2026-10-08: done, `docs/security-case-type-inventory.md`. 36 of 37 CCRs are
already `request_type: workload-kv-read`, a de-facto type reviewed each time as
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
rollout script. Confidential service clients need a registration plus two or
three KV-read CCRs each. A forge read identity is needed in bulk by
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
```task
id: NK-WP-0048-T03
status: done
depends_on: [NK-WP-0048-T01]
priority: high
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
```
### T03 — Schema, template and validator
- `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`
- `security-case-types/_template/` with `type.yaml`, `README.md` and tests to copy
- a validator, extending `tools/security-scenario-composer/`, that checks a type,
checks a case against its type, and renders the case into the composer's
deterministic plan (still `execution.permitted: false`)
Acceptance: the validator rejects missing sections, secret-shaped parameter
values, unpinned providers and cases against non-`implemented` types, with tests
for each.
2026-10-08: done. `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`; `tools/security-case-types/security_case_validator.py`
(`validate-type`, `validate-case`, `render`, reusing the scenario composer);
`security-case-types/_template/` (a valid, runnable example type over
`net-kingdom.local-identity`, with README sections, an example case and tests that
travel with every copy). 31 tests pass, including the composer's existing suite.
```task
id: NK-WP-0048-T04
status: done
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
priority: high
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
```
### T04 — Define the first type: browser OIDC relying party
`security-case-types/oidc-browser-relying-party/`, version 1.0:
- parameters: client id, display name, exact redirect URIs, scopes, client type
(confidential or public), MFA pin, the groups the consuming service maps,
custody path for the secret (confidential only), consuming service and its
owner
- composition: key-cape client registration (key-cape declares it as a playbook
capability), secret minting into custody (railiance-platform), LLDAP group
existence and membership (net-kingdom identity-provisioner), handover to the
consuming service
- materialization (ops-mason) and follow-through (ops-warden) sections
- verification: discovery lists the redirect URI; a test sign-in returns the
expected claims for one human; anonymous access to the custody path is denied
- rollback: remove exactly this client and its custody path
- README: threat model, MFA rationale, why redirect URIs are exact, which steps
still need an attended login and how that is minimised
- reference instances: informed-decision-approver, coulomb-social, user-engine
portal, openbao-admin
Acceptance: complete per canon section 3, tests green, status `proposed`.
2026-10-08: done. `security-case-types/oidc-browser-relying-party/` 1.0 (proposed):
`type.yaml` with 12 parameters (the custody reference is required only for
confidential clients), composition over key-cape's new
`key-cape.oidc-client-registration` declaration (contract PASS, `maturity:
planned`), materialization and follow-through, verification and rollback, all
conditional on client type where it matters. One attended step,
`register-client`, justified by the live registry being a Kubernetes Secret.
README with threat model, alternatives, an honest fit table (portal and vergabe
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
(checked against the accepted request) and a public portal. The validator gained
`when` / `required_when` conditions and provider discovery. 43 tests pass.
ops-mason's and ops-warden's sections are drafted in the type. Their owners
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
(T05).
```task
id: NK-WP-0048-T05
status: done
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
priority: high
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
```
### T05 — Acknowledge the type through informed-decision
Prepare the Decision Memo from the type's README and `type.yaml` and submit it
through informed-decision's production intake. On acknowledgment the type
moves to `acknowledged`.
2026-10-08: Decision Memo prepared. Hub decision `e418263e-a71f-4f01-a970-c87c9e187c09`.
`security-case-types/oidc-browser-relying-party/decision/`: `packet.md` (question,
one act, what acknowledging does not do, the shape, alternatives, attended
steps, limits, evidence), `build_intake.py` and the built `intake.json` (memo
class `decision-record`, review group `net-kingdom-admins`, resolves the hub
decision on answer). The envelope passes informed-decision's own
`validate_submission`. Queuing it is an attended operator step, a write inside
the production dispatcher pod:
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
< security-case-types/oidc-browser-relying-party/decision/intake.json
The answer then appears for review in informed-decision.
2026-10-08: queued by the founder (`pending`, `queued`) after one refusal: the
first envelope's idempotency key was not State Hub's stable
`state-hub.<decision>.<memo>` form, and the builder now validates against both
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
delivered). T05 waits for the founder's answer in informed-decision, which
resolves hub decision `e418263e`.
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
1.0 `proposed`: `secret_ref` is no longer a parameter, the type derives
`platform/workloads/keycape/{client_id}`; policies are
`workload-kv-read-keycape-{client_id}-eso` and
`workload-kv-read-keycape-{client_id}-{consuming_service}`, each with a templated
body (`read` on that one data path); new `openbao_role`
`external-secrets-keycape-{client_id}` (Kubernetes auth, ESO service account,
15m, no default policy); catalog entry `{consuming_service}-keycape-client`;
the two rollback steps on the KV path collapse into one metadata delete. The
schema and validator now require a `body` on policy and role items. 105 tests
pass. Two consequences: the delivered memo describes the type before these
changes (a case-set custody reference, two differently named policies, no ESO
role), so the founder answers on a superseded shape unless the memo is
re-sent; and Forgejo's custody path moves from the accepted request's proposed
`platform/workloads/forgejo/netkingdom-oidc-client` to
`platform/workloads/keycape/forgejo`, which key-cape (KEY-WP-0038) and
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
(WARDEN-WP-0044-T01) is still in progress.
2026-10-08: revised memo `nk-wp-0048-t05-oidc-browser-relying-party-r2` built
(`packet.md`, `intake.json`, packet `sha256:af6435e2…`); it states that it
supersedes the first memo and passes informed-decision's and the dispatcher's
validation. It needs a new memo id because the dispatcher refuses new content
under a delivered idempotency key (`idempotency_conflict`). Queuing it is the
same attended step as above. Both memos serve hub decision `e418263e`; the
first answer resolves it, so the first memo must not stay open: an accept on it
would acknowledge the superseded shape, and a decline would resolve the
decision against r2. Close it with `return` (a reviewer verb that requests a
new revision and does not resolve the hub decision; only accept and decline
do), commenting that r2 supersedes it, then answer r2. `withdraw` exists in
the vocabulary but is not offered to reviewers, and lifecycle `revise` only
applies to lifecycle-registered memos, not intake-delivered ones.
2026-10-09: the founder reports r2 enqueued and the first memo returned. At
close, hub decision `e418263e` still showed `open` (not verified whether r2
was answered or the answer is still in transit; the dispatcher ledger was not
read). ops-warden's review (WARDEN-WP-0044-T01, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`, d470f2e) arrived at
21:25, before r2 was built, and is not in r2. Required before WARDEN-WP-0044-T02:
R1 a per-step `lane` (missing: identity-provisioner caller credential,
railiance-platform broker grant), R2 canon meaning of `warden_executes: true`
(caller proxy, caller identity, no standing ops-warden token; ADR-0002), R3 a
per-step `value_handling`. Refinements R4 (one type-level catalog lane instead
of a per-case entry, which conflicts with ops-mason's item 5), R5 (ESO Ready gate
before bind-env), R6 (exact accepted memberships), R7 (mfa-pinned and
secret-delivered checks). Next: if r2 was accepted, fold R1-R3 and R5-R7 in as
a follow-up and settle R4 between ops-warden and ops-mason before T06; if not,
hold r2, fold them in and build r3.
key-cape and railiance-apps were told about Forgejo's new custody
path.
2026-10-09: r2 was not answered (hub decision `e418263e` open; dispatcher 9
delivered), so r2 is held and r3 replaces it. ops-warden withdrew R4 for R4′
(message a66a9204): one catalog entry per case with its body templated in the
type. Folded in:
- Canon section 3: a narrow meaning of `warden_executes: true` (caller proxy,
caller identity, no standing credential; ops-warden ADR-0002), plus per-step
`lane`, `value_handling` and an optional `ready_check`. A catalog entry body is
now required, like policy and role bodies.
- Schema and validator enforce all of these.
- The type: lanes `identity-provisioner-groups` and
`keycape-client-secret-generate` (both new) and `key-cape-oidc-login`;
`mint-secret` is `generated_in_custody`; a Ready gate on `deliver-secret`;
`ensure-groups` adds exactly the accepted case's memberships; new checks
`secret-delivered` and `mfa-pinned`; the catalog-entry body follows R4′.
- 110 tests pass.
Memo r3 (`nk-wp-0048-t05-oidc-browser-relying-party-r3`, packet
`sha256:726dfc01…`) passes informed-decision's and the dispatcher's validation.
The founder queues it with the same command, returns r2 with a comment that r3
supersedes it, then answers r3.
Prerequisites for T06, not for T05:
- the two new lanes, each through its owner;
- a founder-approved rollout of an identity-provisioner image containing
630ebfd (the deployed one is main-6c4fcaf from 2026-09-24, without
`/v1/groups/ensure`).
ops-mason's request to create LLDAP group `fluid-telegram-operators` (message
259ec26d) was answered: not possible from here by that path; the founder creates
it in the LLDAP admin UI.
2026-10-09: ops-warden finished its side of WARDEN-WP-0044-T01 (message
`58d33f40`, commit 0e7ceb8): the revised type conforms to `warden case
check-type`, with no further changes asked of the type. This acknowledges that
review. Its later addendum R8 (message `af274783`, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`) showed that lane
`identity-provisioner-groups` cannot exist as declared: the provisioner's only
HTTP credential is user-engine's runtime token, and its NetworkPolicy admits
only user-engine pods, so a caller-proxy lane would act as user-engine. Taken
option 1 (ops-warden's recommendation): `ensure-groups` and its rollback are
attended, in the `register-client` session on `key-cape-oidc-login`, run inside
the provisioner pod; no token is read. Option 2 (an operator credential of its
own on the provisioner) stays open for a later version. `warden case
check-type` now reports only `keycape-client-secret-generate`
(railiance-platform) as missing. 89 tests pass.
Hub decision `e418263e` was still open, so memo r4
(`nk-wp-0048-t05-oidc-browser-relying-party-r4`, packet `sha256:452ea421…`)
replaces r3; it passes informed-decision's and the dispatcher's validation. The
founder queues it with the same command, returns r2/r3 if still open with a
comment that r4 supersedes them, then answers r4.
Prerequisites for T06 are now: lane `keycape-client-secret-generate`
(railiance-platform), and a founder-approved rollout of an identity-provisioner
image containing 630ebfd.
2026-10-09: done. The founder queued r4 (`pending`, `queued`), returned r3 as
superseded and accepted r4 in informed-decision. informed-decision's hub
write-back is off in production (`INFD_HUB_WRITEBACK` unset), so net-kingdom
resolved hub decision `e418263e` from the founder's report, naming memo r4 in
the rationale. The type is `acknowledged`; it becomes `implemented` with T06.
```task
id: NK-WP-0048-T06
status: wait
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, NK-WP-0048-T18, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
```
### T06 — First KnownSecurityCase: Forgejo
Write `security-cases/forgejo-netkingdom-oidc.yaml` from the parameters the
founder accepted in KEY-WP-0038-T01 (`key-cape/docs/forgejo-oidc-client-request.yaml`),
so that acceptance carries over as the case acceptance. ops-mason materializes it;
ops-warden follows through and verifies it. The type becomes `implemented` once
this reference case is live.
This replaces the hand-built path proposed for KEY-WP-0038-T02.
2026-10-09: case written, `security-cases/forgejo-netkingdom-oidc.yaml`, status
`accepted`: the example's parameters plus the founder's 2026-10-08 acceptance of
the key-cape request (owner and founder), with the custody-path derivation
covered by the type's acknowledgment (memo r4). It validates and renders (plan
`execution.permitted: false`; three attended steps on `key-cape-oidc-login`);
it needs `--allow-unimplemented` until it is live, since it is the case that
makes the type `implemented`. A test keeps it equal to the type's example.
Still open before it runs: ops-mason renders and builds it (MASON-WP-0010-T02,
T03, unblocked by T05), ops-warden plans and runs it (WARDEN-WP-0044-T02, T03),
railiance-platform's `keycape-client-secret-generate` lane (asked by ops-mason
`789feb39` and ops-warden `d9f889b1`, both unread), and a founder-approved
identity-provisioner rollout containing 630ebfd.
2026-10-09: identity-provisioner rolled out, founder-approved in session:
`main-630ebfd` (`sha256:bafb4860…`, built by the image workflow) replaces
`main-6c4fcaf` (`sha256:ffacd5d7…`; only change: 630ebfd, additive). Applied
with `kubectl set image` on the Deployment only (generation 17 → 18, 1/1 Ready);
the file's Service, NetworkPolicies and Ingress were not reapplied. Verified in
the pod: `ensure_service_groups` and `revoke_service_groups` present, `/readyz`
200; no token used, no group changed. Rollback: `kubectl -n sso set image
deploy/identity-provisioner provisioner=forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ffacd5d790a5b7800e0535f3aa95b7de52c7a68e4f575b9192c3e1622fa672c2`.
The pin in `sso-mfa/k8s/identity-provisioner/deployment.yaml` follows.
2026-10-09: ops-warden closed WARDEN-WP-0044-T01 and shipped `warden case plan`
(T02, message `06303d1d`); the accepted Forgejo case is refused until ops-mason
materializes it, and a materialized copy plans cleanly once
`keycape-client-secret-generate` exists. Its observation that `bind-env` carries
a literal `KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`: the validator now renders a
`{param:env}` format spec (upper case, `-` as `_`, as `keycape-client.py`
derives the name) and rejects unknown specs, and follow-through and rollback
text is now checked for unknown placeholders too (patch-level, canon section 6).
The type keeps the literal until ops-warden's planner (`text.format`) renders the
spec as well; asked of ops-warden. 53 tests pass.
2026-10-10: ops-warden rendered `{param:env}` in `warden case plan`
(23ba584, message `c1df14b7`; other specs and `!conversion` refused). The type
adopts it as a 1.0 patch (canon section 6): `bind-env` and its rollback now read
`KEYCAPE_{client_id:env}_CLIENT_SECRET`. ops-mason reports the Forgejo case
rendered and built (message `1ebddae9`; CCR drafts handed to railiance-platform),
so T06 now waits on the platform apply, the mint-secret lane and the attended
register-client session.
2026-10-10: key-cape adopted the type's custody path (key-cape ab5cccd, message
`88a4559c`): `docs/forgejo-oidc-client-request.yaml` and KEY-WP-0038-T02 name
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, delivered as
`sso/keycape-forgejo-client` through `external-secrets-keycape-forgejo`, and the
`forgejo/netkingdom-oidc-client` proposal is withdrawn. This matches the type and
the Forgejo example; no change needed here.
2026-10-10: key-cape's type-composition review (key-cape 6038483, message
`8e5ed5f6`) found the composition sound with two blocking findings, taken as a
1.0 patch (stricter validation and documentation, canon section 6):
R1, redirect URIs now refuse `?` and `#` in the type pattern and in
`keycape-client.py`'s `REDIRECT` (KeyCape refuses `?` at startup and the single
replica would stop); R2, `allowed_scopes` always includes `groups`, the renderer
refuses `access_groups` without it, and `access_groups` now says the consumer
enforces the gate because KeyCape has none; R3, `mfa_required` "false" means the
per-user privacyIDEA policy; R4, register-client's justification records the
restart for every client, the quiet window, and that the rendered registry must
pass KeyCape's client validation before it. The accepted Forgejo case and both
examples still validate; 116 tests pass.
Open, needs a decision: R2's verification step that the consumer enforces the
group gate is a new verification step, hence 1.1 (minor), and the validator pins
cases to the exact type version, so the accepted 1.0 Forgejo case would need
re-pinning. Also open with key-cape: a KeyCape entry point that validates a
rendered client registry without starting the server; until then
`keycape-client.py` mirrors the redirect rule.
2026-10-10: founder go for 1.1, so no 1.0 run overlaps. The type adds
verification `consumer-enforces-groups`, and hand-over now requires the consumer
to enforce `access_groups` (minor, canon section 6; risk class unchanged, no new
acknowledgment; history in the type README). The accepted Forgejo case and both
examples are re-pinned to `oidc-browser-relying-party@1.1` before any apply; the
acknowledgment intake keeps `@1.0` as the record of what was acknowledged.
ops-mason's build and ops-warden's case-ref digest predate this and need a
re-render.
2026-10-10: ops-mason re-rendered the Forgejo case from `@1.1` (message
`22c6bba6`; plan digest `af1f4f81…`, no collisions) and rebuilt the bundle. The
rendered OpenBao objects are identical to 1.0, so the already applied
CCR-2026-0041 stands; 1.1 changes only consumer-side steps. key-cape shipped
`keycape check-config` (c528daa, message `a7b0aeff`): KeyCape's startup checks
without secret values, including the new duplicate-clientId rule.
`keycape-client.py --check-config <binary>` now runs it on the live and the
proposed registry, fed on stdin, and apply refuses a proposed registry that
fails; register-client's entry point names the option (1.1 patch). The deployed
image predates c528daa, so the binary is built from key-cape source for now.
Before the next KeyCape rollout (Forgejo register-client or the CCR-2026-0018
window), the attended operator runs `inspect --check-config` so the live
registry is checked too.
```task
id: NK-WP-0048-T07
status: wait
depends_on: [NK-WP-0048-T06]
blocking_reason: "The next types follow once the first one is proven end to end."
priority: medium
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
```
### T07 — Queue the next types
From the T02 ranking, open the next type drafts (likely the confidential service
client and the workload KV read lane), each to be acknowledged on its own.
Record which existing hand-built instances get adopted as cases, without
rewriting them in place.
```task
id: NK-WP-0048-T08
status: done
priority: high
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
```
### T08 — identity-provisioner: ensure service-access groups
The type's `ensure-groups` step calls `identity-provisioner groups ensure --groups
<list> --members <group=user+user;...>`. The provisioner already has idempotent
`_ensure_group` and `_add_group`, but exposes only tenant-scoped operations. Add
the general operation (create if absent, add the listed memberships, never
remove others), its rollback (remove only this case's memberships), tests, and
the ops-warden route for its service identity.
2026-10-08: done. `identity-provisioner/provisioner.py` gained
`ensure_service_groups` (`POST /v1/groups/ensure`) and `revoke_service_groups`
(`POST /v1/groups/revoke`), behind the existing service-token check. Names
matching `tenant*`, `netkingdom-*` and `lldap_*` are reserved; members must
already exist; everything is validated before any change; memberships are read
back before success; nothing else is ever removed; groups are deleted only when
asked and empty. 7 new tests (39 in the provisioner suite). The type's
`ensure-groups` step and its rollback now name these endpoints. The ops-warden
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
```task
id: NK-WP-0048-T09
status: done
priority: high
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
```
### T09 — The `keycape-client` entry point and its declaration
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
live registration needs already lives in net-kingdom, where KeyCape is deployed
(`sso-mfa/k8s/keycape/`): the contained kubectl, the cluster-identity pin and the
issuer pin. A copy in key-cape would drift.
2026-10-08: done. `sso-mfa/k8s/keycape/keycape-client.py` derives the KeyCape
client record from a case (`client-from-case`). It adds or removes exactly one
client with the existing safeguards (byte-preserving merge, exact match or
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
requires the Deployment env binding first for confidential clients, and requires
the case acceptance record for `apply` instead of a per-client founder decision
id. 15 tests, including a regression showing that the vergabe case reproduces
its hand-built registration exactly; all 71 helper tests pass. Declaration
`capabilities/playbooks/net-kingdom.keycape-client-registration.yaml` (contract
PASS, `maturity: implemented`) replaces key-cape's draft. The type now pins it,
and for confidential clients adds `deliver-secret` (delivery from custody into
the sso namespace) and `bind-env` (Deployment env), attended in the same session
as `register-client`. The per-client helpers stay until their clients are
adopted as cases.
2026-10-10 — NetKingdom owner review of the execution seam, source only:
ops-warden/docs/security-case-execution-proposal.md SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
was reviewed against INTENT, SecurityCase canon, acknowledged type1.1, current
case and owner source. docs/security-case-execution-owner-review.md supports
the closed adapter/receipt direction with explicit versioned schema, structural
materialization provenance, unchanged caller/session boundaries, safe CAS
reconciliation and exact-created-object rollback requirements. This is owner
review, not approval. Supporting file digests are in the adjacent sources JSON.
Current source exposes a circular wait: ops-mason T03 waits on mint while Warden
and the broker refuse a non-materialized case. Structural acceptance therefore
needs its own native pinned receipt before mint, not a successful mint before
materialized. The platform broker additionally pins type1.0 while Forgejo is
accepted at1.1; platform-owner alignment and tests are required, not an unchecked
version bypass. T06 remains wait. No case/type lifecycle status, grant, authority
or live runtime changed by this review. A proposed informed-decision would
adopt the source contract only; native materialization and execution acceptance
stay distinct.
Final proposal re-review: staged Warden proposal SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
adds only the explicitly local reducer appendix, not an accepted wire protocol
or executor. The exact-final review and sources JSON supersede the earlier
1035f049... proposal review. Platform staged the exact type1.1 broker correction
with ten fake-backend subprocess checks (script SHA256
9576cd211cfc06526ec4cc9bd0a5b06f0f59287a68931bb2d6e3592a951ddc7d).
The next source work is the versioned closed adapter/receipt/materialization
contract, not another relaxation of the broker's status gate. Native work still
requires installed broker provenance, structural reader receipts, exact new
materialized-case bytes, caller grant/session revocation and verified execution.
No installed/native acceptance is inferred from that staged correction.
2026-10-11 — new source-only type1.2 architecture acknowledgment:
Confirmed native founder ACCEPT of infd-20261011-forgejo-type12-source-r1@1
at06:47:39Z, approval8e9332c8-754a-420c-b8b7-445cb4fe6765. Exact frozen packet
0b377d647ca748cd03c92f9792396412048055d4bde9f7bb06ff6d413f7acd68 and submission
585cee731de30d2b37883a57abfd9eb32f023ee694e8d911989109d5caf80569; native receipt
retained in docs/evidence/2026-10-11-forgejo-type12-source-acknowledgment/.
The complete acknowledged1.2 source package is isolated under
security-case-type-revisions/1.2/oidc-browser-relying-party; default loader and
current accepted1.1 case remain untouched because load_type is single-version.
The new proposed1.2 case is NOT accepted/materialized/live and has no acceptance
fields. Actual reference-migration design approval is a separate Apps owner
receipt; no1.1 approval is inherited. Source-recording review/test proof and exact
next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md.
T06 remains wait and T07 remains its existing dependent task; type is NOT
implemented and no first-case/native grant or broader workplan finish is asserted.
2026-10-11 — bounded explicit version selection source preparation for T06:
The validator now offers optional exact version_roots/--version-root VERSION=PATH
selection with duplicate/mismatch/traversal/symlink refusal and no fallback. The
default1.1 source and historicalacceptedcase hashes/ordinaryrefusal/preparation
render stayunchanged; actual1.1 is acknowledged, not implemented. Acknowledged1.2
ordinaryadmission also refuses; optionalpreparation remains nonexecuting.43local
validatorchecks pass and original1.1 CLIrenderbytes match exactly. Makerproof:
docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
FormalNetownerreview/reference-source integration and all existing native adapter,
provenance/broker/materialization/attendedproof gates remain; T06 stayswait, no
new residual or firstcase/typeimplementation/native grant is inferred.
2026-10-16 — isolated source preparation for T06 (canonical integration pending):
Added `check-evidence` to the existing validator CLI. It reuses exact version
selection and ordinary type/case/render admission, checks actual Mason structural
request source/inventory/projection pins and reads actual Warden journal rows
read-only. Successful rows require exact type-declared descriptors and attempt/
revocation fingerprints. Existing acknowledged types without machine descriptors
remain plan-only. The compatibility result never claims authenticated native
completion: Request and journal fingerprints are not portable native evidence.
Owner authentication/status binding and actual native gates remain unresolved;
T06/T07 statuses and both type/case lifecycles remain unchanged. No dispatch,
credential access or authority expansion is introduced. See tool README for the
existing owner protocol boundaries and source integration tests.
Concrete next Warden owner integration: current `Executor.admit` refuses every
case parameter whose sensitivity is not public/operational, so browser
`redirect_uris`, `allowed_scopes`, `client_type`, `mfa_required` (and 1.2
`consumer_namespace`) fail after otherwise prepared admission. The sensitive
classification is a security-change review level, not secret content. The
Warden owner must validate through NetKingdom's existing exact-version case/type
validator, then project only descriptor-declared safe target metadata; do not
remove the credential/value refusal or copy complete parameters into journals.
Actual owner descriptor acknowledgment and independent evidence admission remain
prerequisites, with normal admission still refusing unimplemented types.
## T10 — Source compatibility checker for Mason and Warden evidence
```task
id: NK-WP-0048-T10
status: done
priority: high
state_hub_task_id: "4b06ca97-c1e4-5680-bf22-ab75ab1646a4"
```
Completed 2026-10-16 in published source `c31d21ba`. The read-only
`check-evidence` command checks the actual Mason structural request and Warden
journal source protocols against an exact version/render. Independent source
review passed 103 tests at that checkpoint. It always reports native completion
false and execution prohibited. Fixtures and local journal fingerprints do not
provide authenticated owner/native evidence. This records completed source scope
previously described under T06; it does not duplicate native acceptance credit.
## T11 — Validate the optional machine-descriptor pin contract
```task
id: NK-WP-0048-T11
status: done
priority: high
state_hub_task_id: "ef2a0119-f59e-5e45-9519-115da9df7b85"
```
Completed 2026-10-16. Reproduced the JSON schema rejecting a valid
`execution_descriptors` map while the Python validator accepted malformed maps.
The schema now supports the existing Warden optional exact-byte pin map; semantic
validation binds keys to declared steps, bounds the map and checks digest shape.
The direct evidence API refuses malformed maps before accessing them. Twenty-one
new checks and all 127 affected source checks passed. No current type/case bytes,
status, owner descriptors or native acceptance changed. Source evidence and
publication receipt: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-acceptance-next/`.
## T12 — Prepare NetKingdom-owned attended step adapters
```task
id: NK-WP-0048-T12
status: todo
priority: high
state_hub_task_id: "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd"
```
Owner: NetKingdom. Retained scope: prepare bounded attended Deployment env,
config verification and restart/readiness steps around the existing helpers.
Next action: identify exact current entry points and their revision/readback
contracts, then implement fixed source-pinned callbacks or receipt-only attended
steps. Completion requires positive/refusal checks, exact target/source pins and
uncertainty behavior without acquiring user-engine runtime identity or exposing
registry/client secrets. Existing caller/session, cluster/issuer, CAS/readback
and status-only output boundaries remain mandatory.
2026-10-16 task refinement: the original broad adapter scope is split into T13
(completed registration source candidate), T14 (group creation provenance), T15
(durable registration recovery) and T16 (descriptor/artifact selection and owner
review). This preserves T12's task ID and Hub UUID while giving each remaining
outcome an owner and completion gate. T06 now names every source dependency;
its native/reference lifecycle acceptance scope remains unchanged. Completion
of any source task supplies no Warden, Platform or Apps assent and no native
execution authority.
## T13 — Prepare the fixed registration adapter source candidate
```task
id: NK-WP-0048-T13
status: done
priority: high
state_hub_task_id: "b264c10b-565c-5d29-b788-03cf5fbd2883"
```
Completed 2026-10-16, source implementation and synthetic fixture proof only.
Added an injection-only adapter around actual `keycape-client.run`: fixed
case/principal/human/lane/source/acceptance binding, existing CAS/readback,
metadata-only owner evidence and per-attempt parent-session revocation boundary.
The descriptor source composition pins the exact checker executable digest
alongside adapter/helper source. Creation-only rollback authenticates its
creation receipt and refuses changed postcreation UID/resourceVersion;
pre-existing clients confer no rollback authority. Missing readback, evidence,
revocation or process-local creation provenance cannot become retry-safe success.
Evidence: Custodian `docs/changes/netkingdom-target-2026-10-16/browser-adapter-next/`
R3 candidate, explicit patch and postimages, retained R1 rejected custody finding
and R2 revision, and 36 passing source checks (16 new, 20 existing). Three checks
use actual Warden kernel admission/dispatch/rollback with published Warden
`d97652a3bb5fa733b063d71a1b74068efcccf572` producer pins unchanged. Owner trust,
descriptor/type/case and Kubernetes objects remain synthetic fixtures. This
records a completed source deliverable, not independent review, publication,
type acknowledgment, exercised procedure, identity admission or native browser
acceptance. Independent root source review: Custodian browser-adapter-next/coordinator-review-r3.json. Source integration and normal publication/synchronization receipts: Custodian browser-adapter-next/canonical-integration.json. These receipts supply no other-owner assent or native authority.
Source publication: `0775c2bfa41af236301155bb5a69bbf088bdf823`, independently
reviewed by the Custodian coordinator before integration. The required initial
projection and ordinary UUID writeback both completed with zero failures and
zero automation errors. Generated-only brief commits were refused by the normal
hook; exact pre-existing staged/working brief bytes were restored. The separate
canonical integration receipt retains those refusals and projection evidence.
Native admission and all other-owner acceptance remain open.
## T14 — Preserve created-only provenance for group ensure and revoke
```task
id: NK-WP-0048-T14
status: todo
priority: high
state_hub_task_id: "fce95440-a25b-5f27-8ef3-a3d182099113"
```
Owner: NetKingdom. Next action: extend or wrap the existing identity-provisioner
ensure/revoke contract with independently retained prestate and exact newly
created groups/memberships before exposing reversible owner callbacks. Current
ensure returns no membership-creation inventory; current revoke may remove a
membership that predated this case. Completion requires actual helper-backed
positive/refusal fixtures, all validation before mutation, per-attempt evidence
and revocation, readback, ambiguity handling, and rollback that preserves every
pre-existing/shared group and membership. No existing endpoint or service token
is treated as new authorization.
## T15 — Define authenticated recovery for lost registration process state
```task
id: NK-WP-0048-T15
status: todo
priority: high
state_hub_task_id: "1b825705-4848-5edc-832f-b1622cd9bdb7"
```
Owner: NetKingdom for registration state, with Warden owning kernel evidence
admission. Next action: select the exact existing owner evidence holder/readback
contract and define authenticated lookup for the original creation attempt.
Completion requires restart/lost-receipt fixtures that bind case/source/principal,
original created-only provenance and current native revision before any undo;
uncertain outcomes remain non-replayable. The T13 adapter deliberately refuses
post-restart rollback and installs no reconciliation callback. A new broker or
unverified JSON receipt is not a recovery contract. Actual evidence-holder
acceptance and attended recovery remain separately authorized native work.
## T16 — Select and review the actual machine descriptor composition
```task
id: NK-WP-0048-T16
status: todo
priority: high
state_hub_task_id: "200bf500-5084-5ae7-be26-3033fe1f296e"
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
independently owns global provenance/admission. Next action: obtain review of the
exact checker source/artifact proposed by T17 and identify native owner evidence/
session callback contracts, then assemble the complete step descriptor set and
a proposed explicit type version selection. T13's historical descriptor retains
its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
pins for NetKingdom 015694ee; it does not attest T13's new adapter. Pinning any
machine descriptor into an acknowledged type is a separately reviewed source
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.
## T17 — Build and exercise the exact offline config-checker artifact
```task
id: NK-WP-0048-T17
status: done
priority: high
```
Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
existing `key-cape/src/cmd/keycape` command from published clean source revision
`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
read-only module selection. The full retained closure pins 108 source files,
9876 toolchain files and ten used module source directories. Three isolated
build locations produced identical 13633409-byte artifacts:
`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
Eight disposable checks use the actual executable and maintained helper/kernel,
with complete synthetic configs and a minimal environment: add/created-only
removal, normal pinned producer admission, rollback, lost-readback uncertainty,
stale revisions, substituted checker refusal, invalid startup config and missing
secret-env refusal. No checker stub or producer-pin override remains. The
artifact-bound descriptor proposal is
`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
Evidence: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
The first two build attempts succeeded but their closure collection failed;
both failures are retained, as is refusal of the older incomplete synthetic
fixture. No production config, credentials or cluster mutation was used.
This extracts completed build/fixture scope from T16 without claiming KeyCape
owner endorsement, native checker selection/storage custody, acknowledged type
integration, native evidence/session callbacks or attended browser acceptance.
T16 retains exact owner review and composition/admission scope. The attended
registration procedure remains unexercised.
## T18 — Make checker input and executable selection deterministic
```task
id: NK-WP-0048-T18
status: done
priority: high
```
Completed 2026-10-16, isolated source correction and real-artifact fixture proof
only; independent integration/publication remain separately recorded. KeyCape
source review identified non-secret `KEYCAPE_ACCOUNT_PORTAL_URL` and
`KEYCAPE_BROWSER_LOGOUT_URL` overrides in config.Load. The original helper
inherited ambient environment, allowing valid supplied configuration to be
rejected or invalid supplied URL configuration to be admitted. A bare relative
checker filename could also be replaced by PATH lookup after the adapter had
hashed the selected local file. All three counterexamples are retained against
the unchanged predecessor.
The existing helper now resolves the selected checker file to an absolute path
and passes an explicit empty subprocess environment. Three opt-in real-checker
regressions verify both URL-verdict directions and relative-file selection under
a non-checker PATH decoy. All 39 affected checks pass with the exact supplied
artifact, as do 11 real-artifact helper/kernel scenarios. Source evidence:
Custodian `docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/`.
Historical descriptors remain retained; the helper correction changes successor
source-composition pins and requires their separate review/admission. An absolute
path is not immutable custody and does not eliminate file replacement races;
T16 retains native executable storage/custody and callback/type admission.
No production configuration, credentials or cluster mutation was used.

View file

@ -0,0 +1,60 @@
test_actual_add_and_created_only_rollback_preserve_original_bytes (__main__.ArtifactAdapterChecks.test_actual_add_and_created_only_rollback_preserve_original_bytes) ... ok
test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust (__main__.ArtifactAdapterChecks.test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust) ... ok
test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo (__main__.ArtifactAdapterChecks.test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo) ... ok
test_kernel_external_revision_change_retains_uncertain_rollback (__main__.ArtifactAdapterChecks.test_kernel_external_revision_change_retains_uncertain_rollback) ... ok
test_stale_revision_refuses_mutation (__main__.ArtifactAdapterChecks.test_stale_revision_refuses_mutation) ... ok
test_substituting_caller_selected_checker_under_same_descriptor_is_refused (__main__.ArtifactAdapterChecks.test_substituting_caller_selected_checker_under_same_descriptor_is_refused) ... ok
test_real_checker_rejects_invalid_startup_config_before_cas (__main__.ArtifactAdapterChecks.test_real_checker_rejects_invalid_startup_config_before_cas) ... ok
test_real_checker_rejects_missing_declared_secret_env_before_cas (__main__.ArtifactAdapterChecks.test_real_checker_rejects_missing_declared_secret_env_before_cas) ... ok
test_ambient_urls_cannot_reject_supplied_valid_config (__main__.ArtifactAdapterChecks.test_ambient_urls_cannot_reject_supplied_valid_config) ... ERROR
test_ambient_urls_cannot_admit_supplied_invalid_config (__main__.ArtifactAdapterChecks.test_ambient_urls_cannot_admit_supplied_invalid_config) ... FAIL
test_relative_selected_real_binary_is_not_replaced_by_path_decoy (__main__.ArtifactAdapterChecks.test_relative_selected_real_binary_is_not_replaced_by_path_decoy) ... ERROR
======================================================================
ERROR: test_ambient_urls_cannot_reject_supplied_valid_config (__main__.ArtifactAdapterChecks.test_ambient_urls_cannot_reject_supplied_valid_config)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/test_real_checker_adapter.py", line 55, in test_ambient_urls_cannot_reject_supplied_valid_config
result=t.a.build(**self.kw).invoke(self.inv)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-registration-adapter.py", line 179, in <lambda>
return Adapter(REFERENCE, reviewed_source, lambda inv: execute(inv, 'add'),
^^^^^^^^^^^^^^^^^^^
File "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-registration-adapter.py", line 112, in execute
with contained_output():
File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "/home/worsch/ops-warden/src/warden/case_run.py", line 62, in contained_output
raise Refused("owner callback failed") from None
warden.case_receipt_state.Refused: owner callback failed
======================================================================
ERROR: test_relative_selected_real_binary_is_not_replaced_by_path_decoy (__main__.ArtifactAdapterChecks.test_relative_selected_real_binary_is_not_replaced_by_path_decoy)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/test_real_checker_adapter.py", line 73, in test_relative_selected_real_binary_is_not_replaced_by_path_decoy
result=t.a.build(**dict(self.kw,check_config=Path('keycape'))).invoke(self.inv)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-registration-adapter.py", line 179, in <lambda>
return Adapter(REFERENCE, reviewed_source, lambda inv: execute(inv, 'add'),
^^^^^^^^^^^^^^^^^^^
File "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/keycape-registration-adapter.py", line 112, in execute
with contained_output():
File "/usr/lib/python3.12/contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "/home/worsch/ops-warden/src/warden/case_run.py", line 62, in contained_output
raise Refused("owner callback failed") from None
warden.case_receipt_state.Refused: owner callback failed
======================================================================
FAIL: test_ambient_urls_cannot_admit_supplied_invalid_config (__main__.ArtifactAdapterChecks.test_ambient_urls_cannot_admit_supplied_invalid_config)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/test_real_checker_adapter.py", line 65, in test_ambient_urls_cannot_admit_supplied_invalid_config
with self.assertRaises(t.Refused):t.a.build(**self.kw).invoke(self.inv)
AssertionError: Refused not raised
----------------------------------------------------------------------
Ran 11 tests in 1.759s
FAILED (failures=1, errors=2)

View file

@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""Register or remove exactly one KeyCape client from a SecurityCase, without exporting Secret values.
Entry point of the playbook capability net-kingdom.keycape-client-registration,
composed by the SecurityCaseType oidc-browser-relying-party
(security-case-types/, canon security-case-types_v0.1). It generalizes the
per-client helpers in this directory (portal-client-rollout,
register-coulomb-social, vergabe-client-rollout,
secrets-engine-openbao-client-rollout) and keeps their safeguards:
- contained kubectl, cluster-identity pin and verified upstream issuer pin
- byte-preserving merge: only this client's lines change; every other config
byte, the signing key and unrelated clients are preserved
- exact match: absent -> add; identical -> noop; drift, duplicates or a shared
secretRef -> refuse (update = remove, then add, each accepted)
- compare-and-swap on the Secret's uid and resourceVersion, then read-back
- confidential clients: the KeyCape Deployment must already bind the client's
secret env var (delivered from custody), or the next pod start fails closed
- KeyCape's own startup checks (keycape check-config, key-cape c528daa) on the
live and the proposed registry, fed on stdin and never written to disk; apply
refuses a registry that would stop the single replica at its next start
- no Secret value, parser message or Kubernetes error text leaves the process
Modes: inspect (default), dry-run (server-side), apply. apply requires the
case's acceptance record instead of a per-client founder decision id: the case
was accepted under an acknowledged type.
keycape-client.py client-from-case <case.yaml>
keycape-client.py inspect --client-file client.yaml [--operation remove]
keycape-client.py apply --client-file client.yaml --case-acceptance <record> \\
--check-config <keycape binary> --expected-uid <uid> --expected-resource-version <rv>
"""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
import re
import subprocess
import sys
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
lane = module('portal_rollout', 'portal-client-rollout.py')
pin = lane.pin
Refused = lane.Refused
require = lane.require
CASE_TYPE = re.compile(r'^oidc-browser-relying-party@1\.\d+$')
CLIENT_ID = re.compile(r'^[a-z0-9][a-z0-9-]{1,62}$')
# KeyCape refuses '*' and '?' at startup; OAuth 2.0 forbids fragments.
REDIRECT = re.compile(r'^https://[^*?#\s,]+$')
SCOPES = {'openid', 'profile', 'email', 'groups'}
def env_name(client_id):
return 'KEYCAPE_' + client_id.upper().replace('-', '_') + '_CLIENT_SECRET'
def env_secret_name(client_id):
return f'keycape-{client_id}-client'
def client_from_params(params):
"""The KeyCape client record for a case of oidc-browser-relying-party."""
cid = params['client_id']
require(CLIENT_ID.fullmatch(cid or '') is not None, 'invalid_client_id')
redirects = [r for r in params['redirect_uris'].split(',')]
require(redirects and all(REDIRECT.fullmatch(r) for r in redirects), 'redirect_uris_must_be_exact_https')
scopes = params['allowed_scopes'].split(',')
require(scopes[0] == 'openid' and set(scopes) <= SCOPES and len(set(scopes)) == len(scopes),
'allowed_scopes_invalid')
# KeyCape has no per-client group gate; the consumer reads groups, and UserInfo
# returns them only for the groups scope.
require(not params.get('access_groups') or 'groups' in scopes, 'access_groups_require_groups_scope')
require(params['client_type'] in ('confidential', 'public'), 'client_type_invalid')
# The type derives custody at platform/workloads/keycape/<client_id>; a case cannot choose it.
require('secret_ref' not in params, 'secret_ref_is_derived_by_the_type')
require(params['mfa_required'] in ('true', 'false'), 'mfa_required_invalid')
client = {
'clientId': cid,
'displayName': params['display_name'],
'redirectUris': redirects,
'allowedScopes': scopes,
'grantTypes': ['authorization_code'],
'clientType': params['client_type'],
}
if params['client_type'] == 'confidential':
client['secretRef'] = 'env:' + env_name(cid)
if params['mfa_required'] == 'true':
client['mfaRequired'] = True
return client
def client_from_case(case):
spec = case.get('spec') or {}
require(CASE_TYPE.fullmatch(str(spec.get('type', ''))) is not None, 'case_type_not_oidc_browser_relying_party')
return client_from_params(spec.get('parameters') or {})
def env_binding(client):
return {'name': env_name(client['clientId']), 'valueFrom': {
'secretKeyRef': {'name': env_secret_name(client['clientId']), 'key': 'client-secret'}}}
def _client_node(raw):
root = yaml.compose(raw)
node = next(value for key, value in root.value if key.value == 'clients')
require(isinstance(node, yaml.nodes.SequenceNode) and not node.flow_style,
'block_client_sequence_required')
return node
def _line_start(raw, index):
start = raw.rfind('\n', 0, index) + 1
return start if not raw[start:index].strip() else index
def _inspect(secret, client):
raw, config, _ = pin.issuer_document(secret)
require(config['authelia'].get('issuer') == pin.VERIFIED_UPSTREAM_ISSUER, 'verified_issuer_pin_required')
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
require(all(isinstance(c, dict) and 'clientId' in c for c in clients), 'client_shape_unexpected')
ids = [c['clientId'] for c in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
if 'secretRef' in client:
others = [c for c in clients if c['clientId'] != client['clientId']]
require(all(c.get('secretRef') != client['secretRef'] for c in others), 'secret_ref_already_bound')
return raw, config, ids
def replacement(secret, client):
"""Return (base64 config, changed) with only this client added."""
raw, config, ids = _inspect(secret, client)
if client['clientId'] in ids:
require(config['clients'][ids.index(client['clientId'])] == client,
'existing_registration_differs_requires_reconciliation')
return secret['data']['config.yaml'], False
node = _client_node(raw)
index = _line_start(raw, node.end_mark.index)
indent = ' ' * node.start_mark.column
fragment = yaml.safe_dump([client], sort_keys=False, width=120)
fragment = ''.join(indent + line if line.strip() else line for line in fragment.splitlines(True))
if index and raw[index - 1] != '\n':
fragment = '\n' + fragment
updated = raw[:index] + fragment + raw[index:]
expected = copy.deepcopy(config)
expected['clients'].append(client)
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def removal(secret, client):
"""Rollback: return (base64 config, changed) with only this exact client removed."""
raw, config, ids = _inspect(secret, client)
if client['clientId'] not in ids:
return secret['data']['config.yaml'], False
position = ids.index(client['clientId'])
require(config['clients'][position] == client, 'existing_registration_differs_requires_reconciliation')
require(len(ids) > 1, 'removal_would_empty_client_sequence')
node = _client_node(raw)
item = node.value[position]
start = raw.rfind('\n', 0, item.start_mark.index) + 1
require(raw[start:item.start_mark.index].strip() == '-', 'unexpected_item_layout')
if position + 1 < len(node.value):
end = raw.rfind('\n', 0, node.value[position + 1].start_mark.index) + 1
else:
end = _line_start(raw, node.end_mark.index)
updated = raw[:start] + raw[end:]
expected = copy.deepcopy(config)
del expected['clients'][position]
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def deployment_bound(deployment, client):
"""Whether the live Deployment carries exactly this client's secret binding."""
if 'secretRef' not in client:
return None
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
matches = [e for e in container.get('env', []) if e.get('name') == env_name(client['clientId'])]
require(len(matches) <= 1 and all(e == env_binding(client) for e in matches), 'env_binding_drift')
return bool(matches)
def check_config(binary, config_text, env_names, policy_store):
"""KeyCape's startup checks on one registry: names and messages only, no values."""
command = [binary, 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
if policy_store:
command.append('--policy-store')
result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60)
report = json.loads(result.stdout)
require(isinstance(report, dict) and isinstance(report.get('valid'), bool)
and report['valid'] == (result.returncode == 0), 'check_config_output_shape')
return {'valid': report['valid'], 'clients': report.get('clients'),
'errors': [str(e) for e in report.get('errors') or []]}
def deployment_env_names(deployment):
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
return sorted(e['name'] for e in container.get('env', []))
def run(args, client):
kubectl = lane.kubectl
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == lane.CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = (replacement if args.operation == 'add' else removal)(before, client)
bound = deployment = None
if 'secretRef' in client or args.check_config:
deployment = kubectl(['-n', 'sso', 'get', 'deployment', 'keycape', '-o', 'json'])
bound = deployment_bound(deployment, client)
checks = None
if args.check_config:
names = deployment_env_names(deployment)
policy_store = 'KEYCAPE_POLICY_PATH' in names
checks = {stage: check_config(args.check_config, base64.b64decode(config).decode(), names, policy_store)
for stage, config in (('live', before['data']['config.yaml']), ('proposed', encoded))}
receipt = {'schema': 'netkingdom.keycape-client.v1', 'target': 'sso/keycape-config',
'before': metadata, 'operation': args.operation, 'client_id': client['clientId'],
'client_type': client['clientType'], 'change_needed': changed, 'mode': args.mode,
'deployment_env_bound': bound, 'check_config': checks, 'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
if args.mode == 'apply':
require(bool(args.case_acceptance), 'case_acceptance_record_required')
receipt['case_acceptance'] = args.case_acceptance
require(checks is not None, 'keycape_check_config_required')
require(checks['proposed']['valid'], 'proposed_registry_fails_keycape_check_config')
if args.operation == 'add' and bound is not None:
require(bound, 'deployment_env_binding_required_first')
require(metadata == {'uid': args.expected_uid, 'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}), 'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid']
and after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after), restart_required=changed)
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('mode', nargs='?', default='inspect',
choices=['inspect', 'dry-run', 'apply', 'client-from-case'])
parser.add_argument('case', nargs='?', help='case file, for client-from-case')
parser.add_argument('--client-file', help='KeyCape client record (output of client-from-case)')
parser.add_argument('--operation', default='add', choices=['add', 'remove'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
parser.add_argument('--case-acceptance', help='the case acceptance record; required for apply')
parser.add_argument('--check-config', help='a keycape binary with check-config (key-cape c528daa or later); required for apply')
args = parser.parse_args(argv)
try:
if args.mode == 'client-from-case':
require(bool(args.case), 'case_file_required')
print(yaml.safe_dump(client_from_case(yaml.safe_load(Path(args.case).read_text())), sort_keys=False), end='')
return 0
require(bool(args.client_file), 'client_file_required')
client = yaml.safe_load(Path(args.client_file).read_text())
require(isinstance(client, dict) and 'clientId' in client, 'client_file_shape')
print(json.dumps(run(args, client), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except pin.IssuerPinError as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,59 @@
# Attended registration adapter candidate
Exercise status: unexercised
This is a source-only, injection-only adapter for the existing Warden closed
kernel. It installs no command, runtime dispatcher, credential provider, evidence
trust root or new service. NetKingdom type/case status and bytes remain unchanged.
The generated descriptor is a proposal; it is not owner acknowledgment.
`keycape-registration-adapter.py` wraps maintained `keycape-client.run` in apply
mode. A fixed invocation binds principal, human actor, lane, case/type/descriptor/
catalog/acceptance/run-sheet digests and target. The descriptor pins this adapter
and its complete local helper import closure together with the exact reviewed
`check-config` executable digest. Changing that digest changes the descriptor
source pin and descriptor bytes, requiring independently reviewed admission.
The fixed executable is rehashed before each attempt. The caller constructs the
proposed invocation from exact source inputs, installs the fixed adapter once,
and calls normal `Executor.admit`; independently derived admission must match.
No callable replacement after admission is required or supported by this recipe.
The operation uses existing cluster/issuer checks, byte-preserving merge,
Deployment secret-env prerequisite, config validation, UID/resourceVersion CAS
and whole-data readback. Only closed metadata is passed to the injected durable
owner publisher. The owner authenticator must independently authenticate those
exact bytes. The session-revocation capability must end the same attempt's
parent session; Warden separately verifies its exact revocation evidence.
These capabilities are owner-selected callables, never case-supplied commands.
No implementation of these native capabilities is supplied or implied here.
An identical existing client returns no change and creates no rollback authority.
Removal requires this process's independently authenticated successful creation
receipt, exact created-object label, unchanged postcreation UID/resourceVersion,
and the original exact client record. External drift, a lost readback, receipt
publication/revocation failure or process loss requires owner reconciliation.
No uncertainty is labeled retry-safe. No automatic reconciliation is installed.
Rollback after restart intentionally remains unavailable until an owner supplies
independently authenticated durable creation provenance and reviews recovery.
The source tests execute actual maintained helper CAS/readback against synthetic
Kubernetes objects. Three checks exercise actual Warden admission/run/rollback
and uncertainty transitions with synthetic owner trust. Warden d97652a's exact
published producer source pins apply unchanged through actual BrowserInputs.
This is a source compatibility fixture, not acknowledgment, owner provenance,
normal native admission or deployment acceptance.
Remaining work belongs in live work records before T12 closure:
- Warden owns global evidence admission and exact native evidence/session
capabilities. Published d97652a refreshes BrowserInputs producer pins for
NetKingdom 015694ee; that source assent does not attest this new adapter.
- NetKingdom owns separately reviewed descriptor/type pin selection, the env
binding/config/restart steps, and group ensure/revoke provenance. Current group
ensure does not report newly added memberships; current revoke can remove
memberships that predate the case. Wrapping these as reversible creation would
be unsound. Preserve them until a reviewed created-only contract exists.
- Platform owns structural/mint/broker and secret delivery evidence; Apps owns
actual consumer login and group enforcement. No assent is supplied here.
- Attended use still requires existing owner authorization, current caller
identity, native source/config/image pins, native receipts and cleanup proof.

View file

@ -0,0 +1,748 @@
---
id: NK-WP-0048
type: workplan
title: "Establish SecurityCaseTypes so that known security cases are light"
domain: infotech
repo: net-kingdom
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-08"
updated: "2026-10-16"
depends_on: []
related_repos:
- ops-mason
- ops-warden
- key-cape
- railiance-platform
- informed-decision
state_hub_workstream_id: "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
---
# NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
## Why
The founder's direction (2026-10-08): setting up a security integration of a
kind that has been done before should be easy, meaning accept the case, then
ops-mason arranges it and ops-warden follows through. Setting up a new kind
needs ample documentation and tests and an informed-decision, because its shape
is part of the security architecture.
Today every integration is hand-built: per-client helper scripts, per-lane
custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC
client), whose next step was going to be yet another hand-written rollout
script.
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
into **SecurityCaseTypes** that ops-mason and ops-warden can execute. Canon:
`canon/standards/security-case-types_v0.1.md` (proposed).
Companion plans: ops-mason `MASON-WP-0010` (materialization) and ops-warden
`WARDEN-WP-0044` (follow-through).
## Tasks
```task
id: NK-WP-0048-T01
status: done
priority: high
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
```
### T01 — Accept the terminology and the canon
Founder review of `canon/standards/security-case-types_v0.1.md`, including its
section 10 open questions. On acceptance the canon status goes from `proposed`
to `accepted`.
2026-10-08: accepted by the founder (in session). Canon status `accepted`;
section 10's open questions are carried forward to the first type (T04, T05).
```task
id: NK-WP-0048-T02
status: done
priority: high
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
```
### T02 — Inventory the de-facto types already built by hand
Read-only. List every security integration built so far and group them by shape.
Known starting points:
- browser OIDC relying parties on key-cape: informed-decision-approver,
coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
- confidential service clients with `client_credentials`: secrets-engine
approval, requester and openbao clients, approval-engine operator
- workload KV read lanes on OpenBao: the railiance-platform credential lane
designs and CCRs, for example `railiance-backup-offsite-lane`
- short-lived SSH certificates through ops-warden (`warden sign`)
- tunnelled operator access (ops-bridge)
For each: the repositories involved, the hand-built steps, attended human steps,
custody paths, verification evidence, and rollback (if any). Output
`docs/security-case-type-inventory.md`, ranking candidate types by how often the
shape recurs and how much manual work each instance costs today.
2026-10-08: done, `docs/security-case-type-inventory.md`. 36 of 37 CCRs are
already `request_type: workload-kv-read`, a de-facto type reviewed each time as
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
rollout script. Confidential service clients need a registration plus two or
three KV-read CCRs each. A forge read identity is needed in bulk by
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
```task
id: NK-WP-0048-T03
status: done
depends_on: [NK-WP-0048-T01]
priority: high
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
```
### T03 — Schema, template and validator
- `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`
- `security-case-types/_template/` with `type.yaml`, `README.md` and tests to copy
- a validator, extending `tools/security-scenario-composer/`, that checks a type,
checks a case against its type, and renders the case into the composer's
deterministic plan (still `execution.permitted: false`)
Acceptance: the validator rejects missing sections, secret-shaped parameter
values, unpinned providers and cases against non-`implemented` types, with tests
for each.
2026-10-08: done. `canon/schemas/security-case-type_v0.1.schema.json` and
`security-case_v0.1.schema.json`; `tools/security-case-types/security_case_validator.py`
(`validate-type`, `validate-case`, `render`, reusing the scenario composer);
`security-case-types/_template/` (a valid, runnable example type over
`net-kingdom.local-identity`, with README sections, an example case and tests that
travel with every copy). 31 tests pass, including the composer's existing suite.
```task
id: NK-WP-0048-T04
status: done
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
priority: high
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
```
### T04 — Define the first type: browser OIDC relying party
`security-case-types/oidc-browser-relying-party/`, version 1.0:
- parameters: client id, display name, exact redirect URIs, scopes, client type
(confidential or public), MFA pin, the groups the consuming service maps,
custody path for the secret (confidential only), consuming service and its
owner
- composition: key-cape client registration (key-cape declares it as a playbook
capability), secret minting into custody (railiance-platform), LLDAP group
existence and membership (net-kingdom identity-provisioner), handover to the
consuming service
- materialization (ops-mason) and follow-through (ops-warden) sections
- verification: discovery lists the redirect URI; a test sign-in returns the
expected claims for one human; anonymous access to the custody path is denied
- rollback: remove exactly this client and its custody path
- README: threat model, MFA rationale, why redirect URIs are exact, which steps
still need an attended login and how that is minimised
- reference instances: informed-decision-approver, coulomb-social, user-engine
portal, openbao-admin
Acceptance: complete per canon section 3, tests green, status `proposed`.
2026-10-08: done. `security-case-types/oidc-browser-relying-party/` 1.0 (proposed):
`type.yaml` with 12 parameters (the custody reference is required only for
confidential clients), composition over key-cape's new
`key-cape.oidc-client-registration` declaration (contract PASS, `maturity:
planned`), materialization and follow-through, verification and rollback, all
conditional on client type where it matters. One attended step,
`register-client`, justified by the live registry being a Kubernetes Secret.
README with threat model, alternatives, an honest fit table (portal and vergabe
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
(checked against the accepted request) and a public portal. The validator gained
`when` / `required_when` conditions and provider discovery. 43 tests pass.
ops-mason's and ops-warden's sections are drafted in the type. Their owners
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
(T05).
```task
id: NK-WP-0048-T05
status: done
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
priority: high
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
```
### T05 — Acknowledge the type through informed-decision
Prepare the Decision Memo from the type's README and `type.yaml` and submit it
through informed-decision's production intake. On acknowledgment the type
moves to `acknowledged`.
2026-10-08: Decision Memo prepared. Hub decision `e418263e-a71f-4f01-a970-c87c9e187c09`.
`security-case-types/oidc-browser-relying-party/decision/`: `packet.md` (question,
one act, what acknowledging does not do, the shape, alternatives, attended
steps, limits, evidence), `build_intake.py` and the built `intake.json` (memo
class `decision-record`, review group `net-kingdom-admins`, resolves the hub
decision on answer). The envelope passes informed-decision's own
`validate_submission`. Queuing it is an attended operator step, a write inside
the production dispatcher pod:
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
< security-case-types/oidc-browser-relying-party/decision/intake.json
The answer then appears for review in informed-decision.
2026-10-08: queued by the founder (`pending`, `queued`) after one refusal: the
first envelope's idempotency key was not State Hub's stable
`state-hub.<decision>.<memo>` form, and the builder now validates against both
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
delivered). T05 waits for the founder's answer in informed-decision, which
resolves hub decision `e418263e`.
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
1.0 `proposed`: `secret_ref` is no longer a parameter, the type derives
`platform/workloads/keycape/{client_id}`; policies are
`workload-kv-read-keycape-{client_id}-eso` and
`workload-kv-read-keycape-{client_id}-{consuming_service}`, each with a templated
body (`read` on that one data path); new `openbao_role`
`external-secrets-keycape-{client_id}` (Kubernetes auth, ESO service account,
15m, no default policy); catalog entry `{consuming_service}-keycape-client`;
the two rollback steps on the KV path collapse into one metadata delete. The
schema and validator now require a `body` on policy and role items. 105 tests
pass. Two consequences: the delivered memo describes the type before these
changes (a case-set custody reference, two differently named policies, no ESO
role), so the founder answers on a superseded shape unless the memo is
re-sent; and Forgejo's custody path moves from the accepted request's proposed
`platform/workloads/forgejo/netkingdom-oidc-client` to
`platform/workloads/keycape/forgejo`, which key-cape (KEY-WP-0038) and
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
(WARDEN-WP-0044-T01) is still in progress.
2026-10-08: revised memo `nk-wp-0048-t05-oidc-browser-relying-party-r2` built
(`packet.md`, `intake.json`, packet `sha256:af6435e2…`); it states that it
supersedes the first memo and passes informed-decision's and the dispatcher's
validation. It needs a new memo id because the dispatcher refuses new content
under a delivered idempotency key (`idempotency_conflict`). Queuing it is the
same attended step as above. Both memos serve hub decision `e418263e`; the
first answer resolves it, so the first memo must not stay open: an accept on it
would acknowledge the superseded shape, and a decline would resolve the
decision against r2. Close it with `return` (a reviewer verb that requests a
new revision and does not resolve the hub decision; only accept and decline
do), commenting that r2 supersedes it, then answer r2. `withdraw` exists in
the vocabulary but is not offered to reviewers, and lifecycle `revise` only
applies to lifecycle-registered memos, not intake-delivered ones.
2026-10-09: the founder reports r2 enqueued and the first memo returned. At
close, hub decision `e418263e` still showed `open` (not verified whether r2
was answered or the answer is still in transit; the dispatcher ledger was not
read). ops-warden's review (WARDEN-WP-0044-T01, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`, d470f2e) arrived at
21:25, before r2 was built, and is not in r2. Required before WARDEN-WP-0044-T02:
R1 a per-step `lane` (missing: identity-provisioner caller credential,
railiance-platform broker grant), R2 canon meaning of `warden_executes: true`
(caller proxy, caller identity, no standing ops-warden token; ADR-0002), R3 a
per-step `value_handling`. Refinements R4 (one type-level catalog lane instead
of a per-case entry, which conflicts with ops-mason's item 5), R5 (ESO Ready gate
before bind-env), R6 (exact accepted memberships), R7 (mfa-pinned and
secret-delivered checks). Next: if r2 was accepted, fold R1-R3 and R5-R7 in as
a follow-up and settle R4 between ops-warden and ops-mason before T06; if not,
hold r2, fold them in and build r3.
key-cape and railiance-apps were told about Forgejo's new custody
path.
2026-10-09: r2 was not answered (hub decision `e418263e` open; dispatcher 9
delivered), so r2 is held and r3 replaces it. ops-warden withdrew R4 for R4′
(message a66a9204): one catalog entry per case with its body templated in the
type. Folded in:
- Canon section 3: a narrow meaning of `warden_executes: true` (caller proxy,
caller identity, no standing credential; ops-warden ADR-0002), plus per-step
`lane`, `value_handling` and an optional `ready_check`. A catalog entry body is
now required, like policy and role bodies.
- Schema and validator enforce all of these.
- The type: lanes `identity-provisioner-groups` and
`keycape-client-secret-generate` (both new) and `key-cape-oidc-login`;
`mint-secret` is `generated_in_custody`; a Ready gate on `deliver-secret`;
`ensure-groups` adds exactly the accepted case's memberships; new checks
`secret-delivered` and `mfa-pinned`; the catalog-entry body follows R4′.
- 110 tests pass.
Memo r3 (`nk-wp-0048-t05-oidc-browser-relying-party-r3`, packet
`sha256:726dfc01…`) passes informed-decision's and the dispatcher's validation.
The founder queues it with the same command, returns r2 with a comment that r3
supersedes it, then answers r3.
Prerequisites for T06, not for T05:
- the two new lanes, each through its owner;
- a founder-approved rollout of an identity-provisioner image containing
630ebfd (the deployed one is main-6c4fcaf from 2026-09-24, without
`/v1/groups/ensure`).
ops-mason's request to create LLDAP group `fluid-telegram-operators` (message
259ec26d) was answered: not possible from here by that path; the founder creates
it in the LLDAP admin UI.
2026-10-09: ops-warden finished its side of WARDEN-WP-0044-T01 (message
`58d33f40`, commit 0e7ceb8): the revised type conforms to `warden case
check-type`, with no further changes asked of the type. This acknowledges that
review. Its later addendum R8 (message `af274783`, ops-warden
`history/2026-10-08-oidc-rp-follow-through-review.md`) showed that lane
`identity-provisioner-groups` cannot exist as declared: the provisioner's only
HTTP credential is user-engine's runtime token, and its NetworkPolicy admits
only user-engine pods, so a caller-proxy lane would act as user-engine. Taken
option 1 (ops-warden's recommendation): `ensure-groups` and its rollback are
attended, in the `register-client` session on `key-cape-oidc-login`, run inside
the provisioner pod; no token is read. Option 2 (an operator credential of its
own on the provisioner) stays open for a later version. `warden case
check-type` now reports only `keycape-client-secret-generate`
(railiance-platform) as missing. 89 tests pass.
Hub decision `e418263e` was still open, so memo r4
(`nk-wp-0048-t05-oidc-browser-relying-party-r4`, packet `sha256:452ea421…`)
replaces r3; it passes informed-decision's and the dispatcher's validation. The
founder queues it with the same command, returns r2/r3 if still open with a
comment that r4 supersedes them, then answers r4.
Prerequisites for T06 are now: lane `keycape-client-secret-generate`
(railiance-platform), and a founder-approved rollout of an identity-provisioner
image containing 630ebfd.
2026-10-09: done. The founder queued r4 (`pending`, `queued`), returned r3 as
superseded and accepted r4 in informed-decision. informed-decision's hub
write-back is off in production (`INFD_HUB_WRITEBACK` unset), so net-kingdom
resolved hub decision `e418263e` from the founder's report, naming memo r4 in
the rationale. The type is `acknowledged`; it becomes `implemented` with T06.
```task
id: NK-WP-0048-T06
status: wait
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
priority: high
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
```
### T06 — First KnownSecurityCase: Forgejo
Write `security-cases/forgejo-netkingdom-oidc.yaml` from the parameters the
founder accepted in KEY-WP-0038-T01 (`key-cape/docs/forgejo-oidc-client-request.yaml`),
so that acceptance carries over as the case acceptance. ops-mason materializes it;
ops-warden follows through and verifies it. The type becomes `implemented` once
this reference case is live.
This replaces the hand-built path proposed for KEY-WP-0038-T02.
2026-10-09: case written, `security-cases/forgejo-netkingdom-oidc.yaml`, status
`accepted`: the example's parameters plus the founder's 2026-10-08 acceptance of
the key-cape request (owner and founder), with the custody-path derivation
covered by the type's acknowledgment (memo r4). It validates and renders (plan
`execution.permitted: false`; three attended steps on `key-cape-oidc-login`);
it needs `--allow-unimplemented` until it is live, since it is the case that
makes the type `implemented`. A test keeps it equal to the type's example.
Still open before it runs: ops-mason renders and builds it (MASON-WP-0010-T02,
T03, unblocked by T05), ops-warden plans and runs it (WARDEN-WP-0044-T02, T03),
railiance-platform's `keycape-client-secret-generate` lane (asked by ops-mason
`789feb39` and ops-warden `d9f889b1`, both unread), and a founder-approved
identity-provisioner rollout containing 630ebfd.
2026-10-09: identity-provisioner rolled out, founder-approved in session:
`main-630ebfd` (`sha256:bafb4860…`, built by the image workflow) replaces
`main-6c4fcaf` (`sha256:ffacd5d7…`; only change: 630ebfd, additive). Applied
with `kubectl set image` on the Deployment only (generation 17 → 18, 1/1 Ready);
the file's Service, NetworkPolicies and Ingress were not reapplied. Verified in
the pod: `ensure_service_groups` and `revoke_service_groups` present, `/readyz`
200; no token used, no group changed. Rollback: `kubectl -n sso set image
deploy/identity-provisioner provisioner=forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ffacd5d790a5b7800e0535f3aa95b7de52c7a68e4f575b9192c3e1622fa672c2`.
The pin in `sso-mfa/k8s/identity-provisioner/deployment.yaml` follows.
2026-10-09: ops-warden closed WARDEN-WP-0044-T01 and shipped `warden case plan`
(T02, message `06303d1d`); the accepted Forgejo case is refused until ops-mason
materializes it, and a materialized copy plans cleanly once
`keycape-client-secret-generate` exists. Its observation that `bind-env` carries
a literal `KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`: the validator now renders a
`{param:env}` format spec (upper case, `-` as `_`, as `keycape-client.py`
derives the name) and rejects unknown specs, and follow-through and rollback
text is now checked for unknown placeholders too (patch-level, canon section 6).
The type keeps the literal until ops-warden's planner (`text.format`) renders the
spec as well; asked of ops-warden. 53 tests pass.
2026-10-10: ops-warden rendered `{param:env}` in `warden case plan`
(23ba584, message `c1df14b7`; other specs and `!conversion` refused). The type
adopts it as a 1.0 patch (canon section 6): `bind-env` and its rollback now read
`KEYCAPE_{client_id:env}_CLIENT_SECRET`. ops-mason reports the Forgejo case
rendered and built (message `1ebddae9`; CCR drafts handed to railiance-platform),
so T06 now waits on the platform apply, the mint-secret lane and the attended
register-client session.
2026-10-10: key-cape adopted the type's custody path (key-cape ab5cccd, message
`88a4559c`): `docs/forgejo-oidc-client-request.yaml` and KEY-WP-0038-T02 name
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, delivered as
`sso/keycape-forgejo-client` through `external-secrets-keycape-forgejo`, and the
`forgejo/netkingdom-oidc-client` proposal is withdrawn. This matches the type and
the Forgejo example; no change needed here.
2026-10-10: key-cape's type-composition review (key-cape 6038483, message
`8e5ed5f6`) found the composition sound with two blocking findings, taken as a
1.0 patch (stricter validation and documentation, canon section 6):
R1, redirect URIs now refuse `?` and `#` in the type pattern and in
`keycape-client.py`'s `REDIRECT` (KeyCape refuses `?` at startup and the single
replica would stop); R2, `allowed_scopes` always includes `groups`, the renderer
refuses `access_groups` without it, and `access_groups` now says the consumer
enforces the gate because KeyCape has none; R3, `mfa_required` "false" means the
per-user privacyIDEA policy; R4, register-client's justification records the
restart for every client, the quiet window, and that the rendered registry must
pass KeyCape's client validation before it. The accepted Forgejo case and both
examples still validate; 116 tests pass.
Open, needs a decision: R2's verification step that the consumer enforces the
group gate is a new verification step, hence 1.1 (minor), and the validator pins
cases to the exact type version, so the accepted 1.0 Forgejo case would need
re-pinning. Also open with key-cape: a KeyCape entry point that validates a
rendered client registry without starting the server; until then
`keycape-client.py` mirrors the redirect rule.
2026-10-10: founder go for 1.1, so no 1.0 run overlaps. The type adds
verification `consumer-enforces-groups`, and hand-over now requires the consumer
to enforce `access_groups` (minor, canon section 6; risk class unchanged, no new
acknowledgment; history in the type README). The accepted Forgejo case and both
examples are re-pinned to `oidc-browser-relying-party@1.1` before any apply; the
acknowledgment intake keeps `@1.0` as the record of what was acknowledged.
ops-mason's build and ops-warden's case-ref digest predate this and need a
re-render.
2026-10-10: ops-mason re-rendered the Forgejo case from `@1.1` (message
`22c6bba6`; plan digest `af1f4f81…`, no collisions) and rebuilt the bundle. The
rendered OpenBao objects are identical to 1.0, so the already applied
CCR-2026-0041 stands; 1.1 changes only consumer-side steps. key-cape shipped
`keycape check-config` (c528daa, message `a7b0aeff`): KeyCape's startup checks
without secret values, including the new duplicate-clientId rule.
`keycape-client.py --check-config <binary>` now runs it on the live and the
proposed registry, fed on stdin, and apply refuses a proposed registry that
fails; register-client's entry point names the option (1.1 patch). The deployed
image predates c528daa, so the binary is built from key-cape source for now.
Before the next KeyCape rollout (Forgejo register-client or the CCR-2026-0018
window), the attended operator runs `inspect --check-config` so the live
registry is checked too.
```task
id: NK-WP-0048-T07
status: wait
depends_on: [NK-WP-0048-T06]
blocking_reason: "The next types follow once the first one is proven end to end."
priority: medium
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
```
### T07 — Queue the next types
From the T02 ranking, open the next type drafts (likely the confidential service
client and the workload KV read lane), each to be acknowledged on its own.
Record which existing hand-built instances get adopted as cases, without
rewriting them in place.
```task
id: NK-WP-0048-T08
status: done
priority: high
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
```
### T08 — identity-provisioner: ensure service-access groups
The type's `ensure-groups` step calls `identity-provisioner groups ensure --groups
<list> --members <group=user+user;...>`. The provisioner already has idempotent
`_ensure_group` and `_add_group`, but exposes only tenant-scoped operations. Add
the general operation (create if absent, add the listed memberships, never
remove others), its rollback (remove only this case's memberships), tests, and
the ops-warden route for its service identity.
2026-10-08: done. `identity-provisioner/provisioner.py` gained
`ensure_service_groups` (`POST /v1/groups/ensure`) and `revoke_service_groups`
(`POST /v1/groups/revoke`), behind the existing service-token check. Names
matching `tenant*`, `netkingdom-*` and `lldap_*` are reserved; members must
already exist; everything is validated before any change; memberships are read
back before success; nothing else is ever removed; groups are deleted only when
asked and empty. 7 new tests (39 in the provisioner suite). The type's
`ensure-groups` step and its rollback now name these endpoints. The ops-warden
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
```task
id: NK-WP-0048-T09
status: done
priority: high
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
```
### T09 — The `keycape-client` entry point and its declaration
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
live registration needs already lives in net-kingdom, where KeyCape is deployed
(`sso-mfa/k8s/keycape/`): the contained kubectl, the cluster-identity pin and the
issuer pin. A copy in key-cape would drift.
2026-10-08: done. `sso-mfa/k8s/keycape/keycape-client.py` derives the KeyCape
client record from a case (`client-from-case`). It adds or removes exactly one
client with the existing safeguards (byte-preserving merge, exact match or
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
requires the Deployment env binding first for confidential clients, and requires
the case acceptance record for `apply` instead of a per-client founder decision
id. 15 tests, including a regression showing that the vergabe case reproduces
its hand-built registration exactly; all 71 helper tests pass. Declaration
`capabilities/playbooks/net-kingdom.keycape-client-registration.yaml` (contract
PASS, `maturity: implemented`) replaces key-cape's draft. The type now pins it,
and for confidential clients adds `deliver-secret` (delivery from custody into
the sso namespace) and `bind-env` (Deployment env), attended in the same session
as `register-client`. The per-client helpers stay until their clients are
adopted as cases.
2026-10-10 — NetKingdom owner review of the execution seam, source only:
ops-warden/docs/security-case-execution-proposal.md SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
was reviewed against INTENT, SecurityCase canon, acknowledged type1.1, current
case and owner source. docs/security-case-execution-owner-review.md supports
the closed adapter/receipt direction with explicit versioned schema, structural
materialization provenance, unchanged caller/session boundaries, safe CAS
reconciliation and exact-created-object rollback requirements. This is owner
review, not approval. Supporting file digests are in the adjacent sources JSON.
Current source exposes a circular wait: ops-mason T03 waits on mint while Warden
and the broker refuse a non-materialized case. Structural acceptance therefore
needs its own native pinned receipt before mint, not a successful mint before
materialized. The platform broker additionally pins type1.0 while Forgejo is
accepted at1.1; platform-owner alignment and tests are required, not an unchecked
version bypass. T06 remains wait. No case/type lifecycle status, grant, authority
or live runtime changed by this review. A proposed informed-decision would
adopt the source contract only; native materialization and execution acceptance
stay distinct.
Final proposal re-review: staged Warden proposal SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
adds only the explicitly local reducer appendix, not an accepted wire protocol
or executor. The exact-final review and sources JSON supersede the earlier
1035f049... proposal review. Platform staged the exact type1.1 broker correction
with ten fake-backend subprocess checks (script SHA256
9576cd211cfc06526ec4cc9bd0a5b06f0f59287a68931bb2d6e3592a951ddc7d).
The next source work is the versioned closed adapter/receipt/materialization
contract, not another relaxation of the broker's status gate. Native work still
requires installed broker provenance, structural reader receipts, exact new
materialized-case bytes, caller grant/session revocation and verified execution.
No installed/native acceptance is inferred from that staged correction.
2026-10-11 — new source-only type1.2 architecture acknowledgment:
Confirmed native founder ACCEPT of infd-20261011-forgejo-type12-source-r1@1
at06:47:39Z, approval8e9332c8-754a-420c-b8b7-445cb4fe6765. Exact frozen packet
0b377d647ca748cd03c92f9792396412048055d4bde9f7bb06ff6d413f7acd68 and submission
585cee731de30d2b37883a57abfd9eb32f023ee694e8d911989109d5caf80569; native receipt
retained in docs/evidence/2026-10-11-forgejo-type12-source-acknowledgment/.
The complete acknowledged1.2 source package is isolated under
security-case-type-revisions/1.2/oidc-browser-relying-party; default loader and
current accepted1.1 case remain untouched because load_type is single-version.
The new proposed1.2 case is NOT accepted/materialized/live and has no acceptance
fields. Actual reference-migration design approval is a separate Apps owner
receipt; no1.1 approval is inherited. Source-recording review/test proof and exact
next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md.
T06 remains wait and T07 remains its existing dependent task; type is NOT
implemented and no first-case/native grant or broader workplan finish is asserted.
2026-10-11 — bounded explicit version selection source preparation for T06:
The validator now offers optional exact version_roots/--version-root VERSION=PATH
selection with duplicate/mismatch/traversal/symlink refusal and no fallback. The
default1.1 source and historicalacceptedcase hashes/ordinaryrefusal/preparation
render stayunchanged; actual1.1 is acknowledged, not implemented. Acknowledged1.2
ordinaryadmission also refuses; optionalpreparation remains nonexecuting.43local
validatorchecks pass and original1.1 CLIrenderbytes match exactly. Makerproof:
docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
FormalNetownerreview/reference-source integration and all existing native adapter,
provenance/broker/materialization/attendedproof gates remain; T06 stayswait, no
new residual or firstcase/typeimplementation/native grant is inferred.
2026-10-16 — isolated source preparation for T06 (canonical integration pending):
Added `check-evidence` to the existing validator CLI. It reuses exact version
selection and ordinary type/case/render admission, checks actual Mason structural
request source/inventory/projection pins and reads actual Warden journal rows
read-only. Successful rows require exact type-declared descriptors and attempt/
revocation fingerprints. Existing acknowledged types without machine descriptors
remain plan-only. The compatibility result never claims authenticated native
completion: Request and journal fingerprints are not portable native evidence.
Owner authentication/status binding and actual native gates remain unresolved;
T06/T07 statuses and both type/case lifecycles remain unchanged. No dispatch,
credential access or authority expansion is introduced. See tool README for the
existing owner protocol boundaries and source integration tests.
Concrete next Warden owner integration: current `Executor.admit` refuses every
case parameter whose sensitivity is not public/operational, so browser
`redirect_uris`, `allowed_scopes`, `client_type`, `mfa_required` (and 1.2
`consumer_namespace`) fail after otherwise prepared admission. The sensitive
classification is a security-change review level, not secret content. The
Warden owner must validate through NetKingdom's existing exact-version case/type
validator, then project only descriptor-declared safe target metadata; do not
remove the credential/value refusal or copy complete parameters into journals.
Actual owner descriptor acknowledgment and independent evidence admission remain
prerequisites, with normal admission still refusing unimplemented types.
## T10 — Source compatibility checker for Mason and Warden evidence
```task
id: NK-WP-0048-T10
status: done
priority: high
state_hub_task_id: "4b06ca97-c1e4-5680-bf22-ab75ab1646a4"
```
Completed 2026-10-16 in published source `c31d21ba`. The read-only
`check-evidence` command checks the actual Mason structural request and Warden
journal source protocols against an exact version/render. Independent source
review passed 103 tests at that checkpoint. It always reports native completion
false and execution prohibited. Fixtures and local journal fingerprints do not
provide authenticated owner/native evidence. This records completed source scope
previously described under T06; it does not duplicate native acceptance credit.
## T11 — Validate the optional machine-descriptor pin contract
```task
id: NK-WP-0048-T11
status: done
priority: high
state_hub_task_id: "ef2a0119-f59e-5e45-9519-115da9df7b85"
```
Completed 2026-10-16. Reproduced the JSON schema rejecting a valid
`execution_descriptors` map while the Python validator accepted malformed maps.
The schema now supports the existing Warden optional exact-byte pin map; semantic
validation binds keys to declared steps, bounds the map and checks digest shape.
The direct evidence API refuses malformed maps before accessing them. Twenty-one
new checks and all 127 affected source checks passed. No current type/case bytes,
status, owner descriptors or native acceptance changed. Source evidence and
publication receipt: Custodian
`docs/changes/netkingdom-target-2026-10-16/browser-acceptance-next/`.
## T12 — Prepare NetKingdom-owned attended step adapters
```task
id: NK-WP-0048-T12
status: todo
priority: high
state_hub_task_id: "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd"
```
Owner: NetKingdom. Retained scope: prepare bounded attended Deployment env,
config verification and restart/readiness steps around the existing helpers.
Next action: identify exact current entry points and their revision/readback
contracts, then implement fixed source-pinned callbacks or receipt-only attended
steps. Completion requires positive/refusal checks, exact target/source pins and
uncertainty behavior without acquiring user-engine runtime identity or exposing
registry/client secrets. Existing caller/session, cluster/issuer, CAS/readback
and status-only output boundaries remain mandatory.
2026-10-16 task refinement: the original broad adapter scope is split into T13
(completed registration source candidate), T14 (group creation provenance), T15
(durable registration recovery) and T16 (descriptor/artifact selection and owner
review). This preserves T12's task ID and Hub UUID while giving each remaining
outcome an owner and completion gate. T06 now names every source dependency;
its native/reference lifecycle acceptance scope remains unchanged. Completion
of any source task supplies no Warden, Platform or Apps assent and no native
execution authority.
## T13 — Prepare the fixed registration adapter source candidate
```task
id: NK-WP-0048-T13
status: done
priority: high
state_hub_task_id: "b264c10b-565c-5d29-b788-03cf5fbd2883"
```
Completed 2026-10-16, source implementation and synthetic fixture proof only.
Added an injection-only adapter around actual `keycape-client.run`: fixed
case/principal/human/lane/source/acceptance binding, existing CAS/readback,
metadata-only owner evidence and per-attempt parent-session revocation boundary.
The descriptor source composition pins the exact checker executable digest
alongside adapter/helper source. Creation-only rollback authenticates its
creation receipt and refuses changed postcreation UID/resourceVersion;
pre-existing clients confer no rollback authority. Missing readback, evidence,
revocation or process-local creation provenance cannot become retry-safe success.
Evidence: Custodian `docs/changes/netkingdom-target-2026-10-16/browser-adapter-next/`
R3 candidate, explicit patch and postimages, retained R1 rejected custody finding
and R2 revision, and 36 passing source checks (16 new, 20 existing). Three checks
use actual Warden kernel admission/dispatch/rollback with published Warden
`d97652a3bb5fa733b063d71a1b74068efcccf572` producer pins unchanged. Owner trust,
descriptor/type/case and Kubernetes objects remain synthetic fixtures. This
records a completed source deliverable, not independent review, publication,
type acknowledgment, exercised procedure, identity admission or native browser
acceptance. Independent root source review: Custodian browser-adapter-next/coordinator-review-r3.json. Source integration and normal publication/synchronization receipts: Custodian browser-adapter-next/canonical-integration.json. These receipts supply no other-owner assent or native authority.
Source publication: `0775c2bfa41af236301155bb5a69bbf088bdf823`, independently
reviewed by the Custodian coordinator before integration. The required initial
projection and ordinary UUID writeback both completed with zero failures and
zero automation errors. Generated-only brief commits were refused by the normal
hook; exact pre-existing staged/working brief bytes were restored. The separate
canonical integration receipt retains those refusals and projection evidence.
Native admission and all other-owner acceptance remain open.
## T14 — Preserve created-only provenance for group ensure and revoke
```task
id: NK-WP-0048-T14
status: todo
priority: high
state_hub_task_id: "fce95440-a25b-5f27-8ef3-a3d182099113"
```
Owner: NetKingdom. Next action: extend or wrap the existing identity-provisioner
ensure/revoke contract with independently retained prestate and exact newly
created groups/memberships before exposing reversible owner callbacks. Current
ensure returns no membership-creation inventory; current revoke may remove a
membership that predated this case. Completion requires actual helper-backed
positive/refusal fixtures, all validation before mutation, per-attempt evidence
and revocation, readback, ambiguity handling, and rollback that preserves every
pre-existing/shared group and membership. No existing endpoint or service token
is treated as new authorization.
## T15 — Define authenticated recovery for lost registration process state
```task
id: NK-WP-0048-T15
status: todo
priority: high
state_hub_task_id: "1b825705-4848-5edc-832f-b1622cd9bdb7"
```
Owner: NetKingdom for registration state, with Warden owning kernel evidence
admission. Next action: select the exact existing owner evidence holder/readback
contract and define authenticated lookup for the original creation attempt.
Completion requires restart/lost-receipt fixtures that bind case/source/principal,
original created-only provenance and current native revision before any undo;
uncertain outcomes remain non-replayable. The T13 adapter deliberately refuses
post-restart rollback and installs no reconciliation callback. A new broker or
unverified JSON receipt is not a recovery contract. Actual evidence-holder
acceptance and attended recovery remain separately authorized native work.
## T16 — Select and review the actual machine descriptor composition
```task
id: NK-WP-0048-T16
status: todo
priority: high
state_hub_task_id: "200bf500-5084-5ae7-be26-3033fe1f296e"
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
independently owns global provenance/admission. Next action: identify the exact
reviewed checker executable artifact and native owner evidence/session callback
contracts, then assemble the complete step descriptor set and a proposed explicit
type version selection. T13's retained descriptor pins a synthetic checker only.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
pins for NetKingdom 015694ee; it does not attest T13's new adapter. Pinning any
machine descriptor into an acknowledged type is a separately reviewed source
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
consumer login/group enforcement. Neither their assent nor native acceptance is
supplied by this task.

View file

@ -0,0 +1,171 @@
[
{
"id": "5be011af-9971-41de-aee2-0df91c79af9c",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "bcbf6c91-2c04-5c16-ae8c-5d6c06fd6b89",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on WARDEN-WP-0044-T03",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-11T21:25:23.339345Z",
"updated_at": "2026-10-11T21:25:23.339345Z"
},
{
"id": "5237ec7c-866c-4f27-a8cb-702783a8a905",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "56cea6be-c613-586c-80cd-125340b374da",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on MASON-WP-0010-T03",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-11T21:25:23.096030Z",
"updated_at": "2026-10-11T21:25:23.096030Z"
},
{
"id": "a90f4024-8184-476d-b739-eaac2351edae",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "8e519082-dc8b-5e46-b660-03d03e55dcf6",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T08",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-11T21:25:22.707856Z",
"updated_at": "2026-10-11T21:25:22.707856Z"
},
{
"id": "5d4af363-6360-43e0-bbb5-8a98328d32ec",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7",
"to_workplan_id": null,
"to_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T07 depends_on NK-WP-0048-T06",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-11T21:25:23.604357Z",
"updated_at": "2026-10-11T21:25:23.604357Z"
},
{
"id": "43cc0e48-0cf4-4b76-b970-1fa614d30120",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "3d987d3d-0797-538b-86e4-51185a327ae2",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T05",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-11T21:25:22.002531Z",
"updated_at": "2026-10-11T21:25:22.002531Z"
},
{
"id": "9c4debbd-3083-4f99-ae87-ea9242e44194",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "0ee12dfc-8260-5038-bd39-cc7fb4113be0",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T09",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-11T21:25:22.899227Z",
"updated_at": "2026-10-11T21:25:22.899227Z"
},
{
"id": "60e1c744-af55-428a-b15d-d58186917a0d",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "b264c10b-565c-5d29-b788-03cf5fbd2883",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T13",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T19:50:38.153890Z",
"updated_at": "2026-10-16T19:50:38.153890Z"
},
{
"id": "b456021f-947c-4bbc-a17b-683bea36476f",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "1b825705-4848-5edc-832f-b1622cd9bdb7",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T15",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T19:50:38.468028Z",
"updated_at": "2026-10-16T19:50:38.468028Z"
},
{
"id": "5d10d290-6c6f-43a3-9f48-f0a25334dd75",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "8c3e4baa-a218-5996-af9e-fe1153a46dac",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T17",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T20:31:41.461786Z",
"updated_at": "2026-10-16T20:31:41.461786Z"
},
{
"id": "953f22a9-db9d-45b7-b9cd-f7a994637ccb",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T12",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T19:47:17.264847Z",
"updated_at": "2026-10-16T19:47:17.264847Z"
},
{
"id": "38ffb7ca-f4b7-414e-8199-0046ca66ee71",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "fce95440-a25b-5f27-8ef3-a3d182099113",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T14",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T19:50:38.342407Z",
"updated_at": "2026-10-16T19:50:38.342407Z"
},
{
"id": "ee7151bc-9ce5-49e0-a6f5-4e2d728506ae",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "200bf500-5084-5ae7-be26-3033fe1f296e",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T16",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T19:50:38.587474Z",
"updated_at": "2026-10-16T19:50:38.587474Z"
},
{
"id": "901aa11b-aa67-4e2f-894c-994a9213d7ac",
"from_workplan_id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb",
"from_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"to_workplan_id": null,
"to_task_id": "9c45da71-fb4c-5f20-9065-90f448f37c64",
"relationship_type": "starts_after",
"description": "NK-WP-0048-T06 depends_on NK-WP-0048-T18",
"source_owner": null,
"source_commit": null,
"created_at": "2026-10-16T20:31:41.647002Z",
"updated_at": "2026-10-16T20:31:41.647002Z"
}
]

View file

@ -0,0 +1,134 @@
[
{
"id": "NK-WP-0048-T01",
"uuid": "930ac3e9-20ec-5473-98cd-3fe8fddb730f",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T02",
"uuid": "19c25d96-a55c-58f0-9168-3fdbc4955fc2",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T03",
"uuid": "e487975b-73b1-516c-971d-da4cbbb93063",
"status": "done",
"depends_on": [
"NK-WP-0048-T01"
]
},
{
"id": "NK-WP-0048-T04",
"uuid": "2fffe112-7d55-5680-9952-78d74f44f87f",
"status": "done",
"depends_on": [
"NK-WP-0048-T02",
"NK-WP-0048-T03"
]
},
{
"id": "NK-WP-0048-T05",
"uuid": "3d987d3d-0797-538b-86e4-51185a327ae2",
"status": "done",
"depends_on": [
"NK-WP-0048-T04",
"MASON-WP-0010-T01",
"WARDEN-WP-0044-T01"
]
},
{
"id": "NK-WP-0048-T06",
"uuid": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c",
"status": "wait",
"depends_on": [
"NK-WP-0048-T05",
"NK-WP-0048-T08",
"NK-WP-0048-T09",
"NK-WP-0048-T12",
"NK-WP-0048-T13",
"NK-WP-0048-T14",
"NK-WP-0048-T15",
"NK-WP-0048-T16",
"NK-WP-0048-T17",
"NK-WP-0048-T18",
"MASON-WP-0010-T03",
"WARDEN-WP-0044-T03"
]
},
{
"id": "NK-WP-0048-T07",
"uuid": "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7",
"status": "wait",
"depends_on": [
"NK-WP-0048-T06"
]
},
{
"id": "NK-WP-0048-T08",
"uuid": "8e519082-dc8b-5e46-b660-03d03e55dcf6",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T09",
"uuid": "0ee12dfc-8260-5038-bd39-cc7fb4113be0",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T10",
"uuid": "4b06ca97-c1e4-5680-bf22-ab75ab1646a4",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T11",
"uuid": "ef2a0119-f59e-5e45-9519-115da9df7b85",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T12",
"uuid": "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd",
"status": "todo",
"depends_on": []
},
{
"id": "NK-WP-0048-T13",
"uuid": "b264c10b-565c-5d29-b788-03cf5fbd2883",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T14",
"uuid": "fce95440-a25b-5f27-8ef3-a3d182099113",
"status": "todo",
"depends_on": []
},
{
"id": "NK-WP-0048-T15",
"uuid": "1b825705-4848-5edc-832f-b1622cd9bdb7",
"status": "todo",
"depends_on": []
},
{
"id": "NK-WP-0048-T16",
"uuid": "200bf500-5084-5ae7-be26-3033fe1f296e",
"status": "todo",
"depends_on": []
},
{
"id": "NK-WP-0048-T17",
"uuid": "8c3e4baa-a218-5996-af9e-fe1153a46dac",
"status": "done",
"depends_on": []
},
{
"id": "NK-WP-0048-T18",
"uuid": "9c45da71-fb4c-5f20-9065-90f448f37c64",
"status": "done",
"depends_on": []
}
]

View file

@ -0,0 +1 @@
{"actor":"human","adapter":"net-kingdom/keycape-registration@v1","adapter_source":"sha256:6c7818ccc864869b3f98fa3b619fbb06a139722aa00907455507c92dcef7f666","allowed_created":["keycape-client"],"effect":"change","lane":"key-cape-oidc-login","owner":"net-kingdom","retry_safe":false,"revocation_scopes":["parent-session"],"step":"register-client","target_parameter":"client_id","version":"warden.case.execution.v1"}