Quartz v5.25

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 end explicit opt-out
  • C4 POST-RESOLVE-IDENT-ASSERTION — QZ9501 PascalCase-only guardrail at MIR lowering entry
  • C5 B4-UNWRAP-IN-LOOP — expand_node read wrong slot for NODE_ASSIGN; macro call never reached the $unwrap expander for compound assignments. Fix was in expand_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):

#ItemEstRiskHas reproducer?
D1EXPAND-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 C21hLowSynthetic probes
D2PSQ-5 — eputs("msg\n") doubled newlines (strip redundant \n or document the auto-newline behavior)30minLow (cosmetic)N/A
D3PSQ-3 — var end = 42 parser error: emit clean diagnostic instead of cascading garbage1-2hLow-medKnown repro
D4PSQ-1 — Method-as-free-function collides with free function at same name + higher arity (typechecker fix: prefer exact-arity free function)2-3hMedium (typechecker change)Yes, workaround documented
D5PSQ-7 — Suspendable-effect leaves audit: verify uses_scheduler flag propagation for every leaf. Also audit weakly-ordered slot reads in __qz_sched_io_poller2-3hMedium (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)

  1. 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.
  2. Design before building; research first — each item has a fix sketch below. Verify against current codebase before editing.
  3. Pragmatism ≠ cowardice; shortcuts = cowardice — if an audit uncovers more than the budget, regression-lock the safe fix, file deeper gap into ROADMAP, move on.
  4. Work spans sessions — if D4 or D5 stalls, commit what’s done, file the rest.
  5. Report reality, not optimism — honest reports, fixpoint ±30 required each commit.
  6. Holes get filled or filed — audit discoveries go into ROADMAP with context.
  7. Delete freely, no compat layers — standard.
  8. Binary discipline = source disciplinequake guard mandatory, fix-specific backups mandatory.
  9. Quartz-time estimation — traditional ÷ 4.
  10. Corrections are calibration, not conflict — no rationalizing past failures.

Cross-cutting findings

  1. 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 eputs probe would have surfaced the bug. D1 audits the rest of expand_node for the same class before the next bug lands. Any finding goes straight into a fix commit.

  2. 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).

  3. 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.”

  4. quake guard cache cleanup between commits is mandatory. Same lesson from Batch A+B/C — stale .quartz-cache can 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.

  1. Enumerate all branches. Grep expand_node for if kind == and elsif kind ==. Record each kind number and the slots the branch reads/writes.

  2. Cross-reference against ast.qz. For each kind, find the matching def ast_<name>(...) constructor. Record which slots (lefts, rights, extras, children, str1s, str2s, int_vals, ops) hold which semantic payloads.

  3. Verify match. For each branch:

    • If expand_node reads slot X and the constructor stores the walkable payload in slot Y ≠ X, it’s a bug.
    • If expand_node writes 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.
  4. Fix every mismatch in one commit. Keep the fix surgical — replace ast_get_right with ast_get_left / ast_get_extra / etc. as the constructor dictates. Don’t refactor beyond what’s needed.

  5. 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 new spec/qspec/expand_node_audit_spec.qz file.

  6. 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
    end

    Compile 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:

  1. return $try(opt) — NODE_RETURN with macro call
  2. var x = $try(opt) — NODE_LET with macro call (already works, regression lock)
  3. sum += $try(opt) — NODE_ASSIGN compound (C5 lock-in)
  4. arr[i] = $try(opt) — NODE_INDEX_ASSIGN with macro call (new path exercised by D1 fix)
  5. obj.field = $try(opt) — NODE_FIELD_ASSIGN with macro call (new path exercised by D1 fix)
  6. if cond then $unwrap(opt) else 0 end — NODE_IF expression position

Steps.

  1. rm -rf .quartz-cache && cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d1-golden
  2. Read expand_node top to bottom. Record every kind branch.
  3. Read ast.qz for each matching constructor. Build a cross-reference table.
  4. Apply fixes. Each fix should be ~3-5 lines (one slot rename).
  5. Write the new spec file with 6 tests.
  6. Run the PSQ-4 worst-case repro manually.
  7. quake build + quake guard + quake smoke + B+C sweep.
  8. Update ROADMAP: close PSQ-4 or file new gap.
  9. 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: eputs is documented as auto-newlining (like puts), and all callers drop trailing \n. Simpler, matches puts semantics.
  • Option B: eputs is like print (no auto-newline), a new eputs_ln handles the newline case. Breaks symmetry with puts.

Recommended: Option A. Leaves eputs semantics unchanged, just cleans up callers.

