Skip to content

WIP: saw-core-lean — SAWCore→Lean 4 backend - #3214

Draft
septract wants to merge 624 commits into
GaloisInc:masterfrom
septract:saw-core-lean
Draft

WIP: saw-core-lean — SAWCore→Lean 4 backend#3214
septract wants to merge 624 commits into
GaloisInc:masterfrom
septract:saw-core-lean

Conversation

@septract

@septract septract commented May 2, 2026

Copy link
Copy Markdown

Note: this branch and PR description are AI-generated (Claude Code, supervised). Treat all claims — including soundness claims — as needing human review.

Summary

Adds saw-core-lean, a SAW proof backend that discharges SAW proof obligations in Lean 4's kernel. It fills the same slot as a solver backend — SAW emits a verification condition, the backend presents it to another trusted engine, and success closes the obligation — except that the artifact is kernel-checked, inspectable, and replayable. Translation of SAWCore/Cryptol to Lean is the means, not the product.

Generated .lean files import a small handwritten support library (saw-core-lean/lean/CryptolToLean/) and elaborate under lake build on pinned toolchain leanprover/lean4:v4.32.0.

Status: NOT RELEASED. Three of the four 0.02 release-gate clauses are met; the outstanding one is a green CI run (gate: saw-core-lean/doc/2026-07-30_convergence-closeout-plan.md §5). The current state of the world is saw-core-lean/STATUS.md; the open work list is saw-core-lean/TODO.md.

Design

The translator normalises each input term with scNormalize to a fixed point, then walks the result: after normalisation only SAWCore primitives, inductives, and recursors remain, mapped 1:1 onto the handwritten Lean support library. User definitions are inlined rather than emitted as separate Lean defs. This sidesteps the Lean cumulativity gap that blocked a direct mirror of the Rocq backend (saw-core-lean/doc/archive/2026-04-23_specialization-approach.md).

Since the original description, the implementation moved to the position/callee calculus (saw-core-lean/doc/2026-07-02_position-callee-calculus.md), which is now the implementation rather than a design on paper:

  • Value-domain expressions translate at Except String T; type-level expressions translate raw. Every translation is directed by a declared ExpectedPosition, callees carry declared ArgMode conventions, and adaptation happens at a single chokepoint (adaptTo) where forbidden adaptations are unrepresentable.
  • Producers stamp TranslatedTerm production records; shape is never re-derived by inspecting emitted Lean (that inspection class is deleted, and a source lint keeps it deleted).
  • Recursors run at a declared convention, and every directly-emitted @Foo.rec carries a Lean-checked constructor-order assertion (saw_ctor_order), so a reordered inductive on either side fails the emitted file loudly.
  • Prelude.fix and partial operations route through proof-carrying obligations with Lean-checked evidence; recognized fix classes lower to proven realizations, everything else rejects with a named diagnostic.

Commands

  • write_lean_term — one Term + type → a noncomputable def.
  • write_lean_cryptol_module — a Cryptol .cry file → a Lean namespace of defs.
  • write_lean_cryptol_primitives_for_sawcore — regenerate the Cryptol primitives module.
  • offline_leanemission-only: writes the goal file and returns SolveUnknown, so the SAW goal stays unsolved and scripts wrap it in fails. SAW never claims a goal on the strength of an export.
  • offline_lean_replay — the discharge path: re-emits the goal fresh (fresh emission is the authority), checks a user-completed proof against it under a factored trust kernel (saw-core-lean/replay/lean-check-core.sh: exact-match axiom allowlist, sorry/placeholder policy, drift and closer probes), and only on full success admits the goal with recorded LeanReplayEvidence. Design: saw-core-lean/doc/2026-07-16_replay-design.md.

Coverage today

Works end-to-end: monomorphic instances; Cryptol modules with {a}-polymorphism over Type 0; the examples/saw-lean/ demo; compositional replay chains (the ChaCha20 core/qround family, llvm_eq_u128, llvm_popcount_eq, llvm_doubleround_comp); recognized fix classes (running sum, popcount32, rec_ones).

Punted, all with named diagnostics or pinned gaps — 72 known-gap rows, re-verified 2026-07-31, tabulated in STATUS.md: iterate-family and paired-stream fixes, direct recursors for Nat/Pos/Z/Bool/Accessible*, user datatypes, class-dictionary primitives (PCmp, PEq, PRing, …), SMT-array/enum/polynomial surfaces, raw-position error and raw-position fix, native Lean.BitVec as the bitvector type. Much of the growth in that census since 0.01 is deliberate withdrawal: the *WithProof primitives (LIB-2) and raw-position fix (S-2) were removed because their emitted statements were strictly weaker than the SAW obligations they claimed, and each withdrawal converts green rows into pinned gaps so the capability loss stays visible.

One live usability defect worth flagging for review: any unused binder in a property makes the emitted file fail to elaborate (\(x : [8]) (y : [8]) -> x == x fails on y alone). It fails closed — replay refuses with emitted-does-not-compile — but the user sees a raw Lean instance error rather than a named refusal, which is the actual defect. Surveyed 2026-07-31; tracked in TODO.md.

Soundness — read before relying on a replayed goal

No skip lists, no sorry in the translator, no close-but-not-equal mappings. Trust authority and axiom inventory: saw-core-lean/doc/2026-05-02_residual-trust.md (the axiom base is exactly two Vec↔BitVec round-trip axioms). Three residuals ship documented, and reviewers should weigh these specifically:

  • LIB-1 (§3.2e) — wrapped-vector carrier. SAW's vectors are element-lazy; the Lean carrier Except String (Vec n T) collapses any erring element into failure of the whole vector. That collapse is non-injective and lands on both sides of an emitted equation, so a SAW-false equation whose falsity hides behind an unread erring slot can close by rfl in a clean kernel — invisible to every replay gate by nature. Reachable from ordinary Cryptol; no landed proof is affected; pinned by otherTests/saw-core-lean/differential/lazy_vector_error_slot. Remedy (0.03): the faithful per-element carrier Vec n (Except String T), which cannot represent the collapse by construction.
  • §3.2f — goal-formation trivialization. The replay-time canary that refused goals closable by rfl/trivial alone was deleted on 2026-07-31 rather than hardened a fourth time (doc/2026-07-31_kernel-design-review.md): three audit rounds in one day showed its implementation could not be kept honest, and it was not one of the checks that ask Lean's kernel a question. What defends against emitter over-reduction instead is the differential corpus and the fact that a trivialized goal is visible in the file you open.
  • §3.2g — hypothesis-bearing goals. Goals carrying folded sequent hypotheses whose Lean image mentions the value carrier are refused, because such a hypothesis can be uninhabited in Lean where the SAW hypothesis is true, making the implication vacuously provable. A CRITICAL was found and fixed on this branch on 2026-07-31: a named hypothesis binder escaped the gate and a false SAW obligation emitted a Lean-provable goal (root cause: doc/2026-07-31_why-gate3-escaped.md). The gate took four cuts that day. It refuses every such goal anyone has built and the corpus is green, but its completeness is not established — the durable fix, deciding this on the SAWCore side where it is a sort check rather than a reconstruction of emitted Lean, is scheduled for 0.03.

Threat model (residual-trust.md, "Threat model"): the replay gates defend against mistakes, ours and yours — not against an adversarial proof author. Elaborating a Lean file executes code, so proof files from an untrusted source deserve the same review as any untrusted program, and third-party LeanReplayEvidence is a claim to be re-established by re-running replay, not a proof.

