diff --git a/docs/changes/netkingdom-target-2026-10-17/nk48-adapters-1.2-review/coordinator-review-r3.json b/docs/changes/netkingdom-target-2026-10-17/nk48-adapters-1.2-review/coordinator-review-r3.json new file mode 100644 index 0000000..451239d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/nk48-adapters-1.2-review/coordinator-review-r3.json @@ -0,0 +1,139 @@ +{ + "schema": "custodian.nk48-adapter-set-source-review.v1", + "revision": "r3", + "reviewed_on": "2026-10-17", + "reviewer": "custodian coordinator (Claude Code, new separate session; no context from the authoring session, the r1/fix session or the r2 session; nothing asked of any of them; source read from git archive only)", + "frozen_target": { + "repository": "net-kingdom", + "commit": "ff8f597d5f2f32ce8e82e370c0ad43aa98dc31b7", + "r2_target": "68f4bbb8ca613940505d72523319a2f6281067d3", + "commits_since_r2": "eeed8e1 (R2-F1), 640292c (R2-F2), d16db30 (R2-F3), merge 2ee4ed0, ff8f597 (artifact regeneration + workplan)", + "diff_since_r2_verified": "git diff --stat 68f4bbb ff8f597 touches only mint-secret, deliver-secret and bind-env adapters (.py and .md), the candidate artifact, test_mint_secret_adapter.py, test_deliver_secret_adapter.py, test_bind_env_adapter.py, new test_adapter_source_closure.py and the NK-WP-0048 workplan. No shared helper (keycape-client.py, portal-client-rollout.py, openbao-client-config.py, register-user-engine-portal.py) changed.", + "kernel": "ops-warden a45cf47 src (out of scope; used as the admission contract)" + }, + "hashes_verified": { + "mint-secret-adapter.py": "sha256:246323c7243ded18229ab063956577e151d56d9be485d3bd44fc38e5af0edf3c", + "deliver-secret-adapter.py": "sha256:cd1a515a1ae2def2093660ed2a810a9c66d1d4cf7126f573ee7e9c4c42afb98d", + "bind-env-adapter.py": "sha256:800e45ecb68cc782c91aafda4242e0034e4156a54f6729d9f61fb845d4325d50", + "descriptor-composition-1.2.candidate.json": "sha256:745bb053e6776e8b96224563188405920227e4a17d7783ffa6b36c122be41b09", + "keycape-registration-adapter.py": "sha256:021eee262ea5d23d46258ed819da57b331f07b814adcafd60e0941214be02aea", + "ensure-groups-adapter.py": "sha256:19325fc599ae76e7ec2b54c132b1cf7dea1b5e931bb5263031dbdf8f4c0bcc7f", + "deliver-consumer-secret-adapter.py": "sha256:d046eed63090d4e0849a931e2650ccfd89d3b686843aed9e5cb35a0e18e513ee", + "handover-adapter.py": "sha256:0efc300a7b2765bce8da140d5c4666d9cb30daf02b4cbf0e0df96cba7c33d026", + "compose-1.2-descriptors.py": "sha256:8755f479f13f8978592a1346bcf8fdbb642eaece99ec29d31cc18091c655cffc", + "test_adapter_source_closure.py": "sha256:63cb442636d996fcf1c36b7b746ed860106a7da35dbaeb1c5129a41420ca4155" + }, + "hashes_match": true, + "approved_files_byte_identical_to_r2": { + "result": true, + "evidence": "cmp of each of the five files between git archive 68f4bbb and git archive ff8f597: identical (keycape-registration-adapter.py, ensure-groups-adapter.py, deliver-consumer-secret-adapter.py, handover-adapter.py, compose-1.2-descriptors.py)" + }, + "independent_reconstruction_passed": 117, + "independent_reconstruction_subtests_passed": 87, + "reconstruction": "git archive of net-kingdom ff8f597 (full tree) and ops-warden a45cf47 src into a scratch directory; README-r3 pytest command (including the composer and admission tests) run there: 117 passed, 87 subtests passed, matching the authors' report. The composer was rerun from the clean export with the artifact's own check_config_digest and params (consumer_owner_repo railiance-apps): exit 0, output byte-identical to the committed artifact.", + "regression_tests_against_r2_sources": { + "method": "Copy of the ff8f597 tree with mint-secret-adapter.py and bind-env-adapter.py replaced by their 68f4bbb versions; new tests run there.", + "result": "4 failed, 26 passed. Failed: test_failing_session_revocation_still_revokes_grant, test_both_revocations_failing_attempts_both_and_refuses, test_adapter_source_closure::test_bind_env_adapter, test_source_digest_covers_executed_helper_closure. test_failing_grant_revocation_still_revokes_session passes on 68f4bbb too, as the authors disclosed (session is already called first there).", + "deliver_secret": "The new deliver-secret tests cannot run against the 68f4bbb build() signature (it takes caller references and no case_source); the r2 defect is instead re-demonstrated below." + }, + "r2_findings_disposition": { + "NK48-R2-F1": { + "status": "resolved", + "evidence": [ + "mint-secret-adapter.py lines 115-130: revoke_session and revoke_grant each run in their own try; the first exception is re-raised only after both were attempted, inside contained_output, so the kernel sees Refused('owner callback failed') (or OwnerInterrupted for a BaseException).", + "Scratch demonstration through adapter.invoke on a run attempt with stub capabilities: session raises -> calls [session, grant], refused, nothing published; grant raises -> [session, grant], refused, nothing published; both raise -> [session, grant], refused, nothing published.", + "Tests: the session-raises and both-fail tests fail on the 68f4bbb source and pass on ff8f597, so they demonstrate the defect. The grant-raises test is order-only coverage (passes on both) and is correctly described as such by the authors. The both-fail test uses the rollback path, so run and rollback are both covered by at least one defect-detecting test." + ], + "residual_note": "When mint succeeds in custody but a revocation fails, no evidence is published and creation stays unset, so the adapter refuses later rollback of that version. It is left for owner reconciliation under a kernel UNCERTAIN record. This behaviour predates the fix, it is conservative, and it is not a defect for this review." + }, + "NK48-R2-F2": { + "status": "resolved", + "evidence": [ + "build() no longer takes namespace, name, custody_reference or key_names. It takes case_source, refuses unless digest(case_source) == admitted.pins.case, refuses unless client_type == confidential, and refuses unless the case client_id equals admitted.target. Namespace 'sso', Secret 'keycape-{client_id}-client', key ('client-secret',) and custody 'platform/workloads/keycape/{client_id}' are derived from the admitted target. That matches type 1.2 deliver-secret (ExternalSecret sso/keycape-{client_id}-client, key client-secret, from bao:platform/workloads/keycape/{client_id}, when confidential) and the paths mint-secret and bind-env derive.", + "Owner receipts must name the same namespace, name, custody reference and key names, or they are refused.", + "The Secret name is checked against the DNS-label pattern, so a target accepted by IDENTIFIER but containing '.', '/', ':' or uppercase is refused before any custody path is formed.", + "r2 scratch demonstration re-run against ff8f597 (admitted forgejo): passing the grafana name and custody path -> TypeError (no such parameter); forgejo admission with grafana case bytes -> Refused 'case source pin differs'; grafana case pinned with target forgejo -> Refused 'case target differs'; forgejo case pinned with target grafana -> Refused 'case target differs'; public example case -> Refused 'deliver-secret applies only to confidential clients'; forgejo case flipped to public -> refused the same way. A legitimate forgejo run delivers exactly {namespace sso, name keycape-forgejo-client, custody_reference platform/workloads/keycape/forgejo, key_names [client-secret], client_id forgejo}. The r2 grafana delivery now refuses." + ] + }, + "NK48-R2-F3": { + "status": "resolved", + "evidence": [ + "SOURCE_FILES now pins bind-env-adapter.py, keycape-client.py, portal-client-rollout.py, openbao-client-config.py and register-user-engine-portal.py. These are the same five files the approved registration adapter pins. The closure was re-derived from source: keycape-client.py loads portal-client-rollout.py (line 57), which loads openbao-client-config.py and register-user-engine-portal.py (lines 25-26); neither of those loads further local files, and no helper loads a local file lazily or reads a local data file on the adapter path.", + "r2 scratch demonstration re-run: appending to portal-client-rollout.py, openbao-client-config.py, register-user-engine-portal.py or keycape-client.py in a copy changes bind-env's source_digest() each time, and restoring the bytes restores it. The per-attempt check (source_digest() != reviewed_source) therefore refuses that drift before any read or write.", + "Artifact: the bind-env descriptor adapter_source equals the new module source_digest() from the clean export." + ] + } + }, + "closure_test_assessment": { + "file": "test_adapter_source_closure.py", + "proves": "For each *-adapter.py in this directory, every local .py file loaded when the adapter module is imported in a fresh interpreter (every importlib.util.spec_from_file_location target plus every sys.modules __file__ under the repo, excluding .venv and site-packages) is listed in that adapter's SOURCE_FILES. All seven pass at ff8f597, and the r2 bind-env fails it, so it detects NK48-R2-F3.", + "does_not_prove": [ + "Files loaded after import, for example inside build() or an attempt. None exist in this tree (grep of all adapters and the four helpers), but the test would not catch one added later.", + "Non-.py inputs, for example data files read at runtime or the registration check-config binary. The binary is pinned separately through check_config_digest.", + "Code loaded by other means (exec of read bytes, runpy, subprocess).", + "Equality: it checks loaded is a subset of pinned, so over-pinning passes. That is harmless (ensure-groups pins provisioner files it does not import, as r2 accepted)." + ], + "verdict": "Sufficient evidence for the claim 'SOURCE_FILES covers the import-time local Python closure' at ff8f597. Together with the manual closure derivation above, that is the whole executed local closure for this tree. It is a regression guard for import-time loads only." + }, + "artifact_assessment": { + "fresh_regeneration_equal": true, + "complete_executable_descriptor_set": true, + "admitted": false, + "dispatch_permitted": false, + "changed_since_r2": "Only the mint-secret, deliver-secret and bind-env descriptor digests and adapter_source values; the other four descriptors are unchanged.", + "source_pins_checked_independently": "For the three changed steps the artifact's adapter_source equals each module's source_digest() computed from the clean export. This closes the gap r2 noted, where the admission test's stand-ins take adapter_source from the descriptor itself.", + "kernel_admission": "test_admit_1_2_adapter_descriptors.py (reads the committed artifact) passes under a45cf47: 4 passed, all seven descriptors admitted under fixture trust. Fixture-only, not owner admission, as r2 recorded." + }, + "verdicts": { + "mint-secret-adapter.py": { + "verdict": "approved", + "finding_resolved": "NK48-R2-F1", + "checks": "1: single-file source, no local imports (closure test and grep); drift refused per attempt. 2: closed value-free custody receipt (exact key set, values_emitted false, path pinned); payload carries path, version and revocation refs only. 3: rollback destroys only the version minted and authenticated in this process, by exact version. 4: session and broker-grant revocation now each attempted on every run and rollback, including failures; any failure refuses with no evidence published. 5: step/owner/actor/lane, case pin, descriptor pin, acceptance digest, confidential-only and target == case client_id checked; attempt binding per call. 6: selects no version itself (kernel case/type pin, as r2 observed). 7: n/a. 8: artifact covered below." + }, + "deliver-secret-adapter.py": { + "verdict": "approved", + "finding_resolved": "NK48-R2-F2", + "checks": "1: single-file source (yaml is third-party only); drift refused per attempt. 2: closed receipt, values_emitted must be false, binding and payload hold references and uid/resource_version only. 3: only a delivery reported created (before None) and authenticated in this process can be removed, and only at the exact created revision; pre-existing Secret gives NO_CHANGE. 4: revoke_session in finally on deliver and remove. 5: case pin, descriptor pin, acceptance digest, lane/owner/actor, confidential-only and target == case client_id checked; reference derived, not caller-chosen; attempt binding per call. 6: selects no version itself (same kernel dependency as mint-secret). 7: n/a. 8: artifact covered below." + }, + "bind-env-adapter.py": { + "verdict": "approved", + "finding_resolved": "NK48-R2-F3", + "checks": "1: source digest now covers the full five-file executed closure; drift in any of them changes the digest and is refused per attempt. 2: reference-only env entry, no value handling; payload holds env name, revisions and refs. 3: CAS write with readback; pre-existing identical entry gives NO_CHANGE; rollback only of the in-process authenticated entry at its exact revision. 4: revoke_session in finally. 5: case pin, descriptor pin, acceptance digest, lane/owner/actor, target == helper clientId checked; attempt binding per call. 6: allow_unimplemented refused; helper accepts 1.x, version via kernel pin. 7: n/a. 8: artifact covered below. Only the SOURCE_FILES tuple and a docstring changed since r2." + }, + "descriptor-composition-1.2.candidate.json": { + "verdict": "approved", + "note": "Equals a fresh regeneration byte for byte; complete_executable_descriptor_set true; all seven descriptors pass Executor.admit under a45cf47 (fixture trust); the three changed adapter_source values equal the reviewed modules' source digests. Still a candidate: admitted false, dispatch_permitted false, owner_review false." + }, + "keycape-registration-adapter.py": { + "verdict": "approved", + "note": "Unchanged since r2 (byte-identical, helper closure unchanged), approved there." + }, + "ensure-groups-adapter.py": { + "verdict": "approved", + "note": "Unchanged since r2 (byte-identical), approved there." + }, + "deliver-consumer-secret-adapter.py": { + "verdict": "approved", + "note": "Unchanged since r2 (byte-identical), approved there." + }, + "handover-adapter.py": { + "verdict": "approved", + "note": "Unchanged since r2 (byte-identical), approved there." + }, + "compose-1.2-descriptors.py": { + "verdict": "approved", + "note": "Unchanged since r2 (byte-identical), approved there; regeneration re-verified at ff8f597." + } + }, + "findings": [], + "observations_not_findings": [ + "deliver-secret and mint-secret read client_type and client_id from case parameters but do not check spec.type themselves; bind-env does, through the helper's CASE_TYPE check. The case pin binds them to the case the kernel admitted for this type, so this is consistent with r2's kernel-dependency observation and is not a defect.", + "deliver-secret and mint-secret parse case structure with (case or {}).get('spec', {}).get('parameters', {}) outside contained_output. A pinned case whose spec is not a mapping would raise AttributeError from build() rather than Refused. That fails closed, before any capability runs.", + "The r2 observations reserved for owner review under T16 are unchanged by these fixes: composer completeness from decode, import-time versus build-time drift (still applies to bind-env and registration), kernel-dependent version selection, and warden_executes: false steps with execution descriptors." + ], + "canonical_integration": "not approved", + "native_acceptance": false, + "grants": "none: no owner admission, pin or native authority; those stay with NK-WP-0048-T16 and the founder", + "hard_limits_observed": "no live or native action, no kubectl, SSH or secret access; net-kingdom and ops-warden not edited (read via git archive into scratch only; demonstrations ran on scratch copies with stub callbacks); nothing fixed during review", + "remaining": "Source review of the seven-step 1.2 adapter set is complete at ff8f597 with no open findings. Next: owner review and admission under NK-WP-0048-T16 (including the warden_executes question and the r2 observations), owner pinning, then native acceptance by the founder. This review grants none of these." +}