Fix procedure.

  1. Grep all eputs(".*\\n" patterns across self-hosted/, std/, spec/, benchmarks/, Quakefile.qz, tools/.
  2. Strip trailing \n from each call. Leave internal \n alone (intentional multi-line messages).
  3. Update docs/INTRINSICS.md (or wherever eputs is documented) to explicitly say “auto-appends newline, like puts.”

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.

  1. cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d2-golden
  2. Grep audit.
  3. Batch edit.
  4. Optional spec.
  5. quake build + quake guard + smoke.
  6. Update ROADMAP PSQ-5 → RESOLVED.
  7. 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:

  1. Find ps_parse_let (the var/let handler).
  2. 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.
  3. 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.
  4. 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:

  1. var end = 42 → QZ0530 for end
  2. def foo(end: Int): Int = end → QZ0530 for end
  3. for end in 0..3 → QZ0530 for end
  4. match x { Some(end) => end; None => 0 } → QZ0530 for end (match binding)
  5. Control: var endpoint = 42 → compiles fine (only exact keywords rejected)

Tensions. None — clean fix, no ambiguity.

Steps.

  1. cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d3-golden
  2. Read ps_parse_let and adjacent parser paths.
  3. Build a “reserved token types” set (or an array of [TOK_END, TOK_DEF, ...]).
  4. Add the check at each binding-name site.
  5. Register QZ0530 explain entry.
  6. Write spec, run, fix.
  7. quake guard + smoke + B+C sweep.
  8. Update ROADMAP PSQ-3 → RESOLVED.
  9. 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.failProgress.fail_with.

Fix direction. In self-hosted/middle/typecheck_expr_handlers.qz call resolution:

  1. When resolving fail(args) where args.size == N:

    • First pass: look for a free function (tag 0) named fail with 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$fail where T matches 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.

  2. 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:

  1. Free function beats method: def fail(msg: String): Int = 1 + impl Progress { def fail(self, msg: String): Int = 2 }, call fail("x") at a site where Progress isn’t in arg position → returns 1.
  2. Method still works via dot syntax: p.fail("x") where p: Progress → returns 2.
  3. Method still works via UFCS if no colliding free function: delete the free function, call fail(p, "x") → returns 2.
  4. Arity-mangled free functions participate in the pass: def fail$1 beats Progress.fail.
  5. Regression: the original reproducer from PSQ-1 (import * from progress + local def 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.

  1. cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d4-golden
  2. Find the UFCS dispatch pass in typecheck_expr_handlers.qz — probably around tc_expr_call at line 1319 (per C3’s investigation).
  3. Add the exact-arity free function pre-check before the UFCS rewrite.
  4. Write the 5-test spec.
  5. Rebuild, run, verify both positive (free fn wins) and negative (method still works via dot) tests pass.
  6. Revert the workaround: rename Progress.fail_withProgress.fail and Progress.warn_withProgress.warn in std/progress.qz. Confirm progress_spec still passes.
  7. quake guard + smoke + B+C sweep (especially impl_trait, hybrid_ufcs_traits, iterable_trait).
  8. Update ROADMAP PSQ-1 → RESOLVED.
  9. 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 by std/process.qz async sh helpers
  • channel_send / channel_recv / try_recv / try_send / try_recv_or_closed / recv_timeout — channel ops
  • mutex_lock / mutex_unlock / mutex_try_lock — mutex ops
  • rwlock_read_lock / rwlock_write_lock / rwlock_read_unlock / rwlock_write_unlock — rwlock ops
  • condvar_wait / condvar_signal / condvar_broadcast — condition variables
  • completion_block / completion_watch / completion_unwatch / completion_watch_multi — direct completion ops
  • sched_yield / sched_spawn / sched_shutdown / sched_idle_hook_set / sched_idle_hook_clear / sched_is_active / sched_register_proc / proc_suspend
  • go_priority(task, level)
  • spawn (the go do -> end form)

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:

SlotPurposeWriterReader
8shutdownmainpoller
10initmainpoller
16active_tasksmainpoller
34drainmainpoller

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.

  1. cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-d5-golden
  2. 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.
  3. For each failing leaf: find its MIR emission site and add mir_set_uses_scheduler(prog) in the non-async path.
  4. Rebuild and re-run the audit until all leaves pass.
  5. Audit codegen_runtime.qz poller slot reads (slots 8, 10, 16, 34). Add atomic seq_cst where missing.
  6. Write the spec file.
  7. quake guard + smoke + B+C sweep + new scheduler spec.
  8. Update ROADMAP PSQ-7 → RESOLVED with a list of leaves fixed.
  9. 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

ItemProbeNew specRegression sweep
D1Synthesized $try / $unwrap calls in every assign shapeexpand_node_audit_spec.qz (6+ tests)B+C sweep
D2Visual: quake build output free of double newlinesOptional eputs_spec.qzsmoke + guard
D3Reserved-keyword in var position fires QZ0530reserved_ident_spec.qz (5 tests)B+C sweep
D4fail("msg") resolves to free fn, not methodmethod_arity_collision_spec.qz (5 tests)B+C sweep + progress_spec + impl_trait + hybrid_ufcs_traits + iterable_trait
D5Each 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)

  1. Snapshot binary: cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-dN-golden
  2. Clean cache: rm -rf .quartz-cache — #1 cause of false reproductions (A2/B3/B4 lesson)
  3. Read affected files before editing — line numbers in this plan may have drifted
  4. Edit the source
  5. Rebuild: ./self-hosted/bin/quake build
  6. Probe test: manually verify the specific fix before running the full spec
  7. quake guard: fixpoint verified, 2285 ± 30 functions
  8. quake smoke: brainfuck 4/4, expr_eval 22/22
  9. New spec: run the item’s new spec file, all tests pass
  10. Regression sweep: run the B+C-sprint set, all green
  11. Update ROADMAP: mark the row RESOLVED with commit SHA and a one-line summary
  12. 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 guard fixpoint 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:

  1. What landed: N of 5 items, commit SHAs, fixpoint count
  2. What didn’t: any deferred items with reason + investigation notes
  3. Surprises: anything that contradicted the plan (e.g., D1 audit found 5 more mismatches, D5 leaf count higher than expected)
  4. Follow-ups filed: any new ROADMAP entries created during execution
  5. 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 progress cascade) — BLOCKER for sh_with_progress lift, 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.