Audit trail: seven release-gate audit waves plus whole-surface soundness and fidelity reviews, indexed in saw-core-lean/doc/audit-history.md and doc/decision-log.md. One real soundness defect in the trusted support layer was found and fixed this way (bvToInt realized as signed where SAW's is unsigned), which prompted a 200+-case labeled differential edge-case matrix.

Verification layers

  • cabal test saw-core-lean-smoketest — 94 Tasty tests (translator internals, source lints, fix-recognizer classifier, goal-shape gate).
  • otherTests/saw-core-lean/ — data-driven suite run by one orchestrator: drivers/ and workflows/ (SAW output diffed against goldens, emitted Lean elaborated), differential/ (SAW-vs-Lean observations compared case by case), obligations/, proofs/, support-lemmas/, proof-gaps/ (honest inventory of undischargeable obligations), negative/, saw-boundary/ (rejection diagnostics), stretch/.
  • saw-core-lean/lean/lake build green on the pinned toolchain, including saw_ctor_order positive/negative self-tests, #guard_msgs behavior fences, and linter.missingDocs over all public declarations.
  • An emitted-Lean byte-diff snapshot oracle (350 artifacts) used to certify refactors as behaviourally inert.
  • CI runs all three layers on every push (.github/workflows/ci.yml).

Reviewing this

The diff is large but concentrated: ~1580 files under otherTests/saw-core-lean/ are test rows and goldens, ~120 are dated design/audit docs, and the translator itself is saw-core-lean/src/ plus small touches to saw-script/src/SAWScript/Interpreter.hs, saw-central, and saw.cabal. Outside saw-core-lean/ the footprint is deliberately short pointers.

Suggested entry points: saw-core-lean/README.md (limitations first), saw-core-lean/STATUS.md (what works today), doc/getting-started.md (a 30-minute walkthrough from a Cryptol property to a goal SAW accepts on Lean's authority), doc/architecture.md, and doc/2026-05-02_residual-trust.md for anything soundness-shaped.

@sauclovian-g

Copy link
Copy Markdown
Contributor

It seems to have dumped a lot of lean-only stuff in intTests, where it really doesn't belong. Assuming those have any value at all (not immediately clear), they probably ought to be in otherTests/saw-core-lean. FWIW

septract and others added 27 commits July 9, 2026 06:28
adaptTo is now the single raw/wrapped adaptation chokepoint, total
over the calculus's allowed adapters and loud on the forbidden ones:

- adaptTo rho t: identity at the same position; raw -> runtime via
  Pure.pure; non-lambda terms accepted at function position. Wrapping
  a function, demanding a wrapped value raw without a bind context,
  or wrapping a motive throws the new ForbiddenAdaptation error
  (never to be caught and defaulted).
- translatedTermAsWrapped is DELETED. All ~20 call sites (bind
  chains, wrapped-formal tables, equality subjects, recursor case
  bodies, ArrayValue lifts, top-level def wraps in Term.hs and
  CryptolModule.hs) now route through adaptTo/adaptToRuntime.
  adaptWrappedFormal survives as a thin monadic wrapper over adaptTo.
- subjectTerm (equality subject emission) is monadic and adapts at
  the declared EqualitySubjectRep's position.

Emitted Lean verified BYTE-IDENTICAL to the Slice 0 baseline across
all 151 artifacts - no current row exercises a forbidden adaptation.
Conformance exit 0 (188 OK rows); smoketest 54/54.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bindingShapeOfTerm / bindingShapeOfLeanTermM (shape guessed from the
emitted Lean AST) are deleted. Shape is an output of translation or a
record in Gamma, never re-derived from generated syntax:

- Variables read their introduction site's BindingInfo from Gamma
  (keyed through namedEnvironment; absent keeps the historical raw
  default).
- Module-identifier constants reuse the shape the ident dispatch
  already computed (translateIdentWithArgsWithShape) instead of
  re-guessing from the emitted term; imported realizations derive
  shape from the constant's SAWCore type.
- Sites where the old guess was structurally constant (App heads,
  bare global references) now state BindingRaw explicitly with a
  note pointing at the Slice 4 convention that will own them.
- A shared-let name referenced before its Gamma record is now a loud
  internal error instead of a silent raw guess.

Fence: smoketest 54/54; conformance exit 0 (188 OK); emitted Lean
byte-identical to baseline. drivers/arithmetic and
drivers/conformance_stream fail with stale goldens, verified
PRE-EXISTING by rebuilding at 89a6cef (before this refactor);
tracked in TODO.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…3 groundwork)

The shared/unshared term walk now takes the expected position as an
explicit Maybe parameter: translateAt enters translateSharedAt
(Just rho); legacy translateTermWithShape/translateTermUnsharedWithShape
are Nothing-specializations. The position applies to the current term
only - deliberately an argument, not a reader field, so a declared rho
can never leak one level too deep into subterms that did not declare
one. Case arms consume it family by family as Slice 3 lands (3a value
lambdas, 3b index binders, 3c motives, 3d let sharing).

Behavior-inert: smoketest 54/54, conformance exit 0, emitted Lean
byte-identical to baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
drivers/arithmetic and drivers/conformance_stream goldens predated the
2026-07-03 position-callee-conventions work (55e4fe0, 4294528) and
have been failing since. Every diff hunk was verified to be one of the
two intended emission changes from that work, nothing else:

- unsafeAssert obligations state the Eq universe explicitly:
  @eq Num x y -> @eq.{1} Num x y (t11/t12).
- Stream recursor case handlers own the Pure.pure lift, with motive
  Except String Nat instead of Nat - error behavior of the scrutinee
  case is preserved inside the handler rather than wrapped outside.

Both rows now exit 0; make conformance exit 0; emitted-Lean baseline
snapshot recaptured (166 files).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ExpectFunctionPosition now carries Maybe FunctionConvention (per-binder
positions + result position, calculus 'R(FunctionConvention c, Pi) =
F(c)'); Nothing remains as the tracked convention-not-yet-declared
bridge that Slice 4 shrinks.

- translateBinderAt: binder introduction optionally driven by a
  declared position; Nothing reproduces the legacy flag-driven wrap
  exactly (translateBinder' is its specialization). A convention
  demanding a runtime value for a sort binder throws
  ForbiddenAdaptation. Gamma records the declared position verbatim.
- translateLambdaAtConvention + a Lambda arm in
  translateTermUnsharedWithShapeAt: a lambda entered at a declared
  convention takes binder wraps and body position from the convention,
  not from shouldWrapBinder re-derivation. Arity mismatch rejects.
- Producers: MkStream index functions and non-dependent UseArgFunction
  wrapped-helper lambdas compute their convention once (the value-slot
  predicates are now convention-internal at those sites, per plan
  Slice 3.4) and push it down. They translate the lambda in place
  (bypassing the sharing lookup) because the legacy path destructured
  it inline - preserves byte-parity for shared lambdas. Dependent
  (typeIxs /= []) and sort-binder lambdas stay legacy until Slice 3b.

Implemented by an Opus subagent against the written work order;
independently reviewed and re-validated. Fence: smoketest 54/54;
conformance exit 0 (188 OK); emitted Lean byte-identical to baseline;
position trace confirms the new path live on obligations/mkstream_total
(ExpectRaw index binder -> ExpectRuntimeValue result, no inconsistent
productions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends the Slice 3a machinery to dependent value lambdas: both
producer sites (MkStream index functions and UseArgFunction wrapped-
helper lambdas) drop their non-dependent guards, so every lambda they
accept translates at a fully-declared FunctionConvention.

- Index binders (variable free in later binder types / result type)
  declare ExpectRaw RawIndexPosition; sort-typed type binders declare
  ExpectRaw RawTypePosition.
- translateBinderAt: a sort-typed binder at declared RawTypePosition
  allocates its universe under the legacy SortBinderAsType mode when
  Phase-beta is enabled (reproducing translateBindersSelective's
  per-binder enterCtx), scoped to that binder's type translation only.
- The slot predicates remain convention-internal at the producers.

Implemented by an Opus subagent against a written work order;
independently reviewed and re-validated. Fence: smoketest 54/54;
conformance exit 0 (188 OK); emitted Lean byte-identical to baseline;
additionally ~150 driver rows (incl. llvm/salsa20 verification rows)
all golden-green under this binary. The dependent positions are
correct-by-parity but likely dormant in the current corpus: most
helper function formals are non-dependent by construction, and the
dependent lambda family mostly flows through the still-legacy generic
Lambda case (remaining Slice 3/4 work).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recursor motives now translate at a declared MotiveConvention instead
of rediscovering their shape through the blanket skipBinderWrap flag:

- MotiveConvention { mcBinderPositions, mcResultMode }: the calculus's
  'motive binder positions' and 'motive result position' fields
  (doc/2026-07-02, section Recursors / Eq.rec) as data. The result
  mode is deliberately not a FunctionConvention result position: a
  motive body is a TYPE-level expression, and a value-computing motive
  wraps its body type in Except String (wrapExcept), never a
  Pure.pure value lift.
- motiveConventionFor derives the convention at the recursor dispatch
  from recursorNumIxs and the already-classified RecursorConvention
  result mode: index binders declare ExpectRaw RawIndexPosition, the
  eliminated scrutinee ExpectRaw StructuralRecursorFieldPosition.
  Neither is RawTypePosition, so sort-typed motive binders keep the
  surrounding sortBinderMode (unlike value-lambda type-binder slots).
- translateMotiveAtConvention introduces binders via translateBinderAt
  at the declared positions and replaces the where-local
  translateRecursorMotive; its 'set skipBinderWrap True' site is
  deleted. Motive translation is traced/stamped at
  ExpectRaw RawMotivePosition.

Fence: smoketest 54/54; conformance exit 0 (188 OK); emitted Lean
byte-identical to the 312-file baseline, including freshly re-emitted
recursor-heavy driver rows (conformance_stream, records,
conformance_record, cryptol_module_stream_fibs,
cryptol_module_rec_ones - all exit 0); position trace live on
conformance_stream (rho=ExpectRaw RawMotivePosition, consistent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
translateTermLetAt: the let-sharing entry takes the expected position;
the shared RHSs still translate at their own natural positions with
exact Gamma records (Slices 1-2), and the BODY - whose value the
let-chain delivers - now translates at the demanded position.
translateTermLetWithShape is the Nothing specialization;
translateLambdaAtConvention passes its declared result position.

A shared RHS demanded at an incompatible position already fails loudly
in adaptTo (ForbiddenAdaptation); emitting separate bindings for
genuinely position-polymorphic shares stays future work, to be pinned
by a fixture if one ever demands it.

Fence: smoketest 54/54; conformance exit 0; emitted Lean byte-identical
to baseline; sharing-heavy driver rows re-run under this binary
(cryptol_module_dag_sharing green; cryptol_chained_projection_share
fails on a PRE-EXISTING stale golden of the known @eq.{1} class -
golden dated 2026-07-01, emission byte-identical to the pre-3d
baseline; tracked for the golden sweep).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Swept all 18 driver rows whose goldens contained un-universed @eq
(candidates for staleness since the 2026-07-03 emission changes).
Outcome:

- REFRESHED (every hunk verified in a known intended class):
  conformance_proof_obligations, cryptol_chained_projection_share,
  llvm_point_verify, offline_lean (t1-t7), offline_lean_e_series
  (E1-E7) - the @eq.{k} explicit-universe class; plus
  llvm_popcount_verify and cryptol_running_sum_verify - the
  checked-access class (atWithDefaultM / saw_throw_error replaced by
  atWithProof_checkedM with visible h_bounds obligations, from
  27d749f/d16870367). All refreshed rows re-run green including
  Lean elaboration.

- NOT refreshed: llvm_chacha20_core_verify. Its current emission does
  NOT elaborate: the checked-access contract feeds a wrapped shared
  index (x__... : Except String Nat) raw into the LT.lt bounds
  proposition and atWithProof_checkedM - CheckedArgRaw performs no
  adaptation, so a wrapped actual escapes into raw positions.
  Pre-existing from the 2026-07-03 checked-access work; goldens kept
  at the last elaborating emission. Recorded in TODO.md as a live
  specimen for Slice 4: the checked-application convention must bind
  wrapped index actuals through error-preserving Bind.bind before the
  proposition/helper consume them, or reject.

Conformance exit 0; emitted-Lean baseline recaptured (312 files).
Sweep executed by an Opus subagent (classes 1-2) with supervisor
verification and class-3 handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the golden sweep. The four remaining bounds-overhaul rows
(llvm_chacha20_q_verify, llvm_eq_u128_verify, llvm_salsa20_q_verify,
offline_lean_popcount32) were refreshed and re-run green INCLUDING
Lean elaboration - unlike llvm_chacha20_core_verify, whose emission
does not elaborate and which stays red as the Slice 4 specimen.

Also records a second pre-existing upstream regression pair, verified
failing at pre-refactor commit 89a6cef:
cryptol_chacha20_core_iterate and cryptol_chacha20_iround_zero reject
with 'Refusing to translate primitive Prelude::Stream@core' (wrapped-
scrutinee recursor convention) while their goldens expect successful
translation. Tracked in TODO.md for an upstream decision (restore a
stream-comprehension translation path or migrate to an expected-
rejection category); NOT golden-refreshed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slices 0-3 landed fully byte-identical, so the behavioral risk the
plan spread across Slices 2-4 is concentrated in Slice 4; give it the
same sub-slice discipline as Slice 3. 4a fixes the live
llvm_chacha20_core_verify specimen (wrapped index at raw checked-
application slots) and is the first reviewed-diff fence; 4b covers
phase-beta definitions + partial-op unification and the dormant
dependent-convention fixtures; 4c retires CalleeTransitional and
decomposes the dispatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lice 4a)

The calculus's callee-convention vocabulary is now data (ArgMode with
TypeArg/IndexArg/RuntimeArg/ProofArg/FunctionArg/... and ResultMode),
and the checked-application contracts are re-expressed in it: the old
three-way CheckedArgRaw bucket splits into its true IndexArg (width,
index) and TypeArg (element type) slots, matching the checked helpers'
Lean signatures. CheckedApplicationArgMode is deleted.

The interpreter (checkedApplicationHelperArgsFor /
lowerCheckedApplicationHelperArgs) returns per-actual verdicts:

- RuntimeArg adapts through adaptTo ExpectRuntimeValue; TypeArg and
  RawValueArg adapt at their raw positions (forbidden shapes throw).
- IndexArg with a WRAPPED actual - a runtime-computed index - is
  sequenced through an error-preserving Bind.bind ahead of the bounds
  obligation, in application order; the bound RAW variable is what
  both the proposition and the checked helper consume. The wrapped
  value is never opened and never defaulted. A function-shaped index
  throws ForbiddenAdaptation.
- The contract's declared ResultMode drives the result shape.
- A proof-carrying generator bound that is itself a runtime-computed
  index rejects loudly (no convention for it yet).

This fixes the live specimen drivers/llvm_chacha20_core_verify:
previously the wrapped shared index escaped raw into LT.lt and
atWithProof_checkedM and the emission did not elaborate. It now
emits Bind.bind idx (fun v_idx_k => obligation over v_idx_k; helper
... v_idx_k h_bounds_) and is green end-to-end including elaboration;
goldens refreshed to the corrected emission (the only row whose
emission changed - everything else byte-identical to baseline).

New fast fence row obligations/vector_at_runtime_index pins the
bind-chain shape (bvToNat of a bitvector sum as the index to at).

Fence: smoketest 54/54; conformance exit 0 (189 OK incl. the new
row); snapshot byte-identical except the specimen; specimen and
fixture green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… step 1)

PartialOpWrapped's private two-way arg-mode enum is deleted; its
tables now declare calculus ArgModes (bitvector widths IndexArg,
value operands RuntimeArg), and the wrapped partial-op lowering
shares the checked-application machinery:

- lowerProofCarryingActuals generalizes the 4a lowering over the
  obligation name stem, proof-script builder, proposition, and helper
  head; lowerCheckedApplicationHelperArgs and
  buildWrappedProofCarryingApplication are now thin instantiations
  (h_bounds_/boundsProofScript vs h_nonzero_/partialOpProofScript).
- A WRAPPED actual at a width IndexArg slot is sequenced through the
  error-preserving bind chain instead of escaping raw into the
  nonzero proposition - the same protection 4a added for vector
  indices, closing the same latent hazard class for bvUDiv/bvURem/
  bvSDiv/bvSRem/ecSDiv/ecSMod before any row trips it.

Proof-primitive contracts deliberately NOT relabeled yet: their raw
slots are raw-LOGICAL translations (withRawTranslationMode), so an
ArgMode relabel has no behavioral content until 4c unifies the
tables with the SAWCore signatures in hand.

Fence: smoketest 54/54; conformance exit 0 (189 OK incl. the
partial_* zero-divisor rows); emitted Lean byte-identical to
baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CalleePhaseBetaDefinition analysis: the convention derivation must
take supplied type actuals (paramActualAlreadyExpected is the last
emitted-AST-inspection class); legacy bind semantics per formal
family; migration via a behavior-inert equivalence assert against
argumentBindPlan before the swap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tep 2)

Two loud equivalence asserts now run inside 'applied', both inert
(the legacy bind plan remains authoritative):

1. phaseBetaArgModesFor derives the CalleePhaseBetaDefinition
   argument modes from the callee's SAWCore Pi type + supplied
   actuals; phaseBetaBindFromMode recomputes the bind plan from the
   modes; any disagreement with argumentBindPlan throws. Silent
   across smoketest, conformance (189 OK), and the full driver
   corpus (subagent sweep + re-runs).

2. polymorphicFormalInstantiatedExpectedSrc is the source-based
   replacement for the emitted-Lean-type instantiation predicate
   (the translator's last emitted-AST-inspection class), asserted
   equivalent to it. The first candidate (shouldWrapBinder on the
   instantiation) was REJECTED BY THE ORACLE on the smoketest:
   value-domain instantiation (PairValue's a := Vec 8 Bool) is not
   the same question as wrapped-representation instantiation - those
   formals bind to raw Lean constructor formals. The corrected
   candidate (Pi-instantiation only) is silent across smoketest,
   conformance, and the polymorphic-heavy driver rows
   (records/tuples/sequences/offline_lean).

The emitted-type predicate is quarantined in
polymorphicFormalInstantiatedExpected with a note; the swap deletes
it once the two-oracle binary has swept the remaining driver rows.

Fence: smoketest 54/54; conformance exit 0; emitted Lean
byte-identical to baseline.

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

The two-oracle driver sweep surfaced three failing rows, all
bisect-or-class verified PRE-EXISTING:

- cryptol_module_popcount and cryptol_module_salsa20_q: bounds-
  overhaul golden staleness (atWithDefaultM/saw_throw_error ->
  h_bounds obligations, from the 2026-07-03 checked-access work).
  Missed by the earlier @Eq-grep sweep because their goldens contain
  no bare @eq. Refreshed per-row; both re-run green including
  elaboration.
- sawcore_prelude_auto_emit: write_lean_sawcore_prelude rejects on a
  function-carrier equality in a prelude lemma (the 55e4fe0
  raw-logical slice's pinned rejection) while its golden expects
  successful emission. Verified failing at pre-refactor commit
  89a6cef. Tracked in TODO.md as likely resolved by Slice 5.4's
  function-carrier equality convention; goldens NOT refreshed.

Both convention oracles remained silent across the entire driver
corpus (zero hits for either assert string).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…th (4b step 3)

The CalleePhaseBetaDefinition convention is now authoritative on
'applied''s full-application path: phaseBetaArgModesFor derives the
argument modes from the callee's SAWCore Pi type plus the SUPPLIED
SOURCE actuals (polymorphic formals classify by
polymorphicFormalInstantiatedExpectedSrc - Pi-instantiation from the
source term, no emitted-Lean inspection), and the bind plan is
computed from the modes. The two inert oracles that proved
equivalence across the whole corpus are removed with the swap.

Legacy argumentBindPlan/argumentBindPlanFromWrapped remain only on
the eta/partial-application path and the PartialOpRaw path, each
quarantined until its own step; the emitted-type predicate
polymorphicFormalInstantiatedExpected survives solely for the eta
path and is deleted with it.

Fence: smoketest 54/54; conformance exit 0; emitted Lean
byte-identical to baseline (zero changed/missing corpus-wide).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hs (4b step 4)

argumentBindPlan / argumentBindPlanFromWrapped and the emitted-type
instantiation predicate polymorphicFormalInstantiatedExpected are
DELETED. Every consumer now derives its bind plan from the declared
convention (phaseBetaArgModesFor + phaseBetaBindFromMode):

- applied's eta/partial-application path: eta formals present the
  convention's declared representations (RawValueArg formals wrapped,
  missing Nat IndexArg formals wrapped-and-rebound, types/props/
  functions raw); the bind plan covers supplied prefix + eta vars
  uniformly. A missing polymorphic formal classifies as a raw value
  formal (no instantiating actual to consult) - exactly the legacy
  Var-never-Except behavior.
- etaExpandWrappedFunctionResult (recursor function-result eta):
  modes with no supplied actuals.
- lowerPartialOpContract's PartialOpRaw path (divNat family).

One classification correction en route: Num is TypeArg, not IndexArg
- it is Cryptol's singleton width classifier (per shouldWrapBinder's
contract), and TypeArg's never-bind is exactly the legacy semantics
in ALL cases, where IndexArg would have diverged on a (nonexistent
today) wrapped Num actual.

No shape or bind decision is inferred from emitted Lean TERMS
anywhere in the translator now. Two type-classification self-mirrors
remain (bindingShapeOfType; the Except/Pi peel in
applyKnownFunctionWithShape), documented as 4c demotion targets.

Fence: smoketest 54/54; conformance exit 0 (189 OK); emitted Lean
byte-identical corpus-wide; partial-application-heavy driver rows
(sequences, conformance_stream, records, cryptol_module_popcount)
re-run green.

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p 1a)

phaseBetaFunctionValueModesFor derives the argument modes of a
phase-beta FUNCTION VALUE callee (bound variable/constant with
phase-beta formals) from its SAWCore Pi type - a distinct family from
the raw-formal targets: RawValueArg here means the emitted formal is
WRAPPED. An inert assert in applyKnownFunctionWithShape requires the
derived modes to reproduce the peelLeanPiTypes/isExceptStringType
inspection exactly.

The oracle already rejected one candidate: var-headed formals
special-cased raw (correct for the raw-target family) - but THIS
family's emitted Pi wraps them (shouldWrapBinder is True for
variables), which the fix/iterate smoketest shapes caught
immediately. The corrected candidate is the exact shouldWrapBinder
mirror.

