Skip to content

JS-SAM vs TLA+: fair Phase-3 study + lean-contract prototype - #3

Open
jdubray wants to merge 31 commits into
js-sam-experimentsfrom
js-sam-tla-phase3
Open

JS-SAM vs TLA+: fair Phase-3 study + lean-contract prototype#3
jdubray wants to merge 31 commits into
js-sam-experimentsfrom
js-sam-tla-phase3

Conversation

@jdubray

@jdubray jdubray commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Stacked on #2 (js-sam-experiments). Makes the JS-SAM-vs-TLA+ Phase-3 comparison fair, runs a paired N=5 study across four arms, and prototypes the fix the results point to.

What this establishes

The deployed JS-SAM Phase-3 gap vs TLA+ is the SAM pattern's machinery, not JavaScript. A bare next(state, action, data) over the two observable keys ties TLA+ at 100% for every model.

Model JS (deployed) JS (constrained) plain-JS TLA+
Opus 4.8 81.4% 89.3% 100% 100%
Fable 5 57.9% 95.7% 100% 100%
Sonnet 4.6 50.0% 100% 100% 100%
Haiku 4.5 28.6% 40.7% 100% 100%

plain-JS vs TLA+: McNemar b=0, c=0, χ²=0 for all four models. The residual is contract ceremony, not the language or the modeling approach.

Methodological controls (why the headline is defensible)

  • Removed the agent confound — Phase 3 replays each (pre, action, post) window directly (scripts/tla_direct_tv.py for TLA+ via a tiny TLC replay module; sandboxed next() for JS), no agent translator in the loop.
  • Removed the prompt-prescriptiveness confound — added a constrained-JS arm so the constrained TLA+ prompt isn't compared against a laxer JS prompt. This is why there are four arms, not two.
  • Answered the SAM-substrate question — the plain-JS arm (next(state,action,data), two keys, no SAM library) is structurally isomorphic to the TLA+ relation. It ties TLA+, isolating the gap to SAM's wiring.
  • Paired N=5 design; McNemar over per-window outcomes (continuity-corrected, df=1).

Raw data travels with the claims

output/tla_phase3_study.json — all four arms × 5 runs × per-window statuses (80 cells). The post-hoc plain-JS arm is reproducible from this file, not just the summary table.

Lean-contract prototype

The results point to a leaner spec shape. docs/js_sam_lean_contract.md proposes it; the prototype shows a pure {init, next} contract runs the entire benchmark pipeline without the SAM library:

  • tools/plain-js/explore.mjs — generic bounded explorer (Phase 2 runtime + Phase 4 invariants) over {init, next}
  • tools/plain-js/tv.mjs + scripts/plain_js_tv.py — Phase 3 replay
  • scripts/lean_demo.py on the reference spec: Phase 2 PASS (3 states) · Phase 4 3/3 · Phase 3 28/28

Caveat kept honest in the doc: clean 100% is on tiny spin state; re-run on ringbuffer/locksvc before generalizing.

Paper

paper/ (methodology note + manuscript) is now tracked alongside the data it cites.

Commits

  • 68aace8 constrained prompt + direct TLC replay (fair-Phase-3 blockers)
  • f069835 paired N=5 study driver
  • a347471 plain-JS arm + raw study JSON
  • 5cf432f lean {init, next} prototype
  • 0d5a8dc track paper/ with the data

🤖 Generated with Claude Code

kizo-core and others added 19 commits July 2, 2026 05:10
…kers)

Removes the agent confound from the JS-SAM-vs-TLA+ Phase-3 comparison by
equalizing the observable-state contract and replaying transitions
mechanically in TLC.

- tasks/spin/prompts/direct_call_constrained.txt: constrains the TLA+ spec
  to VARIABLES lockHeld, lockHolder + AcquireLock(thread,callType) /
  ReleaseLock(thread), matching the trace schema, with a TLC-safe
  NONE == "none" (no unbounded CHOOSE — the trap that broke free-form TLA+).
- scripts/tla_direct_tv.py: direct TLC trace-validation. Per window, a TV
  module pins the pre-state, takes one action step, and asserts the
  post-state unreachable (INVARIANT NoPost); a violation => window passes.
  Classifies pass/fail/unscoreable and reports conditional (over scoreable)
  and unconditional (unscoreable=fail) numbers.
- docs/js_sam_tla_phase3_plan.md: pre-registered design + interpretations.

Validated: constrained Opus scores 28/28 (100%); breaking ReleaseLock to a
no-op fails all 11 release windows (60.7%) — the replay discriminates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Runs the three-arm paired study (deployed-JS, constrained-JS, constrained-
TLA), N=5 per model, 28-window corpus, scored per-window.

Key result (docs/js_sam_tla_phase3_results.md): most of the raw TLA+ ≫
JS-SAM reversal was PROMPT PRESCRIPTIVENESS — adding the single-step
semantics to the JS prompt closed most of the gap (Fable 58->96%, Sonnet
50->100%). A real, one-directional residual survives: with identical
semantics, TLA+(constrained) hits 100% for every model/generation while
JS(constrained) does not for 3/4 (Opus 89%, Fable 96%, Haiku 41%; Sonnet
ties at 100%); b=0 in every McNemar (JS never beats TLA on any window).
Reads as a partial "formal directness forces precision" effect that strong
models close. Checkability fully solved by the constraint (0 unscoreable).

