Skip to content

complexity - #6420

Closed
briansrls wants to merge 9 commits into
mainfrom
pr-6373
Closed

briansrls wants to merge 9 commits into
mainfrom
pr-6373

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Auto-opened by session-dashboard for session eager-ram-612.
Pushing to pr-6373 advances this PR.

Worker attestation

Before flipping this PR to ready for review, confirm each item:

  • Title describes the change (not the session id or branch).
  • PR body summarises what and why (replace the TODO below).
  • Tests run: name the command (e.g. npm test, cargo test) and the result.
  • If this closes a work item, the body contains a Closes #N directive.
  • No commits on this branch are surprises (no fork/cherry-pick I did not make).
  • No secrets / credentials / large binaries staged.

Summary

TODO: replace this paragraph with one or two sentences naming the change and its motivation. Reviewers read this first.

Test plan

  • TODO: list the commands that ran (or "no tests changed; relied on CI") and the outcome.

claude and others added 9 commits July 9, 2026 04:12
… + claim-bin cwd fix

Reworked to drop the forked complexity machinery. The cost-shape lowering
(dag_surface_lower_*) was a second, hand-rolled body producer running beside
the compiler's own — a §3/§4 fork: it re-lowered the surface parse tree by
hand (one if-arm per production), skipping resolve + normalize + body_producer,
and produced a shape that matched neither the real inferred tree nor its own
synthetic test subjects. It caught zero real quadratics and generated a
cascade of point-fixes (per-form lowering lanes, a lexeme-recovery module, a
GeneratedArtifact name collision, missing Branch-descent). All of it is backed
out. The general body producer that would replace it — §4's "one grammar read
in both directions" (row-driven ingest, the inverse of the row-driven emitter),
which also subsumes the pre-existing MVP1 03_body_producer — is left as
compiler work, not landed as a fork.

Kept: the genuinely fork-free, DESIGN-clean behavioral changes.

- 04_infer gains the Loop arm: an iteration-fold body is typed as a
  per-iteration transform (τ → τ under the measure); divergent and
  refinement-shaped bodies refuse with the located infer_loop_iteration_type_mismatch.
- Measure-derived loop termination (loop_multiplicity over the cited
  measure_descent_fact_registry, lattice meet + lexicographic strict-dimension
  proof), grounded on the dag/std/termination.dag single authority.
- claim_batch / claim_executor resolve relative path args against the process
  cwd and refuse (exit 2, located) on missing paths — closes the baked-root
  mixed-tree fail-open.

Verified by execution against the merged base: 8 loop-multiplicity + 5
loop-infer witnesses PASS; roster-shape, generated-artifact drift, and
enforcement-consistency + repo-wide-complexity meta-gates PASS. The two new
loop tests are CI-enrolled so the kept behavior gates.

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

The first real piece of the general body producer that replaces the removed
cost-shape fork (and, eventually, the hand-rolled MVP1 loop shapes). A surface
fold has no dedicated grammar production — it parses as an ordinary call — so
its lowering is a SEMANTIC desugaring keyed on the RESOLVED callee identity
(DESIGN §4: fold/recursion is sugar over Loop), never a lexeme scan. Given a
resolved fold call (positional children [callee, collection, init,
iteration_body]), fold_call_to_loop lifts the iteration body into the canonical
seam Loop bounded by the registered fold-iteration measure, and the kept
loop_multiplicity derives PROVEN termination from it — the same real derivation
the foundation's loop witnesses use, now reached from a fold-call shape.

This pins the seam (M1) and lands the desugaring unit (core of M2), verified by
execution: fold_call_lowers_to_terminating_loop (proven termination) +
short_fold_call_refuses_no_loop_fabricated (fail-closed red control). It is
deliberately resolve-agnostic — it consumes an already-resolved fold-call node —
so it is the same desugaring the whole-tree body producer will run on real
resolved fold calls once corpus resolution is affordable.

Remaining toward catching real quadratics: wire this to real resolved corpus
source (M2 full), corpus-scale resolution affordability (M4), re-key the
accumulator-copy rule to the seam Loop shape (M3).

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

