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