Batch D — Implementation Plan
Branch target: trunk Baseline: 73185be8 (Batch C complete, fixpoint 2285, smoke 4/4 + 22/22 + full B-sprint regression sweep + 17-spec B+C sweep green)
Sprint size: 5 commits. Strict order: D1 → D2 → D3 → D4 → D5.
Theme: Cleanup & hygiene — clear the progress sprint quirks backlog (PSQ-1, PSQ-3, PSQ-5, PSQ-7) and add defense-in-depth for the macro-expansion slot-layout class of bugs surfaced in C5.
Protocol: quake guard + quake smoke + per-item regression sweep before each commit. No --no-verify, no skipping fixpoint, no dropping the fix-specific backup until the commit lands.
Context
Batch C (Apr 14, 2026) landed 5 of 5 items in one overnight session:
- C1 QUAKE-STDERR-AUDIT — Quakefile build/link sites migrated to
sh_buffered - C2 B3-DIRECT-INDEX-FIELD —
Vec<Struct>ptype renders correctly (this ALSO closed the less-dangerous symptom of PSQ-4; the silent-wrong-offset worse symptom needs verification in D1) - C3 IMPL-NOT-SEND —
impl !Send / !Sync for X endexplicit opt-out - C4 POST-RESOLVE-IDENT-ASSERTION — QZ9501 PascalCase-only guardrail at MIR lowering entry
- C5 B4-UNWRAP-IN-LOOP —
expand_noderead wrong slot for NODE_ASSIGN; macro call never reached the$unwrapexpander for compound assignments. Fix was inexpand_node, not the macro or the match lowering.
The C5 root cause — a slot-layout mismatch between expand_node and ast.qz constructors — is a class of bug. There are ~30 other if kind == N branches in expand_node that could have similar mismatches that nothing currently exercises. D1 is a preemptive audit of that class.
The remaining open PSQs (progress sprint quirks) are small, well-scoped, and keep ships cleaner without burning multi-session budgets. Bundling them into one sprint completes the backlog in a single context window. No multi-session architectural work in this sprint — those items (#11 resolver full scope, #12 pattern exhaustiveness, #19 parser O(n²) fix) need their own dedicated sessions and shouldn’t compete for attention here.
Items in strict execution order (easiest → hardest):
| # | Item | Est | Risk | Has reproducer? |
|---|---|---|---|---|
| D1 | EXPAND-NODE-AUDIT — walk every kind == N branch in expand_node vs. actual AST slot layouts, fix any mismatches, verify PSQ-4 worst-case is actually closed by C2 | 1h | Low | Synthetic probes |
| D2 | PSQ-5 — eputs("msg\n") doubled newlines (strip redundant \n or document the auto-newline behavior) | 30min | Low (cosmetic) | N/A |
| D3 | PSQ-3 — var end = 42 parser error: emit clean diagnostic instead of cascading garbage | 1-2h | Low-med | Known repro |
| D4 | PSQ-1 — Method-as-free-function collides with free function at same name + higher arity (typechecker fix: prefer exact-arity free function) | 2-3h | Medium (typechecker change) | Yes, workaround documented |
| D5 | PSQ-7 — Suspendable-effect leaves audit: verify uses_scheduler flag propagation for every leaf. Also audit weakly-ordered slot reads in __qz_sched_io_poller | 2-3h | Medium (systematic audit) | One leaf already fixed |
Total estimate: ~7–10 quartz-hours. Overnight-sized. Ordering is easiest-first for commit safety; even if D4 or D5 consumes its full budget without landing, 3-4 commits still reach trunk.
Prime directives (non-negotiable, from CLAUDE.md)
- Pick highest-impact, not easiest — this sprint is explicitly a cleanup bundle. The highest-impact items in the roadmap (#11, #12, #19 Phase 3) are multi-session and deferred.
- Design before building; research first — each item has a fix sketch below. Verify against current codebase before editing.
- Pragmatism ≠ cowardice; shortcuts = cowardice — if an audit uncovers more than the budget, regression-lock the safe fix, file deeper gap into ROADMAP, move on.
- Work spans sessions — if D4 or D5 stalls, commit what’s done, file the rest.
- Report reality, not optimism — honest reports, fixpoint ±30 required each commit.
- Holes get filled or filed — audit discoveries go into ROADMAP with context.
- Delete freely, no compat layers — standard.
- Binary discipline = source discipline —
quake guardmandatory, fix-specific backups mandatory. - Quartz-time estimation — traditional ÷ 4.
- Corrections are calibration, not conflict — no rationalizing past failures.
Cross-cutting findings
-
D1 IS the lesson from C5 — “when two fix attempts both no-op, suspect the fix site isn’t reached at all.” The C5 commit message included a note on how a 5-minute
eputsprobe would have surfaced the bug. D1 audits the rest ofexpand_nodefor the same class before the next bug lands. Any finding goes straight into a fix commit. -
PSQ-4 status is ambiguous after C2. The less-dangerous symptom (“Unknown struct: Struct, unknown”) was the exact shape C2 fixed. The worse symptom (two structs sharing a field name, silent wrong-offset reads) may or may not still reproduce. D1’s tail-end task is to verify this — construct the two-struct repro from
1a60633c, compile, check IR for correct offset. If the worse symptom is closed, close PSQ-4 in ROADMAP. If it reproduces, file as its own follow-up (NOT inlined into D1’s commit). -
PSQ-4 worst-case and D1 are the only items with cross-cutting risk. D2–D5 are independent and have no ordering constraint beyond “commit safely.”
-
quake guardcache cleanup between commits is mandatory. Same lesson from Batch A+B/C — stale.quartz-cachecan mask both fixes and regressions.
Per-item design
D1 — EXPAND-NODE-AUDIT: slot-layout hygiene for macro expansion
Goal. Every kind == N branch in self-hosted/frontend/macro_expand.qz’s expand_node function reads/writes the correct AST slots per the constructor in self-hosted/frontend/ast.qz. Catch any remaining mismatches of the C5 class before they bite.
Procedure.
-
Enumerate all branches. Grep
expand_nodeforif kind ==andelsif kind ==. Record each kind number and the slots the branch reads/writes. -
Cross-reference against
ast.qz. For each kind, find the matchingdef ast_<name>(...)constructor. Record which slots (lefts,rights,extras,children,str1s,str2s,int_vals,ops) hold which semantic payloads. -
Verify match. For each branch:
- If
expand_nodereads slot X and the constructor stores the walkable payload in slot Y ≠ X, it’s a bug. - If
expand_nodewrites back to slot X but the constructor expects slot Y, it’s a bug. - If a branch doesn’t recurse into a payload slot that could contain a macro call, it’s a latent bug.
- If
-
Fix every mismatch in one commit. Keep the fix surgical — replace
ast_get_rightwithast_get_left/ast_get_extra/ etc. as the constructor dictates. Don’t refactor beyond what’s needed. -
Add tests. For each fix (or the most impactful one), write a QSpec that exercises the macro-in-that-context path. E.g., if NODE_RETURN’s walker was wrong, test
return $try(opt)where the macro must expand. Add these tests to a newspec/qspec/expand_node_audit_spec.qzfile. -
Verify PSQ-4 worst-case. Construct the two-struct-shared-field repro from commit
1a60633c:struct Progress { name: String; state: Int; pct: Int; ..; _ended: Int } # _ended at offset 5 struct Live { screen: Int; ..; _ended: Int } # _ended at offset 10 def walker(actives: Vec<Live>): Int var total = 0 for item in actives total += item._ended # must resolve to Live._ended, not Progress._ended end return total endCompile to IR and check that the load offset is 10 (Live’s offset), not 5 (Progress’s offset). If fixed, close PSQ-4 in ROADMAP. If reproduces, file as a new row with the minimal case.
Test plan. New spec spec/qspec/expand_node_audit_spec.qz:
return $try(opt)— NODE_RETURN with macro callvar x = $try(opt)— NODE_LET with macro call (already works, regression lock)sum += $try(opt)— NODE_ASSIGN compound (C5 lock-in)arr[i] = $try(opt)— NODE_INDEX_ASSIGN with macro call (new path exercised by D1 fix)obj.field = $try(opt)— NODE_FIELD_ASSIGN with macro call (new path exercised by D1 fix)if cond then $unwrap(opt) else 0 end— NODE_IF expression position
Steps.
rm -rf .quartz-cache && cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d1-golden- Read
expand_nodetop to bottom. Record every kind branch. - Read
ast.qzfor each matching constructor. Build a cross-reference table. - Apply fixes. Each fix should be ~3-5 lines (one slot rename).
- Write the new spec file with 6 tests.
- Run the PSQ-4 worst-case repro manually.
quake build+quake guard+quake smoke+ B+C sweep.- Update ROADMAP: close PSQ-4 or file new gap.
- Commit:
D1: EXPAND-NODE-AUDIT — slot-layout hygiene for macro expansion.
Complexity. S-M (1-1.5h). Risk. Low — each fix is mechanical, the test cases exercise all three assign kinds + the straightforward node types.
D2 — PSQ-5: eputs("msg\n") doubled newlines
Goal. eputs("error\n") produces one newline, not two. Standardize on one of:
- Option A:
eputsis documented as auto-newlining (likeputs), and all callers drop trailing\n. Simpler, matchesputssemantics. - Option B:
eputsis likeprint(no auto-newline), a neweputs_lnhandles the newline case. Breaks symmetry withputs.
Recommended: Option A. Leaves eputs semantics unchanged, just cleans up callers.
Fix procedure.
- Grep all
eputs(".*\\n"patterns acrossself-hosted/,std/,spec/,benchmarks/,Quakefile.qz,tools/. - Strip trailing
\nfrom each call. Leave internal\nalone (intentional multi-line messages). - Update
docs/INTRINSICS.md(or wherevereputsis documented) to explicitly say “auto-appends newline, likeputs.”
Test plan. Visual — run quake build and confirm no doubled newlines in compile/guard output. Optionally add a QSpec eputs_spec.qz that captures stderr via assert_output to lock in the single-newline contract.
Steps.
cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d2-golden- Grep audit.
- Batch edit.
- Optional spec.
quake build+quake guard+ smoke.- Update ROADMAP PSQ-5 → RESOLVED.
- Commit:
D2: PSQ-5 — strip redundant newlines from eputs callers.
Complexity. XS (30min). Risk. Low — pure string scan.
D3 — PSQ-3: var end = 42 parser diagnostic
Goal. Using a reserved keyword as an identifier in a var binding produces a clean error instead of cascading garbage. Example:
error[QZ0530]: 'end' is a reserved keyword and cannot be used as an identifier
--> file.qz:2:7
|
2 | var end = 42
| ^^^
Apply to all reserved words: end, begin, def, var, if, elsif, else, for, while, match, impl, trait, import, return, self, true, false, nil, None, and, or, not, is, in, break, continue, do, then, when, async, await, go, spawn, priv, pub.
Fix direction. In frontend/parser.qz:
- Find
ps_parse_let(thevar/lethandler). - After
ps_expect(TOK_VAR)/ps_expect(TOK_LET), check if the next token type is one of the reserved-keyword token types (TOK_END, TOK_DEF, etc.) instead of TOK_IDENT. - If so, emit QZ0530 with the keyword name + location and return an error sentinel. Do NOT advance past the keyword — let the outer error recovery pick up the next statement.
- Apply the same check to parameter-name parsing, for-loop iter vars, and match arm binding patterns.
Test plan. New spec spec/qspec/reserved_ident_spec.qz using assert_compile_error:
var end = 42→ QZ0530 forenddef foo(end: Int): Int = end→ QZ0530 forendfor end in 0..3→ QZ0530 forendmatch x { Some(end) => end; None => 0 }→ QZ0530 forend(match binding)- Control:
var endpoint = 42→ compiles fine (only exact keywords rejected)
Tensions. None — clean fix, no ambiguity.
Steps.
cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d3-golden- Read
ps_parse_letand adjacent parser paths. - Build a “reserved token types” set (or an array of
[TOK_END, TOK_DEF, ...]). - Add the check at each binding-name site.
- Register QZ0530 explain entry.
- Write spec, run, fix.
quake guard+ smoke + B+C sweep.- Update ROADMAP PSQ-3 → RESOLVED.
- Commit:
D3: PSQ-3 — reject reserved keywords as identifiers at binding sites.
Complexity. M (1-2h). Risk. Low — parser change but the detection is purely additive (reject what was previously undefined behavior).
D4 — PSQ-1: Method-as-free-function arity collision
Goal. A free function def fail(msg: String) and an impl method def Progress.fail(self, msg: String) can coexist. A single-arg call fail("oops") resolves to the free function (arity 1), not the 2-arg method-as-free-function expansion via UFCS.
Current symptom (from workaround in tree): calling fail("message") errors with:
Function fail requires at least 2 arguments, got 1
The typechecker’s call resolution picks the UFCS-expanded method first, then rejects the call because of arity mismatch. The workaround in tree is to rename: Progress.fail → Progress.fail_with.
Fix direction. In self-hosted/middle/typecheck_expr_handlers.qz call resolution:
-
When resolving
fail(args)whereargs.size == N:- First pass: look for a free function (tag 0) named
failwith exactly N params. If found, use it. Done. - Second pass: look for arity-mangled free functions
fail$0,fail$1, …,fail$10. If any matches N, use it. - Third pass (UFCS): look for impl methods
T$failwhereTmatches the first arg’s type and the method has N+1 params. If found, use it. - If none match, report “unknown function
fail”.
This ensures exact-arity free functions always beat UFCS-expanded methods.
- First pass: look for a free function (tag 0) named
-
Alternative (simpler, stricter): disallow method-as-free-function resolution entirely and require dot-syntax for impl methods. This breaks currently-working code that invokes impl methods as free functions. Probably too invasive for D4.
Recommended: pass-ordering fix (option 1).
Test plan. New spec spec/qspec/method_arity_collision_spec.qz:
- Free function beats method:
def fail(msg: String): Int = 1+impl Progress { def fail(self, msg: String): Int = 2 }, callfail("x")at a site where Progress isn’t in arg position → returns 1. - Method still works via dot syntax:
p.fail("x")wherep: Progress→ returns 2. - Method still works via UFCS if no colliding free function: delete the free function, call
fail(p, "x")→ returns 2. - Arity-mangled free functions participate in the pass:
def fail$1beatsProgress.fail. - Regression: the original reproducer from PSQ-1 (
import * from progress+ localdef fail) compiles and calls dispatch correctly.
Tensions. The UFCS path has its own well-tested callers (impl_trait, hybrid_ufcs_traits, iterable_trait). The fix must not regress any of those. Run the full B+C regression sweep with special attention to these three specs.
Steps.
cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d4-golden- Find the UFCS dispatch pass in
typecheck_expr_handlers.qz— probably aroundtc_expr_callat line 1319 (per C3’s investigation). - Add the exact-arity free function pre-check before the UFCS rewrite.
- Write the 5-test spec.
- Rebuild, run, verify both positive (free fn wins) and negative (method still works via dot) tests pass.
- Revert the workaround: rename
Progress.fail_with→Progress.failandProgress.warn_with→Progress.warninstd/progress.qz. Confirm progress_spec still passes. quake guard+ smoke + B+C sweep (especially impl_trait, hybrid_ufcs_traits, iterable_trait).- Update ROADMAP PSQ-1 → RESOLVED.
- Commit:
D4: PSQ-1 — prefer exact-arity free function over UFCS-expanded method.
Complexity. M (2-3h). Risk. Medium — call resolution change that could regress UFCS callers. If any spec fails that was previously green, either refine the pass ordering or revert and file partial.
D5 — PSQ-7: Suspendable-effect leaves audit
Goal. For every suspendable-effect leaf intrinsic, using it from a non-async main() without ever calling sched_init() still correctly flips uses_scheduler to pull in the runtime decls (@__qz_sched*, @__qz_completion*). Currently only NODE_AWAIT is confirmed fixed (in commit 1a60633c); the other leaves are un-audited.
Leaves to audit (from PSQ-7 ROADMAP row):
io_suspend(fd)— used bystd/process.qzasync sh helperschannel_send/channel_recv/try_recv/try_send/try_recv_or_closed/recv_timeout— channel opsmutex_lock/mutex_unlock/mutex_try_lock— mutex opsrwlock_read_lock/rwlock_write_lock/rwlock_read_unlock/rwlock_write_unlock— rwlock opscondvar_wait/condvar_signal/condvar_broadcast— condition variablescompletion_block/completion_watch/completion_unwatch/completion_watch_multi— direct completion opssched_yield/sched_spawn/sched_shutdown/sched_idle_hook_set/sched_idle_hook_clear/sched_is_active/sched_register_proc/proc_suspendgo_priority(task, level)spawn(thego do -> endform)
Audit procedure. For each leaf, construct a minimal program that uses it from main() without any other scheduler intrinsic:
def main(): Int
var ch = channel_new(4)
channel_send(ch, 42)
return 0
end
Compile to IR:
./self-hosted/bin/quartz --no-cache /tmp/probe.qz > /tmp/probe.ll 2>&1
llc /tmp/probe.ll -o /tmp/probe.s 2>&1
If llc rejects with use of undefined value '@__qz_sched*' or @__qz_completion*, that’s a miss — the leaf doesn’t flip uses_scheduler.
Fix pattern. In self-hosted/backend/mir_lower.qz or mir_lower_expr_handlers.qz at the intrinsic emission site for each missing leaf: call mir_set_uses_scheduler(prog) in the non-async path, same pattern as the mir_emit_completion_await fix in 1a60633c.
Also: weakly-ordered slot reads in poller. Audit self-hosted/backend/codegen_runtime.qz’s __qz_sched_io_poller for non-atomic loads of scheduler slots written by the main thread. Per PSQ-7, these slots need attention:
| Slot | Purpose | Writer | Reader |
|---|---|---|---|
| 8 | shutdown | main | poller |
| 10 | init | main | poller |
| 16 | active_tasks | main | poller |
| 34 | drain | main | poller |
Each must use load atomic ... seq_cst when read by the poller (same pattern as slot 35’s fix in 1a60633c).
Test plan. New spec spec/qspec/scheduler_effect_leaves_spec.qz — for each leaf, a compile-and-link smoke test that constructs a minimal program using the leaf from main() without any explicit sched_init. The test passes if the program compiles, links, and runs without @__qz_sched* undefined-symbol errors at the llc/clang stage. Use assert_run_exits with exit 0.
Suggested test count: 1 per leaf category (io_suspend, channel, mutex, rwlock, condvar, completion, sched_*, spawn), so ~8 tests.
Steps.
cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d5-golden- Script the audit: write a bash loop that, for each leaf, creates a probe .qz, compiles, llc, grep for
use of undefined value '@__qz_sched'or'@__qz_completion'. Report which leaves fail. - For each failing leaf: find its MIR emission site and add
mir_set_uses_scheduler(prog)in the non-async path. - Rebuild and re-run the audit until all leaves pass.
- Audit
codegen_runtime.qzpoller slot reads (slots 8, 10, 16, 34). Addatomic seq_cstwhere missing. - Write the spec file.
quake guard+ smoke + B+C sweep + new scheduler spec.- Update ROADMAP PSQ-7 → RESOLVED with a list of leaves fixed.
- Commit:
D5: PSQ-7 — uses_scheduler propagation + atomic poller slot reads.
Complexity. L (2-3h). Risk. Medium — systematic audit, each finding is a separate fix. If more than 5 leaves need fixing, the budget may stretch; in that case commit partial progress with ROADMAP note on remaining leaves.
Dependency graph
D1 (expand_node audit) — independent, macro_expand.qz only
D2 (eputs newlines) — independent, pure string scan
D3 (reserved ident diag) — independent, parser.qz + explain.qz
D4 (method arity collision) — touches typecheck_expr_handlers.qz
D5 (scheduler leaves audit) — touches mir_lower.qz + codegen_runtime.qz
No cross-item dependencies. All five can run in any order. The easy→hard sequencing is purely for commit safety — if D4 or D5 stalls, D1-D3 still land.
Verification matrix
| Item | Probe | New spec | Regression sweep |
|---|---|---|---|
| D1 | Synthesized $try / $unwrap calls in every assign shape | expand_node_audit_spec.qz (6+ tests) | B+C sweep |
| D2 | Visual: quake build output free of double newlines | Optional eputs_spec.qz | smoke + guard |
| D3 | Reserved-keyword in var position fires QZ0530 | reserved_ident_spec.qz (5 tests) | B+C sweep |
| D4 | fail("msg") resolves to free fn, not method | method_arity_collision_spec.qz (5 tests) | B+C sweep + progress_spec + impl_trait + hybrid_ufcs_traits + iterable_trait |
| D5 | Each effect leaf compiles+links from bare main() | scheduler_effect_leaves_spec.qz (~8 tests) | B+C sweep + sched_lifecycle + proc_suspend + process_async + await_nonasync + async_channel |
Mandatory regression sweep for every commit (the B+C-sprint set — 17 specs):
impl_trait abstract_trait traits hybrid_ufcs_traits
collection_stubs arity_overload match_unknown_variant
iterable_trait iterator_protocol enum_ctor_in_impl
closure_intrinsic_shadow send_auto_trait vec_element_type
unwrap_in_loop sched_lifecycle impl_not_send post_resolve_assertion
Workflow rules (mandatory per commit)
- Snapshot binary:
cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-dN-golden - Clean cache:
rm -rf .quartz-cache— #1 cause of false reproductions (A2/B3/B4 lesson) - Read affected files before editing — line numbers in this plan may have drifted
- Edit the source
- Rebuild:
./self-hosted/bin/quake build - Probe test: manually verify the specific fix before running the full spec
quake guard: fixpoint verified, 2285 ± 30 functionsquake smoke: brainfuck 4/4, expr_eval 22/22- New spec: run the item’s new spec file, all tests pass
- Regression sweep: run the B+C-sprint set, all green
- Update ROADMAP: mark the row RESOLVED with commit SHA and a one-line summary
- Commit: include commit message body with root cause, fix, verification, and any follow-ups discovered
On failure: restore from quartz-pre-dN-golden, document what was tried, decide whether to retry or file as a partial commit with notes.
Never use --no-verify, --no-gpg-sign, or skip quake guard. Never run the full qspec suite from Claude Code — give the user the command and have them paste the result back (the 10-min suite hangs in Claude Code’s PTY).
Session-start checklist
cd /Users/mathisto/projects/quartz
# 1. Verify baseline
git log --oneline -6 # should show 73185be8 C5 at top
git status # should be clean
./self-hosted/bin/quake guard:check # "Fixpoint stamp valid"
./self-hosted/bin/quake smoke 2>&1 | tail -8 # 4/4 + 22/22
# 2. Read the key docs
cat docs/handoff/batch-d-implementation-plan.md # this document
cat docs/bugs/PROGRESS_SPRINT_QUIRKS.md | head -310 # PSQ-1, PSQ-3, PSQ-5, PSQ-7 details
# 3. Start D1
rm -rf .quartz-cache
cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d1-golden
Success criteria at handoff
- Minimum viable: 3 of 5 items committed (D1, D2, D3). Paper cuts and defense-in-depth — enough progress for one session.
- Target: 4 of 5 items committed (above + D4 OR D5).
- Stretch: all 5 items committed.
Each committed item must:
- Have
quake guardfixpoint verified (±30 from 2285) - Pass
quake smoke(4/4 + 22/22) - Pass the B+C-sprint regression sweep
- Either have a regression test locking in the fix, OR a clear reason a test isn’t feasible
- Have an updated ROADMAP row with commit SHA
- Have any new error codes registered with explain entries
Deferred items (if any) must:
- Have the ROADMAP row updated with a status note (“investigated 2h, blocked on X”) and enough detail that a future session can pick up from where execution stopped
- NOT be silently skipped — always document what was attempted
Wake-up report format
At session end, post a summary to the chat mirroring the Batch C wake-up:
- What landed: N of 5 items, commit SHAs, fixpoint count
- What didn’t: any deferred items with reason + investigation notes
- Surprises: anything that contradicted the plan (e.g., D1 audit found 5 more mismatches, D5 leaf count higher than expected)
- Follow-ups filed: any new ROADMAP entries created during execution
- Next session recommendation: one sentence on what the next logical target is
Estimated quartz-time
Traditional ÷ 4 calibration:
- D1: 1-1.5h (audit + fixes + PSQ-4 worst-case verification)
- D2: 0.5h (pure string scan)
- D3: 1-2h (parser diagnostic + spec)
- D4: 2-3h (typechecker pass ordering + regression sweep carefully)
- D5: 2-3h (systematic audit, each finding is a separate fix)
Total: ~7–10h quartz-time. Overnight-sized for autonomous execution.
What Batch D deliberately does NOT include
These are called out to keep the sprint scope tight:
- #11 Resolver full scope tracking — 1-2d, own session. UFCS module collision for local vars is bigger than D4’s arity fix and needs dedicated design work.
- #12 Rust-style pattern matrix exhaustiveness — 3-5d, multi-session. Needs its own design phase.
- #19 Compiler memory optimization Phase 3 — parser O(n²) fix is 1-2w, own session. Don’t let it compete.
- PSQ-2 (
import progresscascade) — BLOCKER forsh_with_progresslift, but the root cause is the module resolver load order which is its own rabbit hole. File as own session if time permits after D5. - PSQ-4 worst-case — IF D1 proves it still reproduces, file as own follow-up; don’t inline the fix into Batch D. The less-dangerous symptom is already closed by C2.
- PSQ-6 (Vec.size reads 0 from poller thread) — shares diagnosis with other cross-thread global reads. Own session.
Prime directives, in one sentence: World-class only, no shortcuts, no silent compromises, honest reports, holes get filled or filed, delete freely, binary discipline = source discipline, quartz-time estimation, corrections are calibration.