feat(identity): prepare fixed attended registration adapter
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
tegwick 2026-10-16 21:42:33 +02:00
parent 015694ee41
commit 0775c2bfa4
4 changed files with 635 additions and 15 deletions

View file

@ -0,0 +1,180 @@
"""Unexercised, injection-only attended registration seam for Warden's v1 kernel.
No CLI, credentials, evidence trust root or live registration is installed here.
Owner publication/authentication and parent-session revocation are mandatory
injected capabilities. The kernel must independently admit both evidence kinds.
"""
from __future__ import annotations
import argparse
from dataclasses import asdict, replace
import json
import importlib.util
from pathlib import Path
import yaml
from warden.case_run import Adapter, Descriptor, Invocation, OwnerResult, contained_output, digest, IDENTIFIER, DIGEST
from warden.case_receipt_state import Outcome, Refused
ROOT = Path(__file__).resolve().parent
SOURCE_FILES = ('keycape-registration-adapter.py', 'keycape-client.py',
'portal-client-rollout.py', 'openbao-client-config.py',
'register-user-engine-portal.py')
REFERENCE = 'net-kingdom/keycape-registration@v1'
# Importing the maintained helper performs no tooling operation.
_spec = importlib.util.spec_from_file_location('registration_owner_helper', ROOT / 'keycape-client.py')
helper = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(helper)
def source_digest(check_config_digest):
"""Pin adapter plus the complete local helper import closure, in filename order."""
if not isinstance(check_config_digest, str) or not DIGEST.fullmatch(check_config_digest):
raise Refused('exact reviewed checker digest required')
composition = {'files': {name: digest((ROOT / name).read_bytes()) for name in SOURCE_FILES},
'check_config_binary': check_config_digest}
return digest(json.dumps(composition, sort_keys=True, separators=(',', ':')).encode())
def descriptor(check_config_digest):
return Descriptor('warden.case.execution.v1', 'register-client', 'net-kingdom',
REFERENCE, source_digest(check_config_digest), 'human', 'key-cape-oidc-login',
'client_id', 'change', ('keycape-client',), False,
('parent-session',))
def build(*, admitted: Invocation, case_source: bytes, expected_uid: str,
expected_resource_version: str, case_acceptance: str,
check_config: Path, check_config_digest: str, publish, authenticate,
revoke_session):
"""Bind one admitted case and observed revision; return a closed kernel Adapter.
publish(bytes)->identifier durably retains only the closed metadata payload.
authenticate(identifier, bytes)->True must verify independent owner custody.
revoke_session(invocation)->identifier must revoke this attempt's parent
session; Warden independently authenticates it using its own evidence verifier.
These callbacks are capabilities, never command strings or case parameters.
Only a verified creation in this process may be rolled back. Process loss,
write/readback ambiguity, publication/revocation failure and source drift
require owner reconciliation; none are converted to retry-safe failures.
"""
reviewed_source = source_digest(check_config_digest)
desc = descriptor(check_config_digest)
if type(admitted) is not Invocation or not all(
isinstance(value, str) and IDENTIFIER.fullmatch(value)
for value in (admitted.step, admitted.owner, admitted.target,
admitted.actor, admitted.principal, admitted.lane)):
raise Refused('bounded invocation required')
if desc.adapter_source != reviewed_source:
raise Refused('owner source changed during binding')
if (admitted.step, admitted.owner, admitted.actor, admitted.lane) != (
desc.step, desc.owner, desc.actor, desc.lane):
raise Refused('registration invocation boundary differs')
if admitted.operation != 'admission' or admitted.attempt_id:
raise Refused('expected admission invocation')
if type(case_source) is not bytes or len(case_source) > 262144 or digest(case_source) != admitted.pins.case:
raise Refused('case source pin differs')
# Pins.adapter is the descriptor digest, not the owner source digest.
descriptor_bytes = json.dumps(asdict(desc), sort_keys=True, separators=(',', ':')).encode()
if admitted.pins.adapter != digest(descriptor_bytes):
raise Refused('descriptor pin differs')
if not all(isinstance(x, str) and 0 < len(x) <= 192 for x in
(expected_uid, expected_resource_version, case_acceptance)):
raise Refused('bounded attended binding required')
if digest(case_acceptance.encode()) != admitted.pins.acceptance:
raise Refused('acceptance evidence binding differs')
if not all(callable(x) for x in (publish, authenticate, revoke_session)):
raise Refused('owner evidence and revocation capabilities required')
with contained_output():
case = yaml.safe_load(case_source)
client = helper.client_from_case(case)
if admitted.target != client['clientId']:
raise Refused('case target differs')
check_config = Path(check_config)
records = {}
creation = None
def binding(inv, operation):
if (replace(inv, attempt_id='', operation='admission') != admitted
or inv.operation != operation or not IDENTIFIER.fullmatch(inv.attempt_id)):
raise Refused('attempt binding differs')
if source_digest(check_config_digest) != reviewed_source:
raise Refused('owner source changed')
if digest(check_config.read_bytes()) != check_config_digest:
raise Refused('check-config binary changed')
def execute(inv, operation, created=()):
nonlocal creation
try:
with contained_output():
binding(inv, 'run' if operation == 'add' else 'rollback')
uid, revision = expected_uid, expected_resource_version
if operation == 'remove':
if (created != ('keycape-client',) or creation is None
or not verify(*creation)):
raise Refused('no authenticated case-created client')
original_payload = json.loads(records[creation[1].evidence][1])
uid, revision = original_payload['after']['uid'], original_payload['after']['resource_version']
args = argparse.Namespace(mode='apply', operation=operation,
expected_uid=uid, expected_resource_version=revision,
case_acceptance=case_acceptance, check_config=str(check_config))
receipt = helper.run(args, client)
# The helper has completed CAS, whole-data readback and preservation.
# Never retain check-config diagnostics or arbitrary helper fields.
if not (receipt['schema'] == 'netkingdom.keycape-client.v1'
and receipt['target'] == 'sso/keycape-config'
and receipt['client_id'] == admitted.target
and receipt['mode'] == 'apply' and receipt['operation'] == operation
and receipt['values_emitted'] is False
and type(receipt['changed']) is bool
and receipt['unrelated_config_bytes_preserved'] is True
and receipt['other_secret_data_unchanged'] is True
and receipt['before'] == {'uid': uid, 'resource_version': revision}
and receipt['after']['uid'] == uid
and receipt['check_config']['proposed']['valid'] is True):
raise Refused('owner readback contract differs')
after = receipt['after']
if (set(after) != {'uid', 'resource_version'} or
not all(isinstance(v, str) and 0 < len(v) <= 192 for v in after.values())):
raise Refused('owner revision contract differs')
changed = receipt['changed']
outcome = (Outcome.ROLLED_BACK if operation == 'remove' else
Outcome.SUCCESS if changed else Outcome.NO_CHANGE)
objects = ('keycape-client',) if operation == 'add' and changed else ()
finally:
# Even helper failure must end the parent session. Failures propagate
# contained to the kernel and become uncertain, never safe to replay.
with contained_output():
revocation = revoke_session(inv)
if not isinstance(revocation, str) or not IDENTIFIER.fullmatch(revocation):
raise Refused('session revocation evidence missing')
payload = json.dumps({'schema': 'netkingdom.keycape-registration.attempt.v1',
'invocation': asdict(inv), 'source': reviewed_source,
'outcome': outcome.value, 'created': objects, 'before': receipt['before'],
'after': after, 'session_revocation': revocation},
sort_keys=True, separators=(',', ':')).encode()
with contained_output():
evidence = publish(payload)
if not isinstance(evidence, str) or not IDENTIFIER.fullmatch(evidence) or evidence in records:
raise Refused('owner evidence identifier invalid or reused')
result = OwnerResult(outcome, evidence, objects, True, revocation,
attempt_id=inv.attempt_id, operation=inv.operation)
records[evidence] = (inv, payload, result)
if operation == 'add' and changed:
creation = (inv, result)
elif operation == 'remove':
creation = None
return result
def verify(inv, result):
record = records.get(result.evidence) if type(result) is OwnerResult else None
if record is None or record[0] != inv or record[2] != result:
return False
with contained_output():
return authenticate(result.evidence, record[1]) is True
return Adapter(REFERENCE, reviewed_source, lambda inv: execute(inv, 'add'),
verify, lambda inv, created: execute(inv, 'remove', created))