Fence: smoketest 54/54; conformance exit 0, zero disagreements;
emitted Lean byte-identical. Driver-corpus sweep next; swap follows
oracle silence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts (4c step 1b)

applyKnownFunctionWithShape's formal expectations now come from the
declared function-value convention (phaseBetaFunctionValueModesFor);
the per-argument peelLeanPiTypes/isExceptStringType inspection is
deleted. Equivalence was proven corpus-wide by the inert oracle
(58/58 driver rows + conformance, zero disagreements; the oracle
rejected one bad candidate en route - var-headed formals wrap in this
family, unlike the raw-target family). The RESULT-type peel remains
as the one type self-mirror on this path, tracked with
bindingShapeOfType for demotion.

Also records the user-reviewed 'deliberate emission-quality debts'
in TODO.md, each isolated at exactly one SUSPECT-marked chokepoint:

- phaseBetaBindFromMode: RawValueArg binds RAW actuals too (identity
  but monadic noise) - fix is bind-iff-wrapped as a dedicated
  reviewed-diff slice after Slice 5.
- phaseBetaArgModesFor: var-headed formals past the Pi-instantiation
  lookup are ASSUMED value-domain - fix is instantiation-directed
  modes with the dependent FunctionArg convention.

The two-family asymmetry itself (raw-formal external targets vs
wrapped-formal translated function values) is documented as FORCED,
not a debt: propositions need raw operands; partial application over
computed prefixes needs effectful closure interfaces.

