Skip to content

feat(certify): S2 step 12 — Final Evidence Certification (own-fix subscriptions certify) - #292

Merged
PhysShell merged 13 commits into
mainfrom
claude/s2-step12-certify
Jul 18, 2026
Merged

feat(certify): S2 step 12 — Final Evidence Certification (own-fix subscriptions certify)#292
PhysShell merged 13 commits into
mainfrom
claude/s2-step12-certify

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 18, 2026

Copy link
Copy Markdown
Owner

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 10 delta-result.json, step 11
target-result.json) plus the reference closure into one deterministic
certification-result.json (status evidence_complete). It runs no dotnet, applies no patch,
mutates no source, and re-executes nothing.

Lineage

  • Base SHA (current main): 9fee6444ddf234e8ffba3e4b4fc45e5f1abbd05b (Step 11 merge anchor, PR S2 Step 11 — Verified Target Wrapper gate (own-fix subscriptions verify-target) #291)
  • Final HEAD: 834e69f5ac6964b4790ae66ebb98036a0b726a82
  • Verified before editing: origin/main contains the anchor and the Step 11 accepted head
    c7ab5e4fafcb7c2d0bc0f3eedcd96b8976294469; the seven frozen modules are byte-identical
    between the merge and the accepted head.

Contract

  • Implements: S2_STEP12_SCOPE_PROPOSAL_revD + locked amendments P1-P3.
  • Reported rev D contract SHA-256: 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:

  • P1 — honest claims array. certification.claims is 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_satisfied is not published or documented anywhere (asserted by a Tier A
    and a Tier B check).
  • P2 — explicit evidence boundary. The step documents (module docstring + spec/CLI.md) that
    Step 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.
  • P3 — single-valued taxonomy. Exactly ten refusal categories, no catch-all chain-mismatch;
    status remains evidence_complete; chain_kind ∈ {converted, manual_only}, a mixed plan
    certifying as converted.

Changed files (exactly six; nothing else)

File Status
ownlang/fix_certify.py new — the Step-12-local binder / publisher
tests/test_certify.py new — Tier A (SDK-free)
tests/test_certify_tierb.py new — Tier B (full public CLI)
ownlang/__main__.py modified — one _cmd_certify + one dispatch line + usage
spec/CLI.md modified — one certify row
.github/workflows/ci.yml modified — one required Tier B step

Reused 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 the
physical-path / staging predicates. The forbidden helpers (fix_target.bind_bundle,
fix_target._reval_inputs, fix_target._publish_target, and validate_plan on the seven-key
plan) are not called; Step 12 defines its own bundle binder, original-authority revalidation,
and single-file publisher.

Red-to-green history (unsquashed)

# Commit
1 6f6c887 test(certify): red — the Step 12 Tier A contract, before the module exists
2 211c2f5 feat(certify): green — the core Final Evidence Certification binder
3 10a5c28 test(certify): red — the representable, verdict and isolation invariants
4 1bd5c06 feat(certify): green — the representable, verdict and isolation enforcement
5 aa19ba8 test(certify): red — the required Tier B public-CLI acceptance
6 bc6d27f feat(certify): green — wire the CLI, the spec row and the required CI step
7 87072cc test(certify): red — the arbiter's malformed-but-rehashed regressions
8 59cdb83 fix(certify): green — close the eight malformed-but-rehashed holes
9 8b3c51b test(certify): harden the Tier B acceptance to a full public-CLI, tighter gate
10 857af59 test(certify): red — R_C authorization and the pre-rename originals boundary
11 028c2e7 fix(certify): green — bridge-authorized R_C and the pre-rename originals boundary
12 3384d33 test(certify): red — a converted chain must change the file (D6), + exact check-status
13 834e69f fix(certify): green — a converted chain must actually change the file (D6)

Arbiter 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):

  1. delta.gate_binding is now a closed schema — bound / step9_operation / step9_version are
    validated (exact key set; version == 1 exact-int; bound is True) → DELTA_BINDING.
  2. target.delta_binding is a closed schema — an extra "forged" key → TARGET_BINDING.
  3. Step 10 toolchain scalars are typed (dotnet_host/core_runner/python_executable are sha256;
    dotnet_version/python_* are strings) and every extractor/ownlang/probe path is a canonical
    relative path → DELTA_BINDING / TARGET_BINDING.
  4. probe_runtime_identity is bound back to the Step 10 runtime (all five fields), and
    probe_runner_sha256 must be a probe-deployment digest → TARGET_BINDING.
  5. selected_wrapper.ordinal is exact-int-guarded before the equality (the False == 0 trap) →
    TARGET_BINDING.
  6. manual-only no-op bundle — a manual chain must carry the empty patch with postimage ==
    preimage; a converted chain 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 → PUBLICATION.
  8. lone surrogate / non-UTF-8 refuses in the artifact's own binding category via an
    encodability guard, never leaking a UnicodeEncodeError as INFRASTRUCTURE.