View file

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

View file

@ -0,0 +1,289 @@
"""Actual helper/CAS execution against synthetic Kubernetes objects only."""
from dataclasses import asdict, replace
import copy
import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from warden.case_run import Invocation, digest
from warden.case_receipt_state import Outcome, Pins, Refused
import yaml
ROOT = Path(__file__).parent
spec = importlib.util.spec_from_file_location('registration_adapter', ROOT / 'keycape-registration-adapter.py')
a = importlib.util.module_from_spec(spec)
spec.loader.exec_module(a)
spec = importlib.util.spec_from_file_location('registration_fixture', ROOT / 'test_keycape_client.py')
f = importlib.util.module_from_spec(spec)
spec.loader.exec_module(f)
class RegistrationTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.binary = Path(self.temp.name) / 'keycape'
self.binary.write_bytes(b'synthetic checker binary')
self.case = yaml.safe_dump({'spec': {'type': 'oidc-browser-relying-party@1.1',
'parameters': f.FORGEJO_PARAMS}}).encode()
desc = a.descriptor(digest(self.binary.read_bytes()))
self.desc_bytes = json.dumps(asdict(desc), sort_keys=True, separators=(',', ':')).encode()
pins = Pins(digest(self.case), digest(b'type'), digest(self.desc_bytes),
digest(b'catalog'), digest(b'case-acceptance:test'), digest(b'run sheet'))
self.admitted = Invocation('register-client', 'net-kingdom', 'forgejo', 'human',
'founder', 'key-cape-oidc-login', pins)
self.inv = replace(self.admitted, attempt_id='attempt-one', operation='run')
self.secret, self.raw = f.fixture()
self.effects = []
self.evidence = {}
self.revocations = []
self.kw = dict(admitted=self.admitted, case_source=self.case,
expected_uid=f.UID, expected_resource_version='7', case_acceptance='case-acceptance:test',
check_config=self.binary, check_config_digest=digest(self.binary.read_bytes()),
publish=self.publish, authenticate=lambda ref, payload: self.evidence.get(ref) == payload,
revoke_session=self.revoke)
self.check_patch = patch.object(a.helper, 'check_config', return_value={'valid': True})
self.kube_patch = patch.object(a.helper.lane, 'kubectl', side_effect=self.kube)
self.check_patch.start()
self.kube_patch.start()
self.addCleanup(self.check_patch.stop)
self.addCleanup(self.kube_patch.stop)
self.addCleanup(self.temp.cleanup)
def publish(self, payload):
ref = 'owner-evidence:attempt-' + str(len(self.evidence))
self.evidence[ref] = payload
return ref
def revoke(self, inv):
self.revocations.append(inv)
return 'session-revoked:' + inv.operation + ':' + inv.attempt_id
def kube(self, args, payload=None):
if args[:3] == ['get', 'namespace', 'kube-system']:
return {'metadata': {'uid': a.helper.lane.CLUSTER_UID}}
if args[:5] == ['-n', 'sso', 'get', 'secret', 'keycape-config']:
return copy.deepcopy(self.secret)
if args[:5] == ['-n', 'sso', 'get', 'deployment', 'keycape']:
return {'spec': {'template': {'spec': {'containers': [{'name': 'keycape',
'env': [a.helper.env_binding(f.FORGEJO)]}]}}}}
if args[:5] == ['-n', 'sso', 'patch', 'secret', 'keycape-config']:
self.assertEqual(payload[:2], [
{'op': 'test', 'path': '/metadata/uid', 'value': self.secret['metadata']['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': self.secret['metadata']['resourceVersion']}])
self.effects.append(copy.deepcopy(payload))
self.secret['data']['config.yaml'] = payload[2]['value']
self.secret['metadata']['resourceVersion'] = str(int(self.secret['metadata']['resourceVersion']) + 1)
return copy.deepcopy(self.secret)
raise AssertionError('unexpected tooling operation')
def test_actual_add_and_created_only_rollback_preserve_original_bytes(self):
adapter = a.build(**self.kw)
original = copy.deepcopy(self.secret['data'])
result = adapter.invoke(self.inv)
self.assertEqual(result.outcome, Outcome.SUCCESS)
self.assertEqual(result.created, ('keycape-client',))
self.assertTrue(adapter.verify(self.inv, result))
undone = adapter.rollback(replace(self.inv, attempt_id='attempt-two', operation='rollback'), result.created)
self.assertEqual(undone.outcome, Outcome.ROLLED_BACK)
self.assertEqual(self.secret['data'], original)
self.assertEqual(len(self.effects), 2)
self.assertEqual(len(self.revocations), 2)
for payload in self.evidence.values():
self.assertNotIn(b'synthetic-marker', payload)
self.assertNotIn(b'config.yaml', payload)
self.assertNotIn(b'signing-key', payload)
def test_identical_client_is_no_change_and_cannot_be_removed(self):
self.secret, _ = f.fixture(f.EXISTING + [f.FORGEJO])
adapter = a.build(**self.kw)
result = adapter.invoke(self.inv)
self.assertEqual(result.outcome, Outcome.NO_CHANGE)
self.assertEqual(result.created, ())
with self.assertRaises(Refused):
adapter.rollback(replace(self.inv, operation='rollback'), ('keycape-client',))
self.assertFalse(self.effects)
def test_existing_drift_refused_and_session_revoked(self):
self.secret, _ = f.fixture(f.EXISTING + [dict(f.FORGEJO, displayName='Drift')])
with self.assertRaises(Refused):
a.build(**self.kw).invoke(self.inv)
self.assertFalse(self.effects)
self.assertEqual(self.revocations, [self.inv])
def test_invocation_rebinding_refused_before_tooling(self):
for changes in ({'target': 'other'}, {'principal': 'other'}, {'actor': 'agent'},
{'lane': 'other'}, {'operation': 'rollback'}, {'attempt_id': ''},
{'pins': replace(self.inv.pins, case=digest(b'other'))}):
with self.subTest(changes=changes), self.assertRaises(Refused):
a.build(**self.kw).invoke(replace(self.inv, **changes))
self.assertFalse(self.effects)
def test_initial_case_descriptor_and_target_pins_are_required(self):
for changes in ({'case_source': b'{}'}, {'case_acceptance': 'other'}, {'admitted': replace(self.admitted, target='other')},
{'admitted': replace(self.admitted, actor='agent')},
{'admitted': replace(self.admitted, pins=replace(self.admitted.pins, adapter=digest(b'wrong')))}):
with self.subTest(changes=changes), self.assertRaises(Refused):
a.build(**dict(self.kw, **changes))
self.assertFalse(self.effects)
def test_binary_or_source_drift_refused_before_tooling(self):
adapter = a.build(**self.kw)
self.binary.write_bytes(b'changed')
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.binary.write_bytes(b'synthetic checker binary')
with patch.object(a, 'source_digest', return_value=digest(b'changed')):
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.assertFalse(self.effects)
def test_substituting_caller_selected_checker_under_same_descriptor_is_refused(self):
substitute = Path(self.temp.name) / 'other-checker'
substitute.write_bytes(b'caller-selected executable')
with self.assertRaisesRegex(Refused, 'descriptor pin differs'):
a.build(**dict(self.kw, check_config=substitute,
check_config_digest=digest(substitute.read_bytes())))
self.assertFalse(self.effects)
def test_stale_revision_refuses_mutation(self):
adapter = a.build(**self.kw)
self.secret['metadata']['resourceVersion'] = '8'
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.assertFalse(self.effects)
def test_rollback_requires_exact_provenance_and_postcreation_revision(self):
adapter = a.build(**self.kw)
result = adapter.invoke(self.inv)
rollback = replace(self.inv, attempt_id='attempt-two', operation='rollback')
for created in ((), ('other',), ('keycape-client', 'other')):
with self.subTest(created=created), self.assertRaises(Refused):
adapter.rollback(rollback, created)
self.secret['metadata']['resourceVersion'] = '9'
with self.assertRaises(Refused):
adapter.rollback(rollback, result.created)
self.assertEqual(len(self.effects), 1)
def test_missing_or_tampered_evidence_never_verifies_or_authorizes_undo(self):
adapter = a.build(**self.kw)
result = adapter.invoke(self.inv)
self.assertFalse(adapter.verify(replace(self.inv, attempt_id='other'), result))
self.assertFalse(adapter.verify(self.inv, replace(result, revoked=False)))
self.evidence[result.evidence] = b'forged'
self.assertFalse(adapter.verify(self.inv, result))
with self.assertRaises(Refused):
adapter.rollback(replace(self.inv, operation='rollback'), result.created)
self.assertEqual(len(self.effects), 1)
def test_process_loss_does_not_reconstruct_created_authority(self):
result = a.build(**self.kw).invoke(self.inv)
fresh = a.build(**self.kw)
with self.assertRaises(Refused):
fresh.rollback(replace(self.inv, operation='rollback'), result.created)
self.assertIsNone(fresh.reconcile)
self.assertEqual(len(self.effects), 1)
def test_failed_readback_is_uncertain_and_never_provides_creation(self):
original_kube = self.kube
def lost(args, payload=None):
if self.effects and args[:5] == ['-n', 'sso', 'get', 'secret', 'keycape-config']:
raise RuntimeError('synthetic sensitive detail')
return original_kube(args, payload)
with patch.object(a.helper.lane, 'kubectl', side_effect=lost):
adapter = a.build(**self.kw)
with self.assertRaisesRegex(Refused, '^owner callback failed$'):
adapter.invoke(self.inv)
self.assertEqual(len(self.effects), 1)
self.assertFalse(self.evidence)
self.assertEqual(self.revocations, [self.inv])
def test_revocation_or_publication_failure_cannot_produce_verified_success(self):
for capability in ('publish', 'revoke_session'):
self.secret, _ = f.fixture()
with self.subTest(capability=capability):
kw = dict(self.kw, **{capability: lambda *args: (_ for _ in ()).throw(RuntimeError('private'))})
adapter = a.build(**kw)
with self.assertRaisesRegex(Refused, '^owner callback failed$'):
adapter.invoke(self.inv)
self.assertFalse(self.evidence)
def kernel_fixture(self):
# Fixture-only proposed type/case; canonical files are never edited.
# Warden's published producer pins apply unchanged. Owner trust and
# proposed descriptor/type/case are fixture-only, never native admission.
import warden.case_inputs as inputs
from warden.case_run import Executor, Journal
root = ROOT.parents[2]
case = yaml.safe_load((root / 'security-cases/forgejo-netkingdom-oidc.yaml').read_bytes())
case['spec']['status'] = 'materialized'
case_source = yaml.safe_dump(case).encode()
typ = yaml.safe_load((root / 'security-case-types/oidc-browser-relying-party/type.yaml').read_bytes())
typ['spec']['execution_descriptors'] = {'register-client': digest(self.desc_bytes)}
type_source = yaml.safe_dump(typ).encode()
catalog = b'fixture-only-catalog'
sheet = json.dumps(['register-client', 'net-kingdom', 'forgejo', 'human',
'founder', 'key-cape-oidc-login', 'fixture-acceptance', 'fixture-materialization',
'fixture-descriptor', 'fixture-session'], separators=(',', ':')).encode()
pins = Pins(digest(case_source), digest(type_source), digest(self.desc_bytes),
digest(catalog), digest(b'fixture-acceptance'), digest(sheet))
invocation = replace(self.admitted, pins=pins)
adapter = a.build(**dict(self.kw, admitted=invocation, case_source=case_source,
case_acceptance='fixture-acceptance'))
def fixture_verifier(purpose, source, evidence):
if purpose.startswith('session-revocation:'):
_, lane, operation, attempt = purpose.split(':')
return source == digest(case_source) and lane == invocation.lane and evidence == (
'session-revoked:' + operation + ':' + attempt)
return evidence in {'fixture-acceptance', 'fixture-materialization',
'fixture-descriptor', 'fixture-session'}
executor = Executor(Journal(Path(self.temp.name) / 'private'),
{adapter.reference: adapter}, fixture_verifier, browser_inputs=inputs.BrowserInputs(root))
admitted = executor.admit(case_source, type_source, self.desc_bytes, catalog,
principal='founder', actor='human', lane=invocation.lane,
acceptance_evidence='fixture-acceptance', materialization_evidence='fixture-materialization',
descriptor_evidence='fixture-descriptor', session_evidence='fixture-session')
self.assertEqual(admitted[1], invocation)
return executor, admitted
def test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust(self):
executor, admitted = self.kernel_fixture()
result = executor.run(*admitted)
self.assertEqual(result.last.outcome, Outcome.SUCCESS)
self.assertEqual(result.last.created, ('keycape-client',))
undone = executor.rollback(*admitted)
self.assertEqual(undone.last.outcome, Outcome.ROLLED_BACK)
self.assertEqual(len(self.effects), 2)
self.assertEqual(len(self.revocations), 2)
def test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo(self):
executor, admitted = self.kernel_fixture()
original_kube = self.kube
def lost(args, payload=None):
if self.effects and args[:5] == ['-n', 'sso', 'get', 'secret', 'keycape-config']:
raise RuntimeError('synthetic sensitive detail')
return original_kube(args, payload)
with patch.object(a.helper.lane, 'kubectl', side_effect=lost):
result = executor.run(*admitted)
self.assertEqual(result.last.outcome, Outcome.UNCERTAIN)
self.assertFalse(result.last.created)
with self.assertRaises(Refused):
executor.run(*admitted, retry=True)
with self.assertRaises(Refused):
executor.rollback(*admitted)
self.assertEqual(len(self.effects), 1)
self.assertEqual(len(self.revocations), 1)
def test_kernel_external_revision_change_retains_uncertain_rollback(self):
executor, admitted = self.kernel_fixture()
self.assertEqual(executor.run(*admitted).last.outcome, Outcome.SUCCESS)
self.secret['metadata']['resourceVersion'] = '9'
self.assertEqual(executor.rollback(*admitted).last.outcome, Outcome.ROLLBACK_UNCERTAIN)
self.assertEqual(len(self.effects), 1)
self.assertEqual(len(self.revocations), 2)
if __name__ == '__main__':
unittest.main()

View file

@ -323,7 +323,7 @@ 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, 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, 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"
@ -592,6 +592,7 @@ prerequisites, with normal admission still refusing unimplemented types.
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
@ -608,6 +609,7 @@ previously described under T06; it does not duplicate native acceptance credit.
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
@ -626,19 +628,109 @@ publication receipt: Custodian
id: NK-WP-0048-T12
status: todo
priority: high
state_hub_task_id: "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd"
```
Owner: NetKingdom. Next action: wrap the existing contained `keycape-client.py`
registration/config/env and identity-provisioner ensure/revoke entry points in
bounded owner adapters or receipt-only attended steps for Warden's closed kernel.
Reuse the existing caller/session boundary, CAS/readback and status-only outputs;
never acquire user-engine's runtime identity or expose registry/client secrets.
Completion requires exact adapter/source descriptors, positive and refusal source
checks, uncertainty/created-only rollback behavior and a separate owner review of
the proposed machine seam. Source preparation can begin without credentials.
Warden independently owns global provenance/admission integration; Platform owns
mint/broker and native structural receipts; Apps owns consumer delivery/group
acceptance. Their assent is not supplied by this task. Pinning descriptors into
an acknowledged type remains a separately reviewed type-source change. Actual
attended execution still requires its existing owner authorization and native
receipts. T06 retains all native/reference lifecycle acceptance scope.
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
```
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.
## T14 — Preserve created-only provenance for group ensure and revoke
```task
id: NK-WP-0048-T14
status: todo
priority: high
```
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
```
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
```
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.