diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/artifact-build-info.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/artifact-build-info.txt new file mode 100644 index 00000000..8aeca9e1 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/artifact-build-info.txt @@ -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 diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build.stderr.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build.stdout.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build.stdout.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build_checker.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build_checker.py new file mode 100644 index 00000000..b9ef2291 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/build_checker.py @@ -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')})) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build.stderr.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build.stdout.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build.stdout.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build_checker.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build_checker.py new file mode 100644 index 00000000..50d545ac --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r1/build_checker.py @@ -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')})) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build.stderr.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build.stdout.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build.stdout.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build_checker.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build_checker.py new file mode 100644 index 00000000..c2f02967 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/closure-collection-r2/build_checker.py @@ -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')})) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/manifest.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/manifest.json new file mode 100644 index 00000000..f8f1dab8 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/manifest.json @@ -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" + } +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/minimal-fixture-refusal.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/minimal-fixture-refusal.json new file mode 100644 index 00000000..4ce9d5c3 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/minimal-fixture-refusal.json @@ -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" +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/postimages/workplans/NK-WP-0048-security-case-types.md b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/postimages/workplans/NK-WP-0048-security-case-types.md new file mode 100644 index 00000000..d5fac9c9 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/postimages/workplans/NK-WP-0048-security-case-types.md @@ -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..` 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_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 ` 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 + --members `. 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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/preimages/workplans/NK-WP-0048-security-case-types.md b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/preimages/workplans/NK-WP-0048-security-case-types.md new file mode 100644 index 00000000..efc99856 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/preimages/workplans/NK-WP-0048-security-case-types.md @@ -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..` 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_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 ` 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 + --members `. 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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/proposed-descriptor.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/proposed-descriptor.json new file mode 100644 index 00000000..9c69df1a --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/proposed-descriptor.json @@ -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"} \ No newline at end of file diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/real-checker-tests.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/real-checker-tests.txt new file mode 100644 index 00000000..d197932c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/real-checker-tests.txt @@ -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 diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/repeated-build-artifacts.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/repeated-build-artifacts.json new file mode 100644 index 00000000..5e06f8f3 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/repeated-build-artifacts.json @@ -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 +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/test_real_checker_adapter.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/test_real_checker_adapter.py new file mode 100644 index 00000000..d7d7cacf --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/test_real_checker_adapter.py @@ -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()) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/work-record-proposal.patch b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/work-record-proposal.patch new file mode 100644 index 00000000..caf356ba --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/work-record-proposal.patch @@ -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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/affected-source-tests.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/affected-source-tests.txt new file mode 100644 index 00000000..26b1cc05 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/affected-source-tests.txt @@ -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 diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/build.stderr.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/build.stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/build.stdout.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/build.stdout.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/coordinator-tests-0.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/coordinator-tests-0.txt new file mode 100644 index 00000000..ffee0925 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/coordinator-tests-0.txt @@ -0,0 +1,4 @@ +---------------------------------------------------------------------- +Ran 39 tests in 1.261s + +OK diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/coordinator-tests-1.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/coordinator-tests-1.txt new file mode 100644 index 00000000..80d2f61f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/coordinator-tests-1.txt @@ -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 diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/finalize_receipt.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/finalize_receipt.py new file mode 100644 index 00000000..f25640d5 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/finalize_receipt.py @@ -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')})) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/implementation.patch b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/implementation.patch new file mode 100644 index 00000000..47781a75 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/implementation.patch @@ -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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/integrate_source.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/integrate_source.py new file mode 100644 index 00000000..e314d97e --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/integrate_source.py @@ -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.') diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/mutable-ref-refusal.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/mutable-ref-refusal.json new file mode 100644 index 00000000..b26e9788 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/mutable-ref-refusal.json @@ -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 +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.exit.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.exit.json new file mode 100644 index 00000000..43623d14 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.exit.json @@ -0,0 +1 @@ +{"exit_code": 0} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.stderr.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.stderr.txt new file mode 100644 index 00000000..3345563f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.stderr.txt @@ -0,0 +1 @@ +WARN: git commit failed for writeback: Generated-only commits rejected: staged diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.stdout.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.stdout.json new file mode 100644 index 00000000..fa6d6016 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-final.stdout.json @@ -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" +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.exit.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.exit.json new file mode 100644 index 00000000..43623d14 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.exit.json @@ -0,0 +1 @@ +{"exit_code": 0} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.stderr.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.stderr.txt new file mode 100644 index 00000000..3345563f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.stderr.txt @@ -0,0 +1 @@ +WARN: git commit failed for writeback: Generated-only commits rejected: staged diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.stdout.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.stdout.json new file mode 100644 index 00000000..86e710c3 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/normal-source.stdout.json @@ -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" +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/owner-progress.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/owner-progress.json new file mode 100644 index 00000000..ff867bda --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/owner-progress.json @@ -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" +} diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/keycape-client.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/keycape-client.py new file mode 100644 index 00000000..08be1771 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/keycape-client.py @@ -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 + keycape-client.py inspect --client-file client.yaml [--operation remove] + keycape-client.py apply --client-file client.yaml --case-acceptance \\ + --check-config --expected-uid --expected-resource-version +""" +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/; 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()) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/registration-adapter.md b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/registration-adapter.md new file mode 100644 index 00000000..148e6c3f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/registration-adapter.md @@ -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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/test_keycape_checker_environment.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/sso-mfa/k8s/keycape/test_keycape_checker_environment.py new file mode 100644 index 00000000..6e738100 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/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']) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/workplans/NK-WP-0048-security-case-types.md b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/workplans/NK-WP-0048-security-case-types.md new file mode 100644 index 00000000..7ca0237b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/postimages/workplans/NK-WP-0048-security-case-types.md @@ -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..` 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_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 ` 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 + --members `. 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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/pre-fix-counterexample.txt b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/pre-fix-counterexample.txt new file mode 100644 index 00000000..4a664b08 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/pre-fix-counterexample.txt @@ -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 + 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 + 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) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/sso-mfa/k8s/keycape/keycape-client.py b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/sso-mfa/k8s/keycape/keycape-client.py new file mode 100644 index 00000000..264f6d95 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/sso-mfa/k8s/keycape/keycape-client.py @@ -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 + keycape-client.py inspect --client-file client.yaml [--operation remove] + keycape-client.py apply --client-file client.yaml --case-acceptance \\ + --check-config --expected-uid --expected-resource-version +""" +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/; 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()) diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/sso-mfa/k8s/keycape/registration-adapter.md b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/sso-mfa/k8s/keycape/registration-adapter.md new file mode 100644 index 00000000..1ace7940 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/sso-mfa/k8s/keycape/registration-adapter.md @@ -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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/workplans/NK-WP-0048-security-case-types.md b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/workplans/NK-WP-0048-security-case-types.md new file mode 100644 index 00000000..efc99856 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/preimages/workplans/NK-WP-0048-security-case-types.md @@ -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..` 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_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 ` 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 + --members `. 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. diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/projection-dependencies.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/projection-dependencies.json new file mode 100644 index 00000000..3f2f8851 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/projection-dependencies.json @@ -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" + } +] diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/projection-tasks.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/projection-tasks.json new file mode 100644 index 00000000..9399c120 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/projection-tasks.json @@ -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": [] + } +] diff --git a/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/proposed-descriptor.json b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/proposed-descriptor.json new file mode 100644 index 00000000..40d9042c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/proposed-descriptor.json @@ -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"} \ No newline at end of file