Adds the constrained JS-SAM prompt (deployed + the same semantics block),
adds {source_code} to the constrained TLA prompt for fairness, and the
paired driver with per-model McNemar. Raw per-window data checked in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cript

Adds a fourth arm: a bare next(state, action, data) -> {lockHeld, lockHolder}
pure function (no SAM library, two keys) — structurally isomorphic to the
TLA+ relation — run in the same locked-down Docker sandbox.

Result (docs/js_sam_tla_phase3_results.md): plain-JS ties TLA+ at 100% for
every model and generation (McNemar b=0 c=0 for all four). Haiku goes
40.7% (SAM) -> 100% (plain-JS). So the JS-SAM Phase-3 gap is the SAM
pattern's wiring (proposals/acceptors/intent + mandatory auxiliary state),
NOT JavaScript and NOT "formal vs informal": the dividing line is a minimal
declarative transition shape (TLA+ relation == plain next()), which SAM-JS
lacks. Design implication: a leaner JS-SAM spec contract recovers fidelity.

Adds tools/plain-js/tv.mjs (sandboxed runner), scripts/plain_js_tv.py,
the plain-JS prompt, and the pjs arm + plainJS-vs-TLA McNemar in the driver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follows from the plain-JS finding (the Phase-3 gap is SAM's machinery, not
JavaScript). Prototypes a lean spec contract — a pure next(state, action,
data) over the two observable keys, the JS analogue of the TLA+ relation —
and shows it supports all of Phases 2/3/4 without the SAM library:

- tools/plain-js/explore.mjs: generic bounded explorer over {init, next}
  (Phase 2 runtime: crashes/determinism/serializable; Phase 4: invariants).
- tools/plain-js/reference_spin.js: reference lean spec.
- scripts/plain_js_tv.py: adds plain_js_explore(); generalizes the sandbox runner.
- scripts/lean_demo.py: runs Phases 2/3/4 on a lean spec in the sandbox.
  Reference spec: Phase 2 PASS (3 states), Phase 4 3/3, Phase 3 28/28.
- docs/js_sam_lean_contract.md: design, evidence, integration options
  (lean backend / hybrid SAM-scaffolding / status-quo), tradeoffs.

Note: paper/methodology.md updated locally to the four-arm design (gitignored).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverses the earlier decision to gitignore paper/. The methodology note and
manuscript cite the raw per-window results in output/tla_phase3_study.json
(all four arms, N=5, committed at a347471) and the lean-contract prototype;
tracking them together keeps the paper reproducible from the repo alone.

- paper/methodology.md   — four-arm design + §7 result summary
- paper/js_sam_sysmobench.tex/.pdf — manuscript
- .gitignore: track paper source, ignore only LaTeX build byproducts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merges the two half-results into one coherent claim. The prototype validated
Phases 2/4 on a reference spec; the paired study validated Phase 3 on
model-generated specs but discarded the spec text. This regenerates N lean
specs per model, SAVES each, and runs Phases 2/3/4 on the same saved spec.

Result: 20/20 model-generated lean specs pass every phase (5/5 each for Opus
4.8, Fable 5, Sonnet 4.6, Haiku 4.5) — loadable, Phase 2 clean (3 states),
Phase 3 28/28, Phase 4 3/3.

- scripts/lean_full_pipeline_study.py: generate -> save -> all-phase eval, resumable.
- output/lean_specs/<model>_<gen>.js: the 20 saved generations (the artifacts
  the claim rests on).
- output/lean_full_pipeline.json: per-spec per-phase raw results.
- docs/js_sam_lean_contract.md: path 1 upgraded from plausible to demonstrated;
  adds the N=5x4-model table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The spin lean result carried two caveats: a tiny observable state (3 reachable
states, two scalars) and heavy solution convergence. A structurally different
second task settles both. locksvc is PGo's centralized lock service; its
observable state is {holder, waiters} — a lock holder plus a FIFO wait queue
(an unbounded list) — with four actions and FCFS grant semantics.

Trace capture (real Go execution, distsys trace recorder):
- scripts/harness/locksvc/run.sh already existed; parse_traces.py fixed for
  Windows UTF-8; build_windows.py (new) folds PGo events into {holder, waiters}
  observable (pre,action,post) windows, repairing empty-vclock ordering
  artifacts (a CriticalSection logged before its own Grant) causally.
- data/sys_traces/locksvc/: 60 windows across 5 runs (15 per action).

Study (task-agnostic now):
- scripts/lean_task_config.py: per-task action domain + invariants + reference.
- lean_demo.py / lean_full_pipeline_study.py take --task.
- tools/plain-js/tv.mjs generalized to the projection rule (compare every key
  in the trace post-state; extra model keys ignored) — was hardcoded to spin.
- tools/plain-js/reference_locksvc.js + plain-js prompt.