Fence: smoketest 54/54; conformance exit 0 (189 OK); emitted Lean
byte-identical corpus-wide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…step 2)

ProofPrimitiveArgMode (Raw/Wrapped) is deleted; ppcArgModes are
calculus ArgModes assigned from the SAWCore Prelude signatures:
widths IndexArg, raw-logical equality subjects RawValueArg, source
proof terms ProofArg, carriers TypeArg, wrapped runtime operands
RuntimeArg. Interpretation is UNCHANGED: every raw-family mode
translates under withRawTranslationMode (proof primitives state
propositions over raw logical terms); RuntimeArg adapts to wrapped.
The labels give Slice 5 the declared slot roles it needs when the
equality-subject conventions take over these rows.

Fence: smoketest 54/54; conformance exit 0 (189 OK incl. all
proof_* obligation rows); emitted Lean byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p 3)

Audit finding: the old CalleeConvention enum was vestigial - only its
CalleeRawLogical arm was ever consumed; every other constructor
(including CalleeTransitional, returned for ~every callee) was
constructed or declared but never scrutinized. The dispatch's real
classifier has been the declarative guard chain over the contract
tables all along, and Slices 4a-4c gave those tables declared ArgMode
slots.

So CalleeTransitional retires by deleting the vestigial enum rather
than filling it in: rawLogicalCalleeForIdent/ForRecursor return
Maybe RawLogicalCallee (the one real classification the enum
performed), and CalleeConvention is gone. A NOTE at the old
declaration site records the design decision for the Slice 7 lint.

Also confirmed en route: the UseMapsToWrapped interpreter already
binds wrapped actuals at raw slots (shouldBindRaw), so the 4a hazard
class does not exist on that path; its UseArgShape vocabulary stays,
documented, as table-local.

Fence: smoketest 54/54; conformance exit 0 (189 OK); emitted Lean
byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bindingShapeOfType documented as a convention-internal self-mirror
(legal inputs: types the calling function itself just built from a
known wrap decision; the forbidden class - shape from emitted TERMS -
was deleted in Slices 2/4b). Slice 4 marked complete in TODO with the
4c closing inventory.

Fence: smoketest 54/54; conformance exit 0; emitted Lean
byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restructure plan Slice 5 into 5a-5c (status: the six load-bearing proof
rows are already green positives; they become the fence, not the target).

5a names and bounds the standalone-proposition convention:

- subjectRepFromTranslatedOperands -> standaloneEqualitySubjectRep, with
  the convention contract in its doc comment: rho_eq := joint produced
  domain of the operands' declared production records (ttShape, stamped
  by producers - never emitted-AST inspection, never carrier type
  names). One convention among several, not a universal authority.
- equalityPropositionAtSubjectRep documented as the entry point for
  surrounds that declare rho_eq (unsafeAssert, declared raw); the
  raw-mode-vs-adaptTo two-pipeline seam is documented for 5b to
  reconcile via the Eq.rec field set.
- lowerRawLogicalCallee documented as the standalone consumer; the
  Eq__rec all-raw demand is marked as the conservative subset the 5b
  field set replaces.
- Subject-rep decisions join the SAW_LEAN_TRACE_POSITIONS trace
  ([subjectRep] who/operand shapes/rep) so every rho_eq choice is
  auditable.

Fence: smoketest 54/54, conformance exit 0, emitted Lean byte-identical
to the slice0 baseline (626 artifacts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
septract and others added 30 commits July 30, 2026 18:26
…th, cache-schema bump

Lint (precedence audit F-A/F-B): the "axiom wins" claim was only
true across lines — a lexer-fatal on the SAME line as an axiom
(e.g. `axiom v₁' : False`, legal primed identifier) aborts the line
scan before the axiom rule runs and exits 2; the awk header and
both kernel comments now say exactly that (per-line precedence,
both-lines-printed output shape), rather than fixing the courtesy
layer's granularity (churn the pivot rejected — every path still
rejects). The one-line `if (code == 0)` precedence guard, shown
unpinned by mutation, gains the axiom_then_fatal kernel-selftest
case (axiom line 3, raw string line 5 → axiom-decl-in-user-file).

SHIP-4 audit residues: cache prefix bumped lean- → lean2- (reuse is
marker-existence only, so a pre-fix marker-plus-hole cache would
have short-circuited forever; the bump orphans it as inert debris);
abort message states the writable-tree precondition; the SmokeTest
pin is named by guard, not by the already-stale :350 line number;
fix_error_elem's surfaced .known-gap text now records the
2026-07-30 mechanical re-establishment (display-only text; the
pinned .known-gap.expected is untouched).

Verified: smoketest 94 passed; both selftests ALL CASES OK (incl.
axiom_then_fatal); doc-claim-lint green. Full suite not re-run for
this batch: the deltas are comments, a selftest fixture, a test
name, display text, and a schema bump on the branch no suite
exercises — the 1344s green sweep at a88d1d8 remains the gate for
all behavior the suite observes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five steps on branch saw-core-lean-0.02-closeout: (1) observation
gaps — SHIP-2 data-mode row, SHIP-3 ship-list check, toolchain pin
convergence, FXC-6; (2) threat-model re-score of the wave-3 SHOULD
FIX list; (3) CI round-trip (user action) for the W5-2
determination; (4) wave 5 as the verdict wave; (5) exit criterion
FIXED IN ADVANCE, with a failure clause that reopens the
convergence diagnosis rather than just the finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ence, FXC-6

SHIP-3: new support/ship-list-check.sh (data-files stanza ≡ tracked
runtime assets; non-recursive-glob precondition; Builtins.hs
relFiles duplicates; bundle_files ships the trees). Red-direction
verified by stanza mutation. Wired into the suite.

SHIP-2: new support/data-mode-selftest.sh — the wave-4 verifier's
one-off procedure made repeatable: synthetic datadir built from the
stanza, env -u SAW_LEAN_ROOT + saw_datadir + XDG_CACHE_HOME, the E1
replay goal admitted through the data-files branch, lean2- schema
marker asserted, old-schema dir asserted absent. The ONLY execution
of resolveLeanReplayAssets' installed branch (everything else
exports SAW_LEAN_ROOT). Cold 7.5s / warm 4.3s observed; persistent
gitignored cache re-stages on any shipped-byte change. Wired into
the suite + clean verb.

Toolchain pins CONVERGED: examples/saw-lean/proof/lean-toolchain
4.29.1 -> 4.32.0; demo lake build green, both discharges elaborate.
The destructive clobber class is structurally gone; README Step-3
warning and getting-started caution retired to keep-pins-in-sync
notes; TODO item closed (the share-the-build-tree hygiene question
stays open as 0.03-grade).

FXC-6: the recognizer's one unqualified ident test is now
module-qualified (dtIdent == mkIdent preludeName "Stream");
smoketest 94 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sed; strict-verb env passthrough

CP-3+K-5 (re-scored in-model MEDIUM, fixed): the anti-trivialization
probe read ANY nonzero exit as "not trivial" — tool failure failing
OPEN inside the trust kernel, against rule C3. Now the only accepted
failure is the tactic failing INSIDE the probe (Lean reports it at
triviality-probe.lean:2); a timeout, import failure, crash, or empty
transcript fails closed under the new token
triviality-probe-inconclusive. Hand-mutation verified (simulated
timeout with empty transcript on the honest control stage yields the
token); waiver row carries that evidence; kernel selftest ALL CASES
OK (control admits, trivgoal still rejects, coverage + waiver-audit
green).

