feat(certify): S2 step 12 — Final Evidence Certification (own-fix subscriptions certify) - #292
Conversation
…xists S2 step 12 (Final Evidence Certification) begins with its acceptance in hand. tests/test_certify.py synthesizes a complete, internally-consistent six-artifact evidence chain (candidates, validated plan, step 8 bundle, step 9 gate-result, step 10 delta-result, step 11 target-result) plus a reference closure — in pure Python, no dotnet — for the converted, manual-only and mixed cases, and asserts `own-fix subscriptions certify` binds it into one deterministic certification-result.json (status evidence_complete; the exact P-amendment claims array with no steps_8_11_gates_satisfied; the eight/one/preimage hash classes; the twelve checks) or refuses with exactly one stable category on a tampered artifact (authority / bundle / gate / delta / target / reference / publication). It imports ownlang.fix_certify, which does not exist yet, so the suite is red. The green core lands in the next commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
ownlang/fix_certify.py implements `own-fix subscriptions certify`: a pure verifier +
publisher that binds the six accepted S2 artifacts (validated plan, candidates, step 8
bundle, step 9 gate-result, step 10 delta-result, step 11 target-result) plus the
reference closure into one deterministic certification-result.json (status
evidence_complete).
This core lands the binding spine and makes the Tier A contract green:
* the one snapshot boundary + duplicate-key-refusing loader; candidates/plan carry no
whitespace/newline mandate (N1), the four compact-canonical artifacts do;
* authority via the frozen validate_gate_authority + the O6 two-key minimal-plan
reconstruction (validate_plan is never called on the seven-key materialized plan);
* bind_certification_bundle: exact bundle layout + the canonical apply-manifest
reconstructed from the plan / plan-byte SHA / rel / cross-artifact preimage / postimage
/ patch (fix_target.bind_bundle deliberately not reused);
* gate via fix_delta.bind_gate; delta via fix_target.bind_delta plus the bundle/gate
cross-artifact hash binding and the analysis-scope binding; the converted / manual-only
target closed schema with the delta / input-hash / reference bindings;
* reference-closure reconstruction with ordinal-exact equality to both upstream closures;
* a Step-12-local single-file publisher (the frozen _publish_target hardcodes another
name) with one atomic rename, existing-OUTPUT_DIR and cleanup-failure both PUBLICATION;
* the honest published schema: eight artifact_hashes, one semantic_hashes, the
cross_artifact_only preimage_binding, and the exact P-amendment claims array with no
steps_8_11_gates_satisfied.
The representable Step 10/11 equations, the deterministic-ordering / advisory / self-manifest
checks, the converted-attempt verdict recomputation, and the original-authority
revalidation depth are stubbed here and enforced in the following red-to-green pair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
Extends the Tier A suite with the enforcement the core deliberately stubbed, so each
case fails against the current tree:
* OBS001.advisory typing (a string / null advisory is DELTA_BINDING);
* the Step 10 representable equations (a violated removed-subscription equation, a new
postimage OWN001) and deterministic canonical observation ordering -> DELTA_BINDING;
* the three self-describing toolchain-manifest digests (extractor / ownlang ->
DELTA_BINDING, probe -> TARGET_BINDING);
* the converted-target controls and the recomputed attempt verdict (delivered_count != 1
on a rehashed target, a broken strong control, a published verdict that disagrees with
the recomputation) and the callsite / selected-wrapper invariants -> TARGET_BINDING;
* revalidate_certification_inputs against original-bundle drift (a changed manifest, a
decoy file), original --ref-dir drift (a changed / removed DLL) and materialized-slot
drift -> ISOLATION.
19 of these are red; the no-drift and plain-input-drift controls already pass. The green
enforcement lands next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
…cement
Fills in the three deep validators the core stubbed, turning the previous commit's red
suite green:
* _validate_delta_representable: the closed baseline / postimage / delta observation
schemas (each OWN001 through the frozen _core_key, so advisory MUST be a boolean),
deterministic canonical ordering of every observation list, the subscription /
core-OWN001-multiset / OWN050 / semantic-idempotence equations mirrored from the frozen
classify_delta, and the self-describing extractor / ownlang deployment-manifest digests
(sha256(canonical_json(files)), sorted, duplicate-free);
* _validate_target_converted: the callsite summary (converted_callsites == the converted
count, one source-external symbol, derived == asserted ordinal), the selected-wrapper
slot-identity consistency, the frozen probe-protocol constants, the probe deployment
self-hash, and every attempt — exactly three, ordinals 0..2, strong/collectability
controls, and a verdict RECOMPUTED with the frozen fix_target._attempt_verdict that must
equal the published verdict and be "pass";
* revalidate_certification_inputs: beyond the plain inputs, the ORIGINAL bundle (exact
layout + manifest/patch/postimage bytes), the ORIGINAL --ref-dir closures re-derived
from the caller directories, and the materialized slots — any post-binding drift,
including a change in an originally-empty ref-dir, is ISOLATION.
Tier A: 83/83. ruff + mypy --strict clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
tests/test_certify_tierb.py runs the full public pipeline end to end — real extractor -> candidates -> validate-plan -> apply -> gate -> verify-delta -> verify-target -> certify — for a genuine converted chain (a real weak wrapper shipped as a reference slot) and a manual-only chain, and asserts the published certification-result.json is canonical, is status evidence_complete with the exact chain_kind, carries the P-amendment claims with no steps_8_11_gates_satisfied, and is byte-identical across two independent fresh-workspace runs while the repository stays untouched. Required under OWN_TIERB_REQUIRED=1; a clean skip otherwise. The whole Steps 8-11 chain already runs green; only `own-fix subscriptions certify` is missing, so both cases fail at the certify step (rc=2, unknown subcommand). The CLI / docs / CI wiring that greens it lands next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
… step Adds `own-fix subscriptions certify` to ownlang/__main__.py (one _cmd_certify + one dispatch line + the usage strings), documents it as one row in spec/CLI.md (the exact CLI, the evidence-boundary summary, and the ten-category refusal taxonomy), and adds one required Tier B step (OWN_TIERB_REQUIRED=1) to the wpf-extractor CI job after the Step 11 gate. Also corrects the Tier B fixture to publish the certification output OUTSIDE the input tree: Step 12 refuses (PUBLICATION) when the output parent resolves inside a protected input root (the bundle, a ref-dir, or an input-file parent), which is the intended isolation guarantee — the test now respects it. With the subcommand wired, the full public chain (extract -> candidates -> plan -> apply -> gate -> verify-delta -> verify-target -> certify) certifies both the converted and manual-only chains, byte-identically across two independent runs, with the repository untouched: Tier B 29/29. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
📝 WalkthroughWalkthroughAdds S2 Step 12 final-evidence certification with strict artifact binding, invariant and isolation checks, atomic publication, a public CLI command, documentation, Tier A tests, and required Tier B CI coverage. ChangesS2 Step 12 certification
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant run_certify
participant EvidenceArtifacts
participant certification_result
CLI->>run_certify: certify plan, bundle, gate, delta, target
run_certify->>EvidenceArtifacts: snapshot and validate evidence
EvidenceArtifacts-->>run_certify: bound hashes and closure
run_certify->>certification_result: atomically publish canonical result
certification_result-->>CLI: return output path
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc6d27fd18
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| gb = delta["gate_binding"] | ||
| if gb.get("gate_result_sha256") != gate_sha256: | ||
| raise CertifyError(cat, "delta-result.json is not bound to this gate-result") | ||
| if gb.get("git_gates_status") != ("pass" if auth.applied else "not_applicable"): | ||
| raise CertifyError(cat, "delta-result.json git_gates_status is not the plan's variant") |
There was a problem hiding this comment.
Validate the full Step 10 gate binding
When someone tampers an otherwise hash-consistent delta-result.json (for example, keeps gate_result_sha256/git_gates_status but changes gate_binding.bound to false or step9_operation to another string and recomputes downstream hashes), this block still accepts it because only two fields are checked. That lets certify publish final evidence for a Step 10 artifact that no longer asserts the Step 9 binding it is supposed to certify; validate the exact gate-binding key set and fixed bound/operation/version values here.
Useful? React with 👍 / 👎.
| if sw["ordinal"] != ordinal or sw["sha256"] != slot["sha256"] \ | ||
| or sw["relative_path"] != slot["relative_path"]: | ||
| raise CertifyError(cat, "selected_wrapper is not consistent with its reference slot") |
There was a problem hiding this comment.
Reject boolean selected-wrapper ordinals
When the derived wrapper ordinal is 0, a target-result.json with selected_wrapper.ordinal set to JSON false passes this comparison because Python booleans compare equal to integers (False == 0). The certification then publishes wrapper_identity.ordinal as false even though the closed schema expects a non-negative integer, so this should explicitly require ft._is_int(sw["ordinal"]) before comparing it to the derived ordinal.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
tests/test_certify_tierb.py (1)
39-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPin the certification contract independently of production constants.
Importing
_CHECK_NAMESand_CLAIMSmakes these assertions follow implementation drift automatically. Define frozen expected values in this acceptance test so unintended public-contract changes fail Tier B.Also applies to: 278-280
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_certify_tierb.py` at line 39, Update the acceptance tests in tests/test_certify_tierb.py to stop importing _CHECK_NAMES and _CLAIMS from ownlang.fix_certify. Define frozen expected check names and claims locally in the test, then use those values in the assertions so production constant changes are detected as contract failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ownlang/fix_certify.py`:
- Around line 537-545: Extend the Step 10 validation in the surrounding
certification flow after the closed-key checks for tfp and ca to validate every
toolchain fingerprint value, not only the manifest pairs. Reuse the frozen Step
10 field validators for dotnet_host_sha256, runtime identity, runner/Python
hashes, versions, and implementation fields; otherwise reproduce their exact
nested schemas and scalar checks before publishing the evidence.
- Around line 717-723: Update the publication staging flow around the
certification-result write and _require_single() so all related file creation,
write, close, and validation OSError exceptions are caught and translated into
CertifyError with the PUBLICATION category. Preserve the existing destination
checks and publication behavior, and include the underlying failure context when
constructing the stable error.
- Around line 365-369: Update the delta_binding validation in the certification
flow to enforce the closed schema: require exactly the contract-defined fields
and reject any additional keys, while preserving the existing type and value
checks for delta_result_sha256, step10_operation, step10_status, and bound. Keep
raising CertifyError for any schema or binding mismatch.
In `@tests/test_certify_tierb.py`:
- Around line 176-185: Update the test helper _plan to generate and validate the
plan through the public own-fix subscriptions validate-plan CLI subcommand
instead of importing and calling validate_plan directly. Preserve the existing
candidates input, decisions, output plan path, and action selection while
ensuring the test exercises the public command-line boundary.
- Around line 262-266: Update _no_absolute_paths to recursively inspect JSON
string values and detect every absolute-path form, including drive-letter paths
with either slash style (such as C:/...), POSIX paths such as /Users/ and /opt/,
and CI workspace roots. Preserve the existing check failure behavior and message
while ensuring nested evidence values are covered.
- Around line 322-329: Extend the immutability snapshot around the _CASES loop
in tests/test_certify_tierb.py by capturing local Git configuration with git
config --local --list --show-origin before _run_case and comparing it afterward.
Preserve the existing HEAD and status checks, and fail through check if the
configuration snapshot changes.
In `@tests/test_certify.py`:
- Around line 390-398: Update the certification test around the claims and
checks assertions to use literal specification values instead of fc._CLAIMS and
fc._CHECK_NAMES, including the required twelve check names. Add a second
independent run with identical inputs and compare its raw
certification-result.json bytes to the first run, while retaining the
canonical-byte assertion for each output.
- Around line 771-793: Extend the revalidation tests near the existing ref-dir
drift cases to cover an initially empty --ref-dir: create and bind an empty
reference directory, add a DLL such as WeakEvents.dll afterward, then call
revalidate_certification_inputs through _raises_call and assert fc.ISOLATION.
Preserve the existing nonempty-directory and materialized-slot cases.
---
Nitpick comments:
In `@tests/test_certify_tierb.py`:
- Line 39: Update the acceptance tests in tests/test_certify_tierb.py to stop
importing _CHECK_NAMES and _CLAIMS from ownlang.fix_certify. Define frozen
expected check names and claims locally in the test, then use those values in
the assertions so production constant changes are detected as contract failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: bb4d38b8-349b-4a3d-bdf7-c3e9380e19a3
📒 Files selected for processing (6)
.github/workflows/ci.ymlownlang/__main__.pyownlang/fix_certify.pyspec/CLI.mdtests/test_certify.pytests/test_certify_tierb.py
Arbiter CHANGES-REQUIRED: green CI only proved the tests guarded the doors they were shown.
Refactors the Tier A fixture around Chain._seal() (mutate a primitive, re-seal, stay
hash-consistent) and states the contract's claims / check names as LITERAL expected values in
the test (never imported from the module under test). Adds the malformed-but-rehashed
regressions the suite was missing — each red against the current tree because production waves
them through:
* delta.gate_binding is not a closed schema — bound=false, a wrong step9_operation, step9_version
2, and an extra key all pass -> must be DELTA_BINDING;
* target.delta_binding accepts an extra "forged" key -> must be TARGET_BINDING;
* Step 10 toolchain scalars are unchecked — core_runner_sha256=null, python_version=[],
dotnet_version=int, a malformed dotnet_host_sha256 -> DELTA_BINDING; and a non-canonical
path in extractor_files / probe_files -> DELTA_BINDING / TARGET_BINDING;
* probe_runner_sha256 need not be a probe deployment digest -> TARGET_BINDING;
* probe_runtime_identity is never bound back to the Step 10 runtime (manifest / framework) ->
TARGET_BINDING;
* selected_wrapper.ordinal=false passes as ordinal 0 (bool == 0) -> TARGET_BINDING;
* a manual-only chain with a non-empty patch or post != pre is accepted -> BUNDLE_BINDING;
* a lone surrogate in candidates / delta / target leaks a UnicodeEncodeError instead of a
source-specific refusal -> AUTHORITY_BINDING / DELTA_BINDING / TARGET_BINDING.
Also adds the originally-empty --ref-dir isolation case (a DLL added to it -> ISOLATION), which
already passes. 20 of the new cases are red; the fixes land next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
Addresses the arbiter's blocking defects; every previous-commit regression now passes.
1. delta.gate_binding is a CLOSED schema — exact key set {gate_result_sha256, step9_operation,
step9_version, git_gates_status, bound} with step9_operation frozen, step9_version == 1
(exact int) and bound is True (DELTA_BINDING).
2. target.delta_binding is a CLOSED schema — exact four-key set; an extra "forged" key is now
TARGET_BINDING.
3. Step 10 toolchain SCALARS are typed — dotnet_host_sha256 / core_runner_sha256 /
python_executable_sha256 are sha256, dotnet_version / python_implementation / python_version /
python_cache_tag are strings; and every extractor_files / ownlang_files / probe_files path is a
canonical relative path (DELTA_BINDING / TARGET_BINDING).
4. probe_runtime_identity is bound back to the Step 10 resolved_runtime_identity (framework_name,
tfm, requested/selected version, and selected_runtime_manifest_sha256 == runtime_manifest_sha256),
and probe_runner_sha256 must be a probe deployment file digest (TARGET_BINDING).
5. selected_wrapper.ordinal is exact-int-guarded BEFORE the equality, so a JSON false no longer
passes as ordinal 0 (TARGET_BINDING).
6. bind_certification_bundle binds the variant to the bytes: a manual-only chain must carry the
empty patch with postimage == preimage; a converted chain must carry a non-empty patch
(BUNDLE_BINDING).
7. publication is fully normalized — a Step-12-local strict claimant (no ignore_errors) and every
open / write / scandir / stat / rename OSError maps to PUBLICATION; the frozen _claim_workdir
(which swallows its own cleanup) is no longer reused.
8. a lone-surrogate / non-UTF-8 payload refuses in the artifact's own binding category via an
encodability guard, instead of leaking a UnicodeEncodeError out as INFRASTRUCTURE.
Tier A: 105/105. ruff + mypy --strict clean. Frozen Steps 0-11 untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
…hter gate
Closes the arbiter's acceptance-suite gaps:
* the validated plan is now produced through the PUBLIC `own-fix subscriptions validate-plan`
CLI over a model fix-plan JSON, not by importing validate_plan — so every step of the chain
is a real command-line invocation;
* the contract's claims array and check-name set are stated as LITERAL expected values in the
test, never imported from the module under test;
* the no-mutation guard now also fingerprints .git/config before and after (not just HEAD and
git status);
* absolute-path leak detection is broadened to a Windows drive with either slash, a UNC prefix,
and /Users//opt//mnt//private/ in addition to /tmp//home//var//root/.
Tier B: 30/30 (converted + manual, byte-identical two-run, repo — source / index / config /
worktree — untouched).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ownlang/fix_certify.py`:
- Around line 244-250: Update the bundle validation in the function binding the
accepted Step 8 bundle (around the converted-chain checks) to reject any
converted result whose postimage bytes are identical to the preimage bytes, even
when the patch is non-empty. Preserve the existing manual-only no-op rule and
raise the established BUNDLE_BINDING failure for this invalid converted-chain
state.
In `@tests/test_certify_tierb.py`:
- Around line 315-321: Update _git_config_sha to obtain the repository
configuration through the existing _git helper/CLI rather than opening
.git/config directly. Hash the returned configuration bytes or text consistently
with the current _sha call, and retain the "absent" result only when git reports
that no configuration is available.
- Around line 279-281: Update the leaked-path detection expression in the test
to check for a UNC JSON prefix using the raw four-backslash pattern r"\\\\",
replacing the current "\\\\" check. Preserve the existing drive-letter and Unix
path checks.
In `@tests/test_certify.py`:
- Around line 412-413: Update the assertion in the check-result test to validate
the complete expected status mapping, including each check’s value, rather than
only comparing the check-name set and count. Preserve the existing requirement
that exactly twelve checks are present and use the expected mapping already
defined by the test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0683d021-811c-4ced-aba0-c3434760ab10
📒 Files selected for processing (3)
ownlang/fix_certify.pytests/test_certify.pytests/test_certify_tierb.py
…oundary
Second arbiter round (R1, R2). Adds the two regressions the suite was still missing, each red
against the current tree:
* R1 — removed_all_own001 must equal the bridge-authorized R_C, not merely
baseline - postimage. An unrelated (non-subscription) core OWN001 that also disappeared,
honestly listed and matching baseline - postimage, is currently accepted; it must be
DELTA_BINDING.
* R2 — there is no final originals-only revalidation immediately before the atomic rename, so a
privileged mutation of an authoritative input between the first revalidation and publication
slips through. Covered both as a unit (a Step-12-local `_revalidate_originals` boundary over
the five inputs + original bundle + original ref-dir closure) and end to end (an input mutated
from inside a publish wrapper must be caught by a pre-rename boundary) -> ISOLATION.
Also hardens the Tier B acceptance suite: the validated plan already runs through the public CLI;
the no-absolute-path check now recurses over every published string using posixpath/ntpath.isabs
(not a prefix allowlist); and the no-mutation guard fingerprints `git config --local --list
--show-origin` (robust to a linked-worktree .git file), not a hardcoded .git/config path.
3 of the new cases are red; the fixes land next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
…als boundary R1. removed_all_own001 is now required to equal the bridge-authorized R_C, not merely baseline - postimage. _authorized_removed reconstructs the candidate bridge Step-12-locally, mirroring the frozen fix_delta._bridge_r_c: each accepted candidate's K=(file, component, event, handler) — component the containing type's simple name, event source.event — maps to a validated baseline group (single distinct observation shape, exact eligible/observation cardinality, no mixed convert/manual under one K), and R_C draws one observation per converted candidate. An unrelated non-subscription OWN001 that also vanished is now DELTA_BINDING even when honestly listed and matching baseline - postimage. R2. A second, originals-only revalidation runs immediately before the atomic rename. revalidate_certification_inputs is split into _revalidate_originals (the five inputs + the original bundle + the original --ref-dir closure re-derived from the caller directories) plus the materialized-slot check; publish_certification gains a pre_rename hook invoked after the staging guard and immediately before os.rename; run_certify wires it with a fresh throwaway scratch dir (the execution root is already gone) that is strictly removed afterwards. A privileged mutation of any authoritative input between the first revalidation and publication is now ISOLATION. Tier A: 111/111. ruff + mypy --strict clean. Frozen Steps 0-11 untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
…xact check-status Final arbiter part of the old D6. The converted branch only proved a non-empty change.patch; a hash-consistent converted chain with change.patch != empty but postimage == preimage (manifest / gate / delta / target all rehashed to agree, delta/target still asserting a successful change) is accepted — Step 12 applies no patch and re-runs no analyzer, so the pre == post equality is not self-evident. The regression (converted chain, non-empty patch, postimage = preimage, re-sealed) expects BUNDLE_BINDING and is red. Also tightens both acceptance tiers from the check-name SET to the exact status MAPPING: converted publishes all twelve checks == "pass"; manual-only publishes wrapper_identity == "not_applicable" and the other eleven == "pass" — a "publication": "fail" under the right key is not a pass. The D6 fix lands next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
… (D6) The converted branch of bind_certification_bundle now additionally requires postimage != preimage (sha256(postimage) != the cross-artifact pre_sha256), not just a non-empty change.patch. This is the second half of the frozen D6 no-op invariant: manual-only is patch == empty AND post == pre; converted is patch != empty AND post != pre. A hash-consistent converted chain that rehashes a patch over an unchanged file is now BUNDLE_BINDING, closing the last representable hole (Step 12 applies no patch, so the equality is not otherwise self-evident). Tier A: 114/114. ruff + mypy --strict clean. Frozen Steps 0-11 untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
S2 Step 12 — Final Evidence Certification (
own-fix subscriptions certify)A pure verifier + publisher that binds the six accepted S2 artifacts (validated plan,
candidates, step 8 bundle, step 9
gate-result.json, step 10delta-result.json, step 11target-result.json) plus the reference closure into one deterministiccertification-result.json(statusevidence_complete). It runs no dotnet, applies no patch,mutates no source, and re-executes nothing.
Lineage
9fee6444ddf234e8ffba3e4b4fc45e5f1abbd05b(Step 11 merge anchor, PR S2 Step 11 — Verified Target Wrapper gate (own-fix subscriptions verify-target) #291)834e69f5ac6964b4790ae66ebb98036a0b726a82origin/maincontains the anchor and the Step 11 accepted headc7ab5e4fafcb7c2d0bc0f3eedcd96b8976294469; the seven frozen modules are byte-identicalbetween the merge and the accepted head.
Contract
S2_STEP12_SCOPE_PROPOSAL_revD+ locked amendments P1-P3.885aff8d8b0ca82dcaa5a64ed921c3ac2bf360cdd077ff6bacb3f3027a40f960— recorded as reported. The rev D file was supplied to this work as pasted text, not an
attachment, so the arbiter did not independently verify the file bytes against this digest.
Locked amendments P1-P3, as applied:
certification.claimsis exactly["evidence_chain_hash_bound_and_canonical", "representable_internal_invariants_revalidated", "upstream_pass_results_bound_not_reexecuted", "preimage_digest_cross_artifact_bound_bytes_not_supplied"].steps_8_11_gates_satisfiedis not published or documented anywhere (asserted by a Tier Aand a Tier B check).
spec/CLI.md) thatStep 12 does not prove the upstream artifacts were produced by the accepted implementations,
and does not independently re-execute Git, Roslyn, the analyzer, the target probe, or the
external toolchains; it verifies canonicality, hash bindings, closed schemas, the representable
equations, upstream pass-status evidence and the reference-closure binding.
status remains
evidence_complete;chain_kind∈ {converted,manual_only}, a mixed plancertifying as
converted.Changed files (exactly six; nothing else)
ownlang/fix_certify.pytests/test_certify.pytests/test_certify_tierb.pyownlang/__main__.py_cmd_certify+ one dispatch line + usagespec/CLI.mdcertifyrow.github/workflows/ci.ymlReused frozen helpers by import only:
validate_gate_authority,validate_candidates_bundle,validate_plan(only on a reconstructed two-key minimal plan, O6),bind_delta,bind_gate,build_manifest/manifest_bytes,snapshot_reference_closure,_snapshot,_sha_bytes,_canonical_json/_canonical_bytes,_execution_root,_remove_root_strict, and thephysical-path / staging predicates. The forbidden helpers (
fix_target.bind_bundle,fix_target._reval_inputs,fix_target._publish_target, andvalidate_planon the seven-keyplan) are not called; Step 12 defines its own bundle binder, original-authority revalidation,
and single-file publisher.
Red-to-green history (unsquashed)
6f6c887211c2f510a5c281bd5c06aa19ba8bc6d27f87072cc59cdb838b3c51b857af59028c2e73384d33834e69fArbiter corrective round (commits 7-9) — the eight blocking defects, all closed
Each defect got a red regression first (commit 7) then its fix (commit 8):
bound/step9_operation/step9_versionarevalidated (exact key set; version == 1 exact-int; bound is True) → DELTA_BINDING.
"forged"key → TARGET_BINDING.dotnet_version/python_* are strings) and every extractor/ownlang/probe
pathis a canonicalrelative path → DELTA_BINDING / TARGET_BINDING.
probe_runner_sha256 must be a probe-deployment digest → TARGET_BINDING.
False == 0trap) →TARGET_BINDING.
preimage; a converted chain a non-empty patch → BUNDLE_BINDING.
ignore_errors) andevery open/write/scandir/stat/rename OSError → PUBLICATION.
encodability guard, never leaking a UnicodeEncodeError as INFRASTRUCTURE.
Acceptance-suite hardening (commit 9): Tier B now drives
validate-planthrough the public CLI(not by import); the contract's claims/check-names are literal expected values in both tiers; the
no-mutation guard also fingerprints
.git/config; absolute-path leak detection is broadened.Second arbiter round (commits 10-11) — R1, R2, both red then green
removed_all_own001was only bound tobaseline − postimage, so an unrelated non-subscription OWN001 that also vanished (honestlylisted, matching the multiset) was accepted. Now
_authorized_removedreconstructs the candidatebridge Step-12-locally (mirroring the frozen
fix_delta._bridge_r_c:K=(file, component, event, handler), single-shape group, exact cardinality, no mixed convert/manual under one K) andrequires
removed_all_own001 == R_C→ DELTA_BINDING.(
_revalidate_originals: the five inputs + the original bundle + the original--ref-dirclosurere-derived from the caller directories) now runs inside
publish_certificationimmediately beforeos.rename, wired via apre_renamehook with a fresh throwaway scratch dir. A privilegedmutation of an authoritative input between the first revalidation and publication is now
ISOLATION.
Acceptance-suite: the no-absolute-path check now recurses over every published string with
posixpath/ntpath.isabs(not a prefix allowlist); the no-mutation guard fingerprintsgit config --local --list --show-origin(robust to a linked-worktree.gitfile).Third arbiter round (commits 12-13) — D6 second half + exact check-status, red then green
non-empty
change.patch; a hash-consistent chain withchange.patch != emptybutpostimage == preimagewas accepted (Step 12 applies no patch, so the equality is not otherwiseself-evident).
bind_certification_bundlenow also requiressha256(postimage) != pre_sha256fora converted chain — mirroring the frozen no-op invariant (manual-only: empty patch AND
post == pre; converted: non-empty patch ANDpost != pre) → BUNDLE_BINDING.names: converted publishes all twelve ==
"pass"; manual-only publisheswrapper_identity == "not_applicable"and the other eleven =="pass".Test counts
tests/test_certify.py, SDK-free): 114/114 pass (83 core + 31 corrective/coverage).tests/test_certify_tierb.py,OWN_TIERB_REQUIRED=1, full public CLI, local): 32/32 pass— the real chain
extract → candidates → validate-plan (CLI) → apply → gate → verify-delta → verify-target → certifyfor the converted and manual-only chains, two independent fresh-workspaceruns byte-identical, repository (source / index / config / worktree) untouched.
Frozen Step 8-11 suites (unchanged)
tests/test_patch_bundle.pytests/test_gate_patch.pytests/test_verify_delta.py(Tier A)tests/test_verify_target.py(Tier A)The frozen Tier B suites (
test_verify_delta_tierb.py,test_verify_target_tierb.py) share no codewith Step 12 and run in the same
wpf-extractorCI job as the new Step 12 Tier B step.Lint / type
python -m ruff check .→ All checks passed!python -m mypy --strict ownlang→ Success: no issues found in 29 source files.Two-run certification evidence SHA-256 (deterministic Tier A chains)
Two independent runs over fresh workspaces produce byte-identical
certification-result.json:380fd0078f8d935eea291d91874513c66727453cb36b372dd41be2476898e656bd5351d4fca1cf8a0c79e329b0a1402378f991240ad30f731a43567fc517de8491f31b66254723e86dfd5382d41430de98bbb30cc84e723ae3f4c602cc82cfe3Explicit evidence-claim limitations
The certification asserts only that the frozen S2 evidence chain is internally consistent
(canonical bytes, closed schemas, hash bindings, the representable Step 10/11 equations, the
cross-artifact preimage digest, and the reference-closure binding) and that upstream pass-status
is bound. It claims nothing about: patch application, compilation, production runtime
compatibility, absence of all leaks, general wrapper correctness, that the artifacts were produced
by the accepted implementations, or approval for unattended modification. The preimage digest is
cross-artifact-bound; the preimage bytes are never supplied to or reverified by Step 12.
Confirmations
git diff origin/main...HEADover the seven frozenmodules (
fix_candidates,fix_plan,fix_apply,fix_bundle,fix_gate,fix_delta,fix_target) is empty. No frozen module or test was modified.CI (final head
834e69f, all green)29640563323296405642162964056421529640564219/29640596854The CI workflow's
wpf-extractorjob ran the required step "S2 step 12 final-evidencecertification (Tier B, full public CLI)" → success (alongside the frozen Step 10 and Step 11
Tier B steps). Every prior head was likewise fully green:
bc6d27f(CI29636185615/29636220106),8b3c51b(CI29638982166/29638983823),028c2e7(CI29639774465/29639775528). Kept OPENand unmerged for arbiter review.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G