Foundational name-resolution fix. dag_grammar_primary_expr_core stamped the
general expression ident head with StampClass, so a general call's callee came
out as ^dag_token_ident — the NAME dropped at parse. That made every operation
(fold, contains, list_append, ...) unidentifiable in real code and left
resolution nothing to bind: a self-inconsistency in the grammar, since
qualified names (dag_grammar_qualified_name_expr) already StampLexeme.

Switch that one terminal to dag_grammar_terminal_lexeme, identical to how
qualified names already carry their name. Now a general call carries its callee
name, resolution can bind it (resolve_atom looks up the identity in scope), and
consumers read the canonical identity the homogeneous way the R1 lens already
does (r1_symbol_of = atom.identity, matched against a symbol-keyed registry).
This is the existing identification path, not a new mechanism — and NOT the
removed fork's post-hoc lexeme-recovery hack (StampLexeme preserves the name AT
parse; the hack recovered it from the token stream after the fact).

This unblocks operation-identification in real code — the prerequisite for the
complexity feature (and general name resolution broadly). v2 is not in
production, so this is the right window for the change.

Verified: all 4 v2 language parse tests pass; the whole-tree compile-clean gate,
emit-host, source-root-ingest, and self-host gates pass (the content-hash ripple
is contained); parse floor witness green (parse perf witness runs the v1 parser,
unaffected — a transient timing FAIL re-ran green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
First check on real source that the StampLexeme grammar fix does its job: a real
fold snippet ingested through the census path (tokenize -> parse_module ->
normalize, v2.lens.enforcement.cost_coverage) yields a tree in which ^fold (the
callee) and ^xs (the collection) appear as atom identities — the names survive,
so operation recognition is now possible the homogeneous way the R1 lens reads
atom.identity. fold_callee_name_survives_parse is the RED control the pre-fix
StampClass regression breaks. CI-enrolled.

Grounding note recorded for the next slice: the fold call is NOT shaped as a
head-positional callee (that probe failed); it carries the surface call
production structure, so the desugaring will navigate it homogeneously via
parse_subtree_find_production_captured (the same helper cost_coverage already
uses to locate fn bodies) to extract the fn-literal iteration body, then desugar
via v2.compiler.fold_lowering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
Correct fold_call_to_loop to consume the actual surface fold-call subtree
(the primary_expr captured child) rather than a guessed
ComputationNode-with-positional-children shape whose index-3 body slot was
wrong for real folds (fold's iteration body is a NAMED fn-literal arg, not
positional[3]).

The desugaring now identifies the call homogeneously:
- fold_call_head_symbol reads the sequence-left head projection (the callee
  ^fold), the same way the qualified-name parser reads sequence heads;
- fold_call_iteration_body locates the sole ^dag_surface_fn_literal argument
  and lifts its ^dag_surface_fn_body, the same way cost_coverage locates fn
  bodies (parse_subtree_find_production_captured).

Keyed on the callee identity (DESIGN §4: fold/recursion is sugar over Loop),
never a lexeme scan. Fail-closed twice: a call whose head is not ^fold
refuses (^fold_lowering_not_a_fold_call); a fold call carrying no fn-literal
iteration body refuses (^fold_lowering_shape_invalid) — never fabricates a
loop.

Verified end-to-end on real source through the census ingest
(tokenize -> parse_module -> normalize): fold(xs, init: 0, f: fn(acc, x) { acc })
lowers to the canonical seam Loop and loop_multiplicity derives PROVEN
termination from the fold-iteration measure. Both fail-closed arms proven
discriminating by perturbation (each red control flips to FAIL when its arm
is defeated).

Consolidate the two probe test files into one end-to-end witness
(fold_lowering_test), folding in the StampLexeme survival checks (^fold/^xs
survive parse) as the grammar red control; drop the now-redundant
fold_real_source_test and update the CI witness roster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
…l quadratic

Deliver the accumulator-copy lens's first real-source consumer: a surface
projection that reads a parsed fold call and builds R1's fold-step subject
(Instantiation[acc_binder, combiner, call[port0, port1]]), so R1 catches a
real quadratic end-to-end instead of only judging synthetic fixtures.

This is the peer projection of M2's fold_call_to_loop (DESIGN §2 Realization:
one surface fold call, N consumers — the loop projection is for termination,
this one for cost/copy-detection). Both read the SAME located fold call
through the SAME fn-literal locator, now factored out as
v2.compiler.fold_lowering.fold_call_step_fn (§3 single authority for "where
the step function is").

Foundational grammar fix: fn-literal parameters were stamped as the token
class (^dag_token_ident), dropping their names — the same StampLexeme drop
already fixed for call heads. Verified by execution: an unused param vanished
before the fix, survives after. R1 needs the accumulator's name (param 0) to
check whether it lands in the combiner's copied port, so the name must
survive parse.

The distiller (v2.lens.complexity_r1_accumulator_copy.surface_subject) reads
four symbols at the surface/lexeme level: acc_binder = step-fn param 0;
combiner = the head of the call in the step-fn body; port0/port1 = that
call's first two argument values in declared order. Fail-closed at every
step: no step fn / no first param / body not a two-argument call / an
argument not a bare name all REFUSE with a located diagnostic — never
fabricate a subject a fold R1 cannot read as clean.

Proven end-to-end on real source (v2.test.claim.complexity.r1_surface_subject),
through the census ingest: fold(..., fn(acc, x) { list_append(left: acc,
right: x) }) projects Poly(2); the SAME combiner with the accumulator flipped
to the non-copied port (list_append(left: x, right: acc)) is clean —
isolating the copied-port check as the sole difference; an identity-body fold
refuses. Discrimination proven by perturbation: a bogus acc_binder flips the
quadratic to clean (acc_binder is read independently, not circularly).

Surface-level because corpus resolution/body-production is not yet
affordable; a resolved-body read supersedes this once it is, with the seam
(fold_call_step_fn) unchanged. Whole-corpus enrollment (sweep every fold, not
these snippets) remains future work. Full CI floor green (4 batches, 30
witnesses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
…e refusals

Following the methodology "run the model on the real system and let where it
comes back lacking be the signal it's not done" — challenged the M3 distiller
against the actual corpus suspect shape (src/v2/workflow/glob_discovery_law.dag),
not the textbook snippet the M3 tests were written around.

Finding (verified by execution): the distiller MISSES the real quadratic. The
real suspect is fold_list with the copying list_append(left: acc, right: Cons{..})
buried inside an if-branch and a struct-literal grow-by-one arg. The distiller
reads only "the first call in the step body" — which is the if-CONDITION, a
one-argument call — so it reads the branch condition as the combiner and refuses
(cause: port1_opaque) rather than looking inside the branch. Corpus scale for
context: ~676 fold + ~413 fold_list + ~37 fold_node calls; the textbook direct-
combiner-body shape the distiller handles is the minority.

Captured as a committed coverage-boundary witness
(real_branch_body_quadratic_is_currently_missed): a RED that flips to FAIL the
moment the distiller learns to traverse branches — the flip is the dissolution
trigger to widen it into a positive catch. This is the honest "green on the
textbook shape, not done on real code" marker.

Made the distiller's refusals per-cause (FoldSurfaceRead classification →
no_step_fn / no_accumulator_param / body_not_located / body_head_opaque /
port0_opaque / port1_opaque), so the gap is a typed, located, legible fact
rather than one opaque shape-invalid (DESIGN §5: a refusal must be a typed,
located diagnostic). fold_call_to_r1_subject's public Outcome contract is
unchanged; the M3 tests pass unchanged.

Note: a whole-corpus quantified sweep of the distiller hits the same
affordability wall as M4 (per-file ingest + O(folds×subtree) walks), so
quantifying at scale waits on that infra; the qualitative finding here is
decisive on its own. Full CI floor green (4 batches, 31 witnesses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
Replace the 230-line surface-navigation distiller (surface_subject.dag) and the
bespoke r1_fold_step_subject intermediate with a single linear fold, so the lens
is stage1→stage2→… with each stage consuming the previous stage's natural type
(a Node), no adapter.

Two halves, cleanly split:
- COST MODEL (complexity_r1_accumulator_copy.dag) — navigation-free. Owns the
  copied-port registry lookup and classify_call(at, combiner, port_syms,
  carriers) → Optional<Finding>. Finding = Poly2Suspect | AnalysisOpaque.
- TRAVERSAL (complexity_r1_accumulator_copy/analyze.dag) — the fold.
  analyze(node, carriers) recurses over the whole tree threading the carrier
  environment down: at a fold-family call it binds the step-fn's accumulator; at
  every combiner call it asks classify_call.

Because the fold VISITS every node instead of doing targeted "first call in the
body" lookup, a copy inside an if/match/let is found by construction. The real
glob_discovery_law shape (fold_list, copying list_append(left: acc, ...) inside
an if-branch, grow-by-one struct arg) that the bridge MISSED is now CAUGHT — the
old coverage-boundary witness flipped from "currently missed" to
real_branch_body_quadratic_is_caught. classify_call reads only the copied port,
so the old over-strict "both ports must be bare names" gap is gone too. Ports are
read as DIRECT args (no descent into an arg's value) so a nested call doesn't
shift the copied-port index (map_merge's copied port is index 1).

Fail-closed preserved: a known combiner whose copied port is not a bare name is a
counted, located AnalysisOpaque finding — "could not tell" stays distinct from
"clean" (DESIGN §5).

Deletes surface_subject.dag, the r1_fold_step_subject/r1_judge_fold_step judge,
and the 5 synthetic red/green fixtures; the registry coverage they gave is
consolidated into a direct classify_call unit test
(complexity_r1_accumulator_copy_test) covering list_append/list_snoc/map_merge
suspects, non-copied-port and unregistered-combiner cleans, and the opaque case.
r1_surface_subject_test → r1_fold_analysis_test (drives the fold end-to-end).

Remaining (the follow-on "guessing" discussion): three surface reads inside the
fold are still syntactic — carrier = positional first param, combiner = lexeme
name, carrier-in-copied-port = symbol equality not dataflow — which want to be
edge lookups in a resolved/bound tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZJjv8DKX4PpywPNTy6wpc
…real, resolve loses its vacuity

The foundation PR lexeme-stamped identifier REFERENCES (primary_expr_core -> ^x)
but binding INTRODUCTIONS (typed params via dag_grammar_binding_name_terminal)
still stamped ^dag_token_ident, so resolve's real name lookup rejected the valid
bisect module (the rust_tests red: witness_bisect_wave1_parse_module_add_correctness_holds).

Root cause, proven by execution: the normalized wave1 tree has NO Arrow nodes
(normalize is shape-preserving; Arrows are built only by the post-resolve MVP
body producer), so add_arrow_domain_named_params never fires and nothing binds
param names. The pre-PR green was VACUOUS: class-stamped refs collapse to
^dag_token_ident, which is grammar-carried and therefore canonical, so resolve
on main accepts a module referencing an undefined variable (proven with and
without params in scope).

Fix (interim, dissolution trigger on the carrier):
- dag_grammar_binding_name_terminal ident arm -> StampLexeme (names are identities,
  consistent with the namespace-only-resolution direction)
- dag_fn_decl_param_binding_atoms + dag_param_binding_atom_harvest: harvest exactly
  the binding-name atoms from the ^dag_surface_param_list capture (preserved
  qualified_name/module_header subtrees and lex token-class atoms skipped)
- resolve: scope_with_fn_decl_params pushes a ScopeFrame at fn_decl production
  wrappers so param declarations AND body references resolve under it
- dissolves into add_arrow_domain_named_params when body-lowering lands
  fn_decl -> Arrow lowering in normalize (note data row beside the readers)

Red controls (new, CI-gated via interpreted_parse_termination_test):
witness_bisect_wave1_unbound_reference_rejected + _no_binding_rejected pin the
non-vacuous behavior - an unbound body reference must resolve-reject. They go
false if the vacuity ever returns.

Verified by execution in an isolated worktree: bisect witness green, both new
red controls green, fold_lowering 6/6, r1_fold_analysis, budget_roster gate,
loop witnesses, truncated-source rejection all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Closing: the session auto-WIP sweeper pushed my local working branch and auto-opened this draft. The commit it carries is the real fix and has been pushed to its proper home, PR #6373 (claude/complexity-work-status-97zmt9). — sent from eager-ram-612

@gunbai-bot gunbai-bot Bot closed this Jul 9, 2026
@gunbai-bot
gunbai-bot Bot deleted the pr-6373 branch July 9, 2026 20:57
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