Gate-path divergences (partial): SAW_LEAN_FAIL_ON_KNOWN_GAPS added
to Test.hs's env passthrough — the strict verb's variable was
honored on the Makefile path and silently dropped on the cabal
path. SAW_LEAN_ROOT stays deliberately absent (comment records
why). The remaining divergences (env-construction ownership,
binary identity, CI third path) are re-scored and dispositioned in
the ledger, not refactored here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… LIB-W2-2 precision; census prune

W3-REF-1 (re-scored HIGH -> LOW by an opus pass, mechanism fixed):
checkedEvidenceScript's tactic was the emitter's one emission of
bare identifiers built as a raw STRING — invisible to the
smoketest's Lean.Ident extractor, with exactly nine citations
registered nowhere. The script now renders from
checkedEvidenceSimpSet :: [Lean.Ident], which feeds
contractEmittedNames: registration by construction; a user
definition named after any simp lemma is refused by the F-7 gate
instead of silently captured. Rendering is byte-identical (order
preserved; goldens unmoved). The lint's over-claiming comment
narrowed to inline-literals scope. Verified churn-free (no
.cry/.sawcore defines any of the nine); smoketest 94 passed.

LIB-W2-2 (re-scored MEDIUM-HIGH -> LOW): the drifted line range
covered two functions; the sorry-placeholder half was already
closed by two kernel gates, and the actually-cited
unsafeAssertProofScript docstring now mirrors the Lean-side
"PRECISION (LIB-W2-2)" injectivity paragraph its twin gained on
2026-07-29. Member count corrected: two latent (Integer, Rational
-> LIB-W2-3), not three; bitvector was withdrawn.

Census/oracle prune: the first sweep after the SHIP-2 selftest
landed counted its 8 untracked staged library copies as emitted
artifacts (lib1-census 362 vs 354). Both discovery walks now prune
.data-mode-cache with the rationale recorded; census and oracle
selftest re-verified green standalone.

Ledger: dispositions written for every wave-3 SHOULD FIX item —
the list is now fully threat-model-scored (two fixed, two
closed-by-pin/downgraded-with-fix, one dispositioned to 0.03).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…glob); cold leg; pin equality

Step-2 audit F1 (MEDIUM, demonstrated end-to-end): the tightened
triviality discriminator still failed OPEN on resource give-ups —
Lean reports "maximum recursion depth" AT the probe's line 2, so a
goal rfl provably closes at higher depth was admitted as "not
trivial", landing exactly on the collapsed-computation shape a
trivialized emission produces. The accept condition is now an
ALLOWLIST of refutation shapes measured on the pinned toolchain
("error: Tactic .* failed" / "unsolved goals"); give-ups,
warnings-only, and any future phrasing fail closed — an explicit
availability-for-soundness trade on a pinned toolchain. New
trivgoal_deep selftest case pins the give-up branch; the env-class
waiver (whose rationale the audit showed false for the timeout
branch, F2) is deleted per the redundancy rule — the token now has
a live crafted-stage pin.

Step-1 audit F1/F2 (MEDIUM): both new scripts now expand stanza
globs with :(glob) pathspec magic — bare git pathspecs glob
recursively where Cabal's do not, so the synthetic install could be
richer than a real cabal install and the stanza check could pass
where cabal misses files. F6: ship-list-check gains the
demo/library toolchain pin-equality check (the convergence retired
the doc warnings that were the previous guard). F3: the data-mode
selftest runs a COLD leg (per-run scratch XDG — staging observed
every sweep) plus the WARM persistent-cache leg (reuse path); both
legs assert admission + schema. F4 reconciled by measurement: a
from-scratch library build is ~3.2s, the cold leg ~7.5s;
getting-started's "a few minutes" was the elan-download case and
now says so; the 120s-cap residue is retired to the network-bound
download sliver, unmeasurable here and not a CI exposure.