Acceptance-suite hardening (commit 9): Tier B now drives validate-plan through 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

  • R1 — removed core OWN001 must equal the authorized R_C. removed_all_own001 was only bound to
    baseline − postimage, so an unrelated non-subscription OWN001 that also vanished (honestly
    listed, matching the multiset) was accepted. Now _authorized_removed reconstructs the candidate
    bridge 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) and
    requires removed_all_own001 == R_CDELTA_BINDING.
  • R2 — final originals-only revalidation before the atomic rename. A second boundary
    (_revalidate_originals: the five inputs + the original bundle + the original --ref-dir closure
    re-derived from the caller directories) now runs inside publish_certification immediately before
    os.rename, wired via a pre_rename hook with a fresh throwaway scratch dir. A privileged
    mutation 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 fingerprints
git config --local --list --show-origin (robust to a linked-worktree .git file).

Third arbiter round (commits 12-13) — D6 second half + exact check-status, red then green

  • D6 — a converted chain must actually change the file. The converted branch only required a
    non-empty change.patch; a hash-consistent chain with change.patch != empty but
    postimage == preimage was accepted (Step 12 applies no patch, so the equality is not otherwise
    self-evident). bind_certification_bundle now also requires sha256(postimage) != pre_sha256 for
    a converted chain — mirroring the frozen no-op invariant (manual-only: empty patch AND
    post == pre; converted: non-empty patch AND post != pre) → BUNDLE_BINDING.
  • Exact check-status mapping. Both tiers now assert the status VALUES, not just the twelve key
    names: converted publishes all twelve == "pass"; manual-only publishes wrapper_identity == "not_applicable" and the other eleven == "pass".

