Skip to content

the sworn action and the browser verifier: a second implementation agrees on all 1689 vectors in scope - #64

Merged
fathomlab merged 19 commits into
mainfrom
leg/action-and-browser-verifier
Sep 5, 2026
Merged

the sworn action and the browser verifier: a second implementation agrees on all 1689 vectors in scope#64
fathomlab merged 19 commits into
mainfrom
leg/action-and-browser-verifier

Conversation

@fathomlab

Copy link
Copy Markdown
Contributor

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 is feat/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:

what disagreed what the vector said
the class name Python prints for a parsed JSON object _Obj, not dict
absent over a receipt with no declared completeness UNRESOLVED manifest_no_completeness
absent over a receipt declared incomplete MALFORMED absent_over_partial, provenance carried
a manifest's numbers plain JSON, while receipt bytes stay decimal-exact
the last line of an L-anchor its terminating newline is excluded

All five were repaired in the JavaScript. styxx/sworn.py is 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 a sworn/manifest/0.2 after 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.

  • The composed receipt numbering is stated, not guessed: r1r4 are the JUnit adapter's, r5r9 the GitHub adapter's shifted by four. A document citing an rN the runner never minted reads UNRESOLVED, not an error.
  • The fork sentence is printed into every manifest: on a pull_request from 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.
  • A committed sample run drives the whole action over a temporary repository with no network: a held body, a five-span held document, a contradicted document, an unresolved citation, two skips with reasons, exit 0 throughout.
  • 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.yml is 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 so sha256(utf8(jcs(core))) equals what styxx.sworn computes. Pure — no I/O, no clock, no globals, no network; byte offsets over Uint8Array, never string indices; sha256 implemented in the file because crypto.subtle is 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.py 35 · tests/test_sworn_verify_js.py 7 · tests/test_capsule_sworn.py 14 · tests/test_sworn_eol.py 163.
  • node conformance/sworn/replay_js.js: 1689 ran, 1689 passed, 0 failed, 1929 skipped, against set cb5d5148….
  • Both RESULTs re-derive: python -m styxx.sworn check VERIFIED.
  • 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

darkflobi and others added 11 commits September 5, 2026 15:23
… 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>
@fathomlab
fathomlab changed the base branch from feat/charon-v01 to main September 5, 2026 21:37
darkflobi and others added 8 commits September 5, 2026 17:46
…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>
…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>
@fathomlab
fathomlab merged commit 1e6b8fe into main Sep 5, 2026
7 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