the sworn action and the browser verifier: a second implementation agrees on all 1689 vectors in scope - #64
Merged
Merged
Conversation
… action needs the JUnit and GitHub adapters, the browser verifier needs the conformance vectors; both CHANGELOG blocks kept Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…after the turn and exits zero on every verdict, frozen before any code Leg 3, item 4 of the plan. The spec fixes what a pull_request job does in order (checkout of the head, the project's tests to JUnit XML, the JUnit and GitHub adapters called as library code after the turn, every changed sworn document and the body verified against one composed manifest, a summary with the rung and the harness string on every row, exit zero on every verdict), the composed layout r1-r9 (r1-r4 the JUnit adapter's id for id, r5-r9 the GitHub adapter's shifted by four, an absence never renumbers), the rung matrix (declared by the workflow, lowered with a reason, never raised; pull_request_target reads DID NOT RUN), the fork rule the README states verbatim, what reads UNRESOLVED rather than erroring, nine rules each with its attack, the tests it commits to, and what it does not say. It licenses nothing until the code, the sample run and the RESULT are committed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tory that mints the manifest after the turn through the adapters and exits zero on every verdict Built to papers/sworn/SPEC_sworn_action_v01_2026_09_05.md, frozen at 9a2aa95. sworn/action.yml mirrors the root action's injection-safe pattern (every input reaches python through env; the event is read from the file; no strict, no soft-fail, no token) and sworn/sworn_action.py reads GITHUB_EVENT_PATH, runs the project's command with SWORN_JUNIT exported, calls styxx.harness.junit and styxx.harness.github as library code at the rung the workflow declares (lowered to L1 with the printed reason on a fork pull request without base-pinned-workflow and whenever after-turn-on-base is not declared; pull_request_target reads DID NOT RUN), composes one manifest (r1-r4 the JUnit adapter's id for id, r5-r9 the GitHub adapter's shifted by four, an absence never renumbering), records every blob the turn added or modified into authored_sha256, verifies the body as submitted and every changed .md carrying <sworn at the head commit through GitTree, writes LF-only receipts, run.json and a summary with the rung and the harness string on every row, and returns 0. sworn/README.md opens with the report-only sentence and the fork sentence and carries the layout an author may cite; sworn/examples/sworn.yml is the workflow the operator copies (nothing under .github/). papers/sworn/sworn_action_sample.py runs the action over a fixture repository with the clock and git dates pinned and writes the committed sample outputs beside itself, refusing to overwrite them; --check regenerates and compares. tests/test_sworn_action.py drives main() end to end in a temporary git repository with a fake event and a canned report, no network. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…st report of the action tests The turn manifest the RESULT swears rN spans against: r1 passed, r2 failures, r3 the report bytes, r4 the evidence reader output in canonical form. Rung L1, declared: minted on the author box by the author own shell right after that shell ran the tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
; CHANGELOG entry Every count bound: the sample run's leaves (the action string, the harness, exit 0, rung L2, five authored blobs, the four documents' verdicts and counts, the skip reason, the manifest digest) and rN spans against the manifest the JUnit adapter minted over the run of the action's own tests (35 passed, 0 failures, the evidence reader's outcome PASSED). SWORN-HELD, 0 failed, 0 malformed. Two spans were MALFORMED on the first pass and are repaired rather than excused: a numeric span whose sentence carried a second digit-bearing token (the 256 inside authored_sha256), and a hash span over a pointer into a receipt rather than over a whole one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ectors before anyone is asked to trust it — frozen before any code Leg 3, item 5. Seven rules, each with its attack: the subset is declared and everything outside it is counted (mode inline; rN and rN#/pointer against an embedded manifest; path:/prereg: with no tree, which is UNRESOLVED and must be reproduced as such; all four kinds); the bar is the core digest and nothing weaker; the acceptance bar is frozen at 1689 vectors, all of which must pass; the ten semantics Python and JavaScript do not share are ported deliberately and each is named and pinned by a vector; the verifier is a pure function with no I/O; the capsule sworn profile seals document, manifest, receipt and the verifier's own bytes, and fails closed on five named refusals; no verdict moves and the vector set digest may not shift. The label the plan writes is repeated wherever the artifact is described: re-derives sworn span verdicts offline; a forger controlling the whole file passes both browser layers; the package at the named commit is the check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e reproduces the verdict core on all 1689 vectors in scope Built to SPEC_sworn_browser_verifier_v01_2026_09_05.md, frozen before it. styxx/_data/sworn_verify.js is a pure function of bytes: the lexer, the receipt grammar, the manifest, the four kinds and the core assembly, ported so that sha256(utf8(jcs(core))) equals what styxx.sworn computes. No I/O, no clock, no globals; sha256 is implemented in the file because crypto.subtle is async-only and a capsule layer must answer without a promise chain. conformance/sworn/replay_js.js holds it to the committed set and prints, per family, what it ran and what it skipped with the vector's own requires as the reason. The run: 1689 in scope, 1689 passed, 0 failed, 1929 skipped (every mode but inline, and everything needing a tree). The bar the SPEC froze before any code was 1689, and tests/test_sworn_verify_js.py asserts the number run equals it, so a set that grows fails the test rather than quietly re-fitting the bar. Five disagreements were found by the vectors and repaired in the JavaScript, never in the verifier: the class name Python prints for a parsed JSON object (_Obj, not dict); the two completeness rules for `absent` (no declaration is UNRESOLVED manifest_no_completeness, a false one is MALFORMED absent_over_partial); a MALFORMED that comes out of resolution carries the resolution's provenance; a manifest is plain JSON (numbers are numbers) while receipt bytes are read with the decimal-exact reader; and the last selected line of an L-anchor excludes its terminating newline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ser verifier sealed together, failing closed on five named refusals SPEC B6. create_capsule_sworn seals the document bytes, the manifest the spans resolve against, the verdict receipt styxx.sworn issued, and sworn_verify.js itself; the page is written LF-only so the copy sealed as bytes and the copy inlined in the page are byte-identical on disk. Layer 1 re-derives the PORTABLE core — the receipt minus verifier and minus coverage, the number the conformance vectors pin — in the reader's browser with no network; layer 2 re-runs styxx.sworn and is the one that checks the build the receipt names. Refuses to mint, by name: sworn_document_mismatch, sworn_tree_receipt (this profile seals no tree, so a path:/prereg: span could only ever be UNRESOLVED), sworn_no_manifest, sworn_manifest_mismatch, sworn_receipt_mismatch. On verify, INSTRUMENT SKEW is advisory and named apart from tamper: a receipt issued by another build whose core still re-derives is skew, not a lie. tests/test_capsule_sworn.py, 14 tests: both layers on a sound capsule; each refusal on its own input; and a tamper battery. What each layer catches is asserted rather than assumed — a receipt edited without its sealed digest moves nothing layer 1 re-derives from and is layer 2's to catch, while moving the digest too makes layer 1 disagree; a manifest whose declared digest no longer re-derives sends every rN UNRESOLVED and both layers see it; and the last test builds the forger the label describes, a page whose inlined verifier quietly un-does the change to the document, so that layer 1 agrees with itself and layer 2 names the inlined copy as not the sealed one. The first sworn capsule is committed beside the document it seals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ond implementation agrees on all 1689 vectors in scope Every number bound to a leaf of conformance/sworn/replay_js_result.json or to a blob at the sworn commit: 1689 in scope, 1689 passed, 0 failed, 1929 skipped, 3618 in the committed set, 1452 of the run being the uncapped fuzz corpus, and the set digest quoted. The verifier's own bytes and the first sworn capsule's bytes are sworn by hash. The five disagreements the vectors caught are tabulated as what they were: places where Python and JavaScript read the same bytes differently, which is the only reason the set exists. The RESULT says what it does not say — that agreement makes either verifier correct (the same hands wrote both), that the format is covered, that path: receipts can be checked offline, that a capsule is self-verifying. CHANGELOG entry at the top. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eceipts resolve at The dogfood guard caught it: tests/test_sworn_dogfood.py re-derives every committed receipt at the commit its SIDECAR names, and both sidecars named none — canon was run without --commit, so the receipt carried the commit and the sidecar carried null. A receipt that cannot be re-derived from its own sidecar is not a receipt anybody else can check. Re-canoned and re-verified at the commits their path: receipts resolve at: the action RESULT at 00595d0 (16 spans, SWORN-HELD) and the browser verifier RESULT at 59b13f3 (9 spans, SWORN-HELD). No verdict moved and no number changed; the sidecar and the receipt now agree about where they stand. tests/test_sworn_dogfood.py: 82 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…kes no newline argument there Found by the 3.9 job on the sibling leg. The sworn capsule wrote its page with write_text(..., newline="\n") so the sealed verifier and the inlined verifier would be byte-identical on disk; on 3.9 that keyword does not exist and minting would raise. Writing the encoded bytes is the same result on every version, and the byte-identity test still holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…thom-lab/styxx into leg/action-and-browser-verifier
…ing copy's bytes, so sworn.py is pinned After the worked-examples repair the set still refused to regenerate here, and the drift report named four families — cli, gaming, receipt_v1, rules — all of them receipt_check vectors, which is the class the SPEC's ERRATA E1 already says embeds the issuing build. Decoding one named the field: verifier.sworn_sha256, which styxx.sworn computes over THIS FILE'S WORKING-COPY BYTES. This worktree had sworn.py CRLF; git holds it LF, which is what Linux checks out and what the committed set was generated against. So every sworn receipt, and every conformance vector that embeds one, depended on the line endings of the checkout that produced it. styxx/sworn.py is now `-text` pinned: the blob does not move (the staged diff is empty), and a checkout can no longer change the hash every receipt carries. The working copy here was normalised to match, and the set regenerates to 96dfe159… — the digest #63 produces and the one Linux produced before either repair. Also in this commit: conformance/sworn/replay_js_result.json is renamed replay_js_report.json, because the conformance suite forbids `result` in a file name under conformance/ (a suffix another sweep claims) and this branch had been failing that test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gest is the one both platforms produce The browser verifier RESULT swears to the conformance set digest, which moved to 96dfe159… when the two platform dependences were repaired; it is re-sworn at this commit, 9 spans SWORN-HELD, and the number it now binds is the digest Windows and Linux both produce. The action RESULT's receipt is re-issued at the pinned build so its check reads same-build rather than carrying a permanent skew advisory for a line-ending difference that no longer exists. Neither verdict and no other number moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…thom-lab/styxx into leg/action-and-browser-verifier # Conflicts: # CHANGELOG.md
…9 unchanged Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r-verifier # Conflicts: # .gitattributes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Leg 3, items 4 and 5 of
papers/PLAN_the_next_level_2026_09_02.md: the report-only sworn action, and the browser verifier held to the conformance vectors with the capsule profile that seals what it re-derives. Base isfeat/charon-v01; this branch contains the conformance-vectors leg (#63) and the harness-adapters leg (#62) by merge, because the action needs the adapters and the verifier needs the vectors. Review after those two, or review this alone and take all four legs together.The headline
A second implementation of the sworn verifier, written in another language, reproduces the verdict core digest on every conformance vector in scope: 1689 of 1689, zero disagreements.
The vector set exists so a second verifier can be shown where it disagrees, byte by byte, before anyone is asked to trust it. Nothing had ever been held to it. Now something has, and the five places where it first disagreed were exactly the places where Python and JavaScript read the same bytes differently:
_Obj, notdictabsentover a receipt with no declared completenessmanifest_no_completenessabsentover a receipt declared incompleteabsent_over_partial, provenance carriedL-anchorAll five were repaired in the JavaScript.
styxx/sworn.pyis not touched by this branch, and the vector set digest does not move.Item 4 — the sworn action (report-only)
sworn/action.yml+sworn/sworn_action.py, in its own subdirectory because the root action is diffgate's. In a pull-request job it runs the project's test command, mints asworn/manifest/0.2after the turn through the JUnit and GitHub adapters, verifies every sworn document the pull request touched, writes a job-summary table carrying the rung and the harness string on every row, and exits zero on every verdict — the command's exit status is a record, never the job's, because a gate this lab has not measured is a gate this lab does not ship.r1–r4are the JUnit adapter's,r5–r9the GitHub adapter's shifted by four. A document citing anrNthe runner never minted reads UNRESOLVED, not an error.pull_requestfrom a fork the minting job runs the workflow file as it exists in the head, so the manifest is minted by a party the claimant controls and the sentence L2 rests on does not hold.RESULT_sworn_action_v01_ships_2026_09_05.md, sworn, 16 spans.Nothing under
.github/is touched — no token in this lab can push a workflow file.sworn/examples/sworn.ymlis there for the operator to copy, and this repository enables nothing.Item 5 — the browser verifier and the capsule profile
styxx/_data/sworn_verify.js: the lexer, the receipt grammar, the manifest, the four kinds and the core assembly, ported sosha256(utf8(jcs(core)))equals whatstyxx.sworncomputes. Pure — no I/O, no clock, no globals, no network; byte offsets overUint8Array, never string indices; sha256 implemented in the file becausecrypto.subtleis async-only and a capsule layer must answer without a promise chain.The capsule's sworn profile seals the document bytes, the manifest, the verdict receipt and the verifier's own bytes, LF-only so the sealed copy and the copy the browser runs are byte-identical. Five named refusals at mint (
sworn_document_mismatch,sworn_tree_receipt,sworn_no_manifest,sworn_manifest_mismatch,sworn_receipt_mismatch); INSTRUMENT SKEW kept apart from tamper on verify.The label is demonstrated, not asserted. The tamper battery builds the forger the plan's own words describe — a page whose inlined verifier quietly un-does the change made to the document — and shows layer 1 believing it while layer 2 names the inlined copy as not the sealed one. That is what a forger controlling the whole file passes both browser layers; the package at the named commit is the check means, and it is now a test.
Verification
tests/test_sworn_action.py35 ·tests/test_sworn_verify_js.py7 ·tests/test_capsule_sworn.py14 ·tests/test_sworn_eol.py163.node conformance/sworn/replay_js.js: 1689 ran, 1689 passed, 0 failed, 1929 skipped, against setcb5d5148….python -m styxx.sworn checkVERIFIED.python -m ruff check styxx: clean. Full suite on this branch (Windows, Python 3.12): 4192 passed, 11 skipped, 4 xfailed. The dogfood guard caught both RESULT sidecars naming no commit at first; they were re-canoned at the commits their receipts resolve at, with no verdict and no number moved.What none of it says
That the action has run on GitHub. That L2 has been verified — the rung is the workflow's declaration, printed, never checked. That agreement on the vectors makes either verifier correct: the same hands wrote both, and that is the objection to press. That the format is covered — 1929 vectors sit outside this subset, including every sidecar and everything needing a tree. That
path:receipts can be checked offline. That anything here is self-verifying.🤖 Generated with Claude Code