Result: 20/20 model-generated lean specs pass every phase on locksvc (5/5 each
for Opus 4.8, Fable 5, Sonnet 4.6, Haiku 4.5). Bounded exploration reaches 31
states (vs spin's 3); solutions de-converge to 11 distinct specs of 20 (spin:
9/20), 36-48 lines with per-model clustering. Artifacts:
output/lean_specs_locksvc/, output/lean_full_pipeline_locksvc.json.

docs/js_sam_lean_contract.md: second-task section + caveats updated.
(paper/ tex edits left to the author; PDF rebuild needs pdflatex, absent here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…symmetry review

Review: the JS and TLA+ Phase-3 replays did not check the same relation, and the
asymmetry favored TLA+. JS is functional (next(pre) must equal post); the TLC
replay was existential (post merely reachable). An over-permissive TLA+ action
passes existentially while admitting wrong post-states; the no-op negative
control only proved failure on unreachable posts, not on permissive specs.

Fix + evidence:
- tla_direct_tv.py: add branching_factor() + functional_replay_window() +
  --functional. A window passes only if the action's one-step image from the
  pinned pre is exactly {post} (branching factor 1) — as strict as next(pre)==post.
- tools/tla/permissive_spin.tla (negative control): holder := any thread on
  acquire. Passes the EXISTENTIAL replay 28/28 (100%) but the functional check
  flags 17 windows over-permissive (bf=2) -> 39.3%. tools/tla/reference_spin.tla
  passes both (28/28, bf=1). (Fully mixed int/string permissiveness is caught as
  unscoreable — TLC throws comparing "none" to an int thread id.)
- tla_functional_audit.py: regenerate + SAVE 20 constrained TLA+ specs (5x4
  models) and re-score under the functional check. Result: existential =
  functional = 140/140 per model, 0 over-permissive windows, max bf = 1. Every
  generated spec is deterministic — the TLA+ 100% is earned under the strict
  relation, not an existential artifact.
- tla_phase3_study.py: score_tla now uses the functional check (over_permissive
  counts as fail), so the paired comparison applies one relation to both languages.
- output/tla_specs/*.tla + tla_functional_audit.json: the audited artifacts.
- docs/js_sam_tla_phase3_results.md: "Replay-check symmetry" section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review: the identical 326,592 "states" across all four models was a red flag —
the metric counts explored action-sequences, not states, making Phase 2's
number model-independent and the paper's "structurally equivalent state
spaces" claim wrong.

Audit confirmed, and worse: steps = (d+1)*|intents|^d = 7*6^6 = 326,592 —
safety-callback invocations over the intent-permutation tree, a pure function
of the contract-pinned intent domain and depthMax (verified empirically at
depths 1/2/3: 12/108/864). Identical for ANY conforming spec by construction.
Counting distinct semantic states (unique model snapshots) separates them:
Opus/Fable/Sonnet 7, Haiku 1 — Haiku's intent actions drop their arguments so
every proposal is rejected; its exploration never leaves init. Haiku's Phase-2
PASS (and vacuously its Phase-4 3/3) certified a spec that cannot move. Two
Claude generations also differ (June spec: 5; July spec: 7) at identical steps.
Nuance on the review's premise: Fable's spec DOES release in exploration
(7 states) — its Phase-3 Release 0% is the pinned-observable-state replay
hitting its auxiliary threadStatus guard, not an unreachable release.

Fixes (TDD: fixture + failing tests first):
- tools/js-sam/cli.mjs: explore() tracks a seen-set of serialized snapshots ->
  distinctStates, surfaced by check + invariants; determinism digest includes it.
- js_sam.py: states_explored = distinctStates (semantic); the combinatorial
  step count stays available as steps_explored/raw_output.
- runtime_check.py parse_tlc_output: states_explored = TLC "distinct states
  found" (was "states generated", a work counter with duplicate visits).
  Fable's TLA spec: 574 generated / 159 distinct.
- tests: spin-neverrelease.js fixture (release no-op) + regression asserting
  identical steps but differing distinct states; TLC parsing unit tests;
  repaired test_missing_traces_reported_cleanly (its premise — spin traces not
  checked in — went stale when the corpus was committed).

Docs/paper: comparison tables now show distinct states (7/7/7/1; 159 for the
TLA arm); "structurally equivalent state spaces" retracted and replaced by the
audit; the three incommensurable counts (326,592 steps / 574 generated / 3
observable) reconciled explicitly in docs/js_sam_vs_tla_comparison.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
…ers revised

Review: repaired specs were only re-checked on Phase 1 + Phase 3, on the same
28 windows quoted in the repair prompt; nothing prevented memorizing the
failing windows, making "Fable repairs to 100%" possibly a prompt-following
result, and Experiment 2's "three tiers" unverified as modeling.

scripts/repair_generalization.py re-runs the repair loop with artifacts SAVED
(output/repair_specs/) and scores every baseline and repaired spec on:
P3-seen (28 windows = 8 distinct combos), P3-heldout (the 10 combos of the
observable domain the corpus never shows), P2 (distinct-semantic-state
metric), and P4 (three observable invariants).

Results:
- Opus/Fable/Sonnet repaired: 28/28 seen, 10/10 held-out, 7 distinct states,
  3/3 invariants. Haiku: unchanged (6/28, 1 state — still inert).
- No memorization: zero state-equality tables/window literals in any repaired
  spec. Fable's and Sonnet's repairs are the same root-cause semantic fix —
  release ownership moved from the auxiliary threadStatus guard (unsatisfiable
  under a pinned observable pre-state) to authoritative lockHeld/lockHolder;
  Sonnet's repair documents the diagnosis in a comment.
- Honest structural limit, stated in script+doc+paper: the kernel emits
  acquire events at acquisition success, so every held-out combo is an
  observable no-op — held-out alone refutes only unsafe-default memorizers;
  inspection and P2/P4 carry the conclusion. A state-changing held-out set
  (locksvc) is flagged as the stronger follow-up vehicle.
- Replication surprise: Sonnet repaired to 28/28 here vs 60.7% originally.
  The "three clean tiers (full/partial/none)" claim is revised everywhere
  (doc + paper abstract/intro/section): stable boundary is repairers vs
  Haiku; mid-tier single-round outcomes are sampling-dependent at N=1. The
  original "Sonnet cannot fix its release path" contrast is retracted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
…eanalysis

Review: pooling windows across 5 generations treats 140 outcomes as
independent when generations collapse to a handful of unique solutions — the
same bug counted five times, chi-square inflated (c=59, c=83). Correct unit
is the unique solution or the generation; report uniqueness for every arm.

Audit confirms, worse than estimated: behavioral fingerprints (per-window
outcome vectors) collapse to 1-2 unique per (model, arm) for ALL 16 cells —
e.g. Haiku's c=83 was one bug counted four times plus one partial.

scripts/tla_phase3_analysis.py (new) reanalyzes the committed raw JSON:
1. Uniqueness for EVERY arm (fingerprints; plus text-level uniqueness of the
   saved regenerated sets as corroboration: pjs 9/20, locksvc 11/20, tla 10/20
   unique texts, each collapsing to one behavior per arm).
2. Exact generation-level permutation test (unit=generation, diff in mean
   pass rate, all C(10,5)=252 relabelings, two-sided; floor p=2/252≈0.0079).
3. Unique-solution view (n=1-2/cell: effect sizes only, no tests).

Results at the defensible unit:
- deployed-JS vs TLA: significant for ALL four models (p=.0079, at floor) —
  the contract-tax effect is not a pooling artifact.
- constrained-JS vs TLA: significant for Opus and Haiku only. FABLE'S STARRED
  DEFICIT IS WITHDRAWN (chi2=4.2* pooled -> p=0.44 at generation level).
- plain-JS vs TLA: identical in every generation (delta=0; no test needed).

All pooled chi-square values retracted in docs + paper + methodology note;
tla_phase3_study.py now prints b/c as descriptive only and points to the
analysis script; the plan doc records the post-hoc analysis revision; the
paper's McNemar table is replaced by uniqueness + permutation results and the
"significant for Opus, Fable, and Haiku" finding corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
…ss checks

Review: Phase 4 cannot catch the paper's own headline bug — a never-releasing
lock satisfies mutual exclusion BECAUSE it is broken; the three JS-SAM
invariants are safety-only and demonstrably too weak; the interesting lock
properties are liveness, and neither the SAM checker nor the lean explorer
checks any temporal property. Not a "template count" caveat: Phase 4 as
implemented could not fail Fable's spec in principle.

Verified, precisely:
- The shipped NoDeadlock is "some thread is not 'trying'" — satisfied by the
  stuck HOLDER being 'locked'. Empirical demo: a release-is-a-no-op mutant of
  the known-good spec gets the IDENTICAL 3/3 Phase-4 verdict as the correct
  spec. Pinned as a characterization test
  (test_safety_only_phase4_passes_a_never_releasing_lock) that fails loudly if
  Phase 4 ever gains the capability.
- Fable's defect is doubly invisible: it manifests only under a pinned
  observable pre-state, so even a liveness-capable from-init checker would
  pass its spec — only trace replay catches it. Both layers stated.
- The TLA+ template set declares two liveness properties (PROPERTY-routed), so
  the P4 gap is qualitative, not just numeric — noted.

Mitigation (TDD): the lean explorer gains bounded PROGRESS checks —
EF-reachability over the explored graph ("from every reachable state
satisfying `from`, a `goal` state is reachable within the bound"). Explicitly
NOT liveness (no fairness, bounded horizon) and labeled as such everywhere.
- tools/plain-js/explore.mjs: state graph + progressViolations.
- lean_task_config.py: Acquire/Release progress for spin; Grant/Release for
  locksvc (state-changing — also strengthens future held-out replay).
- Results: never-releasing lean mutant fails ReleaseProgress (safety P4
  passes it); the inert-spec class fails AcquireProgress; reference specs and
  ALL 40 saved model-generated lean specs pass on both tasks — the
  strengthened Phase 4 hardens, not overturns, the 20/20 results.

Docs/paper: "template count differs" limitation replaced by the precise
in-principle statement + empirical demo + mitigation in
docs/js_sam_vs_tla_comparison.md, docs/js_sam_lean_contract.md, and the
paper's Experiment-3 setup + limitations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
… unconditionally

Review: the 2x2 in (contract, prompt) had an empty cell — the lean arm always
carried the semantics block, so "contract minimality buys transcription
fidelity" was conditional on spec-level semantics in the prompt, and the
prompt>contract>language ranking rested on an incomplete design with ceiling
effects.

Added the missing cell (pjd): the identical lean contract with the entire
single-step semantics block removed — the model derives the transition
semantics from the Rust source alone
(tla_eval/tasks/spin/prompts/plain-js/direct_call_nosemantics.txt).

Result: lean-without-semantics = 100% for EVERY generation of EVERY model —
including Haiku, at 28.6% under SAM with the same absent semantics. 20/20
generations, 5/5 unique spec texts per model (saved: output/study_specs/),
all behaviorally identical and correct. The suspected conditionality is
refuted: the contract effect holds without prompt semantics.

Completed factorial contrasts (generation-level exact permutation):
- contract effect, no semantics (js vs pjd): significant for ALL FOUR models
  (delta .186/.421/.500/.714, each p=.0079, the floor).
- prompt effect within SAM (js vs jsc): significant for only Fable (.379,
  p=.024) and Sonnet (.500, p=.0079); Opus and Haiku p=1.0.
- prompt effect within lean (pjd vs pjs): delta=0 — unidentifiable at ceiling.
This INVERTS the earlier headline: "prompt explains most of the reversal" was
an artifact of examining only the SAM column. Revised everywhere to:
contract first (uniform), prompt second (model-dependent), language nowhere.
Ceiling honesty stated: ordering among the 100% cells and any
prompt x contract interaction are not identifiable at the top; identifiable
claims are contract >= prompt per model and lean-sufficiency without
semantics vs SAM-insufficiency with it.

- tla_phase3_study.py: pjd arm; saves spec text for every new cell.
- tla_phase3_analysis.py: 5 arms, four factorial contrasts + 2x2 table.
- results doc: completed-2x2 section; finding 4 revised.
- paper: abstract, intro bullet, missing-cell paragraph, tab:crosslang
  column, findings rewrite, discussion + conclusion ranking corrected
  (contract, then prompt, then language), derivation open-question updated.
- methodology note aligned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
…he base rate

Review: of the 17 acquire windows, contention windows have post = pre — a
no-op acquire passes them free; report no-change counts and per-class rates.

Verified: 6 of 28 windows (all contention acquires) are no-change =>
identity base rate 21.4% overall, 6/17 = 35.3% on acquires. Experiment 1's
Haiku row is EXACTLY that base rate: Acquire 6/17 is precisely the six
freebies, Release 0/11, zero state-changing windows passed. Conditional on
the 22 change windows the Experiment-1 spread is 0% (Haiku) / 36.4%
(Fable, Sonnet) / 86.4% (Opus) — not 21.4–89.3%.

- tla_phase3_analysis.py section 0: corpus composition, identity base rate,
  per-(model, arm) change/no-change split, freebie share of passes. Study
  arms: every arm passes all 6 freebies; Haiku SAM+none is 9.1%
  conditional-on-change (75% of its headline passes were freebies),
  SAM+sem 24.5% (52.6%). Lean/TLA cells unaffected (100% everywhere).
- docs/js_sam_model_comparison.md: base-rate section; finding restated on
  the conditional metric (0–86.4%). Replication note: re-scoring saved
  Haiku specs shows a ±2-window sensitivity in sequential replay
  (async-error attribution timing); either value sits at/near the base rate.
- docs/js_sam_tla_phase3_results.md: conditional-on-change table for all
  five arms; correction largest where scores are lowest; no factorial
  conclusion flips.
- paper: abstract, intro bullet, Experiment-1 finding + tab:models caption,
  conclusion — spreads restated as 0–86.4% on state-changing windows with
  the base rate named.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
…order unobservable)

Found by the point-7 base-rate audit: 10 of the 60 locksvc windows were
no-op grants/releases — grants to a non-HEAD client under the fold's FCFS
assumption. Root cause: the fold ordered `waiters` by client-SEND order, but
the real server grants in ARRIVAL order, which the network reorders (one
capture logged sends 1,3,2 while the server queued <<3,2,1>> — exactly the
reordering upstream locksvc.tla's NoPriorityInversion comment warns about).
The old fold silently converted real state changes into no-op windows —
wrong ground truth.

Arrival order is not a function of the client-side single steps the trace
exposes, so at this projection waiters is a SET (canonically sorted array):
grant valid for any waiter of a free lock; FCFS is unobservable here.

- build_windows.py: set-semantics fold + documentation of the bug.
- reference_locksvc.js + plain-js prompt: set semantics.
- Corpus re-folded: 60 windows, 15 no-change (all legitimately the CS
  no-ops; identity base rate 25%), zero bogus no-op grants.
- Study invalidated and REGENERATED from scratch (old specs implemented
  head-grant FCFS): 20/20 fresh lean specs pass every phase again —
  including the bounded progress checks — over a 20-state space, 14/20
  unique texts (4/4/4/2 per model), 45/45 on state-changing windows.
- docs/js_sam_lean_contract.md + paper locksvc section: correction
  documented in-line; 31-state/11-20 numbers replaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
… fidelity"

Review: two hand-authored deterministic scenarios; no blocking-waiter wakeup
(the actual spin); locksvc's exactly-15-per-action suggests a deterministic
schedule; the vector-clock repair reorders ground truth per an asserted
assumption; and the conclusion's "interchangeable on fidelity" doesn't carry
the qualifier the Limitations admit.

Audited (scripts/trace_coverage_audit.py), with each sub-claim resolved:
- spin: confirmed — 2 deterministic ktest scenarios; 8/18 observable combos
  covered; contention is try-only (2 combos); ZERO blocking-lock-on-held
  windows. Sharper than the review: the instrumentation emits acquire at
  acquisition SUCCESS (pre=free), so the spin phase is invisible in this
  projection BY CONSTRUCTION — a woken blocking waiter is indistinguishable
  from an uncontended acquire.
- locksvc: half-confirmed — the exactly-15/action is protocol completion
  (one fixed terminating workload: 3 one-shot clients = 12 windows/run), not
  schedule determinism: the 5 runs are 5/5 distinct action sequences with 4
  distinct grant orders (including the network reordering that exposed the
  fold bug). No re-requests, no deeper contention, no failure paths.
- reorder premise: now DEMONSTRATED, not asserted — every CriticalSection
  event reads GrantMsg=3 from its mailbox (message-passing happens-before),
  verified per event; build_windows gains validate_cs_consumed_grant() and
  refuses traces where the premise fails.

Docs/paper: "Trace coverage" section in the results doc; the paper's
28-window limitation bullet replaced with the full audit; the conclusion's
"yes, interchangeably on fidelity" now reads "interchangeable on fidelity ON
WHAT WE MEASURED — a two-variable projection of deterministic, easy slices —
untested on uncovered combos, projection-invisible behavior, richer
workloads, or adversarial interleavings."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
Review: derivation (both languages), as-deployed agent path, multi-round
repair, other vendors, locksvc factorial — acknowledged but load-bearing;
plus independent replication given experimenter-authored materials on both
sides.

RUN — TLA+ derivation arm (tld: constrained TLA contract, semantics block
removed, mirroring the lean derivation arm). The factorial is now a full
3x2 (contract SAM/lean/TLA x prompt none/sem), and derivation splits the
languages for the first time:
- lean+none beats TLA+none for Opus and Sonnet (100% vs 78.6%, delta=.214,
  p=.0079 each), directionally for Haiku (87.1%, p=.17), tie for Fable.
- Mechanism precise, and NOT wrong posts: every TLA shortfall window is
  unscoreable. Without instruction, Opus/Sonnet (5/5 gens) and Haiku (3/5)
  write the classic TLA idiom — AcquireLock GUARDED on the lock being free —
  making a failed attempt a disabled action, not an observable no-op step;
  pinned contention windows then have no successor (TLC deadlock). Fable
  writes no-op-tolerant actions unprompted.
- Readings: the lean contract's TOTALITY (next() must return) structurally
  forbids the partial-action idiom; the semantics block was doing real,
  previously invisible work for TLA+ specifically (tld vs tla p=.0079 for
  the affected models); under pre-registered conditional scoring the
  guard-idiom specs are 100% on scoreable windows — both numbers reported.
  "Contract first, prompt second" refines to: the prompt matters exactly
  where the contract under-constrains.

RUN — multi-round repair (scripts/repair_multiround.py): Haiku does NOT
converge — 6/28 -> 6/28 -> 6/28 across three rounds of full-failure
feedback, exploration still 1-state; every round's spec saved and audited
(held-out + P2). Sonnet full-repairs in round 1 again (2nd full repair in 3
runs — more evidence the original "partial" tier was sampling noise).

NOT RUN, with stated reasons: as-deployed agent path (reintroduces the
confound the direct replay removed); other vendors (registry entries exist,
credentials don't — user-level unblock); locksvc factorial (lean arms done;
SAM/TLA arms need locksvc prompts + a locksvc TLC replay module).

REPLICATION.md: rerun every analysis from committed raw data with no API
access; regenerate every experiment from pinned prompts/models; inventory
of every experimenter-authored material (scenarios, semantics block,
derivation prompts, repair prompt, invariant sets, projections) as an
independent variable to audit or vary. Paper's positionality bullet now
names the experimenter-authored-materials problem and calls independent
replication (ideally by the SysMoBench maintainers) a prerequisite for
treating the cross-language conclusions as more than a single-author case
study. Missing-arms inventory bullet added with per-arm status.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
Camera copy incorporating the full nine-point review response: functional
(branching-factor) Phase-3 replay with permissive negative control; Phase-2
distinct-state metric audit (vacuous-pass finding); repair generalization
audit (held-out + inspection) and multi-round repair; generation-level
statistics replacing the pseudo-replicated pooled McNemar; safety-only
Phase-4 precision + bounded progress checks; the completed 3x2
contract-by-prompt factorial including both derivation arms (lean totality
vs TLA+ guard idiom); corpus base-rate analysis; trace-coverage audit with
the qualifier carried into the conclusion; locksvc corpus correction;
missing-arms inventory, replication guide, and the positionality/independent-
replication statement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PX1erummaoUTV7NKhQAdr5
@jdubray

jdubray commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

Review response complete — all nine points addressed, paper finalized

Final camera copy: paper/js_sam_sysmobench.pdf (f3e574e). Point-by-point map:

# Review point Resolution Commits
1 Phase-3 replays check different relations (existential TLC favors TLA+) Functional (branching-factor=1) check both sides; permissive control passes existential 28/28 but scores 39.3% functional; all 20 generated TLA+ specs audited: bf=1 everywhere, 0 over-permissive — the 100% is earned 0fc805d
2 326,592 identical "states" is a red flag Confirmed: steps=(d+1)·6^d, model-independent. Distinct-state metric added: 7/7/7/1 — Haiku's P2 pass was vacuous (inert spec); TLC parser now takes distinct, not generated (574→159); three incommensurable counts reconciled c075252
3 Repaired specs may memorize the shown windows Re-run with artifacts saved; scored on held-out combos + P2/P4; inspection: root-cause fixes (aux-guard→authoritative keys), zero lookup tables. Replication surprise: Sonnet repaired fully — three-tier claim revised to two-tier e0160c9
4 McNemar pseudo-replicated Confirmed worse: 1–2 unique behavioral fingerprints per cell. Pooled χ² retracted; exact generation-level permutation test + uniqueness for every arm. Fable's starred constrained-arm deficit withdrawn (p=.44) 848fe97
5 Phase 4 can't fail the headline bug in principle Demonstrated: never-releasing mutant gets identical 3/3 verdict (pinned as characterization test). Bounded EF-progress checks added (labeled not-liveness); they fail the mutant and the inert class; 40/40 lean specs still pass 238972c
6 2×2 has an empty cell (lean always had semantics) Cell run: lean-without-semantics = 100% for every model incl. Haiku (28.6% under SAM). Headline inverted: contract first (uniform), prompt second (model-dependent), language nowhere; ceiling non-identifiability stated 36e3e22
7 Base-rate analysis of the corpus 6/28 windows are freebies; Haiku's Experiment-1 row IS the identity base rate (0/22 on change windows). Spread restated as 0–86.4% conditional-on-change. Bonus: the audit exposed a real locksvc corpus bug (send-order FCFS fold fabricated 10 no-op windows) — fixed, corpus re-folded, study regenerated: 20/20 stands bcda8fd, 91cba6b
8 Trace coverage is the quiet weakness Audited: spin 8/18 combos, zero blocking-lock-on-held windows (spin phase invisible in projection by construction); locksvc = fixed workload but genuinely 5/5 distinct schedules; reorder premise now verified per event (CS reads GrantMsg) with a build-time check. Conclusion's "interchangeable on fidelity" now carries the qualifier 5b214c0
9 Missing load-bearing arms + independent replication Run: TLA+ derivation arm (3×2 complete — derivation splits the languages: lean 100% vs TLA+ 78.6% for Opus/Sonnet, all shortfall unscoreable guard-idiom partiality; lean totality does structural work); multi-round repair (Haiku doesn't converge: 6→6→6). Not run with stated reasons: agent path, other vendors (key-blocked), locksvc factorial. REPLICATION.md ships: all analyses re-derivable from committed data without API access; every experimenter-authored material inventoried as an auditable variable; paper calls independent replication a prerequisite 0680693

Every claim's raw artifacts are committed (per-window JSON, all generated specs, audit results, trace corpora). Analyses re-run without API access via scripts/tla_phase3_analysis.py and scripts/trace_coverage_audit.py.

🤖 Generated with Claude Code

kizo-core and others added 10 commits July 6, 2026 06:50
…ask, corpora, reference models)

New SysMoBench task extracted from a production restaurant POS (baanbaan
Merchant/v2): pinned 7-variable observable-state contract, 9-action dispatch
alphabet, prompts for the SAM / lean-JS / constrained-TLA+ arms, verbatim
source packet with provenance (EXTRACTION.md).

- Real-execution trace corpora (emulator + chaos proxy): v1 75 windows /
  17 scenarios, v2 101 windows (3 windows per acceptor-rewrite rule),
  post-fix corpora for patch validation; instrumentation diff in
  data/patches/finixpos_trace.patch.
- Hand-written reference models: FinixPOS.tla + environment (TLC-checked),
  crash/staleness/two-attempt/refund extensions, patched-semantics variants
  (FinixPOSFixed*, FixedV2), annotated counterexamples for every confirmed
  gap, sandbox reproduction evidence (real Finix 422 shape), CONTROLS.md
  (positive + mutation controls), CONTAMINATION.md, EXTENDED_FAILURE_MODEL.md.
- trace_loader: accept the harness's {pre, action, data, post} event form
  (+ test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011bXyeoAhL6iz3CibTtU2Qv
- Drivers: finixpos_tv.py (mechanical replay, all three arms; validated by
  positive + mutation controls), finixpos_phase3_study.py (two-phase
  generate-then-score with Anthropic prompt caching), v2 rescore, post-fix
  study (best-model+arm revalidation), final analysis (permutation tests,
  uniqueness counts).
- Results of record: 7 models (Fable 5, Opus 4.8, Sonnet 4.6, Haiku 4.5,
  mistral-large-2512, devstral-2512, labs-leanstral-1-5) x SAM / lean /
  TLA+ x N=5, scored on both corpora; all 105 + 10 generated specs and
  per-window statuses committed for the audit trail.
- Headline: lean next() contract best-or-tied and never unscoreable for all
  seven models across two vendors; TLA+ deficits dominated by checkability
  and the guard idiom; SAM ceremony kills specs in model-specific ways;
  semantic error is language-independent. Replication is of ordering and
  taxonomy, not level - only Claude frontier models reached deployment-grade
  reliability.
- config: mistral-large / devstral / leanstral model entries.
- docs: study plan (pre-registered design) and full results incl. the
  correctness deliverable (7 confirmed gaps + review-response revisions,
  patch v1/v2 validation, accepted-operational-risk scoping).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011bXyeoAhL6iz3CibTtU2Qv
…prompt

Ground Phase 3 for the etcd task. Upstream etcd-io/raft (pinned 26647d5)
ships complete trace instrumentation behind the with_tla build tag; the
harness adds only an NDJSON TraceLogger sink and five deterministic
3-node RawNode scenarios (election, proposals, heartbeats, stale-campaign
vote rejection, higher-term re-election), then folds the event streams
into whole-cluster observable windows {nodes: {id: {role, term, vote,
commit, log}}}.

Corpus: 97 windows across the five canonical target actions, zero
self-consistency violations, byte-identical regeneration, provenance
pinned per run. A hand-written reference lean spec replays 97/97 through
the benchmark's own sandboxed path (positive control); two projection
approximations (index-only vote up-to-dateness, heartbeat commit clamp)
are documented in the README and exact on these corpora by construction.

The plain-js prompt is the lean derivation form: contract pinned (state
shape, action/data schemas, {init, next} module), no semantics block —
transition semantics must be derived from raft.go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…round repair

Study (scripts/lean_full_pipeline_study.py --task etcd --n 5): all 20
specs load and explore cleanly (~130k distinct states at depth 8), none
passes every phase one-shot. Fable reaches 97/97 conformance on 4/5
generations but violates CommitWithinLog under open-world exploration
(unclamped heartbeat commit); the other models fall short on Phase 3,
dominated by HandleVoteRequest (step-down/vote-reset/up-to-dateness).
Per-window failure detail in output/lean_etcd_p3_failures.json.

Repair round (scripts/repair_lean_etcd.py): each failing spec gets its
own module, every failing window with expected vs actual, and invariant
counterexamples; one rewrite; full re-score. Result: fable 4/5 all-pass,
claude 2/5, sonnet 0/5 (3/5 cells regress below baseline), haiku 0/5
(all cells plateau at 93/97 on the same four vote windows). tv.mjs now
returns the module's produced state on failed windows to support the
feedback loop (backward-compatible).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paper sources are working drafts, not benchmark artifacts; keep them out
of the repository.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paper sources are working drafts, not benchmark artifacts; keep them out
of the repository.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kizo-core and others added 2 commits July 22, 2026 18:19
… semantics)

The strict-profile breaking change (frozen pre-state, next-draft writes,
mandatory unchanged() framing) does not affect this helper or generated
specs, which run in default mode: full pytest suite 23/23 green against
2.1.2 in Docker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UfSbyWEBtEwBsxB2CuSYYv
Add an `opus5` model entry (claude-opus-5) and extend the sampling-omit
list to the Claude 5 family. `claude-sonnet-5` had only ever been patched
in the sysmobench-etcd worktree, so the main repo would have 400'd on it.

Smoke test (spin / TLA+ / direct_call, N=5), written up in
docs/opus5_smoke_test_results.md:

  P1 5/5. P2 first shot 1/5 -- four specs die on
  `NULL == CHOOSE v : v \notin Threads`, an unbounded CHOOSE that SANY
  accepts and TLC refuses, so they explore 0 states. One repair round
  takes P2 to 5/5 with zero regressions. P4 is 35/35.

  The deficit is idiom selection, not modeling ability. Single-shot P2
  understates it by 5x; the repair round is where the signal is.

Three harness bugs, two of which corrupt scores independent of model
quality:

1. Phase 4 appends `<Name> == ...` to the generated module, colliding
   with the spec's own operator when the model names its invariants
   after the expert templates. SANY rejects the module and the invariant
   is scored FAIL without TLC running. Perfect correlation across 5 runs
   x 7 invariants. scripts/rescore_p4_name_collision.py re-scores
   mechanically; all 8 collided invariants then pass.

2. extract_tla in repair_tla_spin.py takes the FIRST ```tla fence, so a
   reply that quotes the offending line before emitting the rewrite
   loses its module. repair_tla_spin.py is committed AS IT RAN, unfixed,
   so the recorded Experiment-3 numbers stay reproducible -- but those
   numbers were produced with this extractor and any parse-failure
   repair there should be re-checked. The corrected extractor lives in
   repair_tla_spin_opus5.py.

3. transition_validation is unusable on Windows: transition_validation.py
   passes Windows paths to bash, which eats the backslashes (exit 127).
   Phase 3 never ran, so whether these specs replay real traces is still
   open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HkfxRoMS4KxrSyxLB5PpmY
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.

2 participants