Verified: ship-list-check ALL OK (incl. pin equality), data-mode
both legs OK (11.5s total), kernel selftest ALL CASES OK (control
admits, trivgoal rejects, trivgoal_deep fails closed, coverage +
waiver-audit green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ver the transcript

The final audit proved (from Lean 4.32 source) that give-ups can be
LAUNDERED behind an allowlist-matching line: SynthInstance catches
runtime exceptions and rethrows "failed to synthesize" as a plain
error the probe's tactic alternatives absorb, surfacing the clean
last-alternative refutation (its F1, MEDIUM); and
throwNestedTacticEx formats give-ups as "Tactic X failed with a
nested error:" with the depth text on following lines (F2, latent).
The refutation allowlist is now paired with a give-up DENYLIST over
the whole transcript (maximum recursion depth / (deterministic)
timeout / nested error / failed to synthesize). Residual recorded
in the comment: a future toolchain's NEW launder phrasing behind an
allowlist-matching line — the four markers cover the pinned
toolchain's known channels.

Evidence: denylist mutation-verified (a synthetic laundered
transcript — allowlist first line + failed-to-synthesize body —
fails closed with the right token); the audit's structural
derivation stands as the reachability argument; a bounded empirical
construction attempt (Decidable unification against a deep defeq)
failed CLEAN without a give-up, recorded honestly — the direct
give-up branch keeps its live trivgoal_deep pin, the launder branch
rides the structural proof + transcript mutation.

Also: ship-list pin-equality gains its existence guard (both files
missing compared ""="" and printed OK — audit INFO); ledger W3-REF-1
marked landed (audit F3: the fix had landed in the same commit the
entry called it "queued").

Verified: ship-list ALL OK; kernel selftest ALL CASES OK (control
admits — healthy transcripts carry no denylist marker; trivgoal and
trivgoal_deep reject with their tokens).

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pagation, ledger pass

Wave 5 (doc/2026-07-30_release-gate-audit-wave5.md, 7 agents) judged
the branch against the plan's pre-committed §5 criterion: gate NOT
met at 237310f — clauses 1+2 fail on BOOKKEEPING (zero CRITICALs,
zero translator/kernel defects, failure clause did not fire). The
one MEDIUM (W5C-1, CONFIRMED): CONFORMANCE.md advertised positive
obligation coverage for the *WithProof contracts withdrawn as
unsound 2026-07-25 (LIB-2) — a propagation-failure class with
instances in four files plus shipped docstrings, invisible to
doc-claim-lint by construction.

Remediation step 1 (documentation propagation, prose/comment only):
CONFORMANCE.md rows 60/61/83/111/167/176/181/183/188 restated to
the on-disk truth (known-gap statuses, rejections named instead of
withdrawn contract shapes — restoration hazard, W5C-1 verify);
statuses legend gains `realized`; architecture.md, proof-cookbook.md,
STATUS.md, FixRecognizer.hs Haddock, and the SAWCorePrimitives.lean
_raw docstrings all carry the dated S-2/LIB-2 withdrawal truth; the
refutation row's own header names the three refuted fields (W5C-9).
One adjacent inaccuracy found and fixed during the rewrite (row
83's prefix-partial claim vs its own goldens).

Remediation step 2 (ledger pass): four stale-open rows closed (F7,
FXC-6, SHIP-2, SHIP-3 — all had landed); the CP-3 entry records its
two supersessions (DC5-1); wave-5's 14 residue dispositions folded
into the STILL OPEN list (3 closures, 2 sharpenings); new WAVE 5
section records the verdict and the six clause-2 survivors with
PROPOSED dispositions awaiting user acceptance. Small script fixes:
selftest clean verb now actually cleans (DC5-4, $$ of the cleaning
shell); ship-list sub-check (c) owns its verdict line (DC5-5).

Checks at this commit: doc-claim-lint green (283 identifiers),
ship-list ALL OK, kernel-selftest clean verb verified. Full sweep
owed at the release commit (clause 3 is per-commit).

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ty-gate A/B, rule amendment

Deletion-biased review of the trust kernel at 1cb4bdf, with its
first draft adversarially refuted before reaching the user: the
drafted control-probe redesign of the triviality gate was
demonstrated END-TO-END to admit a trivialized emission the
current kernel fails closed (no [K] check catches goal-formation
defects — the gate is the only defense in its class), re-opened
the CP-3 timeout fail-open, and rested on a false premise about
the completed path. The published review presents the honest menu:
Option A (harden in place per the reviewer's amended design,
CODE-neutral) vs Option B (delete the gate outright per the
threat model's own load-bearing list, ~-50 lines, residual
documented) — recommendation B by D2 precedent; user decision.

Also: re-accretion measured against the D2 baseline (+16 kernel
CODE lines concentrated in two places, +240 comment/test lines);
full check inventory classified [K]/[M]/[T]; lint token collapse
deferred to 0.03 (mirror-implementation blast radius); standing
rule adopted in amended form (no fix may flip an outcome class
from reject to proceed; prefer mechanical discriminators); plan
3b reserve recorded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion B, user decision)

The gate was a text-discriminated negative probe outside the threat
model's load-bearing list whose decoder went through three same-day
audit rounds (fail-open -> position check -> refutation allowlist ->
allowlist+denylist) and ended coupled to one toolchain's error
phrasing with an unpinned denylist half — the empirical proof it
could not be kept "small enough to be kept honest". Deleted per
doc/2026-07-31_kernel-design-review.md §3.1 Option B: both tokens
(goal-formation-trivial, triviality-probe-inconclusive), the probe,
both regexes, ~50 kernel lines; trivgoal/trivgoal_deep retired with
their subject; compact tombstone at the gate's site.

The residual is DOCUMENTED, not silent: residual-trust.md §3.2e
states plainly that a trivialized emission discharged unnoticed is
admitted, what defends the class instead (differential corpus at
development time; goal visibility at discharge time; the required
conjunction), and that any re-entry is an emission-side structural
check, never a replay-side message parser. The trust-surface prose
at §3.2b-adjacent and replay-design.md's three mentions updated;
the CI-divergence instance the wave-2 record measured by this
gate's existence dissolves with it.

contributing.md gains rule C7 (the courtesy-layer fix rule, adopted
with the design reviewer's amendment): resolutions are deletion /
kernel question / documentation / fail-closed-unrecognized; no fix
may flip an outcome class from reject to proceed without its own
audit; mechanical discriminators over text ones. This deletion is
itself such a flip — sanctioned by user decision and receiving its
own audit per the rule.

Ledger: CP-3 entry carries its final supersession; R5-RES-11/14
dissolve. Verified: both selftests ALL CASES OK (coverage
meta-guard green with no dead waivers), doc-claim-lint green;
kernel CODE 232 -> 219 lines, tokens 33 -> 31, zero error-message
regexes left in the trust path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deletion commit numbered the new goal-formation residual
§3.2e, but §3.2e already exists (LIB-1, cited by README:68 and
STATUS:253); the selftest note also said §3.2d, which is the
type-image section. Caught by the deletion audit's partial run
before it was killed by an auth expiry. All five referrers
renumbered to the genuinely free §3.2f; pre-existing §3.2d/§3.2e
citations verified untouched; doc-claim-lint green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… cost honesty, stale prose

The gate-deletion execution-fidelity audit (per rule C7) found no
blocker, no collateral damage, and no smuggled change — the
deletion is clean, the meta-guard consistent, C7's provenance
verbatim. Its five prose findings, fixed:

F-C: the decision log gains D5 (the deletion is the same class of
user-decided kernel scope call as D1-D4 and must be findable where
they are). F-B: residual-trust §3.2f now states each defense WITH
its limit — the corpus cannot reach novel replay goals (the
review's accepted cost, verbatim), and visibility is defeated by
deep-evaluation trivializations (the very shape that decided the
deletion) — so the catalog reads no stronger than the decision.
F-A: the selftest real_goal() comment and replay-design's F9
backstop paragraph no longer describe the deleted gate in the
present tense. F-D/F-E: the design review §5 carries measured
outcomes beside its predictions (CODE 219 not ~185; the honest
claim is zero error-message text in ACCEPT conditions — three
reject-direction greps remain and can only tighten) instead of
quietly restating them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d; dispositions accepted

User decision 2026-07-31 ("go ahead with the fast path"), executed:

OBL-1 FIXED AND PINNED: the five stream-helper obligation rows
shared one byte-identical expected.txt naming no stream operation —
the wave-2-demonstrated shift_l->shift_r emitter mutation passed
every directive. Each row now also pins its OPERATION by lowered
structure (identity read; addNat-on-index without bit0; streamScanl
under Bind.bind; index-arithmetic shift with bit0 and NO
atWithDefaultM/subNat; atWithDefaultM+subNat+genWithBoundsM),
deliberately NOT by probe name (a mutation keeps the name).
Cross-matrix verified at introduction: every set accepts only its
own emission — all 20 cross-pairs fail, and the demonstrated
mutation now fails on the shift_l row's absents. All five rows
green through the real harness.

F8b CLOSED AS UNCONSTRUCTIBLE (F-9 treatment): the owed pin's
triggering .saw script cannot be written — the emitter refuses the
shape upstream of the pinned surface.

Ledger + plan doc: the six clause-2 dispositions recorded as
user-accepted (F11, LIB-W2-3, F12-successor = explicit 0.03
carries with risk notes; W2-UNRUN-2 re-score commissioned);
clause-1 reading (i) recorded as a user decision; clause-4 noted
as the REMOTE Actions run per user confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed and pinned

RELEASE-BLOCKING defect found by the W2-UNRUN-2 threat-model
re-score and verified independently end-to-end before fixing.

Gate 3's TEST 1 exempted every NAMED telescope binder on the
premise "a named domain is never a folded hypothesis". False: the
Lean binder name is copied from the SAWCore VarName regardless of
dependency (Convention.withSAWVar) and parse_core preserves a
hand-written name. So
  prove_core (offline_lean ...) "(h : EqTrue (bvult 8 (at 2 ... [.., error ..] 0) ..)) -> EqTrue (bvEq 8 3 4)"
EMITTED while the identical ANONYMOUS goal was refused. Measured at
the pre-fix HEAD: SAW proves the hypothesis (lazy `at`, error slot
unforced) and refutes the conclusion, so the obligation is FALSE;
the emitted goal proves in Lean via congrArg Except.isOk with
[propext, Quot.sound] — both on the replay allowlist — so
offline_lean_replay would have issued LeanReplayEvidence for a
false claim. In-model (no adversarial author; parse_core/prove_core
are Current builtins), CRITICAL.

FIX (mechanical, C7-safe direction proceed->reject): gate 3 now
runs the printer's own anonymizeUnusedPiBinders before asking
TEST 1's anonymity question, so it inspects the binders the
artifact actually ships — the printer already anonymized
named-but-unused binders, which is why the gate and the emitted
text disagreed. Pretty.hs exports the function; ~2 lines in
Signature.hs. Over-refusal checked: named-dependent and
named-unused VALUE binders still emit (both probed).

PIN: saw-boundary/.../except_carried_named_hypothesis (+ golden),
with the stated mutation (restore the blanket named-binder exempt).

The §5 failure clause fired, so its deliverable lands with the fix:
doc/2026-07-31_why-gate3-escaped.md — the limit was KNOWN, then
"narrowed" by a measurement that answered a different question than
the claim it was cited for (one clause measured, one assumed, the
conclusion drawn as if both were), which converted an open question
into a settled one for three audit waves. New rule C8 governs
limit-narrowing. Also: the OBL-1 fix-audit residues (canonical
owed-pins row (i) struck with the full F-9 treatment; positional
pins closing the audit's three shift-row mutants).

Verified: smoketest 94/94, doc-claim-lint green, witness now
refused, control unchanged. Full sweep in flight.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… in the close-out plan

The §5 failure clause fired; its deliverable was written before the
fix. Clause 3 re-established at d4d4c43 (full sweep PASS); clause
2's last item settled; clause 1 gets an honest asterisk — it
quantified over wave 5, and wave 5 was clean of this because no
wave read the surface. New pre-release wave-6 charge: the
emission-side goal-shape gates, audited by witness-construction
rather than comment-reading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… name is not evidence

The fix audit of d4d4c43 refuted that fix with two further in-model
CRITICALs, both demonstrated end-to-end at the fixed HEAD. Verified
independently before re-cutting:

(A) Asking the anonymity question of the PRINTED text inherited the
printer's opposite safety polarity. `mentionsIdent` over-reports
mentions BY DESIGN (safe for a cosmetic rename, ADMITTING for a
gate) and its Tactic arm is a SUBSTRING test — so a binder named `h`
counted as mentioned by any goal whose conclusion carries an
obligation script containing `h_bounds_obligation_`. Measured:
binder `h` EMITTED, binder `zz` REFUSED, structurally identical
goals. My "safe direction under C7" claim held only for binders the
predicate called unused.

(B) A hypothesis binder can be named AND genuinely used
(`(g : EqTrue X -> Bool) -> (h : EqTrue X) -> … (g h)`), so no
sharpening of "is it used" could ever have closed the class.

THIRD CUT: delete the anonymity test outright. A binder NAME carries
no soundness information about hypothesis-vs-value and never did —
both the original gate and the first repair keyed on it. What remains
is the value-image test applied to EVERY binder: carrier-headed after
peeling Pis means the domain delivers a value, and every such image
is inhabited (`Except.error ""`), so it cannot make the implication
vacuous. The printer export is reverted, so the gate/printer coupling
the audit warned about is gone.

COST, measured not assumed: composite domains (a tuple with a
function component) are not carrier-headed after peeling and now
over-refuse — fail-closed, the C7-safe direction. Full suite PASS
(1350.55s) with this cut, so no corpus shape pays for it. Legitimate
value/function/named-used shapes still emit (probed).

PINNED, one row per escape mechanism, each with its stated mutation:
except_carried_named_hypothesis (named-unused),
named_hypothesis_tactic_conclusion (the printer-coupling substring
escape), named_hypothesis_used_binder (named-and-used). All three ran
inside the green sweep with empty diffs. Also fixes the audit's
wrong-pin-path comment; limit 2 now records all three cuts.

The principled successor stays a wave-6 charge: decide
hypothesis-vs-value SAWCore-side, where "is this domain a Prop" is
unambiguous, instead of recovering it from the Lean image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n the walk

The audit of the third cut refuted it with the strongest witness yet:
it ran the goal through offline_lean_replay and got LeanReplayEvidence
ISSUED for a FALSE SAWCore obligation — not "would have", did.

Mechanism: cut 3 exempted any binder whose type peels to the value
carrier AND STOPPED WALKING. For a binder typed `(EqTrue P) -> Bool`
the peeled codomain is a value, so the poisoned hypothesis `P` in the
binder's own DOMAIN was never inspected. With P's Lean image
uninhabited the Lean function space collapses to one element while the
SAWCore one has many, so `Eq _ g h` is a Lean theorem and a SAW
falsehood. My cut-3 argument — "delivers a value, therefore inhabited,
therefore cannot make the implication vacuous" — was true and
IRRELEVANT: vacuity is not the only way to be weaker. Note SAW cannot
check this goal class itself (sequentToSATQuery refuses function-typed
binders), so for it the gate is the only thing between the user and
false evidence.

FIX: classification is now recursive and uniform (classifyGoalDomain).
At every Pi level what the type ultimately DELIVERS may be a value,
but each domain it CONSUMES is itself a position to classify. Only a
final codomain is exempt; nothing is skipped. Verified: all four
witnesses refuse; value/named-used/value->value-function shapes still
emit; full suite PASS (1349.72s), smoketest 94/94, doc-lint green; all
four pins ran inside the sweep with empty diffs.

Also corrects a rule-C8 violation I committed in the cut-3 pin header:
named_hypothesis_used_binder.saw asserted "g's own image peels to the
value carrier and is correctly exempt (a function from an uninhabited
domain is inhabited)" — an unmeasured clause stated as settled, in the
same commit that added C8. The correction is recorded in place.

New pin: exempt_binder_poisoned_domain, with its stated mutation
(restore cut 3's `= []` early exit).

STANDING RECOMMENDATION, recorded with the fix: this is the FOURTH cut
of this gate in one day and cuts 1-3 were each refuted by a witness,
three of them after I stated a false premise confidently. Cut 4 closes
every witness built so far, which is exactly what I believed of cut 3.
The release should NOT rest on it. The durable fix is the SAWCore-side
decision (is the domain a Prop? — a sort check, not a reconstruction
from the Lean image), designed and adversarially reviewed BEFORE
implementation, as the triviality-gate redesign was. Cut 4 is the
fail-closed stopgap: it strictly refuses a superset of cut 3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ged, redesign logged

User decision 2026-07-31: "roll it into residual trust, and log it as
something to revisit" — rather than hold the release for the
SAWCore-side redesign.

residual-trust.md §3.2g states the residual without softening: the
gate was cut FOUR times in one day, cuts 1-3 each refuted by a
constructed witness (the third after offline_lean_replay had ISSUED
evidence for a false obligation), and cut 4's correctness is NOT
legible — the same was believed of cut 3. Includes the four-cut table,
the measured bounds the decision rests on (one production consumer and
no cascade; enable_experimental opt-in, tested not inferred; the
Cryptol/LLVM/goal_cut routes closed since they emit only anonymous
binders; zero exposure across all 78 goal goldens / 114 binders; every
cut refusing a strict superset of its predecessor, so wrong can only
mean incomplete, never newly broken), and the point where this
residual is WORSE than §3.2f's: goal inspection does not mitigate it,
because an escaped goal reads as an ordinary conditional.

decision-log D6 records the call and its grounds. TODO.md § 0.03 gains
the redesign as the revisit item, with the process requirement the
record earns: design doc, then an ADVERSARIAL review of the DESIGN
before implementation, then implement/pin/sweep/audit — the order
whose absence produced four cuts in a day. W2-UNRUN-2's FpOther
blindness folds in as the same missing distinction seen from the other
side.

Plan doc: the emission-side gate work is no longer a release blocker,
and §5 now carries its final state — clauses 1, 2 and 3 MET (clause 3
at 8d9bdba; this commit is doc-only and does not disturb it), clause
4 the only one outstanding: merge, then the REMOTE Actions run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
19 commits, branch saw-core-lean-0.02-closeout, executing
doc/2026-07-30_convergence-closeout-plan.md whose §5 exit criterion
was fixed IN ADVANCE so it could not be retrofitted.

Steps 1-2 (observation + threat-model re-score):
- SHIP-2 data-mode selftest (cold+warm legs) — the first execution of
  resolveLeanReplayAssets' installed/cache branch, previously dead
  under every harness; SHIP-3 ship-list closed check; demo/library
  Lean toolchain pins CONVERGED, retiring the destructive clobber
  class; FXC-6.
- Every wave-3 "SHOULD FIX" item threat-model-scored with a written
  disposition. Two were real in-model defects and are fixed+pinned:
  the anti-trivialization gate's fail-OPEN branch (tool failure
  failing open in the trust kernel, against C3) and the strict-verb
  env var dropped on the cabal path. W3-REF-1's mechanism fixed by
  construction (checkedEvidenceSimpSet feeds contractEmittedNames);
  LIB-W2-2 reduced to a corrected comment; W3-HR-4 closed by pin.

Wave 5 (the verdict wave) + remediation:
- Verdict: gate NOT met at the time — one CONFIRMED MEDIUM, the
  S-2/LIB-2 documentation-propagation class (CONFORMANCE.md
  advertising coverage for contracts withdrawn as unsound). Fixed
  across five files plus shipped docstrings; ledger pass; four
  stale-open rows closed.

Kernel design review (user charge: don't rebuild the cruft):
- Re-accretion measured against the D2 cut (+16 kernel CODE lines in
  two places, +240 comment/test lines). The review's own first draft
  was adversarially REFUTED before reaching the user — it would have
  admitted a trivialized emission — and the published version
  presented an honest A/B. User chose Option B: the
  anti-trivialization gate DELETED whole (D5), residual cataloged at
  residual-trust §3.2f, rule C7 added to stop the accretion pattern.

The gate-3 CRITICAL (the arc's most serious finding):
- A W2-UNRUN-2 re-score, reading code rather than ledger text,
  constructed a demonstrated unsound-acceptance path: a NAMED
  hypothesis binder walked past goal-shape gate 3 while the identical
  anonymous goal was refused; the SAW obligation was false and the
  emitted goal proved in Lean with allowlisted axioms. Verified
  independently before fixing. §5's failure clause fired and its
  deliverable — doc/2026-07-31_why-gate3-escaped.md — was written
  BEFORE the fix: the limit was known, then "narrowed" by a
  measurement that answered a different question than the claim it
  was cited for, which converted an open question into a settled one
  for three audit waves. Rule C8 added.
- The gate then took FOUR cuts in one day; cuts 1-3 were each refuted
  by a constructed witness (the third after offline_lean_replay had
  ISSUED evidence for a false obligation). Cut 4 makes classification
  recursive and uniform. Four witnesses pinned, one per mechanism.

Disposition (D6, user decision): ship 0.02 on cut 4 with the residual
CATALOGED (residual-trust §3.2g) rather than hold for a redesign —
taken on measured bounds (one consumer, no cascade;
enable_experimental opt-in; the Cryptol/LLVM/goal_cut routes closed;
zero exposure across all 78 goal goldens; every cut refusing a strict
superset of its predecessor). The SAWCore-side sort-check redesign is
logged for 0.03 WITH its process requirement: design doc, then an
adversarial review of the DESIGN, before implementation.

§5 state: clauses 1, 2, 3 MET (clause 3 at 8d9bdba — full suite
PASS 1349.72s, smoketest 94/94, both kernel selftests, doc-claim-lint,
ship-list; the commits after it are doc-only). Clause 4 — the remote
CI run — is the only one outstanding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User direction: keep the backend's footprint outside its own subdir
very limited — references are fine, discussion is not. Net -33 lines
across five shared files, no behavior change (saw builds clean).

CHANGES.md: the entry claimed offline_lean_replay "always fails with
a diagnostic" — FALSE, it admits goals today. Corrected and kept to
four lines, pointing at saw-core-lean/README.md for setup, limits and
the trust model, rather than restating them here.

doc/developer/developer.md: dropped the Phase-1a lockdown-item
taxonomy (L-1..L-14 enumerated across two test-suite entries) from
SAW's developer doc; the suites now get one line each plus a pointer
to saw-core-lean/doc/contributing.md.

.github/ci.sh, .github/workflows/ci.yml: my own comments from earlier
today were 16 and 11 lines of backend audit narrative (wave-4 SHIP-1,
W5-2, DEMO-7, a saw-core-lean/doc path). Cut to the operational facts
a CI maintainer needs — why the assets must ship, that the unpacked
root must be writable, why the export exists — plus a pointer.

saw-central/src/SAWCentral/Builtins.hs: same treatment. The staging
comments keep the invariants they document (per-call-unique names, the
marker-existence reuse rule, the schema bump's purpose) and lose the
finding IDs and dates.

Root README and doc/saw-user-manual were already clean of backend
content; verified, not assumed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A fresh-eyes agent followed the docs literally and could NOT complete
the journey: three failed SAW runs, then it had to read a test row to
discover the replay contract. Fixes, all verified by running them:

BLOCKERS
- getting-started gains Step 4 (offline_lean_replay), the step that
  was missing entirely. Documents the proof-directory contract as a
  table, and the two things that decide acceptance: the theorem must
  be `goal_closed` (not the emitted placeholder's `goal_holds`), and
  `proof.lean` must get `goal` via `import Emitted` rather than
  carrying a local `def goal` (the collision that surfaces as
  closer-wrong-type). Explains that the Lake file you iterate on and
  the proof.lean you hand replay are two different files, and why.
  VERIFIED: wrote the documented four-line proof.lean and ran the
  documented script — "Lean kernel check passed", goal accepted.
- Prerequisites now cover what actually blocks a start: where
  `cabal build exe:saw` puts the binary, that elan installs no
  default toolchain (so `lake new` fails outright), the exact
  toolchain string to pin, and SAW_LEAN_ROOT.
- Step 2 hoists the toolchain-pin instruction BEFORE the first build.
  It previously arrived in a note after `lake build`, i.e. after the
  damage; and the obvious fix (`elan default stable`) is the harmful
  one, since a mismatched pin rebuilds the shared library in place.

MISLEADING
- The walkthrough's regression row was described as holding the
  example "verbatim"; it is in replay form and differs in exactly the
  two ways that decide acceptance. Now says so.
- "Where to read next" pointed trust questions at an archive doc whose
  own banner says its trust model has changed materially and which
  never mentions LIB-1. Replaced with a "before you rely on a
  replayed goal" section pointing at the README's limitations and
  threat model.
- examples/saw-lean/README claimed getting-started "walks this flow";
  it walks a different, smaller example.

GAPS
- proof-cookbook gains "When replay rejects your proof": the 12
  user-causable CHECK-FAIL tokens with cause and remedy, extracted
  from the kernel rather than guessed. None of the ~31 tokens was
  documented anywhere a user reads.
- Documented how to see what replay recorded (LEAN-REPLAY under
  Solvers Used) and pointed at `:help offline_lean_replay`, which is
  the best documentation in the project and was linked from nowhere.

CORRECTIONS TO MY OWN CLAIMS FROM TODAY, caught by verifying rather
than asserting:
- residual-trust §3.2g and the README listed "`enable_experimental`
  opt-in" as a measured BOUND on the gate-3 residual. FALSE, and it
  widens the residual: parse_core, prove_core, offline_lean and
  offline_lean_replay all run with no flag, and a hypothesis-bearing
  goal reaches the gate with no flag. What I had actually tested was
  goal_num_when's flag requirement. Corrected in place, visibly, as
  the C8 failure mode committed in the entry that records C8.
- The README's new "read your emitted goal" advice sat immediately
  after the gate-3 caveat, implying inspection covers that class;
  §3.2g says the opposite. Now states where reading helps (the
  conspicuous over-reduction class) and where it does not (an escaped
  hypothesis goal reads as an ordinary conditional).
- summarize_verification needs enable_experimental; my first draft
  said otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
That branch is long-lived and merged into rather than PR'd per
change, so with only master/release-** on the push trigger it got no
CI coverage. One entry in the existing branch list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The walkthrough called STATUS.md the best "what is this / what works"
page in the project. It was two days and one heavy arc stale, and the
README's Documentation list did not link it at all.

- Smoketest count 73 -> 94 (verified by running it, not inferred).
- Known-gap census RE-VERIFIED, still 72: recounted from disk as 68
  `.known-gap` markers + 3 proof-gaps + 1 stretch. The table stands.
- Conformance row figure (235) marked OWED A RE-MEASURE rather than
  restated. My disk count was of directories; STATUS counts rows, and
  saw-boundary directories hold several rows each, so the two units
  are not interchangeable. Substituting one for the other is exactly
  the error this file warns about (a stale count silently narrowing a
  release claim), so it is flagged instead of guessed.
- New "Recent history" section: waves 4 and 5, the close-out arc, the
  design review that deleted the anti-trivialization gate, and the
  gate-3 CRITICAL with its four cuts — each with its doc pointer.
- Header now states release posture accurately: three of four
  release-gate clauses met, the outstanding one a green CI run.
- Names the three shipped-documented residuals (§3.2e/f/g) and says
  the user-facing summaries live in README.md.
- README's Documentation list now leads with STATUS.md as the "is this
  usable for my case?" page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Items 1-4 of the docs pass.

(1) getting-started Step 4 gains "Goals with obligation placeholders
(completed.lean)" — the path every partial operation (indexing,
division, modulus) takes, previously one row in a table. Explains why
the placeholder cannot be fixed from proof.lean (it is part of the
goal's own text), the four mechanical steps, that the `def goal` line
must stay verbatim, and that completed.lean must contain no `sorry`
while proof.lean still carries the proof. Points at the two
CI-exercised completed.lean rows under proofs/ as worked examples
rather than shipping a toy: they cannot drift from what the checker
accepts. The documented placeholder tactic is one I ran — the edited
outline was accepted past the compile and sorry gates.

(2) The backend README's Documentation list is now split "if you are
trying to USE the backend" (getting-started -> STATUS -> cookbook ->
the limitation sections -> :help) versus "if you are working ON it".
It previously led with the trust authority and release plan.

(3) The cookbook gains "Before you rely on a discharged goal",
pointing at the README's limitations and threat model. A reader who
lives in the cookbook could previously never learn LIB-1 exists —
and the cookbook is exactly where someone is holding a closed goal
and about to believe it.

(4) Root README: five lines, one pointer, in the Documentation
section — the backend exists, it is experimental, read its own docs
including the soundness limitations. Nothing else about it at top
level.

Also filed, found while building the worked example: an unused goal
binder emits a file Lean cannot elaborate (`Pure (?m.N x)` stuck
instance on the `let x := (Pure.pure x)` shadow). Minimal pair
verified: `\(i : [8]) -> i == i` compiles, `\(i : [8]) -> (3 : [8]) ==
3` does not, and with no binder at all it compiles — so it is
specifically the unused-and-shadowed case. Fail-closed (replay says
emitted-does-not-compile), so completeness not soundness. In TODO.md
with the likely fix, and in the README's punted list because a user
hits it with no hint that an unused binder is the cause.

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

Ran eight shapes a newcomer would actually reach for through emission
and elaboration. Six compile: Bool properties, symbolic bv equality,
x + y == y + x, x <= x, symbolic sequence indexing, and concrete
arithmetic. Both failures are the same class, and it is more reachable
than yesterday's filing implied:

ANY unused binder triggers it, not just a lone one —
`\(x : [8]) (y : [8]) -> x == x` fails on `y` alone. So an ordinary
property carrying one parameter it happens not to reference hits this,
which makes it a likely first encounter rather than an exotic case.
Both the TODO entry and the README's punted list now say so; the
README's example is the two-parameter one for that reason.

Bounding good news, recorded with it: this is the SINGLE failure class
among common shapes, not one of many.

(The earlier filing also read as if the pair `i == i` / `(3:[8]) == 3`
characterized the defect. It characterizes the mechanism; it
understated the reach.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first CI run ever to execute on this branch (run 30651900851)
failed `integration-tests` on both ubuntu-24.04 and macos-15, with
2 of 320 tests red: test1646 and test_search. Both are builtin-listing
goldens that drifted when Lean primitives were added without resyncing
them.

Last sync was bd0bbe4 (2026-05-02). Landed since, unsynced:
  write_lean_sawcore_prelude                 b285470  2026-05-11
  write_lean_cryptol_primitives_for_sawcore  0f08890  2026-06-26
  offline_lean_replay                        c39d45e  2026-07-14

All three are registered `prim`s in Interpreter.hs with exactly the
signatures the goldens now record, so the new outputs are correct
(the harness asks that this be checked, not assumed).

Verified locally against bin/saw, both directions: the pre-edit
goldens reproduce the CI diff line for line, and the post-edit
goldens diff clean. `bash ./test.sh` exits 0 in both test dirs.

Note this class of drift is invisible to the backend's own sweeps:
`integration-tests` (hs-source-dirs: intTests) and
`saw-core-lean-tests` (otherTests/saw-core-lean) are disjoint cabal
test-suites, so no backend-local green claim ever exercised these
goldens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
W5-2 asked which disjunct held for 07-18..07-30: the
saw-core-lean-tests CI leg red, or not running. Answer, from `gh`:
NOT RUNNING. Before 7f573bf the workflow's push trigger was
`branches: [master, "release-**"]`, which never matched this
branch, and no PR run fired either — the fork's entire run history
is two runs, both today. This is exactly the disjunct the F4 fix
audit narrowed to ("possible only if the workflow never ran on
this branch at all").

The observation half stays open and got worse, not better: the
first real run failed integration-tests before reaching the demo,
so the SAW_LEAN_ROOT export and the bundle_files bindist change
are still unexecuted. §5 clause 4 is NOT closed.

W5-2b files what that run exposed. integration-tests and
saw-core-lean-tests are disjoint cabal test-suites, so adding a
SAWScript prim reddens intTests goldens that no backend sweep
runs. The tree carried that regression for ~2.5 months while every
sweep reported green. Recorded as a standing C8 consequence: a
"suite green" claim from the backend path must name that path and
must not be read as "CI would be green".

doc-claim-lint: 285 identifiers across 8 docs, all resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An unused goal binder made the emitted file fail to elaborate.
quantifierShadow opens every goal body with a let-shadow chain that
Pure.pure-lifts each value-typed binder, emitted WITHOUT a type
annotation. For a binder the body never references there is nothing
to infer the monad from, so Lean stops with

    typeclass instance problem is stuck
      Pure (?m.31 x y)

Reach, surveyed 2026-07-31 rather than assumed: this is not the
exotic "property that ignores its only parameter". ANY unused binder
trips it, including one unused parameter among several used ones, so
an ordinary property carrying a parameter it happens not to reference
hits it. Severity is completeness and diagnostic quality, never
soundness: offline_lean_replay refuses with emitted-does-not-compile
and offline_lean writes a file the user cannot build.

Fix: gate each shadow on the new Lean.identOccursIn. That helper is
deliberately CONSERVATIVE — binder positions, Tactic source text, and
sort/universe names all count as occurrences — because a False answer
is what licenses deleting a binding. Over-reporting keeps a harmless
shadow; under-reporting would drop a live one. `let n := e; b` with n
absent from b is just b, so the rewrite is meaning-preserving by
construction rather than by argument.

Pinned by workflows/unused_binder_shadow: t1 unused-among-used (the
wide-reach shape), t2 sole-binder-unused, t3 unused in the MIDDLE of
three, t4 all-used control. The row lives in workflows/ because that
harness ELABORATES emitted files — a shape-only pin would not have
caught this, since the bad emission read as perfectly reasonable
text. t4 is load-bearing: without it, deleting the shadow chain
outright would also pass.

MUTATION VERIFIED, not asserted: removing the guard and rebuilding
turns t1/t2/t3 red with the stuck-instance error while t4 stays
green; restoring it returns the row to green.

Emission for all-binders-used goals is byte-identical to the pre-fix
binary, so this strictly widens what compiles.

lib1-census EXPECT_SCANNED 354 -> 358 for the row's four artifacts,
verified as the whole delta by moving exactly those files aside
(354) and restoring them (358).

Green: cabal test saw-core-lean-tests (373 rows, 72 known gaps, the
only failure was the census constant now updated); cabal test
saw-core-lean-smoketest (94); doc-claim-lint (285 identifiers).

Found by executing the getting-started documentation rather than
reading it — three release-gate audit waves read this code and
missed it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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