Test counts

  • Tier A (tests/test_certify.py, SDK-free): 114/114 pass (83 core + 31 corrective/coverage).
  • Tier B (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 → certify for the converted and manual-only chains, two independent fresh-workspace
    runs byte-identical, repository (source / index / config / worktree) untouched.

Frozen Step 8-11 suites (unchanged)

Step Suite Count
S8 tests/test_patch_bundle.py 79/79
S9 tests/test_gate_patch.py 69/69
S10 tests/test_verify_delta.py (Tier A) 104/104
S11 tests/test_verify_target.py (Tier A) 108/108

The frozen Tier B suites (test_verify_delta_tierb.py, test_verify_target_tierb.py) share no code
with Step 12 and run in the same wpf-extractor CI job as the new Step 12 Tier B step.

Lint / type

  • python -m ruff check .All checks passed!
  • python -m mypy --strict ownlangSuccess: 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:

  • converted: 380fd0078f8d935eea291d91874513c66727453cb36b372dd41be2476898e656
  • manual_only: bd5351d4fca1cf8a0c79e329b0a1402378f991240ad30f731a43567fc517de84
  • mixed (certifies converted): 91f31b66254723e86dfd5382d41430de98bbb30cc84e723ae3f4c602cc82cfe3

Explicit 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

  • Frozen Steps 0-11 source-identical: git diff origin/main...HEAD over the seven frozen
    modules (fix_candidates, fix_plan, fix_apply, fix_bundle, fix_gate, fix_delta,
    fix_target) is empty. No frozen module or test was modified.
  • No later step started: the change is Step 12 only; no Step 13+ work exists.

CI (final head 834e69f, all green)

Workflow Run ID Conclusion
CI 29640563323 ✅ success
CI 29640564216 ✅ success
Owen.Cli release 29640564215 ✅ success
pr-issue-validation 29640564219 / 29640596854 ✅ success

The CI workflow's wpf-extractor job ran the required step "S2 step 12 final-evidence
certification (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 (CI 29636185615/29636220106),
8b3c51b (CI 29638982166/29638983823), 028c2e7 (CI 29639774465/29639775528). Kept OPEN
and unmerged
for arbiter review.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G

PhysShell and others added 6 commits July 18, 2026 12:16
…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
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

S2 Step 12 certification

Layer / File(s) Summary
Certification contracts and authority binding
ownlang/fix_certify.py
Defines canonical parsing and refusal categories, validates the Step 8 bundle, and authenticates plan and candidate authority.
Cross-artifact and invariant validation
ownlang/fix_certify.py
Binds gate, delta, target, and reference-closure evidence while enforcing schemas, equations, ordering, and converted-target invariants.
Isolation, assembly, and publication
ownlang/fix_certify.py
Revalidates inputs, builds canonical certification evidence, and atomically publishes certification-result.json.
CLI command and documented contract
ownlang/__main__.py, spec/CLI.md
Adds own-fix subscriptions certify, argument handling, refusal reporting, dispatch routing, and command documentation.
Tier A validation
tests/test_certify.py
Tests successful certification, deterministic output, tampering refusal categories, publication safeguards, representability checks, and isolation detection.
Tier B CLI acceptance and CI enforcement
tests/test_certify_tierb.py, .github/workflows/ci.yml
Runs converted and manual public CLI chains, checks deterministic evidence and repository immutability, and enables required CI execution.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description has rich detail, but it does not follow the required template sections and headings. Rewrite it using the repo template: add 'Что и зачем', 'Тип изменения', 'Как проверено', 'Связанные issue', and 'Чеклист' sections with checkbox items.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly names the new Step 12 certification feature and the CLI subcommand.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/s2-step12-certify

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread ownlang/fix_certify.py
Comment on lines +326 to +330
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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread ownlang/fix_certify.py
Comment on lines +576 to +578
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")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
tests/test_certify_tierb.py (1)

39-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin the certification contract independently of production constants.

Importing _CHECK_NAMES and _CLAIMS makes 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9fee644 and bc6d27f.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • ownlang/__main__.py
  • ownlang/fix_certify.py
  • spec/CLI.md
  • tests/test_certify.py
  • tests/test_certify_tierb.py

Comment thread ownlang/fix_certify.py
Comment thread ownlang/fix_certify.py
Comment thread ownlang/fix_certify.py Outdated
Comment thread tests/test_certify_tierb.py
Comment thread tests/test_certify_tierb.py Outdated
Comment thread tests/test_certify_tierb.py
Comment thread tests/test_certify.py Outdated
Comment thread tests/test_certify.py
PhysShell and others added 3 commits July 18, 2026 14:10
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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between bc6d27f and 8b3c51b.

📒 Files selected for processing (3)
  • ownlang/fix_certify.py
  • tests/test_certify.py
  • tests/test_certify_tierb.py

Comment thread ownlang/fix_certify.py Outdated
Comment thread tests/test_certify_tierb.py Outdated
Comment thread tests/test_certify_tierb.py Outdated
Comment thread tests/test_certify.py
PhysShell and others added 4 commits July 18, 2026 14:45
…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
@PhysShell
PhysShell merged commit 366bbf9 into main Jul 18, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant