Skip to content

test: four-fixture ownership regression suite (Lane 6) - #486

Closed
briansrls wants to merge 3 commits into
mainfrom
glad-koi-274
Closed

briansrls wants to merge 3 commits into
mainfrom
glad-koi-274

Conversation

@briansrls

@briansrls briansrls commented Apr 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Wires docs/dependency-and-rendering-design.md §3.3's four canonical fixtures (id / drop / wrap / is_empty) as executable regression tests so the ownership model's one-fact-shape abstraction is pinned in CI.
  • Assertions today use UnusedParametersLens (the strongest reachability fact pre-Track-1-Phase-2); the doc block shows the ParameterDisposition ∈ {Consumed, Borrowed} tightening path — test interface survives unchanged.
  • Clone-count ratchets per fixture pinned at 0. #[ignore]'d go.dag cross-target variants stubbed for Lane 3 / Track 2.

Fixture adaptations (documented in-file)

  • is_empty swaps List<Int>+Empty/Cons → BoxedFx+BoxedV/Nil because match-on-List core-type lowering isn't wired yet and is_empty/Empty collide with src/v3/std/list.dag. §3.3 abstraction (match-read-only, param inspected not forwarded) preserved; canonical form restorable later without test churn.

Out of scope

  • Any changes to the ownership model itself (that's Track 1 Phase 2).
  • Any fixture types beyond the §3.3 four.

Test plan

  • cargo test -p v3-compiler --test four_fixture_regression_test — 5 passed, 4 ignored (go.dag placeholders)
  • cargo test --workspace --exclude v2-compiler-tests — all green
  • cargo test -p v2-compiler-tests — 415 passed, 54 ignored
  • cargo test -p v2-compiler-tests ci_fixed_point ci_freshness -- --ignored — both pass (compile gates green on this branch)
  • cargo clippy -p v3-compiler --all-targets -- -D warnings

🤖 Generated with Claude Code

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Director Review — Four-fixture ownership regression (#486)

This pins §3.3 of the design doc as a regression suite. Four .dag fixtures (id, drop, wrap, is_empty) cover the four reachability patterns from parameter to return:

Fixture Reachability Expected disposition
id.v3 Direct (param IS the return) Consumed
drop.v3 None (param unused) Borrowed
wrap.v3 Through construct site (record field) Consumed
is_empty.v3 Read-only (match scrutinee) Borrowed

Each fixture asserts: compiles with zero diagnostics + UnusedParametersLens classification matches reachability intent + clone count ratcheted.

The current ratchet is "today: zero clones each" with the future gate locked in for when Phase 2 (#482) lands the Borrowed|Consumed disposition rendering. That's the right shape — the test catches both directions of regression.

LGTM. Ship.

@briansrls

This comment has been minimized.

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex · gpt-5.4 · 6a4b45a5

⚠️ Review (blocking: 1, non-blocking: 1+/0-)

BLOCKING (1)

Root Cause

  • src/v3/compiler/tests/four_fixture_regression_test.rs The suite makes Borrowed and Consumed observationally identical for the consumed fixtures by using Copy-typed data and a clone-string metric, so the green signal can arrive before §3.3 is actually implemented; assert the emitted owned-vs-borrowed shape directly today or use a non-Copy consumed fixture whose wrong contract is observable.

Non-blocking — Strengths

  • src/v3/compiler/tests/four_fixture_pressure/is_empty.v3 The List-to-BoxedFx substitution is documented, bounded, and names the trigger for replacement, so it reads as tracked bridge debt rather than an unbounded workaround.

ROADMAP — Incomplete

  • §3.3 validation gate: A green result here would not yet justify the design doc milestone that says the four-fixture pressure test validates §3.3, because id and wrap still pass under borrowed emission.

⚠️ The fixture set is well chosen, but the current assertions do not yet fail when the two Consumed cases are rendered with the wrong contract, so this is not a sound regression gate for §3.3 yet.

// Ratchet at the current observed count so any regression
// that introduces a clone for this reachability pattern fires.
const ID_CLONE_COUNT: usize = 0;
assert_eq!(

This comment was marked as resolved.

@briansrls

This comment has been minimized.

Wire the four canonical fixtures (id / drop / wrap / is_empty) from
docs/dependency-and-rendering-design.md §3.3 as executable tests.
Each fixture isolates a reachability pattern from the §3.3 table;
together they pin the model's one-fact-shape abstraction claim so a
per-fixture regression fires in one CI run.

Today the assertions use the UnusedParametersLens ("param used vs
unused"), which separates `drop` from the other three — the
strongest reachability fact available pre-Track-1-Phase-2. When
Phase 2 ships ParameterDisposition, the assertions tighten to
`Consumed | Borrowed` per the §3.3 table without the test interface
changing.

Clone-count ratchets pinned at zero per fixture.
`#[ignore]`'d go.dag cross-target variants stubbed for Lane 3.

Notes on fixture adaptations (documented in-file):
- `is_empty` swaps List<Int>+Empty/Cons → BoxedFx+BoxedV/Nil
  because match-on-List core-type lowering isn't wired yet and
  `is_empty`/`Empty` collide with src/v3/std/list.dag. The §3.3
  abstraction (match-read-only, param inspected not forwarded) is
  preserved; canonical form restorable later without test churn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address codex-review blocking comment: the previous clone-count
ratchet used Copy-typed data, which collapses Consumed and
Borrowed renderings observationally — so a green signal could
arrive before §3.3 is actually implemented.

Per §9 ("tests verify the facts, not symptoms"), the assertions
now check the parameter contract directly via emitted Rust
signature shape:

  Borrowed → `fn <name>(p0: &T, ...)`   (leading `&` on param)
  Consumed → `fn <name>(p0: T, ...)`    (no `&` on param)

Signature-shape is a fact, not a symptom: Copy-ness does not
collapse it. `fn id(p0: i64)` and `fn id(p0: &i64)` are
distinguishable regardless of whether the type is Copy.

Structure:
- Borrowed-contract tests (`drop`, `is_empty`) run live today —
  Phase-1 conservative default already matches Borrowed, so they
  verify the §3.3 fact and pass in one step.
- Consumed-contract tests (`id`, `wrap`) are `#[ignore]`'d with
  a named Phase-2 unignore trigger. They encode the sound §3.3
  fact; running them ignored today confirms they fail as expected
  (the emitter renders `&i64` where §3.3 says `i64` for Consumed).
  Removing `#[ignore]` is the single action that promotes Lane 6
  from half-gate to full §3.3 gate when Phase 2 lands.

Removed the clone-count ratchet entirely — it was unsound and
the reviewer was right to flag it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briansrls

Copy link
Copy Markdown
Contributor Author

Addressed in e702a6372. You were right — clone-count on Copy-typed data is a symptom proxy that collapses Consumed and Borrowed observationally. The suite now asserts the §3.3 ParameterContract fact directly via signature shape:

  • Borrowed → fn <name>(p0: &T, ...) — leading &
  • Consumed → fn <name>(p0: T, ...) — no &

This distinguishes the two contracts regardless of Copy-ness.

Structure that keeps Lane 6 independently mergeable while installing the §9 sound gate:

  • Borrowed-contract tests (drop, is_empty) run live today — Phase-1 conservative default already matches Borrowed, so the §3.3 fact is verified in one step.
  • Consumed-contract tests (id, wrap) are #[ignore]'d with a named Phase-2 unignore trigger. They encode the sound §3.3 fact; running them today with --ignored confirms they fail as expected with targeted messages like:

    §3.3 ParameterContract violated for id: expected Consumed, observed Borrowed. First-parameter type: &i64. Full signature: fn id(p0: &i64)

Removing the #[ignore] is the single action that promotes Lane 6 from half-gate to full §3.3 gate when Phase 2 teaches emit_rust to honor Consumed.

The clone-count ratchet was deleted entirely (unsound per your point).

@briansrls

briansrls commented Apr 16, 2026 •

Copy link
Copy Markdown
Contributor Author

ChatGPT Review

Principle audit.

Fail-closed. Satisfied. For a test harness, the failure paths all stop loudly and specifically: fixture I/O, compile, diagnostic, and emission failures are all surfaced at src/v3/compiler/tests/four_fixture_regression_test.rs:77-89, and signature-parsing failures fail with targeted messages at :140-159. The enforcement is behavioral rather than type-level, but for test code that is the right shape.

Illegal states unrepresentable. Mostly satisfied. The only new Rust coproduct is the local ParameterContract enum at src/v3/compiler/tests/four_fixture_regression_test.rs:130-134; it has one meaning per variant and does not overload absence. This is implementation-local test code, so the full substrate dissolution bar does not apply.

Facts flow forward. Mostly satisfied, and this is where the revision materially improved the PR: the old clone-count symptom is gone, and the per-fixture checks now pin the direct Rust consequence of the intended fact at src/v3/compiler/tests/four_fixture_regression_test.rs:119-159, :244-289, with the two Phase-2-only consumed cases explicitly parked at :237-242 and :270-275. My one NON-BLOCKING concern is that the abstraction-level test still collapses the four-case table back to param_reaches_anywhere: bool at :301-340, so the shared path is still a reachability symptom, not the ParameterContract fact the file header says this suite is about. That is the last place where the file still mixes the new fact-level story with the old Phase-1 proxy. Grounded in the thesis’s warning that lossy compression becomes a correctness bug when downstream code rebuilds a coarser proxy, and in the review rule that facts should be carried or explicitly discarded. chatgpt-review-19d8f472-18c6-4c…

chatgpt-review-97e242da-51fe-46…

Coproduct dissolution. Satisfied. ParameterContract is implementation-local, so exempt. The fixture-local BoxedFx scaffold in src/v3/compiler/tests/four_fixture_pressure/is_empty.v3:12-28 is well tracked: it says exactly why the canonical List<Int> form is not available today and names the dissolution trigger. I would accept that as bounded bridge debt.

Single-authority metadata. Mostly satisfied. The fixture files are the authority for the four shapes, and the harness centralizes lookup through find_function_bind() and parameter_is_used() rather than scattering ad hoc DAG walks (src/v3/compiler/tests/four_fixture_regression_test.rs:92-117). The one thing to watch is that the private ParameterContract mirror at :130-134 should disappear once Track 1 Phase 2 exposes the real compiler-side fact; today it is acceptable because the authoritative API does not exist yet, but it should not outlive that gap.

API-level enforcement. Partially satisfied. The new signature-shape assertion is a much stronger behavioral gate than the previous clone proxy, and the ignored consumed tests are explicitly documented with a named unignore trigger (src/v3/compiler/tests/four_fixture_regression_test.rs:42-48, :237-242, :270-275). But the enforcement is still behavioral rather than type-driven, because the compiler does not yet surface ParameterContract directly. Given the stated phase boundary, that reads as an honest tracked bridge, not cheating.

Design question.

When Track 1 Phase 2 lands, is Lane 6 supposed to become a direct consumer of the compiler’s authoritative ParameterContract, or is the local ParameterContract/param_reaches_anywhere machinery intended to remain? What’s at stake is whether this suite finishes as a true single-authority consumer of the ownership fact, or calcifies a second representation that still keeps the cross-fixture abstraction test on the weaker reachability axis.

Verdict.

APPROVE_WITH_COMMENTS. This revision fixes the main structural weakness from the earlier round: the suite no longer infers ownership from clone counts, and the remaining phase gap is tracked honestly rather than hidden. My only concern is to make sure the last shared-path proxy (param_reaches_anywhere) and the local fact mirror dissolve once the real compiler API exists.

LOOP HEALTH: converging — this round replaces an unsound symptom gate with a sound contract-shaped one, and the debt it adds (#[ignore]d consumed cases plus the BoxedFx fixture bridge) is explicitly documented, bounded, and named by trigger.


View conversation

@briansrls

briansrls commented Apr 16, 2026 •

Copy link
Copy Markdown
Contributor Author

ChatGPT Review

Principle audit.

Fail-closed. Satisfied. The v3 harness still fails loudly at every boundary: fixture read, compile, diagnostics, signature extraction, and emit all stop with targeted messages in src/v3/compiler/tests/four_fixture_regression_test.rs:77-89, 140-159. The bootstrap stabilization does the same for copy/rustfmt failures in src/v2/tests/src/bootstrap.rs:68-84, 707-714, 771-775. These checks are behavioral rather than type-level, but for test code that is the right shape.

Illegal states unrepresentable. Satisfied. The new Rust-side state is implementation-local and clean: ParameterContract has one meaning per variant in src/v3/compiler/tests/four_fixture_regression_test.rs:130-134, and I do not see any overloaded None or mutually-exclusive product state. The fixture sum BoxedFx is test input, not compiler substrate, so the full substrate bar does not apply.

Facts flow forward. Satisfied. The four-fixture suite now carries the intended ownership fact forward through its direct emitted-signature consequence instead of backing into it from a symptom proxy in src/v3/compiler/tests/four_fixture_regression_test.rs:13-24, 119-160, 237-275. The bootstrap change also carries the “support modules must exist for normalization/buildability” fact into both CI pass outputs before diffing in src/v2/tests/src/bootstrap.rs:65-84, 707-714, 771-775. These are still behavioral checks, but they are attached to the right downstream consumers.

Coproduct dissolution. Satisfied. No new substrate enum lands here. ParameterContract is implementation-local test scaffolding, so exempt. The fixture-local BoxedFx scaffold in src/v3/compiler/tests/four_fixture_pressure/is_empty.v3:12-28 is documented, bounded, and names its dissolution trigger.

Single authority. One NON-BLOCKING concern in the bootstrap helper. This round correctly centralizes the copied support-module set in copy_stage0_support_modules at src/v2/tests/src/bootstrap.rs:68-78, but the same fact still exists independently in diff_excluding_hand_maintained as two hard-coded --exclude entries at :129-135. That leaves “which files are the hand-maintained bootstrap support modules?” with two sources that can drift. The current enforcement is behavioral, not structural.

API-level enforcement. Mostly satisfied on the v3 side: the live tests assert executable contract consequences, and the parked Phase-2 cases have explicit named triggers in src/v3/compiler/tests/four_fixture_regression_test.rs:42-48, 237-242, 270-275. My only reservation is again bootstrap-side: nothing forces every bootstrap-equivalence reader to go through the same normalization path, so the rule is still convention-level there.

Design question.

Is ci_fixed_point now the canonical bootstrap equivalence gate, or should bootstrap_fixed_point collapse onto the same normalization pipeline? Right now this PR teaches only the CI path that “same bootstrap output” means “support modules copied in, outputs rustfmt-normalized, then diffed” (src/v2/tests/src/bootstrap.rs:707-714, 771-775, 875-883), while the older ignored bootstrap_fixed_point path still diffs raw outputs (:444-489). If both stay, the project has two definitions of bootstrap equivalence, and one of them will eventually rediscover noise the other intentionally dissolved.

Verdict.

APPROVE_WITH_COMMENTS. The four-fixture lane itself looks clean in this round, and the CI bootstrap change is moving in the right direction by removing false diff noise rather than weakening the gate. My only comment is to finish collapsing the bootstrap diff authority so the new stabilization logic lives in one canonical path.

LOOP HEALTH: converging — this round strengthens the ownership suite without inventing new fake greens, and the bootstrap change removes known CI noise; the only remaining debt I see is a small single-authority cleanup around bootstrap diff semantics.


View conversation

@briansrls

briansrls commented Apr 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Meta-Review (Loop Health)

According to a document from 2026-04-16, this loop is making real progress, but it has already reached diminishing returns on this PR. The core blocker was real, it got fixed, and what remains is tracked bridge debt rather than a still-unknown defect class.

Loop summary

Visible in the attached history are 2 substantive patch rounds: the first round got one ChatGPT browser review at 21:04Z and one codex-cli review at 21:11Z; the second round got a revised ChatGPT browser review at 21:43Z. There is also 1 additional browser run still in progress at 22:07Z. So the visible counts are K=1 codex review and L=2 completed browser reviews plus one pending browser pass. The attachments do not expose exact GitHub commit count or separate inline-comment totals, so the honest summary is 2 visible patch revisions / review rounds, over about 63 minutes of review-loop time.

Forward progress evidence

The loop did produce a new consumer. The first browser round explicitly recognized that the PR turned prior ownership discussion into “four concrete fixtures plus a reusable CI harness,” with the same compiled Dag feeding both UnusedParametersLens and emit_rust, so this is not substrate growth with no reader; it is a real test consumer of the ownership story.

More importantly, the loop fixed its most important flaw. Codex flagged that the original suite made Borrowed and Consumed observationally identical by using Copy-typed data plus a clone-string metric, so it was not a sound §3.3 gate. The next browser review says that weakness was materially fixed: the suite “no longer infers ownership from clone counts” and instead pins the direct emitted-signature consequence, which is exactly the right direction for “consumers define correctness” and “facts flow forward.”

This also lines up with the project’s actual ownership roadmap: LS-4 says the long-term target is for read-only parameters to be borrowed and for call sites to stop cloning defensively. PR #486 is now testing that ownership distinction at the signature boundary instead of treating raw .clone() count as the fact itself. That is genuine forward motion toward a real consumer of the planned ownership model, not sideways churn.

Debt accumulation evidence

Debt still accumulated, just in tracked form. The revised round still leaves a local ParameterContract mirror, keeps the two Consumed fixtures behind #[ignore], adds ignored Go placeholders, and keeps the abstraction-level shared test collapsed to param_reaches_anywhere: bool. The second browser review calls that out directly: the shared path is still a reachability symptom rather than the ownership fact, and the local mirror must disappear once Track 1 Phase 2 exposes the authoritative compiler-side fact.

That means principle 3 is only half-satisfied. A recurring finding did not fully graduate to an invariant inside the PR; it was fixed locally in per-fixture assertions, but the abstraction-level test still re-compresses the fact into a coarser proxy. Principle 4 is also only half-satisfied: the suite now spans lens reachability and emitter signature shape, but it still lacks one cross-layer authoritative ownership fact. In project terms, this is the same general disease the semantic-authority invariant was written to prevent: secondary representations hanging around because the authoritative fact is not yet surfaced where the consumer wants it.

The scaffolds themselves are properly accounted for. BoxedFx is explicitly bounded, with a named dissolution trigger, and the ignored consumed tests are likewise parked behind named Phase-2 triggers. So this is not unbounded hidden debt. But it is still net new scaffold debt: BoxedFx bridge, ignored consumed cases, ignored cross-target placeholders, local fact mirror, and the shared bool proxy all entered the tree in exchange for getting a mergeable Phase-1 half-gate now.

Cheating signal

The cheating signal is low. The implementer is not hiding compromises. The reviews explicitly describe the remaining gap as “tracked honestly rather than hidden,” and the named triggers are written down in the test story instead of being smuggled in as silent “good enough for now” behavior.

But the fixes are mixed. The move from clone-count proxy to signature-shape assertion is structural. The remaining ParameterContract mirror plus param_reaches_anywhere shared-path test are not structural; they are bridge-shaped stopgaps. That matters because the project’s own invariants are blunt about this class: short-term bridges and tracked later-removal paths tend to calcify unless the follow-up authority lands soon. So this is “cheating with accounting,” not hidden cheating, and that is acceptable only because the debt is clearly scoped to Track 1 Phase 2 rather than being treated as the finished shape.

Meta-verdict

⚖️ SHIP_WITH_DEBT

This is not the kind of stagnant loop that deserves PAUSE_AND_REGROUP. The history does not show the PR #445 pattern where the same root disease survived round after round until the project had to graduate it into an invariant. Here, one blocking finding landed, the author responded with a materially stronger consumer, and the remaining debt is mostly blocked on Track 1 Phase 2 exposing the authoritative ownership fact.

It is also not KEEP_ITERATING territory anymore. Another round on this PR is unlikely to buy much more than arguing about the same known bridge: the local ParameterContract mirror and the param_reaches_anywhere abstraction test. The big value already landed when the loop replaced the unsound symptom proxy with a contract-shaped assertion. What remains should be paid off in the follow-up that exposes the real compiler ownership fact, not by grinding this PR for one more review pass.

So the honest call is: merge it as a tracked Phase-1 half-gate. It enabled a real consumer, it fixed the main bluff in the loop, and the remaining debt is named. But do not count it as full §3.3 closure until the ignored Consumed tests are live and Lane 6 consumes the compiler’s authoritative ownership fact directly.


View conversation

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codex · gpt-5.4 · 6f062351

✅ Review (blocking: 0, non-blocking: 1+/0-)

Non-blocking — Strengths

  • src/v2/tests/src/bootstrap.rs Copying the hand-maintained stage0 support modules before the rustfmt/diff steps keeps the freshness and fixed-point checks focused on generated output instead of missing support files.

✅ The prior soundness hole is closed; the Borrowed fixtures verify the direct §3.3 consequence today, the Consumed fixtures are explicitly staged behind the named Phase-2 trigger, and I do not see new concerns in this PR.

@briansrls

Copy link
Copy Markdown
Contributor Author

codex · gpt-5.4 · 6f062351

✅ Review (blocking: 0, non-blocking: 1+/0-)

Non-blocking — Strengths

  • src/v2/tests/src/bootstrap.rs Copying the hand-maintained stage0 support modules before the rustfmt/diff steps keeps the freshness and fixed-point checks focused on generated output instead of missing support files.

✅ The prior soundness hole is closed; the Borrowed fixtures verify the direct §3.3 consequence today, the Consumed fixtures are explicitly staged behind the named Phase-2 trigger, and I do not see new concerns in this PR.

@briansrls

Copy link
Copy Markdown
Contributor Author

Closing in favor of a unified reconciliation approach.

The merge-train experiment on ci-fixed-point-stability surfaced that the PRs have overlapping structural concerns that don't reconcile cleanly at merge time (schema drift on TargetExecutionModel, CallableRealization, ParameterDisposition; spec-to-emit boundary leaks; missing dissolution receipts; fail-open paths where fail-closed is required; duplicated/conflicting target dispatch keyed on name prefix rather than typed facts).

Rather than push each branch through another round of rebase-fix-review that would repeatedly rediscover cross-branch conflicts, all six PRs (#482, #483, #484, #485, #486, #487) are being reconciled into a single reconciliation-l1.5 branch that lands the deliverables coherently:

All review comments (codex, chatgpt-browser, director) harvested into .claude/pr-reconciliation.md so nothing is lost.

Class of fixes addressed uniformly in reconciliation:

  • C1: 🟢/🟡/🔴 receipts on every new coproduct (modeling-discipline §4)
  • C2: every spec field consumed by its emitter, fail-closed on unsupported
  • C3: one canonical schema for TargetExecutionModel / CallableRealization / ParameterDisposition across std/ and specs
  • C4: target selection keyed on typed substrate facts, not name prefix
  • C5: all analysis paths return Result — no silent defaults on invariant breaks
  • C6: last-use tracks borrowed + consumed uses (FF-1)
  • C7: four-fixture suite gets non-Copy variants + signature-shape assertions
  • C8: L2 lens migrations follow a single established pattern (structural measure, not fuel; private generated wrapper)
  • C9: per-emitter bugs fixed (Go ListFold scope, Python variant naming, etc.)
  • C10: stage0 regen --output-dir path runs fmt too

Thanks for the work — all of it ships through the reconciliation.

@briansrls briansrls closed this Apr 16, 2026
briansrls added a commit that referenced this pull request Apr 17, 2026
* Migrate cost lens to complexity.dag

* Fix complexity lens clippy naming

* test: pin §3.3 four-fixture ownership regression suite (Lane 6)

Wire the four canonical fixtures (id / drop / wrap / is_empty) from
docs/dependency-and-rendering-design.md §3.3 as executable tests.
Each fixture isolates a reachability pattern from the §3.3 table;
together they pin the model's one-fact-shape abstraction claim so a
per-fixture regression fires in one CI run.

Today the assertions use the UnusedParametersLens ("param used vs
unused"), which separates `drop` from the other three — the
strongest reachability fact available pre-Track-1-Phase-2. When
Phase 2 ships ParameterDisposition, the assertions tighten to
`Consumed | Borrowed` per the §3.3 table without the test interface
changing.

Clone-count ratchets pinned at zero per fixture.
`#[ignore]`'d go.dag cross-target variants stubbed for Lane 3.

Notes on fixture adaptations (documented in-file):
- `is_empty` swaps List<Int>+Empty/Cons → BoxedFx+BoxedV/Nil
  because match-on-List core-type lowering isn't wired yet and
  `is_empty`/`Empty` collide with src/v3/std/list.dag. The §3.3
  abstraction (match-read-only, param inspected not forwarded) is
  preserved; canonical form restorable later without test churn.

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

* test: verify §3.3 ParameterContract facts, not symptoms (Lane 6)

Address codex-review blocking comment: the previous clone-count
ratchet used Copy-typed data, which collapses Consumed and
Borrowed renderings observationally — so a green signal could
arrive before §3.3 is actually implemented.

Per §9 ("tests verify the facts, not symptoms"), the assertions
now check the parameter contract directly via emitted Rust
signature shape:

  Borrowed → `fn <name>(p0: &T, ...)`   (leading `&` on param)
  Consumed → `fn <name>(p0: T, ...)`    (no `&` on param)

Signature-shape is a fact, not a symptom: Copy-ness does not
collapse it. `fn id(p0: i64)` and `fn id(p0: &i64)` are
distinguishable regardless of whether the type is Copy.

Structure:
- Borrowed-contract tests (`drop`, `is_empty`) run live today —
  Phase-1 conservative default already matches Borrowed, so they
  verify the §3.3 fact and pass in one step.
- Consumed-contract tests (`id`, `wrap`) are `#[ignore]`'d with
  a named Phase-2 unignore trigger. They encode the sound §3.3
  fact; running them ignored today confirms they fail as expected
  (the emitter renders `&i64` where §3.3 says `i64` for Consumed).
  Removing `#[ignore]` is the single action that promotes Lane 6
  from half-gate to full §3.3 gate when Phase 2 lands.

Removed the clone-count ratchet entirely — it was unsound and
the reviewer was right to flag it.

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

* Stabilize v2 CI bootstrap diffs

* Stabilize v2 CI bootstrap diffs

* Lane 4: migrate provenance lens to .dag authority

Move the one-hop `produced_by` walk from hand-written Rust to
src/v3/lenses/provenance.dag. Rust callers consume an auto-emitted
snapshot via `v3_compiler::lens_provenance::origin_of`; the
hand-written `lens_provenance.rs` is deleted. Migration test
(m2_lens_provenance_migration_test.rs) pins the .dag snapshot,
ratchets the emitted clone count, and verifies behavioral
equivalence against a hand-written oracle across four fixtures.

Origin reshaped from 4 variants over `Option<NodeId>` to 5 flat
variants because v3's current surface grammar doesn't support
Some/None in expression position — `NoProducer` splits out the
`by: None` case, `Source(NodeId)` subsumes `by: Some(_)`.
Placement is src/v3/lenses/ (sibling to unused_parameters.dag),
not src/v3/std/, to avoid double-loading via STAGED_FILES; moving
into std/ is follow-up once the compile pipeline tolerates
re-lowering files bootstrap already staged.

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

* complexity.dag: fix double-decrement underreport + document fuel scaffold

**Bug fix.** The prior fuel seed was `port_count` but each recursive
step decrements fuel twice (once entering a Transform's SumPorts
mode, once walking the input-list tail). Unary transform chains
therefore ran out of fuel early — a 3-transform chain returned 2
instead of 3. Fix: seed = 2 × port_count, which accommodates the
double-decrement without premature termination.

**Scaffold receipt.** Fuel is not the thesis-correct pattern; it's
a workaround for .dag's termination checker. The header comment
now carries a 🟡 scaffold receipt naming three concrete dissolution
triggers:

  (a) .dag gains mutual recursion support → graph-descent and
      list-descent split into separate structural functions (tried
      in this PR, blocked by "mutual recursion not yet supported").
  (b) .dag termination checker accepts DAG-walk termination via
      substrate invariants (the DAG is acyclic and finite).
  (c) Substrate exposes a fold_backward(d, port, f) or forward-fold-
      by-topological-order primitive (tried in this PR as forward
      fold over d.nodes; blocked by emit_rust dispatch_producer
      failing on the new record-literal shape; investigation
      deferred).

The oracle for correct behavior remains the original Rust cost
lens at `src/v3/compiler/src/lens_cost.rs` on commit 1247f4b —
a clean recursive walk via total Dag.port() / Dag.node() accessors,
no fuel needed (Rust has no termination checker). This .dag lens
agrees with it on the existing oracle fixture; when any dissolution
trigger fires, the fuel and CostMode scaffold disappear and the
lens becomes a pure structural walk matching the Rust oracle's
shape.

All migration tests pass (complexity_dag_compiles_cleanly,
complexity_dag_matches_handwritten_oracle_on_core_fixtures,
complexity_generated_module_matches_checked_in_snapshot). Generated
module regenerated to reflect the new seed.

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

* complexity.dag: replace fuel counter with structural forward fold

The fuel counter was a 🟡 scaffold that papered over a false gap. The
actual blocker in the prior attempt was `Cons(...)` as expression-form
constructor (a parser limitation), misattributed as "substrate needs
fold_backward primitive."

Fix: import `cons` from std.list. Rewrite as forward fold over
`d.nodes` — the substrate already emits nodes in dependency order, so
a single `fold(d.nodes, empty(), |acc, node| cons(entry_for(acc, node), acc))`
walk builds a port→cost entry list in one pass. Termination is
structural on the finite List<Behavior>. No fuel, no mutual recursion.

Outcomes:
- complexity.dag: 271 → 128 lines
- lens_cost_generated.rs: 231 → 128 lines
- All 250 v3-compiler tests pass
- Receipt: 🟡 Scaffold → 🟢 TERMINAL

Also adds src/v3/compiler/src/bin/regen_lens_cost.rs to regenerate
the snapshot after future complexity.dag edits.

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

* fmt: rustfmt regen_lens_cost.rs

Multi-line method chains broken per rustfmt on the new regen bin.

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

* complexity.dag: fail-closed MissingCost; pin Bind via method doc

Two blockers from PR #489 review:

A2 — fail-closed on malformed references. lookup_int returned 0 on
MissingCost, collapsing "port referenced but absent from d.nodes"
into "legitimate zero-cost leaf." Pre-seed the cost accumulator with
FoundCost(0) entries for every bind.params port (the only real
absent-from-d.nodes zero-cost leaves) and propagate MissingCost
through the sum/max helpers via a new CostLookup coproduct. cost_of
now returns CostLookup; CostLens::cost_of panics on MissingCost at
the Rust boundary, surfacing the fail-closed contract.

Bind semantics — explicit boundary. complexity.dag's Bind branch uses
`bind.result_port` (substrate name), tests use `bind.value` (Rust
struct field). BindNode::result_port() is the single point of
agreement; doc comment now says so. Added two Bind-with-parameters
fixtures (`fn double(x)`, `fn add(x, y)`) that exercise both the
seed_bind_params pre-seed and the Bind branch of entry_for.

Test harness: roundtrip harness now compiles once, reads fixtures
from argv, and runs inner rustc with -D warnings. Applied uniformly
across all three lens migration tests.

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

* tests: revert -D warnings + #[allow(warnings)] band-aids

The rustc roundtrip tests added `-D warnings` + `#[allow(warnings, clippy::all)]`
on the emitted `mod emitted { ... }` wrapper. That masks emitter warnings
(unused parens, unused pattern bindings, unused diagnostics import) at the
consumer instead of fixing them at the source.

Deferring the structural fix to a follow-up that teaches emit_rust to:
1. Wrap binops by precedence context, not unconditionally
2. Emit `_` for pattern bindings not referenced in the arm body
3. Include `use v3_compiler::diagnostics::*` only when the module uses it

The compile-once-run-many perf refactor (build_roundtrip_harness) stays
— that is a real structural win independent of the warning story.

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

* m1_3: batch 14 rustc-roundtrip tests into two shared harnesses

Each rustc invocation paid ~3-5s cold linker/codegen on CI; 14 tests
serialized into ~60s of pure rustc. Batch all 9 self-contained-program
fixtures into one binary (compiled with plain rustc) and all 5
reflected-module fixtures into another (compiled with v3_compiler
--extern). Tests dispatch via argv[1]; harnesses are built lazily via
`OnceLock<PathBuf>` on first access.

Local wall time: m1_3 dropped from 3.37s → 1.70s (-50%). CI win scales
with cold-cache rustc cost — expect 40-60s less on the v3 job.

Fixture descriptors live in const arrays (`PROGRAM_FIXTURES`,
`REFLECTED_FIXTURES`); submodule naming namespaces the emissions so
type and function declarations don't collide across fixtures. Group 1
programs get `fn main()` → `pub fn main()` rewritten so the nested
main is callable from the dispatcher.

The hand-written `roundtrip_stdout` helper stays because the
`#[ignore]`d `rustc_roundtrip_int_addition_prints_three` anchor test
uses it as the canonical end-to-end example.

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

* tests/common: extract RustcHarness — lazy DI / RAII for rustc roundtrips

Five test binaries (m1_3_emit_rust, m2_lens_{cost,provenance,unused_parameters}_migration)
each carried identical copies of `deps_dir`, `find_current_rlib`,
`next_roundtrip_dir`, and `compile_with_current_crate`. Extract into
`tests/common/mod.rs`; each test file imports via `mod common;`.

The harness API:
- `RustcHarness::new(scope)` — one per test binary, owns scratch root
  and an atomic child-dir counter.
- `harness.compile(source, bin_name, HarnessLinkMode)` — rustc invoke
  keyed on link mode. `Standalone` for self-contained emissions,
  `WithV3Compiler` for reflected-DAG modules that need the v3_compiler
  rlib.
- `RustcHarness::run(bin, args)` — spawn + assert + capture stdout.

Lazy init: each test file wraps `RustcHarness` in its own
`OnceLock<RustcHarness>` so construction happens at first test access
and the harness is reused across every `#[test]` in that binary.
Batched compilations remain lazy via `OnceLock<PathBuf>` on top of
the shared harness.

Net: 339 lines removed, 214 added. Helpers are declared once, and
the "which link mode does this test need?" question becomes a typed
enum rather than a choice between copy/pasted helpers.

`#[allow(dead_code)]` on common/mod.rs: each test binary exercises a
subset of the API (e.g. the cost lens migration never builds a
Standalone harness), so unused items per-binary would otherwise trip
clippy's -D warnings.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briansrls
briansrls deleted the glad-koi-274 branch June 1, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant