waxwing

Local backlog

Current delivery (2026-10-13): FN001, P001, FX001 (Tasks 1-12), FX008, FX017 and FX018 are merged into main and pushed. Text v1 (Tasks 1-9, docs/plans/2026-10-13-text-plan.md, ADR 011) is implemented on branch text, whole-branch review done (with fixes); merged into main 2026-10-13. The 80-column sweep, IDE001 stage 1 and FX020 (effect namespaces) are merged into main (2026-10-14). R001’s first tranche (records, ADR 013, docs/plans/2026-10-14-records-plan.md Tasks 1-11) is done on branch records, awaiting the user’s merge decision; its fast-follows are R004 and R005. Then the INF001 design. Waxwing / .wxw rename is R003. See docs/next-session.md and docs/progress.md for the current evidence. A003, G001, E005 and T001 are merged into main. Plan and verification evidence are in docs/plans/bootstrap.md and docs/progress.md. No remote tracking.

ID State Next concrete work and acceptance
INF001 Defined; design next Automatic inference and composition of service requirements and typed error channels in named functions, service assembly and business logic. Requirements: milestone. A1-A10 and B1-B8 cover route inference, business failures, provision introducing HttpClient, setup errors, residual errors, recovery, fakes and missing boundary handling. Explicit inference marker recommended for design comparison; no syntax or solver selected. Finish FX008/FX001 first; coordinate FX007, R001/C001, M001 and CF001. Independent and adversarial requirements reviews complete; M1 addressed and re-reviewed, ledger docs/sdd/2026-10-11-inference-milestone/progress.md. Next: reviewed language design, then writing-plans; no implementation authorized. FX020 requirement (2026-10-13): effect operations are always qualified and imported only at a written dependency declaration, so INF001 must provide a way to alias or import without writing the whole inferred row.
V001 Done Documentation/style audit closed after fresh review found no blockers; final closure verification is recorded in docs/progress.md.
A001 Done Closed ADTs, nested match with nil-guarded Go, Maranget coverage with inhabitedness, five layers, structured diagnostics, capability ports. ADRs 003 and 004; evidence in docs/progress.md and .build/a001-final.log (clean-output verify: 64 tests, 0 skips, three regression proofs). Final whole-branch review pending with the controller.
P001 Done Rank-1 polymorphism: parameterized types and generic functions with lowercase implicit variables, mandatory signatures, rigid signature variables, fresh metas per use, occurs-checked unification, comparison groundness, holes with a specialization-time representative (Int; no typing default), the instantiation rule (no polymorphic recursion or nested datatypes inside a component; proof in spec §4.2), coverage over applied types, and whole-program specialization with hash-consed keys and a 10,000-key limit. Spec docs/plans/2026-10-08-polymorphism-design.md; plan docs/plans/2026-10-08-polymorphism-plan.md (Tasks 1-9); decision record docs/adr/007-specialization.md; language docs/language.md (Polymorphism); evidence docs/progress.md ## P001 execution (per-task RED/GREEN logs under .build/p001-task*). Merged into main 2026-10-08 (fast-forward to dcb40fb); clean rm -rf output && npm run verify on main: 318 tests, twelve regression proofs (four new: occurs, rigid, instantiate, spec-key), .build/p001-merge-verify.log; evidence archived in .build/merged-p001-evidence. examples/lists.wxw runs end to end; bootstrap/lists.go snapshot added; answer.go, shapes.go and tree.go byte-identical. Follow-ups: E006, E007, E008-E011.
FN001 Done First-class functions: curried function types ((A, B) -> C is A -> B -> C), bare function and constructor values, strict partial and over-application staged by declared arity, monomorphic lambdas with _ discards and capture by value, left-first |>, the narrow under-application hint, non-comparable and non-printable function types, value references as instantiation edges, and the linked-environment staged lowering of design §13 (Amendment A1, rules 1-8). Decision record docs/adr/008-functions.md; spec docs/plans/2026-10-08-functions-design.md; plan docs/plans/2026-10-08-functions-plan.md; language docs/language.md (Functions); evidence docs/progress.md FN001 Task …. Twenty-two regression proofs (ten new, scripts/regression-fn.mjs). 20,000-parameter milestone (scripts/fn-milestone.mjs) passed, largest go build 13.7 s of 100. Next: whole-branch review, then the user’s merge decision. Follow-ups: FN002-FN005, G002, G003, T002, T003.
FN002 Done (FX001 Task 2) Blocks { let x = e; e; e }, let _, discarded items, {}/()/trailing ; as Unit, monomorphic let scoping and shadowing, Go lowering as lifted waxwingFn{f}Block{k} helpers (E005); evidence docs/progress.md FX001 Task 2: Unit, blocks and let. Original: A local binding form (FN001 non-goal, user 2026-10-08): closures capture parameters and pattern binders only. Design its syntax, scoping, evaluation order and Go lowering (lifted like matches, E005) before implementation.
FN003 Planned Placeholder application such as take(_, xs) for non-final holes (FN001 non-goal; _ is today only a lambda discard). Design how it relates to lambdas and staging before implementation.
FN004 Planned Saturation optimization: applying a known staged value to all its remaining arguments allocates one node and one closure per argument (ADR 008, Consequences). Measure first; any optimization must keep stage-boundary timing (rows stage-value, partial-strict, block-order).
FN005 Planned With I001: a foreign Go function declares its arity, which becomes its stage boundary, and gets a staged wrapper like a named function (design §4, ADR 008).
K001 Planned Explicit kind IR/checker, constructor arities, Type/Row/arrow kinds, HKTs restricted to first-order constructor application; reject ill-kinded programs before type solving.
C001 Planned After FN001; design jointly with R001 and decide early whether explicitly selected instances are ordinary records of functions. Prelude Eq/Ord, derive, opt-out of default instances, terminating resolution, ambiguity diagnostics and dictionary elaboration. Several instances per type without mandatory newtypes; this supersedes ADR 002’s global non-overlap/unique-ownership direction. Design must preserve instance identity for instance-sensitive collections and avoid duplicating service-record mechanisms. P001 keeps variables opaque and has no comparison constraint. Explicit instance construction/composition, optional implicit selection, law evidence and instance-sensitive collection safety: docs/plans/2026-10-10-modules-and-instance-selection-direction.md (direction only).
R002 Done Renamed the language from Sprig to Bumpus (name collided with Masterminds/sprig and Hack Club Sprig): sources, generated Go identifiers and panic texts, packages, npm script, examples. Snapshots equal the old bytes under the name substitution (docs/progress.md).
R003 Done (rename; its FX001 Task 3 continuation was completed by FX001 Tasks 3-12, merged 2026-10-12) Rename Bumpus to Waxwing with .wxw sources after FX001 Task 3 (3481468). CLI, packages, generated Go identity, fixtures, tests and current docs; preserve historical evidence. ADR 009; rename-only verification isolated from the paused worker changes. Combined-tree npm run verify (.build/waxwing-paused-verify.log) passed 634/640 parallel tests: five fn-linear timing failures (FN006) and test/unify-row.test.mjs:57, expected RowMissing but got Subst after the other worker added deferred-row postponement. Task 3 continuation completes those deferred-Fail-key tests/settleRows and oracle coverage; the former rejection assertion is replaced by explicit rollback/postponement/settling requirements. Clean verification now passes; obtain independent review before Task 4. Serial tests/proofs not reached by that combined run. Isolated rename-only validation: 639/640 parallel tests passed (only existing pipe timing failure, FN006), 13/13 serial tests and 22/22 isolated regression proofs passed. Bounded differential against 3481468: zero differences over 5,462 sources and 1,085 identifier probes, with only generated names normalized. Final supplied logo assets integrated. Rename committed separately as 40cf312; Task 3 continuation retains the eight worker source changes and adds deferred-row tests/oracle coverage.
R001 First tranche done on branch records (2026-10-14; not merged) Structural, row-typed records (user decisions 2026-10-14, spec docs/plans/2026-10-14-records-design.md, plan docs/plans/2026-10-14-records-plan.md Tasks 1-11, ADR docs/adr/013-records.md, language docs/language.md “Records”, evidence docs/progress.md “R001”): record types with ...r tails and row sorts, literals, e.l, e { l = v }, row-polymorphic functions with implicit lacks sets, shared remainders, comparison and printing in sorted label order, records as Fail payloads keyed by label set (runtime keys from MinInt32 + 1), and lowering to one Go struct per shape with one function copy per concrete row (ADR 002 confirmed; no accessor evidence or fallback needed). Prerequisite E017 fixed first. Sixteen regression rows (scripts/regression-r001.mjs; two from the final review); scale 200 shapes x 20 links = 4,001 keys, linear; services proxy figures in docs/progress.md (proxy, not validation). Go-vs-interpreter differential 0 disagreements on 11 seeds. Fast-follows R004 (patterns) and R005 (extension, merge, removal); later R006-R009; found during the tranche R010, R011, E018, FX024, T011; the whole-branch review’s fix wave (2026-10-15) fixed C1 (types written in a body over a signature’s record tail, E_TYPE r may already have field a, approved 2026-10-15) and FX024, and filed R012 and E019. Original scope (2026-10-08 onwards): after FN001; joint design review with C001 for records of operations, service provision and instance values. Unique-label row-polymorphic records, lacks constraints, normalized layouts and accessor evidence; extensible variants deferred. Measure row-specialization key/code growth for overlapping composed services; evaluate dictionary/accessor passing versus concrete layouts and revise ADR 002 if choosing a fallback. Polymorphic record fields remain a separate deferred typing problem. FX001 note (2026-10-12): value-level error sums are not provided; Failure(A + B + ...) as possible row sugar for composed error families is an open design question for this milestone and C001 (FX001 spec, MileAhead error-family direction).
R004 Planned (R001 fast-follow) Record patterns in match (user decision 2026-10-14, R001 spec “User decisions” 6). Today a record can only be read by .l; no pattern destructures one. Next: a design for { name: n, ...rest }-style patterns (or without the rest), their coverage in Maranget usefulness (a record is a single-constructor product), and their interaction with punning (R007). Approved texts needed for any new diagnostic.
R005 Planned (R001 fast-follow) Record extension, merge and removal, with written lacks constraints (user decision 2026-10-14). R001 enforces lacks sets implicitly (ADR 013) but none can be written, and no expression adds or removes a field. Next: syntax for written lacks constraints and for extension/merge/removal, and their rules in Features.Check.UnifyRecord and Lacks. The lambda-annotation lacks gap noted here by the R001 Task 4 review was not latent (the final review’s C1 reached go build with a field declared twice) and is fixed on branch records: every type written in a body is held to its signature’s lacks sets (Features.Resolve.Lacking, E_TYPE r may already have field a, approved 2026-10-15; ADR 013 “Unification and lacks sets”). Extension must keep that check for the types it writes.
R006 Later Polymorphic record fields (a field of type forall a. a -> a), a separate higher-rank typing problem (user decision 2026-10-14; earlier R001 note). Signatures stay rank 1 until a design says otherwise.
R007 Later Record punning ({ name } for { name: name }) (user decision 2026-10-14). Today { name } is a block and must stay one unless a design resolves the ambiguity.
R008 Later Type aliases, so a record type can be named (type Person = { name: Text, age: Int } is not syntax today; a constructor wrapping a record is the workaround, as examples/records.wxw does) (user decision 2026-10-14).
R009 Later Effects cannot take record-row parameters (R001 Task 4 ruling): an effect’s parameters are types, so effect E(r) { fn op(y: { ...r }): Int; }; is E_TYPE Expected an effect row, found a type at ...r (test/r001-unify.test.mjs, docs/language.md “Records”). Next: design record-row parameters for effects (sort declaration or inference) when services over records of operations need them.
R010 Done The instantiation rule (ADR 007, Features.Check.Instantiation) does not judge record-row arguments, so a recursive call that grows a record row is caught only by the specialization limit. Found writing ADR 013 (2026-10-14, CLI on branch records at 9e8cf17): fn g(x: { ...r }, n: Int): Int = if n == 0 then 0 else g({ inner: x }, n + -1); fn main(): Int = g({ a: 1 }, 3); is E_SPECIALIZATION More than 10000 specializations at the recursive call (0.47 s wall), where the type-variable analog f(Cons(x, Nil), …) is Recursive call to f changes its type arguments; { a: Int, ...r } with g({ a: 1, inner: x }, …) behaves the same. Finiteness holds through the limit; the diagnostic and the 10,000 copies before it are the defect. Next: include record-sorted instantiation slots in the rule, and check whether the type-reference graph can reach a growing record row at all (when this was written a declared type’s row argument could only be written ...r; since R012, 2026-10-15, T({ inner: Int, ...r }) is a record argument, so a declaration can now write a growing row), test both function forms, and add a regression row. Fixed on r001-followups (75b2aa3; 2026-10-15): Features.Check.Instantiation judges each record row slot by the callee’s sorts with NestedArguments admissibleSlot, the declarations’ R012 judgement: the referrer’s bare { ...r }, or no rigid variable, a record tail included (a hole is ground); type slots are judged as before. The nested, open, lambda-inner, mutual and declared-type (g(H({ a: 1, inner: h }), …)) forms are the existing Recursive call to g changes its type arguments at the call (mutual: h at h({ inner: x }, …)); the declaration form type T(...r) = End({ ...r }) \| More(T({ inner: Int, ...r })) was already Recursive use of T changes its type arguments (confirmed). Pass-through and ground rows stay accepted. Tests test/r010-record-instantiation.test.mjs, seen failing first (.build/evidence/ie-b-r010-red-1.log: 5 failing, More than 10000 specializations or the limit at f(y, n)); regression rows r010-nested-row and r010-mutual-row proved failing with every slot judged as a type. ADR 007 and ADR 013 updated.
R011 Open Each open tail’s layout is interned and emitted as a Go struct even when it is only a specialization-key argument and never a value (R001 Task 7 and 10 reviews). Evidence: the scale chain (test/r001-scale.serial.test.mjs, 200 shapes x 20 links) emits 400 structs for 200 shapes, the 200 { fI: Int } tails unused; bootstrap/records.go declares waxwingRec3 and waxwingRec4 and never uses them; the services proxy’s struct count is one per (shape, service). Harmless (Go accepts unused types) but it inflates output. Next: emit only layouts some value, field or helper uses (Format.Go.RecordUse already computes the helper roots), keeping numbering deterministic; snapshot bootstrap/records.go changes, so it needs the user’s approval like any snapshot change.
R012 Done (branch records, plan Task 12, 2026-10-15; surface and both diagnostics approved by the user 2026-10-15) A declared type’s record-row parameter could not take a concrete row (R001 final review I2): Holder({ y: Bool }) was E_TYPE Expected an effect row, found a type, yet diagnostics printed that form. Now a record type argument is the parameter’s row, the tail of the declaration’s records (Holder({ y: Bool }) holds { x: Int, y: Bool }); Holder({ y: Bool, ...s }) passes an open row; Holder({ ...s }) is Holder(...s). Wrong sorts reuse approved texts (Expected a record, found Int; Expected a type, found an effect row for an effect label); a label shared with the declaration, directly or through nested declared types, is E_DUPLICATE Duplicate field x at the argument’s label, noted first written here at the declaration’s, even with equal field types; an open tail lacks those labels. Effect row parameters unchanged. Also fixed: Resolve.Lacking read a written argument position as the parameter’s variable place (T(...e, ...r) reported e may already have field x). The declarations’ instantiation rule now judges record arguments, so a row cannot grow through recursion (found testing: such a type overflowed the checker’s stack). Evidence: test/r012-arguments.test.mjs, test/r012-diagnostics.test.mjs (28 tests; 20 of the first 23 red before), rows r012-record-argument, r012-argument-clash, r012-argument-lacks, r012-slot-position, r012-spread-argument, r012-nested-row, r012-nested-walk; ADR 013 “Record-row arguments”, docs/language.md, docs/progress.md “R012”.
R013 Open Records-heavy declaration chains compile in super-linear time (found in R001 Task 15, not a regression on existing programs). type R<i>(...r) = R<i>({ f<i>: Int, ...r }, R<i+1>(...r)); for N declarations compiles in 185 ms at N=250, 750 ms at 500, 4.2 s at 1,000 and 25 s at 2,000, the same before and after Task 15 (.build/evidence/t15-records-heavy.log); a profile at N=600 puts about 80% in Features.Resolve.ArgumentClash programClashes (declaredLabels follows each record parameter down the chain per declaration, and RecordArguments applied scans the declared types by name per reference). Next: compute each declaration’s implied labels once (memoised along the chain) and look names up through Features.Resolve.TypeIndices; add a scale test with a bound.
R014 Open Two name scans remain per written type reference (R001 Task 15 review). Features.Resolve.TypeRef resolveTypeWith finds every NamedRef by Array.findIndex (named name) types, as on main, quadratic in the number of types: about 400 ms of the 10,000-type chain’s 900 ms (test/coverage-scale.test.mjs; profile in .superpowers/sdd/2026-10-14-records-plan/task-15-report.md). Features.Resolve.RecordArguments recordArguments (applied) scans the same way per reference (only when a declared type has a record parameter; part of R013’s cost). Next: pass Features.Resolve.TypeIndices (built once in Resolve) to both and look names up with indexOf; differential against the previous commit, chain timing before/after.
R015 Done Pre-existing E_INTERNAL for a row parameter written as a type argument (found by the R001 Task 15 review; reproduced at 24112fb): type T(...e) = T(e); fn main(): Int = 0; is rejected E_INTERNAL Invalid type variable at e (offset 17), not a user-facing sort error. Next: find where the resolver numbers e as a type variable when it is a row parameter (Resolve.Types/TypeRef with the parameter split into type and row variables), report a user-facing sort error at e (for example the existing E_TYPE Expected a type, found an effect row; text to be approved), add a regression test seen failing first. Fixed on r001-followups (b07d11f; 2026-10-15): Features.Resolve.Sorts.rowsAsTypes walks the declarations with a row parameter before settling, and reports a row or record row parameter written as a type with the existing E_TYPE Expected a type, found an effect row at the use (the signature form gives the same text for both sorts). A record row parameter written as a type, type T(...r) = T({ a: Int, ...r }, r);, was accepted unchecked and is rejected too. Tests test/r015-row-as-type.test.mjs, seen failing first (.build/evidence/ie-a-red-1.log); regression rows r015-effect-row and r015-record-row proved failing with the check removed.
R016 Done Misleading diagnostic for an effect row variable used as a record tail inside a lambda annotation (found by the R001 Task 15 review; reproduced at 24112fb): fn g(p: Int): Unit with ...r = { let f = fn(q: { a: Int, ...r }) => q.a; () }; fn main(): Int = 0; is rejected E_TYPE a is not a field of { a: Int, ...r } at q.a’s a (offset 70), though a is written in the type; the real fault is r, an effect row in the signature, used as a record row, presumably the approved r is an effect row, not a record row at the lambda’s ...r, noted at the first use (to be confirmed). Next: run the signature’s row-sort check over lambda annotations in the body (Resolve.Lambda / Lacking scope), add a regression test seen failing first. Fixed on r001-followups (3e610b6; 2026-10-15): Features.Resolve.BodySorts checks each type written in a body (a lambda’s annotations, a handler’s label arguments, a handle clause’s payload) against the signature’s settled sorts before the lacks check: reading one row sort as the other is the approved r is an effect row, not a record row (or r is a record row, not an effect row) at the body’s use, with first written here at the signature’s first use (SortConflict misread). Also fixed: an effect row passed to a record parameter in a body (fn(q: H(...r)), { ...r }, a handler label) was E_INTERNAL Invalid type variable, and a record row as a lambda’s effect row (with ...r, E(...r)) was accepted. Tests test/r016-body-sorts.test.mjs, seen failing first (.build/evidence/ie-b-r016-red-2.log); regression rows r016-effect-as-record and r016-record-as-effect proved failing with the body check emptied. A row read as a type, or a type variable as a row, in a body was R017, since fixed.
R017 Done Body types do not check a signature’s row variable read as a type, or its type variable read as a row (found fixing R016, 2026-10-15, r001-followups at 75b2aa3; probes .build/evidence/ie-b-r017-probes.log, each with fn main(): Int = 0;): fn g(p: { ...r }): Int = { let f = fn(q: r) => 0; 0 }; is accepted; fn g(p: Int): Unit with ...r = { let f = fn(q: r) => 0; () }; is E_INTERNAL Invalid type variable at the lambda; fn g(p: a): Int = { let f = fn(q: { b: Int, ...a }) => q.b; 0 }; is E_TYPE b is not a field of { b: Int, ...a }; fn g(p: a): Int = { let f = fn(q: (Int) -> Int with ...a) => 0; 0 }; is accepted. In a signature the same mistakes are E_TYPE Expected a type, found an effect row and Expected an effect row, found a type (SortConflict conflict). Next: get the texts approved (presumably those, at the body’s use, as R015 did for declarations), then extend Features.Resolve.BodySorts from row-against-row to every sort, with tests seen failing first and a regression row. Fixed on r001-followups (036153b; 2026-10-15; controller ruling: the existing signature-analog texts): Features.Resolve.BodySorts flags every body use whose sort differs from the signature’s settled sort and reports it through SortConflict conflict, so a row read as a type is E_TYPE Expected a type, found an effect row at the body’s r (also inside List(r) and a handle payload), a type variable as a row is Expected an effect row, found a type at ...a or with ...a, both with no note, and row against row keeps R016’s text and note (test/r016-body-sorts.test.mjs unchanged, passing). Tests test/r017-body-type-sorts.test.mjs, seen failing first (.build/evidence/ie-b-r017-red-1.log: accepted, E_INTERNAL, the misleading field text); regression rows r017-row-as-type and r017-type-as-row proved failing with the check restricted back to row sorts (.build/evidence/ie-b-r017-regression-1.log).
FX001 Done (whole-branch review fixes e53cab9; merged into main and pushed 2026-10-12 at 68e34e8) Design docs/plans/2026-10-09-effects-design.md, settled section by section with the user 2026-10-09: Koka-style effect rows and first-class service handlers, restricted to immediately resuming clauses and targeted Fail(E) aborts (resume-ready), ambient open rows, scoped labels, defer cleanup, crash, Unit/blocks/let and a host Console effect; rows erased from specialization, effect layouts bounded by an extended declaration-graph rule; uniform ctx only in effect-using programs. Stateless fakes only (recording fakes are FX003). Whole-spec review applied (fd625a4); §6 diagnostic quality (provenance, row difference first, origin/boundary/path notes, distinguished kinds, bounded abbreviation, regression cases) added at the user’s request, with PureScript diagnostic research cases in LA001. Defect-report format, scoped-occurrence provenance and type-argument abbreviation confirmed; spec approved for planning 2026-10-09. Plan docs/plans/2026-10-09-effects-plan.md (12 tasks) revised after the user’s review; execution authorized 2026-10-09: subagent-driven on branch fx001 in .worktrees/fx001, stopping to report if Task 1 rejects a runtime shape. Original brief: After FN001, coordinated with C001/R001 and I001. Compare ZIO-like environment/error/result effects with Koka-like algebraic effects, effect rows and handlers; select semantics in a reviewed design. Specify sequencing/laws, provision, typed failures, resources, lowering and async/cancellation scope. Proposed acceptance: overlapping services, real/fake injection, missing-capability diagnostics and cleanup semantics. Source effects are distinct from compiler Host ports. Proposed do expression elaborates to bind/lambdas; bindings scope successful results, standalone computations discard results, and the final expression returns a computation. Syntax and bind/pure selection remain open, including whether algebraic effects instead use ordinary sequential calls. Further direction favors evaluating direct-style handlers and familiar Log + ...effects composition with inferred, named row relationships; outer syntax and quantification remain design work. User decision: explicit effect-row constraint syntax is a future feature outside initial FX001, distinct from internal inference and R001 record-row lacks constraints. Capture MileAhead open error-family rows: compose without conversion wrappers, wrap only for context, close at handling boundaries. Evaluate native error-sum syntax hiding Variant/injection plumbing and resolve this need against R001’s deferred general variants in a reviewed scope decision. Direction: docs/plans/2026-10-08-language-direction.md. Update 2026-10-12: Tasks 1-12 are complete on branch fx001-finish. Task 11: acceptance scenario examples/services.wxw and scale tests. Task 12: 15 new regression rows (scripts/regression-fx.mjs, scripts/regression-fx-go.mjs; 81 proofs in verify), ADR 010 (docs/adr/010-effects.md) and the language Effects section (docs/language.md). FX008’s mark rule is part of the shipped design. Remaining: the whole-branch review, then the user’s merge decision. Follow-ups: FX002, FX003, FX005, FX006, FX007, FX010-FX017, D001, R001, STD001, PKG001, I001. Optional follow-up (final review, 2026-10-12, not built): a typed-failure variant of the services scenario, e.g. a Database handler failing for one id, with audited declaring Fail(DbError) and main handling it while the cleanups still run.
L001 Direction only Inspectable target-independent lowered IR coordinated with A002. Default to test: after P001 specialization, monomorphic and above target layouts/runtime. Specify operations and phase position in its design. Proposed acceptance: deterministic IR dump and equivalent Go behavior. No VM or serialized ABI selected. See docs/plans/2026-10-08-language-direction.md.
J001 Direction only Evaluate a JavaScript target for full-stack use after required language foundations. Assistant recommendation, not target approval. Proposed proof: browser UI and Go service share pure Waxwing domain code with explicit serialization and platform bindings. LLVM, WASM and native app support remain alternatives requiring separate designs. See docs/plans/2026-10-08-language-direction.md.
D001 Text done; numbers and collections remain Application data foundations. Text is done (Text v1, ADR 011, branch text merged into main 2026-10-13): literals, interpolation, ++, comparison by code point, raw and block literals, printing and show; no operations beyond those (STD001). Remaining: numeric types beyond Int32 with defined arithmetic, and practical collections. Split into focused designs with executable examples and invalid/boundary cases before implementation. Together with M001, prerequisites for the proposed full-stack shared-domain example. See docs/plans/2026-10-08-language-direction.md. FX001 note (2026-10-12): user Console handlers wait for Text; until then Handler(Console) and Handler(Fail(E)) types are uninhabited and compile to empty Go structs (ruling F3, ADR 010; see FX011 and D002). Text note (2026-10-13): Text v1 does not change this. User Console handlers are still uninhabited, because print accepts any printable value, so a clause has no single parameter type; it needs a design of its own (likely with C001, where print becomes class-based).
M001 Planned Modules/imports, explicit interfaces, deterministic whole-program linking/specialization; stable binary separate compilation deferred. Evaluate explicit exports/re-exports, package visibility, abstract types and type identity, predictable resolution and explicit initialization; compare ML signatures/functors against records/classes/handlers before adding mechanisms. Coordinate C001/R001/PKG001: docs/plans/2026-10-10-modules-and-instance-selection-direction.md (direction only).
LA001 Planned research Structured cross-language/library/runtime audit covering Koka, Haskell/GHC, PureScript, Rust, OCaml, ZIO, Effect TS, fp-ts, Scala, Elm, Idris and Clojure. Plan: docs/plans/2026-10-08-language-audit-plan.md. Freeze primary source versions/sections; produce twelve comparative briefs and an adopt/adapt/defer/reject matrix tied to Waxwing scenarios, laws, interactions, prerequisites, ADRs/backlog and acceptance probes. Independent review and user scope decisions before implementation changes. Feed relevant results into K001/C001/R001/FX001 designs and STD001 coverage without interrupting FN001 or overriding existing advanced-feature deferrals. Audit planned, not executed.
STD001 Design needed Text v1 note (2026-10-13): Text has no operations beyond literals, ++, comparison, printing and show (ADR 011). Text operations such as length, indexing, slicing, split, join, trim and parsing belong here, with a prelude, List and Option decision; they must respect code-point order and the UTF-8 representation. Common FP standard-library helpers with approachable, descriptive names; Rust-informed defaults/recovery and PureScript/Haskell breadth without obscure abbreviations or joke names. After FN001, deliver supported optional/result/function/list helpers in focused plans; coordinate generic operations with K001/C001, modules with M001, data/text/collections with D001, and effects with FX001. Inventory and candidate names: docs/plans/2026-10-08-standard-library-direction.md. Required generic rigor: bimap over any lawful Bifunctor, shared HKT-based Functor/Applicative/Monad APIs, user-defined instances and operation coherence; friendly names do not replace abstractions with per-type-only APIs. Acceptance: upstream coverage matrix with explicit deferrals, pipe-friendly examples, generic clients, exact eager/lazy and branch semantics, total missing-data APIs, algebraic law properties and scale/error-preservation tests. Concrete helpers do not complete generic acceptance before K001/C001. Planning authorized; no helper implementation added to FN001. FX001 note (2026-10-12): no generic Result-to-failure helper exists: a Fail payload needs a concrete family (E_TYPE Fail needs a concrete error family), so helpers are per family or return Result (ADR 010, docs/language.md).
PKG001 Design needed Libraries, platform adapters and package management: distinguish compiled Waxwing source libraries, typed host FFI bindings, and Waxwing service contracts fulfilled by host-library adapters or fakes (Effect Platform as a conceptual precedent). Design source/package identity, manifests/lockfiles, target/runtime capability metadata, combined Waxwing/host dependency resolution and host manifest ownership; assess host-callable compiled library exports without selecting a stable binary ABI. Acceptance: provider substitution/conformance, foreign validation, fresh/offline locked builds, conflict/compatibility diagnostics and target support checks. Coordinate M001/I001/C001/R001/FX001/STD001/J001 and documentation/discovery. Direction: docs/plans/2026-10-08-tooling-direction.md. FX001 note (2026-10-12): export calling convention. In ctx mode (any program whose emitted IR holds an effect construct) every function takes ctx *waxwingCtx first, so a host-callable export or separately consumed library needs adapters whose public convention does not depend on whether the consuming application uses effects (ADR 010).
IDE001 Design needed Syntax highlighting and IDE support: stage basic highlighting, compiler diagnostics, then hover/completion/navigation/rename; evaluate a language server, reuse compiler semantics and source locations, support incomplete edits, and coordinate cross-module symbols with M001. Direction: docs/plans/2026-10-08-tooling-direction.md. Stage 1 done (commits b2be43f..3f1b564 on main; grammar updated for FX020 effect namespaces in the housekeeping branch: fail is no longer a keyword, Effect.op, as aliases and import lists, fixtures/effect-namespaces.wxw). Stage 1 was queued (user, 2026-10-13), to start after Text Task 6 lands the final literal rules: a TextMate grammar and VS Code extension in the repo (language-configuration.json for brackets and auto-closing), also usable by Sublime, JetBrains and Shiki. Tests: the grammar’s keyword list equals the lexer’s reserved words (Format/Lex.purs), and snapshot tests (vscode-tmgrammar-test) over examples/ and the text-literal edge cases (${…} nesting, """ blocks, #"…"# with any # count). Comments are added once CMT001 is designed. Tree-sitter (Neovim, Helix, Zed) and LSP semantic highlighting come later. Stopgap: .gitattributes maps *.wxw to Rust for GitHub, and README Waxwing samples use rust fences.
DOC001 Design needed First-class doctests: discover and execute documentation examples with the ordinary compiler/test runner, source-mapped failures, expected values/diagnostics, reproducible effect examples and CI integration. Syntax, scope/imports/setup and runner interface need design. Direction: docs/plans/2026-10-08-tooling-direction.md. User addition 2026-10-13: doc comments are /// (CMT001), and examples inside them are the doctests. The compiler (or a waxwing doc command) also emits documentation, like rustdoc: per declaration, the signature as written, with effect rows, inferred rows once INF001 exists, and record rows; the doc comment rendered from Markdown; and the checked examples. Output is static HTML and/or Markdown. Depends on CMT001; module-level docs and cross-links coordinate with M001.
PBT001 Design needed Property-based testing for Waxwing users: composable generators, shrinking, deterministic seeds/replay and useful counterexamples; independent contracts and generic law suites coordinated with FN001/D001/STD001/K001/C001. Acceptance includes defect detection, valid shrinking and non-vacuous coverage. Existing compiler properties do not complete this milestone. Direction: docs/plans/2026-10-08-tooling-direction.md.
SCH001 Design needed User-requested first-party Schema foundation: parse boundary input into invariant-preserving domain values; distinct encoded/domain types, composable codecs, structured typed errors, valid-data generators and shrinkers, explicit interpreter/codec selection. Compare Effect Schema v4 and rae-hill/schemata-ts; assess library versus compiler support and derivation prerequisites. Independent properties/invalid inputs, bounded generation and codec-specific laws required. Coordinate D001/R001/C001/K001/M001/I001/STD001/PBT001/PKG001 and a coherent v1 subset. Direction: docs/plans/2026-10-10-schema-direction.md. No implementation authorized.
LIT001 Exploratory Literate capabilities, user preference explicitly tentative: compare executable Markdown via DOC001 with dedicated literate sources; review scope/imports, source mapping, IDE integration and source-of-truth/drift rules before choosing whether to adopt. Direction: docs/plans/2026-10-08-tooling-direction.md.
AI001 Design needed LLM skills and discoverability: authoritative versioned docs/API entry points, checked worked examples and task guidance; evaluate machine-readable symbol/diagnostic discovery without selecting a vendor/protocol. Skills must link to semantic authority, detect stale commands/syntax and state supported versions/limitations. Coordinate with DOC001 and IDE001. Direction: docs/plans/2026-10-08-tooling-direction.md.
I001 Planned Explicit Go FFI with refined ingress, typed foreign errors, effect sequencing and nil/zero validation, including full-value validation of tag/payload consistency of foreign closed-ADT values (folded in from A004; wildcards and binders do not validate skipped fields today, ADR 003); optional Go go/parser and go/types inspection helper. FX001 note (2026-10-12): callback boundary. A foreign Go library could recover a panic raised by a Waxwing callback (typed aborts are Go panics), so I001 must define the permitted boundary (aborts may not cross foreign frames, or adapters re-raise) before transparent abort propagation through host libraries is promised. Injected foreign-panic tests for the defect runtime (panic: <text> causes) belong here. R001 note (2026-10-14, ADR 013): reading a field of declared type inside a record dereferences its pointer unguarded, so a nil there (only foreign code can make one) is a Go nil dereference, not waxwing: malformed value; comparison and printing are guarded (test/r001-go.test.mjs). Ingress validation here must cover record fields.
S001 Planned Port scanner/parser to Waxwing as equivalence pilot, then Stage 1 compiler; preserve Stage 0 and generated compiler-Go snapshot; Stage 2/3 comparisons and full tests.
F001 Environment limit Verify fresh online tool install/package downloads on network-capable host; earlier sandbox npm lookup failed ENOTFOUND. Escalated skills installation now works; fresh compiler tool setup remains unverified.
F002 Blocked by sandbox Apply requested MileAhead guidance patch to trailmapper/AGENTS.md; filesystem approval denied outside workspace. Patch in docs/patches; no external edit claimed.
E001 Planned Full CST complexity, top-down ordering, naming, numeric and branch-expression budgets beyond essential gates. Review conventions already apply; do not spend initial slice on a full lint clone.
E002 Planned Current state (G001, 2026-10-07; history follows). No breadth crash is known at the measured sizes below; the earlier claim that breadth was fixed (Task 2-5) was wrong until the G001 final review, which found a 1,600-field constructor pattern and a 3,000-type chain still overflowing. Grammar lists are tailRecM loops (Format.Parse.Grammar), resolution threads LocalIds through Features.Resolve.Fresh with balanced traverse, coverage’s three searches are Features.Check.Search firstJust loops, and since the final review usefulness (Features.Check.Usefulness) and algorithm I (Features.Check.Missing) keep pending heads and witness continuations on an explicit Stack in a tailRecM loop, and inhabitation (Features.Check.Inhabited) is a worklist in a tailRecM loop of rounds. test/coverage-scale.test.mjs, each bounded at 5 s and each RangeError before: an exhaustive 5,000-field constructor pattern (0.27-0.36 s), a 5,000-field non-exhaustive match with its exact witness (0.08 s), a 5,000-field redundant arm at its exact span (0.05 s), and a 10,000-type chain type T0 = A0(T1); … (0.64-0.69 s). Nesting is bounded: deeper than 128 levels is E_NESTING Nesting exceeds 128 levels (ADR 006), a structured diagnostic, never a raw stack trace (test/depth.test.mjs through the CLI). Corrected thresholds of the fixed failures: coverage overflowed at about 1,929 integer arms on a cold compile, root cause Coverage.redundancy’s Array.foldM over Either (Usefulness’s two constructor searches overflowed near 2,000 constructors); the pre-G001 resolver (Array.foldM over Either) overflowed at about 1,700 arms or arguments. Remaining: (1) name lookups are linear per reference, so quadratic in program size: Features.Resolve.Types resolveType (Array.findIndex over all types per named field type), Features.Resolve.Expression findGlobal and Features.Resolve.Pattern (Array.find over all globals per call, construction or constructor pattern); 20,000 types or functions compile in about 1.7 s (figures below). Not done in G001. Next: a sorted name index with binary search (the pure allowlist has no map), with a 20,000-reference test bounded in time that fails first. (2) Coverage is quadratic in arms (each arm against the earlier ones): 10,000 / 20,000 integer arms take 5.2 / 26.7 s, 5,000 / 10,000 constructors 3.5 / 14.3 s, no overflow. Next: index earlier heads instead of rescanning the rows per arm. (3) Nesting beyond 128 needs heap-based phases (H001); flat operator chains beyond 129 operands need O001. (4) Inhabitation is quadratic in the length of a type-dependency chain: one round per link, each copying the constructor and type flag arrays, O(edges·log edges + rounds·(types + constructors)); 10,000 / 20,000 / 40,000 chained types compile in 0.69 / 2.04 / 7.8 s (before: 2,000 in 1.18 s, RangeError at 3,000). Linear needs per-constructor counters updated in place, which the pure allowlist (no ST, no map) does not offer cheaply. Next, if it matters: a counter array behind an allowlisted module, with a 40,000-type test. (5) Wide patterns are quadratic in fields: 5,000 / 10,000 / 20,000 binder fields compile in 0.27 / 0.92 / 3.55 s. At 20,000 about 3.1 s is Features.Resolve.Pattern uniqueBinders (an Array.filter over all binders per binder; profile 2026-10-07); coverage’s own share is the per-column Array.uncons copies (20,000 Bool wildcard columns in 0.39 s). Next: binder duplicates through Features.Resolve.Repeated, as Task 4b did for parameters, with a 20,000-binder test bounded in time that fails first. History: Deep nesting and long lists inside one declaration still exhaust the JavaScript stack: compile throws RangeError: Maximum call stack size exceeded instead of returning a structured Diagnostic, and the CLI (node scripts/bumpus.mjs emit) dies with a raw JavaScript stack trace on stderr, exit 1, no JSON diagnostic. Nesting (Node 26.6, default stack): constructor-argument nesting Cons(1, × n around Nil overflows at about 420 levels in a fresh (cold) process (A003 final review saw 420-450; re-measured at the CLI 2026-10-07: 416 compiles, 417 crashes), so printed lists deeper than that cannot be recompiled; in one warm process (after A003 Task 3b), 1,536 nested parentheses around a literal (about 3,100 characters), a left-to-right + chain of 2,048 operands (about 8,200 characters) and 1,025 nested if … then … else (about 20,500 characters) each throw, one level less compiles. Breadth (A003 final review, recursive constructorTail/commaTail and arm parsing): a type with about 1,536 constructors, a constructor with about 1,536 fields, a call with about 1,793 arguments and a match with about 1,473 arms each overflow. Long declaration sequences and whitespace do not: Task 3b made Format.Lex an index-based tailRecM loop, Format.Parse.declarations a tailRecM loop, parser token access indexed, accumulation linear (Format.Stack) and function-name duplicate checks O(n log n) (Features.Resolve.Repeated); the A003 final-review fix made type and constructor duplicate checks use Repeated too, typeInfo linear, and Go emission builds one constructor layout per program with two sorts (Format.Go.Layout), O(n log n) instead of O(types × constructors). test/large-source.test.mjs compiles 1,000,000 trailing spaces, 20,000 functions and 20,000 one-constructor types (25.7 s before that fix, 0.2 s after; bounded at 5 s) and reports duplicates among 20,000 types at their first occurrence. Name lookups remain linear per reference, so they are quadratic in program size: Features.Resolve.Types resolveType scans all types (Array.findIndex) for every named field type (O(field references × types)), and Features.Resolve.Expression findGlobal and Features.Resolve.Pattern scan all functions and constructors for every call, construction or constructor pattern (O(references × globals)). Measured 2026-10-07 after the A003 final-review fix (Node 26.6, warm process): 5,000 / 10,000 / 20,000 types type T<i> = C<i>(T<n-1-i>); compile in 0.19 / 0.54 / 1.68 s, and the same counts of functions each calling the previous one in 0.19 / 0.54 / 1.63 s. Not fixed in A003: the pure-layer allowlist has no map, so the fix is a sorted name index with binary search (or an allowlisted map); do it with G001’s resolver rework, with a 20,000-reference test bounded in time that fails first. Corrected figures for the old failure: before Task 3b the lexer overflowed between about 2,300 and 3,500 characters depending on JIT state, and a megabyte exhausted the heap through per-character Array.uncons copies. G001 Task 2 (2026-10-07): declaration and call breadth is fixed (matching on wide types was not; see the final review above); 1,536 constructors, 1,536 fields, 1,793 call arguments and 1,473 arms each compile in under 0.3 s (test/grammar.test.mjs, each RangeError before). Cold-CLI nesting re-measured after the applicative port (Node 26.6, emit; before = 75b5c8b): Cons(1, nesting 313 compiles (was 419), parentheses 620 (was 1,186), if 1,272 (was 1,183); the combinator layers cost more stack per level, which Task 3’s measured depth limit must account for. Frames per nesting level after Task 2: parentheses 13, call/Cons( 24, 1 < ( 20, if 5. G001 Task 3 (2026-10-07): E_NESTING at 128 levels (ADR 006, test/depth.test.mjs); smallest overflowing depth per form with the limit disabled (scripts/depth-probe.mjs, cold CLI emit, Node 26.6): parens 648, if-condition 679, if-branch 1,058, match-scrutinee 801, match-arm 783, call-argument 369, Cons(1, 304, constructor-pattern 373, + chain 2,928; mixed: parenthesized if left of a sum 1,952, parens in calls in constructors 432, comparison of deep matches 765; limit = largest power of two ≤ half of 304. Remaining: heap-based phases would remove the bound (spec section 4). G001 Task 4 (2026-10-07): resolution is stack-safe in breadth (Fresh with balanced traverse; the old Array.foldM over Either overflowed near 1,700 arms or arguments, and 50,000 now resolve); a match of about 1,929 integer arms (binding arms between 1,900 and 1,950; 1,473 pass) still overflowed on a cold full compile (root cause Coverage.redundancy’s Array.foldM over Either, not Usefulness), and the duplicate-parameter check (Features.Resolve.Types uniqueParameter) was quadratic: 10,000 / 20,000 parameters took 0.74 / 3.1 s. G001 Task 4b (2026-10-07): fixed. The three Array.foldM searches over Either (Coverage.redundancy over arms; Usefulness missingColumn for exhaustiveness and usefulColumn for wildcard usefulness, over constructors) are a tailRecM loop (Features.Check.Search.firstJust), and specialize checks arity once then uses mapMaybe instead of a per-row traverse; parameter duplicates use Repeated. A 5,000-arm integer match compiles in about 1.3 s, a 3,000-constructor match in about 1.3 s, 20,000 parameters in 0.17 s (test/large-source.test.mjs; before: RangeError for the 5,000 arms and 3,000 constructors, 2.7-3.1 s for the parameters). Usefulness remains quadratic in arms (each arm against the earlier ones): 10,000 / 20,000 integer arms take 5.2 / 26.7 s and 5,000 / 10,000 constructors 3.5 / 14.3 s, with no overflow; a further fix would index earlier heads instead of rescanning the rows per arm. Next (historical): breadth (comma lists, constructor lists, match arms) is fixed by G001’s tailRecM-based list combinators; deep nesting needs an explicit depth limit that reports a structured Diagnostic (with a test at the limit and one past it, including through the CLI).
W001 Resolved Checkpoint 9624bec preserves Stage 0 and pinned skills; user approved audit worktree/inline execution. HEAD-based task helpers now have a real BASE.
F003 Environment limit Sandboxed Git inspection emits xcrun cache permission errors under /tmp; escalated inspection succeeds. Use approved access for Git operations and record the distinction; do not treat these as compiler failures.
E003 Reviewed target exception src/Format/Diagnostic.purs codeName (fourteen ErrorCode arms), code and message (twenty-seven Problem arms each since FN001 Task 4) exceed the eight-branch review target. They are flat exhaustive table mappings; reassess grouping when diagnostics grow, preserving every tag and text with exact rejection tests (test/diagnostics.test.mjs). Since FN001 Task 3 the expression dispatches Features.Resolve.Expression expression (eleven Syntax.Expr arms) and Features.Check.Infer inferNode (fourteen Resolved.Expr arms since FN001 Task 4) exceed it too, each arm a delegation, as do the checked-IR walks Features.Check.Walk foldTypes/retype, Coverage covered and Specialize.Body copyNode (fourteen Checked.Node arms since Task 4), and since FN001 Task 5 Format.Go.Usage usesBoolOrdering (fourteen IR.Node arms; Format.Go.Expression keeps ten, one a temporary wildcard until Task 6); reassess if a dispatch arm gains logic beyond delegation.
E004 Minor, deferred test/compiler.test.mjs:84 title overstates repeated CLI assertions; test/style.test.mjs:21–22 title overstates lazy-fallback fixture. Rename to snapshot agreement and named helpers with constant fallback; preserve assertions. Manual repeated emissions separately verified.
E005 Done Nested match arms made go build exponential because Format.Go.Match lowered each match to an immediately invoked closure and Go’s inliner expands nested closures. Fixed 2026-10-07 (design approved by the user that day): each match is a top-level Go function waxwingFn{f}Match{k} (k in per-function pre-order, scrutinee before arms) taking its captured locals in LocalId order, then the scrutinee (ADR 003 item 6). Measured go build of match 0 { _ => x d (Go 1.26.4, run-unique leaf, log .build/e005-timing.log): before 0.31 s / 113 MB at d = 16, 0.44 s at 18, 1.26 s / 596 MB at 20, 7.99 s / 2.7 GB at 22 (earlier: 34.6 s / 7.5 GB at 24, killed at 128); after 0.13 s at 16, 0.13 s / 67 MB at 24, 0.14 s / 67 MB at 128. RED: the new 128-deep timing test hit its 10 s spawn timeout (.build/e005-red-timing.log). GREEN: test/depth.test.mjs runs match-arm and compare-matches at the nesting limit (exception removed from ADR 006) and builds and runs 128 nested matches in under 10 s (1.1 s within verify); test/match-lift.test.mjs pins helper names, parameters and order and capture through nested matches; regression row capture.
F004 Done Spago 1.0.4 --strict reported a promoted warning only on the build that compiled the module, so npm run verify after a failed build could pass over it. scripts/build.mjs now removes every workspace module’s output directory before building; scripts/strict-rebuild.mjs (run by verify) requires a shadowed-name fixture to fail two consecutive builds in an isolated copy, and fails with the previous build.mjs (.build/f004-red.log). The AGENTS.md clean-output requirement is dropped.
A002 Planned Decision-tree match compilation replacing sequential first-match lowering, with a target-neutral Features.Lower that Format.Go prints (ADR 003, ADR 004). Accept when emitted behavior equals the first-match interpreter on the existing oracle and tests, with new snapshots.
A003 Done Comparison operators on every type (ADR 005), unrestricted printing main, Waxwing rename and stack-safe lexing folded in. Final whole-branch review fixes (I1-I3, M1-M5) are recorded in docs/plans/adt-printing-review.md; evidence in docs/progress.md A003 execution. Merged into main.
G001 Done Applicative parser and nesting limit, merged into main 2026-10-07; spec docs/plans/2026-10-07-applicative-parser-design.md, plan docs/plans/2026-10-07-applicative-parser-plan.md, evidence docs/progress.md G001 execution. Delivered: Format.Parse.Grammar Parser with a hidden constructor and Functor/Apply/Applicative only (LL(1) dispatch, no Alt), state in Format.Parse.Cursor, every production ported, stack-safe lists; E_NESTING at 128 (ADR 006); Features.Resolve.Fresh numbering; stack-safe coverage search (Features.Check.Search) and linear parameter duplicates; after the final whole-branch review (record docs/plans/applicative-parser-review.md), stack-safe usefulness and algorithm I in pattern columns (explicit Stack), worklist inhabitation, and the Bind gate’s Monad-helper names; CST gates (no do/bind/join/Kleisli in productions; only Format.Parse imports run/initialState); state-thread regression row. Done 2026-10-07 after the final-review fixes, pending the controller’s scoped re-review; merging into main needs user approval. Follow-ups: E002 remainder, E005, O001, H001, F006.
A004 Planned Full-value FFI validation of tag/payload consistency, nil and zero values: folded into I001, not a separate milestone. Wildcards and binders deliberately do not validate skipped fields today (ADR 003).
A005 Planned HostCode ADT for E_USAGE, E_IO and E_TOOL, currently string literals in src/Format/Wire.purs. Accept when wire tests (test/program.test.mjs) pass unchanged and no code string remains outside Format.
F005 Open The nil-guard regression probe (test/regression.mjs) removes its bumpus-regression-* temp directory in a finally block, but four directories from Task 3-era runs remained in $TMPDIR (deleted with user approval 2026-10-07) (observed by the controller). A clean npm run verify on 2026-10-07 left the count at four, so no current leak is demonstrated; a killed or timed-out probe would skip cleanup. The A001 final review found no leak on normal paths; one needs a spawnSync timeout (SIGTERM) to skip the finally block. Next: move the probe’s work directory under .build/regression (removed by scripts/regression.mjs before each run), then delete the stale directories manually.
T001 Done Go built once per test file (test/go-batch.mjs; contract in docs/engineering.md; evidence under ## T001 execution in docs/progress.md). Measured on the t001 worktree, warm caches, rm -rf output && npm run verify: 76.4 s before, 60.7 s after; its node –test phase 33.0 s before, 19.2 s after; node --test alone 27.0 s before, 15.5 s after; 156 tests before, 162 after (six harness self-tests). Remaining test time is dominated by large-source (Waxwing compile, 10.7 s), depth (timed builds, out of scope, 8.0 s) and adt-order (goTest, out of scope, 6.3 s). Original plan: Build Go once per test file instead of once per executed program. Evidence: test time in npm run verify is dominated by go build: each runGo/goTest builds a separate binary (about 31 runGo call sites plus generated-program loops) and the test phase took about 33 s in .build/verify-3a.log, while the JavaScript runtime is negligible (Node versus Bun measured equal on test/adt-types.test.mjs: 1.95 s versus 1.72 s). Next: have each test file collect its programs, emit them as separate packages or functions in one Go module (or one program dispatching on argv), build once, run each case. Accept: identical assertions, the same failure messages per case, and a measurable verify-time reduction recorded in docs/progress.md.
O001 Planned Iterative operator-chain traversal in resolve, check and Go emission (spec section 7), so flat + chains need not count toward the nesting limit. Today a flat chain of n operands counts n - 1 levels (ADR 006): at the CLI a 129-operand sum compiles and 130 is E_NESTING (measured 2026-10-07, G001 Task 5). Go is not the obstacle: the G001 final review measured go build of a 10,000-deep nested waxwingAdd expression at 2.3 s with the correct result (low risk). The parser’s depth accounting must change too: Grammar chainLeftAny/infixed count a flat chain’s operators as nesting (ADR 006), so the lifted chains need a separate bound. Accept: a 10,000-term flat sum compiles and runs through the CLI with the right int32 result; comparisons still do not chain (a < b < c stays E_SYNTAX Comparisons do not chain); deep left operands still count in full.
H001 Planned Heap-based phases (explicit work stacks or trampolined traversal in parse, resolve, check, coverage and Go emission), removing the nesting bound of ADR 006 option 3. Only if a real program needs nesting beyond 128 levels (for example re-reading a printed list of more than 128 elements, ADR 005). Accept: the deepest forms of scripts/depth-forms.mjs compile at depths far past today’s overflow points (304 to 2,928) with no RangeError, and E_NESTING is removed or raised with an ADR.
F006 Open scripts/build.mjs removes the output directory only of workspace modules that still have a source file, so a module whose source was deleted keeps its compiled output: after G001 Task 2 deleted src/Format/Parse/Core.purs, output/Format.Parse.Core stayed on disk in incremental builds (seen in the Task 2 review). Nothing imports it, so the build is not wrong today, but a stale module could satisfy a stray import from tests that load output/ directly. rm -rf output && npm run verify is unaffected. Next: also remove output directories whose module name has a workspace prefix (Domain, Features, Format, Runtime, Program, Style) but no source, with a test that a deleted module’s output disappears and fails first.
F007 Open The structural allowlist in scripts/structure.mjs (Data\.Array(\.|$)) admits Data.Array.ST and Data.Array.ST.Iterator in pure layers, contrary to the “no ST” intent (only Control.Monad.ST is rejected). Found by the G001 final re-review (2026-10-07); no module imports them today. Next: narrow the pattern to the immutable Data.Array modules and add a structure.test.mjs case that rejects Data.Array.ST (seen failing first).
E006 Mitigated (P001 Task 4 fix 1) Features.Check.Unify (P001 Task 3) recurses over type structure in resolve, the occurs check and argument-wise unify; only meta-to-meta chains are followed by a loop (walk, tailRec; 10,000-link chains pass). Measured 2026-10-08, warm Node default stack: a List(…) type nested 2,218 deep unifies and resolves, 2,250 overflows (RangeError). Source types are bounded at 128 (E_NESTING), which the plan takes as the bound, but inferred types can be deeper: a 128-deep nest of calls to a function whose signature type is itself deep composes depths. Next (Task 4 or its review): measure the deepest inferred type a program within the nesting limit can produce; if it can approach 2,000, make resolve, the occurs check and argument unification explicit-stack tailRecM loops, with a test at that depth that fails first. Measured by P001 Task 4 (2026-10-08, warm Node default stack, checking only; .build/p001-task4-measure-depth.log): every existing depth/large program (the twelve depth-forms at 128, 20,000 declarations) checks with deepest checked type 1; id(id(…id(1)…)) 128 deep: depth 1 (a 128-link meta chain, walked); wrap(x: a): L(a) nested 127 deep: depth 128. But a program within every source limit reaches the overflow: fn deep(x: a): L^127(a) nested k deep under sink gives depth 127k+1, checked at k = 40 (5,081 deep, 88 ms) and overflowing at k = 44 (≈5,589) and above (RangeError from resolve, an uncaught exception, not a diagnostic; k may reach 127, ≈16,000). So the answer to the question above is yes. Next: make resolve, the occurs check and argument-wise unification explicit-stack tailRecM loops (and Features.Check.Scheme holes, Features.Check.Walk, Comparable variablesOf and Require typeName, which also recurse over type structure), with a test that deep nested 127 deep checks and fails first. Fix round 1 (ruling R7): the crash is now a diagnostic. inferredTypeLimit = 1000 (Features.Check.Unify): an inferred type deeper than that is E_NESTING Inferred type nesting exceeds 1000 levels (Problem TypeTooDeep), decided by exceedsLimit, an explicit-stack walk that stops past the limit, at three points: each expression’s type once built (reported at that expression), both operands before every unification plus each binding before the occurs check resolves it, and every type of a finished body before it is resolved. deep nested 44 and 127 deep are now E_NESTING in about 2 ms; a type exactly 1,000 deep checks (test/poly-depth.test.mjs). Fix round 2 (re-review finding): the remaining chain path was reachable with a small legal program. Metas bound earlier in one unification (h1 := D7(h2), h2 := D7(h3), likewise g) made two operands, each bounded beforehand, unify as chains about 1,780 deep, and unify’s argument-wise recursion threw a RangeError. Unification now carries a level, one per applied type entered, so a level is a resolved depth. Past inferredTypeLimit it fails with TooDeep, as does a Mismatch whose pair is too deep to resolve for the message. The guarantee therefore holds whatever bindings the unification makes. The report is E_NESTING at the expression whose unification failed (test/poly-depth.test.mjs: two links of deep^7, three links of deep^4, and a binding made too deep within its own unification; test/unify.test.mjs: 1,000 levels unify, 1,001 fail). Measured margins: the binding unit is unify’s recursion, not resolve’s. In unit context unify overflowed between 2,218 and 2,250 levels (Task 3), and inside the checker the probe overflowed between 1,525 levels (three links of deep^4, unified without error before fix 2) and about 1,779 levels (two links of deep^7, RangeError). The bound of 1,000 therefore leaves at least a 1.5x margin, not the 5x stated in fix round 1, which used resolve’s figure. Resolve itself overflows between 5,081 and about 5,589. Fix round 3 (re-review finding): bindMeta bound resolved = resolve subst ty in a strict where, so the compiled code resolved the binding before exceedsLimit ran. A chain of L^997 links bound within one unification overflowed there at 6, 8, 12 and 20 links (2, 4 and 5 gave E_NESTING). The resolve and occurs work now live in bindBounded, called only after the bound; the six-link program is E_NESTING (test/poly-depth.test.mjs). Verified by reading the compiled output of Features.Check*: the remaining strict where/let bindings that recurse over a type are Comparable variables and Check’s settled, both on a finished body that has passed the final bound, and bindBounded’s resolved. This is an audit of those modules’ compiled bindings, not a proof that every input is bounded. P001 close (Task 9): documented in docs/adr/007-specialization.md (inferred-type depth bound) and docs/language.md. The 1.5x margin assumes Node’s default stack: with --stack-size=400, rendering a near-limit diagnostic overflows (Task 4 review). Remaining next action unchanged: explicit-stack resolve, unify and type walks (H001-style) would remove the margin assumption; until then do not lower the default stack.
E007 Open The checked IR stores every type fully resolved as a tree (Features.Check.Scheme resolved per expression), and Unify’s occurs check resolves before searching, so shared subterms expand: fn dup(x: a): Pair(a, a) = Pair(x, x); nested n deep has a 2^n-node type. Measured P001 Task 4 (2026-10-08, checking only, .build/p001-task4-measure-dup.log): n = 8: 4.5 ms; 12: 21 ms; 14: 61 ms; 16: 208 ms; 18: 787 ms; 20: 3.2 s (about 2x per level). Source size is linear in n, so a short program can make checking (and later Specialize’s keys and Go’s type names) exponential. Next: decide in Task 7/8 review whether types should be hash-consed or depth/size bounded (a new E_NESTING-style limit on inferred type size), then add a timing test at n = 24 that fails first. After fix 1 the depth bound walks every expression’s type too: n = 12/16/20 take 42 ms / 441 ms / 6.8 s (.build/p001-task4-fix1-measure-dup.log). P001 close (Task 9): Specialize’s keys are hash-consed (ruling R15), so key comparison no longer grows with type size, but checked-IR types, Expand’s coverage keys (E008) and Go type count still follow type size. Argument-doubling type chains across declarations (A1(a) = C(A2(Pair(a, a))), …; ruling R14) are the same exponential-size class and stay legal and finite. Not fixed in P001 (rulings R8, R14).
E008 Open Coverage’s Features.Check.Expand keys each type application by the whole type (variables forgotten), so a type chain whose argument grows linearly makes coverage quadratic: P001 Task 6 review measured n = 3,000 at 16.6 s. Next: hash-cons Expand’s keys like Specialize.Keys (ruling R15: an application numbered once, keyed as (TypeId, argument numbers)), with a timing test at that size, under a bound, that fails first. P001 final fix (2026-10-08): Expand’s maps are now keyed by a canonical string spelling of each key, a native string comparison instead of Ord (Ty Unit) through dictionaries: poly-depth exactly 1000 deep 2.8 s to 0.35 s, the 3,000-application chain 0.72 s to 0.65 s. Still quadratic in key size (spelling and comparing whole keys); hash-consing remains the fix.
T002 Open Concurrent node --test runs of the same test file in one checkout share .build/go-batches/ (test/go-batch.mjs `build` removes and rebuilds it), so one run's `go build -o program` races another's: `open program: no such file or directory` failed every case of the poly-run and poly-properties batches when the P001 files ran three at once (P001 final fix, .build/p001-final-fix-load-after{1,2,3}.log). `npm run verify` runs each file once, so it is unaffected. Next: give each batch directory a per-process suffix (for example the pid) and remove it afterwards, with a test that two concurrent builds of one id both succeed, failing first.
T003 Open Timing headroom under external load. npm run verify on main with only docs changed (FN001 plan, 2026-10-08, .build/fn001-plan-verify.log) failed test/match-lift.test.mjs a deep, wide match ladder compiles in under 1.5 s at 1,830 ms while another project’s spago build and tests ran (load average 11.5); alone it took 471 ms the same minute. Its headroom is about 3x over the isolated time, and verify’s parallel runner plus outside load exceeded it. Next: measure the ladder’s compile time under verify’s own parallelism on an idle machine and under a fixed synthetic load, then either reduce the workload’s constant (findings: fix the workload, not the bound) or derive the bound from a measured ratio to a calibration workload run in the same process, with a test that the calibration catches a 4x quadratic regression. FN001 Task 4 (2026-10-08): the same test failed once at 1,780 ms in a direct node --test test/*.test.mjs while a second build ran; alone 523-562 ms against 508-516 ms at the previous commit (four alternating runs each, about 3-8% slower), and 1,038 ms inside the green verify (.build/fn001-task4-verify.log). FN001 Task 5 (2026-10-08): passed at 1,441 ms inside the green verify, 59 ms under the bound (.build/fn001-task5-verify.log). FN001 Task 5 review fixes (2026-10-08): failed at 1,732 ms inside rm -rf output && npm run verify (.build/fn001-task5-fix-verify.log; that run also failed specialize.test.mjs for a real defect, fixed), load average 7.37 / 5.99 / 6.28 right after; alone, three runs the same minute took 490-492 ms. The ladder’s path (no arrows, no lambdas) is unchanged by the fixes. The next clean verify passed it at 1,497 ms, 3 ms under the bound (load average 8.14 after; .build/fn001-task5-fix-verify2.log): the next action above is now urgent. T003 fix (2026-10-08, docs/progress.md “T003: match-ladder compile cost”): workload made cheaper, bound untouched; measure with scripts/ladder-profile.mjs (--baseline DIR alternates with a built copy of another commit). Isolated cold compile median 507 → 370 ms (nine alternating rounds, load 5.7), 1.37×, short of the 2× target; three consecutive verifies green with the ladder at 942, 968 and 852 ms (was 1,345-1,732). Under external load 16-30 two direct node --test runs still failed it (1,659 and 2,573 ms) along with coverage-scale and fn-specialize timings. Next: one of the design changes in that entry (a direct expression parser; literal-column redundancy without per-arm queries; fusing the checker’s whole-body passes; an allocation-light parser state), or the calibration-ratio bound named above; decide with the user. User decision 2026-10-08: accept the 1.37× gain and continue FN001; the design options remain for later. FN001 Task 6 (2026-10-08): a second bound with similar headroom: test/fn-scale.test.mjs’s 5,000-parameter go build (design §13 scale rule, 10 s) took 4.0 s alone and 6.8 s inside rm -rf output && npm run verify (load average 1.79 before, 6.71 after; .build/fn001-task6-verify3.log), and the earlier verify of the same code 9.6 s for the whole test (build not then logged); the test now logs its build time. Not failed so far; the bound is the user’s scale rule, so the next action is the workload or the runner’s parallelism, not the bound. FN001 Task 6 review (2026-10-08): the reviewer’s npm run verify failed fn-scale, go build 10584 ms, bound 10000, at load average 18.3 with only verify’s own runner. Scaling of the same mixed-body program (build seconds): 2,500 parameters 1.8; 5,000 4.4 (direct call only 3.0); 10,000 17.9 (direct only 11.2); 20,000 the Go compiler killed at 7.4 GB RSS, also with the direct call only. The cause is the n-ary mixed body’s 2n-term waxwingAdd tree in one Go expression, the pre-existing lowering of +, not Task 6’s staging. User decisions 2026-10-09 (FN001 Task 8): bound and workload unchanged; go build-timing scale tests are named *.serial.test.mjs and scripts/verify.mjs runs them after the parallel node --test phase with --test-concurrency=1 (docs/engineering.md), so only outside load can now slow them; the milestone uses bounded bodies (G003). First verify with the serial phase (2026-10-09, .build/fn001-task8-verify.log, load average 3.69 before, 4.97 after): fn-scale’s 5,000-parameter build 4,733 ms in the serial phase; the ladder 735 ms in the parallel phase. FX001 spec, docs only (2026-10-09): npm run verify failed the ladder at 1,541 ms (bound 1,500) together with large-source’s twenty-thousand-declaration test (T004), load average about 3 before and 12.11 / 4.67 / 2.93 at the end (.build/fx001-spec-verify.log); the immediate rerun passed with the ladder at 604 ms (load 9.46 before, 6.27 after; .build/fx001-spec-verify2.log). No code changed between the runs, so this is the same load exposure; the next action above is unchanged. FX001 Task 3 fix continuation (2026-10-09, .build/fx001-task3-fix-verify.log): clean-output verify passed build/gates/strict-rebuild and 643/644 parallel tests, but the unchanged match-lift ladder bound failed at 1670.670250 ms >1500. Serial tests/proofs were not reached by that command; run them explicitly without retrying this failure. Observed host load after failure: 5.89/5.64/4.54 (uptime), not proof of causation. Controller authorized the existing scheduling policy: extract only the deep/wide ladder block to test/match-lift-timing.serial.test.mjs, keeping semantic tests parallel. Block/source/constants/assertion/bound are byte-identical; clean verification follows this actual scheduling fix. After extraction, clean verification passed 643 parallel +22 serial tests and 22 proofs; the ladder test took 410.995667 ms total under the unchanged 1500 ms compilation bound (.build/fx001-task3-fix-verify-serial-scheduling.log). Next if it recurs serially: profile phase CPU/wall timings and external host load; do not relax the 1500 ms assertion or move more tests blindly. FX001 Task 9 and Task 10 (2026-10-10): the ladder failed in the serial phase on the cloud container (Task 10: 2,082 ms; 1.86-1.98 s alone), with compiler code unchanged by Task 10; the profiling next action stands. FX001 final review (2026-10-12, recorded, not implemented): test/fx-scale.serial’s unwinding (~46 ms) and caught-failure (~38 ms) workloads barely exceed the ~30 ms process start, so they measure startup; retune them so the cost dominates, e.g. repeat the 1,000-frame unwind ~1,000 times and use 10^6 caught failures. timedRun in test/fx-scale-support.mjs calls spawnSync with no timeout, so a looping regression would hang the serial phase; add a timeout of about 10x the bound and report it as a failure. The go build bound has the least headroom (292 ms under verify against a 405 ms bound): expect its first failure under load.
T004 Open Large-source timing failures during the LA001 docs-only verification (2026-10-08, .build/la001-plan-verify.log): test/large-source.test.mjs five-thousand-arm integer match compiled in 6.572 s and three-thousand-constructor match in 5.633 s, both exceeding the unchanged 5 s bound. Verify exited 1: 316/318 tests passed; regression proofs were not reached. Cause is unconfirmed; load averages observed after the run were 5.71/10.98/9.96, which does not establish causation. Next: compare these exact cases in isolation and under verify’s parallel runner with recorded CPU/load, then profile compiler phases to distinguish compiler cost from scheduling contention; coordinate timing methodology with T003 without weakening either assertion. FX001 spec, docs only (2026-10-09, .build/fx001-spec-verify.log): twenty thousand declarations compile and run failed with compiling took 5.317830916999999s (bound 5 s) alongside the T003 ladder, load average rising from about 3 to 12.11 during the run; the immediate rerun passed it at 2,725 ms (.build/fx001-spec-verify2.log). Recurrence under load; same next action. Waxwing rename verification (2026-10-09, .build/waxwing-verify.log): twenty thousand declarations compile and run took 5.106579416 s > 5 s; verify exited 1 with 639/640 parallel tests passed; serial tests/proofs not reached. Unrelated checker edits and output removal were observed concurrently; causation of the timing failure is not established. Same profiling/runner investigation next action; bounds unchanged. FX001 Task 9 (2026-10-10): a three-thousand-constructor match is judged in full failed in the parallel phase of three verifies on the cloud container and passed alone (15/15); bounds unchanged. FX001 Task 10 (2026-10-10): the same test failed in the parallel phase of both Task 10 verifies (5.78 s) and passed alone 15/15; no compiler code changed in Task 10. INF001 isolated main-integration verification (2026-10-11): test/coverage-scale.test.mjs:57, 10,000-type dependency chain, took 7.196051 s against 5 s; npm run verify exit 1, 957/958 parallel tests. Serial/regression phases not reached by that command. Evidence .build/inf001-main-verify.log. Compiler/test inputs unchanged; cause unconfirmed. Next: profile this exact chain in isolation and in the full runner with CPU/load measurements; preserve bounds and distinguish checker cost from concurrent host work.
E009 Open A lowercase builtin-looking type name (int, bool) silently becomes a type variable (design §1, ruling R5): fn main(): int = 1; is E_ENTRY Expected fn main() with a concrete result type and fn f(x: int): Int = x; is E_TYPE Expected Int, found int. Next: a hint such as did you mean Int? when a variable spelled like Int/Bool up to case appears in a rejected signature, without changing the code or span; exact-text rows first.
E010 Open E_TYPE mismatch messages resolve both operands under the substitution before the failing unification, so metas bound inside that unification print as _ (Expected Pair(_, _), found Pair(Int, Bool); P001 Task 4 review). Next: decide the wording (resolve under the partial substitution reached at the failure, or name the innermost differing pair as well); exact-text rows in test/poly-check.test.mjs.
E011 Open Diagnostics near the inferred-type limit render whole types about 1,000 levels deep, roughly 3,000 characters (P001 Task 4 review), and rendering recurses (see E006 on non-default stacks). Next: elide deep subterms in Format.Diagnostic type names (for example List(List(…)) past a fixed depth) with exact-text tests. FX001 Task 9 (2026-10-10): effect diagnostics bound notes by maxCharacters (2,000) and elide labels over 120 characters, but a headline with a very long type or effect name still exceeds the bound (probes 2,535 and 2,548 characters); the same structural elision would close it.
E012 Open Pre-existing diagnostic ambiguity, noted by the FX008 Task 3 review (2026-10-10): a closed row and an ambient row print alike in handler and arrow type names, so a mismatch can read Expected Handler(Log), found Handler(Log) (base build too). Next: print the distinguishing row part (e.g. with pure or with ...) only when the two sides differ in it, with exact-text tests; coordinate with E011’s elision.
G002 Open Recover the expression parser’s stack capacity per nesting level lost in FN001. Evidence (FN001 Task 6 review, bisected with scripts/depth-probe.mjs, limit lifted; ADR 006 “Functions emitted”): constructor-argument (Cons(1, nested) overflowed at 304 through c4e9ed1/14ad3a4, 280 at ceb52c2 (FN001 Task 3: the postfix atom layer applyTo <$> primary <*> dispatch … and the pipeline chainLeft1 "|>" layer add frames per nested level; parens 648 → 521, call-argument 369 → 326), 284 at 3a4868d/0a49dfd, 276 at 8045a4e (Task 4 fixes: primary returning a Primary record through bare <$> named inner, one map layer per level; call-argument 325 → 315), 274-275 thereafter. The Task 6 review fix builds the Primary inside named (283 / 326 / 522 for constructor-argument / call-argument / parens, from 275 / 315 / 522 at d2cf616). plus-chain 2,956 → 2,050 at 4ef13be and → 1,564 at ab81c68 predates FN001 (P001). The minimum overflow (283) is 27 levels above twice the limit (256). Next: merge the operator layers (pipeline, comparison, additive) into one precedence loop, or fold the postfix ( check into primary, to recover Task 3’s ~24 levels; re-measure every form with scripts/depth-probe.mjs, keep productions applicative (style gate) and confirm with scripts/differential.mjs (0 differences). FX020 Task 2 (2026-10-13, limit lifted, scripts/depth-probe.mjs, isolated copies): constructor-argument fb89cd5 259; first Task 2 build 239 (below 256) after a dispatch+ifSecond in front of named; ifSecond variant 250; final (capitalized names choose . or ( in the one dispatch after the name, upperNamed) 259. Other forms fb89cd5 / first build / final: call-argument 296 / 282 / 296, parens 396 / 396 / 396, parens-in-call-in-constructor 326 / 311 / 326, constructor-pattern 371 / 371 / not re-run, match-arm 748 / 748 / not re-run. Current state at the end of FX020 (2026-10-14): the minimum overflow is the constructor-argument 259 (not the 283 quoted above, which predates Task 2), three levels above twice the limit (256); no Task after 2 touched expression parsing, and Task 3 changed only type parsing.
G003 Open Long arithmetic and expression chains are lowered into one Go expression, which the Go compiler builds superlinearly and, at 20,000 parameters, not at all. Evidence (FN001 Task 6 review, the mixed-body 5,000-parameter program of test/fn-scale.serial.test.mjs whose body sums each parameter twice in a balanced 2n-term waxwingAdd tree; go build seconds): 2,500 parameters 1.8; 5,000 4.4 (direct call only 3.0); 10,000 17.9 (direct only 11.2); 20,000 the Go compiler killed at 7.4 GB RSS, also with the direct call only. Pre-existing lowering of +, not FN001’s staging; the FN001 milestone (scripts/fn-milestone.mjs) therefore uses bounded bodies (user decision B, 2026-10-09). Next: split long operator chains into helper functions or statement sequences (like Task 6’s application blocks), with a test that the mixed body builds at 20,000 within the scale rule’s 100 s.
G004 Open Pre-existing (found by the FX008 Task 2 re-review, 2026-10-10, on base bc959aa and on the fix alike): a handler type nested 125 levels deep (Handler(Log with S(Handler(Log with S(… Int))))) ends the compiler in an uncaught RangeError stack overflow instead of the E_NESTING diagnostic (ADR 006, limit 128); depth 124 compiles. Generator: test/fx008-nesting.mjs (worktree fx008-impl). Next: find which pass recurses past the parser’s limit (likely type resolution or a structural pass over handler rows), add a failing test at depth 125, and report E_NESTING or make the pass iterative.
T005 Open Unidentified failing test: during the FN001 Task 6 review fixes, scripts/differential.mjs --regenerate ran the suite under its harvest hook and exited 1; the output was discarded, so the test is unknown (likeliest a timing bound, T003). The tool now keeps <harvest>.jsonl.log and prints failing tests; one rerun exited 0. Next: if it recurs, read the log, identify the test, and fix or file it; close after three clean regenerations.
FN006 Open Whole-branch review minors deferred at the FN001 merge: (1) test/poly-parse.mjs cannot parse zero-argument calls (list(expression, ')') needs one item), so f() and make()(x) are never compared with the interpreter; next: accept empty argument lists and add oracle rows; (2) regression row value-edge removes both the bare FunctionRef edge and the Lambda recursion but its probe needs only the lambda half; next: split into two rows with a bare-reference probe; (3) test/fn-linear.test.mjs runs 3× bounds as low as 525 ms in the parallel phase (T003 exposure); next: move to the serial phase if it flakes. It flaked: the fx001 baseline verify on 2084638 (2026-10-09, .build/fx001-baseline-verify.log in .worktrees/fx001) failed value at 20000 parameters is linear through Specialize with took 2725.4897920000003 ms in the parallel phase, load average 17.60 / 6.55 / 4.13 just after; no code differs from main. Next: move test/fn-linear.test.mjs to fn-linear.serial.test.mjs (bounds unchanged), outside FX001’s scope unless the user folds it in. Rename baseline at 3481468 (2026-10-09): npm run verify exited 1, 638/640 parallel tests passed; lambda 1604.135291 ms exceeded 1545 ms, mismatch 819.499333 ms exceeded 525 ms. Serial tests and proofs were not reached. Evidence .build/waxwing-baseline-verify.log; bounds unchanged, same next action. Paused combined tree (.build/waxwing-paused-verify.log): over 1730.910208 >1620 ms, mismatch 760.219625 >525, partialFirst 1535.980250 >930, partialAll 1075.258750 >675, parenthesized 1112.661958 >1110. An accidental extra combined-tree run (.build/waxwing-paused-extra-verify.log, wrong working directory when starting isolated validation) also failed value 1505.102875 >1200, parenthesized 1350.899625 >1110 and pipe 819.245416 >750, plus the same row assertion; 636/640 passed. All results preserved; timing bounds and parallel/serial scheduling unchanged. Isolated rename-only tree at 3481468 (.build/waxwing-isolated-verify.log): 639/640 parallel tests passed, only pipe timing 942.893 ms >750 failed; this copy excludes the eight paused worker files. Serial continuation passed 13/13 and regression continuation passed all 22 proofs. Same timing next action; no gate, bound or assertion changed. Continuation (2026-10-09): the user authorized moving this suite to test/fn-linear-timing.serial.test.mjs (the existing fn-linear.serial.test.mjs is separate). Contents, workloads, assertions and bounds are byte-identical; runner suffix policy now serializes it. Item (3) scheduling action implemented and clean verification passes 643 parallel +22 serial tests and 22 proofs (.build/fx001-task3-fix-verify-serial-scheduling.log; docs/progress.md). Items (1) and (2) remain open. Update 2026-10-12 (FX001 Task 12): item (1) is half closed. test/poly-parse.mjs and test/fx-oracle-parse.mjs now accept empty argument lists (FX001 Task 2), and u() runs through the oracle in test/fx-block-programs.mjs; but no oracle row compares an application of a zero-argument call’s result such as make()(x) (test/fn-syntax.test.mjs checks only its shape). Next: add that oracle row, then close (1). Item (3) was done (serial suite). Item (2) remains open.
T006 Open Optional differential over the full 25 MB / 2,486-source harvested corpus made no progress report for several minutes and was interrupted (status 130, .build/waxwing-differential-all-interrupted.log). The iterative structural digest revisits shared object trees; that is a suspected cost, not a measured root cause. Next: add per-source progress/phase profiling in an isolated probe, identify the slow source, and test any memoized semantic subtree hashing against independent sharing-equivalence cases before changing the maintained tool. A bounded run (20,000 source characters, 2,433 harvested sources; 53 stress sources excluded only from this optional comparison) completed with zero differences; full stress tests were run separately with their failures preserved. Scope .build/waxwing-differential-scope.json.
T007 Open Fixed timing bounds on the 2026-10-09 cloud container (4 cores, Linux, Node 22.22, Go 1.26.4) are exceeded by unchanged code. Pre-Task-6 baseline 9a6befd built in a separate worktree, run serially at load average about 1.7: every fn-linear-timing.serial.test.mjs 20,000-parameter Specialize case failed (e.g. value 2,098 ms vs 1,200; partialFirst 1,918 vs 930), fn-scale.serial.test.mjs 5,000-parameter value go build 22.5 s vs 10 s and generics phases 2,464 ms vs 1,650. FX001 Task 6 at the same minute: 2,219 / 2,049 ms, 20.6 s, 2,416 ms (within about 5% of baseline). In the parallel phase, large-source five-thousand-arm match took 5.82 s vs 5 s on the pre-change baseline (T004 recurrence). Evidence .build/fx001-task6-baseline.log, .build/fx001-task6-verify.log. Later the same day (Tasks 7-8 verifies, identical set): fn-linear-timing pipe also exceeds (898 ms vs about 750), and fx-block.serial 20,000 let items sits at its 1,500 ms bound (pre-Task-7 build 1,533-1,574 ms vs Task 7 1,386-1,505 ms, alternating). Bounds were calibrated on the user’s laptop; no bound changed. Next: decide with the user whether bounds become ratios to an in-process calibration workload (T003’s option) or cloud verification records these as environment exceptions; until then cloud verifies report these failures explicitly. FX001 Task 10 (2026-10-10, .build/fx001-task10-fix-serial.log): the same set plus fn-scale a 1,000-long written arrow type builds within 10 s (also in Task 9’s serial log) and a chain of 1,000 partial applications builds within 10 s (phases 253 ms in verify, 310-341 ms in three runs alone; compiler, scripts and the test unchanged since 89de11c). No bound changed. INF001 documentation verification (2026-10-11, local macOS, Node 26.6.0): unchanged compiler partialFirst at 20,000 parameters took 973.142 ms against 930 ms; verify exit 1, 958/958 parallel and 28/29 serial tests, regression phase not reached. Evidence .build/inf001-definition-verify.log. No rerun; next action: profile this case in isolation and during verification with CPU/load measurements, preserving the bound and recording concurrent work before attributing the cause. services-di verify (2026-10-13, local macOS, Node 26.6.0, GOTOOLCHAIN=go1.26.4, other agents’ builds running, load average about 25 falling to 16): the serial phase failed fn-scale.serial.test.mjs a 5,000-parameter function type through generics builds in 10 s (generics phases 3,661 ms vs 1,650 bound; go build 7,753 ms vs 10,000; test 11,632 ms) and fx-block.serial.test.mjs 20,000 let items compile in linear time (1,690 ms vs 1,500 bound); the parallel phase passed 1,301/1,301 and verify exited 1 before the regression proofs. Failing run log: .build/services-di-verify.log, which the rerun then overwrote (the figures above are from that run’s output). The rerun at lower load passed: serial 53/53, 84 regression proofs, exit 0. The change touched no compiler source and no bound. Width80 phase 1 (2026-10-13, branch width80, a layout-only change): scripts/differential.mjs --regenerate ran the whole suite in parallel under scripts/differential-hook.mjs on a 10-core Mac, and its test run exited 1 with 11 timing tests failed (log .build/differential-harvest.jsonl.log, bounds in ms): the 20,000-parameter Specialize rows value 1833/1200, over 2424/1620, lambda 2507/1545, mismatch 746/525, partialFirst 2379/930, partialAll 1070/675, parenthesized 2609/1110, pipe 1450/750; 5,000-parameter generics phases 2565/1650; 20,000 let items 1943/1500; unwinding across 1,000 handle frames 194/138. The plain verify run on the same tree passed (parallel phase 1302/1302, serial phase 53/53, exit 0). No base run under the same harness exists, so the cause is not established; next action: run the same harvest on main 734a8d4 for a comparison, and have –regenerate run the serial files one at a time. Text Task 9 (2026-10-13, local macOS, Node 26.6.0, load average about 3 to 6): scripts/differential.mjs --regenerate against f36744d (the whole suite in parallel under the harvest hook) exited its test run 1 with these timing tests failed (ms, bound): 20,000-parameter Specialize value 1324 (1200), over 1787 (1620), partialFirst 1120 (930), parenthesized 1147 (1110); 5,000-parameter generics phases 2118 (1650); 20,000 let items 1899 (1500); unwinding across 1,000 handle frames 172 (138). The plain verify of the same tree passed (1535/1535 parallel, 57/57 serial, exit 0, .build/text-t9-verify.log); a first verify attempt failed one non-timing test (specialize identity, fixed in the same task). Evidence .build/differential-harvest.jsonl.log, .build/text-t9-diff.log. Text v1 review (2026-10-13): under the parallel harvest the 1 MB compile took 1477 ms against a 423 bound. Text v1 (2026-10-13, user decision): the go build time bounds on the 1 MB literal and the 100,000-escape scale cases were dropped (test/text-scale.serial.test.mjs); go build time there is Go’s fixed startup (~130 ms) plus ~17 and ~4 ms of Waxwing output. Their compile bounds and the 10,000-part go build bound stay. Width80 phase 2 (2026-10-13, branch width80b, layout only): the test run inside scripts/differential.mjs --regenerate exited 1 with 10 timing tests failed (log .build/differential-harvest.jsonl.log, ms against bound): the 20,000-parameter Specialize rows value 1228/1200, over 2176/1620, lambda 2134/1545, mismatch 629/525, partialFirst 1479/930, partialAll 1530/675, parenthesized 2044/1110, pipe 979/750; the 5,000-parameter generics phases 1885/1650; 20,000 let items 2100/1500. Plain verify on the same tree passed (parallel phase 1542/1542, serial phase 57/57). FX020 Task 1 (2026-10-13, commit bb083c5, local, load average 1.86 / 2.60 / 3.46 sampled after the run): the differential’s --regenerate run of node --test test/*.test.mjs under the hook (log .build/fx020-task1-diff.log) printed 11 failed timing tests, test duration in ms (bound where the log lists one): the 20,000-parameter Specialize rows value 1586.85, over 1902.64, lambda 2220.16, mismatch 684.58/525, partialFirst 1533.82, partialAll 1232.67, parenthesized 1910.88, pipe 1154.62; the 5,000-parameter generics phases 6215.19; 20,000 let items 1642.11; unwinding (one fail across 1,000 handle frames) 1065.53. Differential result 0 differences; verify on the same tree passed. Bounds unchanged. FX020 Task 2 (2026-10-13, base fb89cd5, local): the differential’s --regenerate run (log .build/fx020-task2-diff.log) printed 8 failed timing tests, test duration in ms: the 20,000-parameter Specialize rows value 1377.29, over 2008.81, lambda 2293.73, mismatch 617.64, partialFirst 1114.10, parenthesized 1306.72; the 5,000-parameter generics phases 6178.39; 20,000 let items 2330.11. Verify on the same tree passed (exit 0). Bounds unchanged. FX020 Task 2 fix round (log .build/fx020-task2-diff2.log): 10 failed timing tests in the differential’s harvest run, ms: 20,000-parameter Specialize value 1320.54, over 2023.62, lambda 1905.95, mismatch 669.50, partialAll 877.07, partialFirst 1580.46, parenthesized 2425.39, pipe 905.45; 5,000-parameter generics 6401.13; 20,000 let items 1712.71. Verify passed. Bounds unchanged. FX020 Task 2b (2026-10-13, base 19784ed, local, load average 1.86 / 2.41 / 3.13 sampled after the run; log .build/fx020-task2b-diff.log): 11 failed timing tests in the differential’s harvest run, ms: 20,000-parameter Specialize value 1399.56, over 2066.00, lambda 2230.97, mismatch 725.40, partialFirst 1663.12, partialAll 1357.90, parenthesized 2478.07, pipe 987.72; 5,000-parameter generics 6330.47; 20,000 let items 1790.46; unwinding one fail across 1,000 handle frames 1018.19. Verify passed. Bounds unchanged. FX020 Task 3 (2026-10-13, base 5d66dcc, local, load average 2.03 / 2.22 / 2.51 sampled after the run; log .build/fx020-task3-diff.log): 6 failed timing tests in the differential’s harvest run, ms: 20,000-parameter Specialize value 1320.05, over 1937.47, partialFirst 1055.27, parenthesized 1498.85; 5,000-parameter generics 7189.55; 20,000 let items 1584.29. Verify passed (exit 0). Bounds unchanged. FX020 Task 3 review round (log .build/fx020-task3-diff2.log): 10 failed timing tests in the harvest run, ms: 20,000-parameter Specialize value 1347.38, over 2292.15, lambda 2054.02, mismatch 764.73, partialFirst 1897.08, partialAll 1381.96, parenthesized 2081.06, pipe 781.77; 5,000-parameter generics 6396.25; 20,000 let items 1692.10. Verify passed (exit 0). Bounds unchanged. FX020 Task 5a (2026-10-14, base 3c5547f, local, load average 1.89 / 3.15 / 3.87 sampled after the run; log .build/fx020-task5a-diff.log): 12 failed timing tests in the differential’s harvest run, test duration in ms: 20,000-parameter Specialize value 1407.39, over 2098.28, lambda 2239.61, mismatch 706.94, partialFirst 1545.82, partialAll 1025.96, parenthesized 2339.68, pipe 981.51; 5,000-parameter generics 6202.02; 20,000 let items 1536.04; unwinding one fail across 1,000 handle frames 1249.57; unwinding 100,000 caught failures 1051.10. Verify passed (exit 0). Bounds unchanged. FX020 Task 5b (2026-10-14, base c4e3920, local, load average 2.29 / 3.54 / 4.30 sampled after the run; log .build/fx020-task5b-diff.log, harvest log copied to .build/fx020-task5b-harvest.log): 10 failed tests in the differential’s harvest run (1702 tests, 1692 passed), all timing, test duration in ms: 20,000-parameter Specialize value 1386.56, over 1840.85, lambda 1978.18, mismatch 751.62, partialFirst 1396.30, partialAll 1002.31, parenthesized 2435.71, pipe 868.01; 5,000-parameter generics 6365.23; 20,000 let items 1743.87. No non-timing failure. Verify passed (exit 0, 1645/1645 parallel, 57/57 serial). Bounds unchanged. FX020 Task 6 (2026-10-14, base fd83293, local macOS, load average 1.5 to 4.7; logs .build/fx020-task6--{orig,mig}-harvest.log, copies of the harvest runs inside scripts/fx020-paired.mjs --regenerate, the baseline tree's and the migrated tree's whole suites in parallel): every run printed only timing failures, ms. c1 migrated: 20,000-parameter Specialize over 1904.82, lambda 1663.35, mismatch 545.39, partialFirst 1639.20, partialAll 791.23, parenthesized 2554.32, pipe 843.24, acceptance-scenario `go build` 645.82 (bound 405); c1 baseline tree: the same rows plus value 1349.57, five-thousand-parameter value `go build` 14119.23, 1,000 partial applications 2877.52, generics phases 7271.70. c2w migrated: value 1271.74, over 1808.57, lambda 1884.13, partialFirst 1356.14, partialAll 829.69, parenthesized 2153.23, pipe 884.49, 20,000 let items 1601.17, acceptance go build 681.12. c3aw migrated: value 1225.32, over 1929.69, lambda 1684.73, mismatch 673.62, partialFirst 1485.85, partialAll 828.77, parenthesized 1894.24, pipe 1085.14, acceptance go build 865.43. c3bw migrated: value 1313.52, over 1772.78, lambda 1686.22, mismatch 589.71, partialFirst 1284.30, parenthesized 2307.41, pipe 890.79, 20,000 let items 1626.88, acceptance go build 494.51. c3cw migrated: over 1787.90, lambda 1636.14, partialFirst 1632.40, partialAll 860.75, parenthesized 2285.13, pipe 807.31, 20,000 let items 1707.37, unwinding one fail across 1,000 handle frames 1463.52, acceptance go build 786.83. The baseline tree failed the same rows in every run. Each plain verify that ran the whole suite passed (exit 0). Bounds unchanged. FX020 Task 7 (2026-10-14, base ee455f6, local macOS, log .build/fx020-task7-diff.log): 10 failed timing tests in the differential's harvest run, test duration in ms: 20,000-parameter Specialize value 1488.78, over 2298.68, lambda 2384.75, mismatch 733.62, partialFirst 1945.21, partialAll 1476.74, parenthesized 2393.81, pipe 987.35; 5,000-parameter generics 6766.80; 20,000 let items 1654.68. Verify passed (exit 0, 1626/1626 parallel, 57/57 serial). Bounds unchanged. FX020 final fix (2026-10-14), differential harvest run under scripts/differential-hook.mjs (verify itself passed, exit 0): value 1297 ms, over 2127 ms, lambda 2022 ms, mismatch 684 ms, partialFirst 1547 ms, partialAll 1140 ms, parenthesized 2339 ms and pipe 1090 ms at 20,000 parameters (linear through Specialize); 5,000-parameter function type through generics 7256 ms; 20,000 let items 1725 ms. Log .build/fx020-final-fix-diff.log. R001 (2026-10-14, branch `records`, local macOS), figures from the differential's instrumented harvest run only (`node --test` under scripts/differential-hook.mjs inside `--regenerate`, concurrent with other load); every verify of every task passed with no timing failure. Task 2 (base 356397c): the harvest printed timing failures (20,000-parameter linearity and similar), figures not recorded. Task 3 (.superpowers/sdd/2026-10-14-records-plan/t007-harvest-timing-t3.txt), ms: 20,000-parameter Specialize value 1586.33, over 2159.41, lambda 2282.10, mismatch 844.89, partialFirst 1792.02, partialAll 1521.99, parenthesized 2096.98, pipe 1194.93; 5,000-parameter function type through generics 7399.83; 20,000 let items 1555.37. Task 4 (base 61d55d4), first run, ms: value 1458, over 2223, lambda 1886, mismatch 685, partialFirst 1595, partialAll 1469, parenthesized 5163, pipe 2318; 5,000-parameter value `go build` 14734, generics 7428; 20,000 lets 2152; acceptance `go build` 666; 1,000 forwarding installations 1542; match ladder 1707; 10,000 interpolation parts 9604; also 1,000 partial applications 1833, unwinding 100,000 1335, 200 mark variables 1264, 1 MB literal 1695. Task 4 fix round, ms: value 2610, over 1745, lambda 1800, mismatch 640, partialFirst 1594, partialAll 788, parenthesized 1296; 5,000-parameter value 17456, generics 8316; 20,000 lets 2279; unwinding 100,000 955; acceptance `go build` 823; 1,000 forwarding installations 1301; 200 mark variables 905; 1 MB literal 1938; 10,000 interpolation parts 6884. Task 5 (base b800a18; 17 timing assertions), ms: value 1432, over 2586, lambda 2282, mismatch 572, partialFirst 2364, partialAll 1218, parenthesized 6224, pipe 911; 5,000-parameter value `go build` 15010, generics 7288; 1,000 partial applications 1893; 10,000 interpolation parts 9078; 20,000 let items 1687; 200 mark variables 456 (bound 345); 1,000 forwarding installations 2170; deep, wide match ladder 1749; acceptance-scenario `go build` 694 (bound 405). Bounds unchanged. R001 final fix wave (2026-10-15, local macOS, Node 26.6.0, instrumented harvest run of `scripts/differential.mjs --regenerate` against base 96e28f2; 10 timing assertions), ms: value 1340, over 2025, mismatch 552, partialFirst 1354, parenthesized 1323, pipe 776 (20,000 parameters through Specialize); 5,000-parameter value `go build` 14097, generics 9099; 20,000 let items 2168; 8,000 defers 3671. Also an ad-hoc run of every non-serial test file at once (not verify, same day): test/large-source.test.mjs `a five-thousand-arm integer match compiles` failed once (test time 5,978 ms; its assertion text was not kept) and passed alone (2,725 ms). The wave's first full verify (93b5a13): test/coverage-scale.test.mjs `a 10,000-type dependency chain settles inhabitation` took 6,329 ms under the parallel run, 5,426 ms in the next verify (a0d3c37), 6,200 ms (d0d7f6e), 3,452 ms passing (d0d7f6e again), then 5,666 and 5,145 ms (2e77dc2), with load averages of 26 to 29 on 10 cores and syspolicyd and XprotectService at 70 to 80% CPU while fresh Go binaries were built; one verify of base 96e28f2 in between gave 2,784 ms, and replays of the parallel phase outside verify gave 1,930 to 2,370 ms on this tree and 2,020 to 2,280 ms on the base; alone 1,722 / 1,745 / 1,761 ms on this tree and 1,746 / 1,769 / 1,718 ms on base 96e28f2. Bounds unchanged. R001 Task 14 (2026-10-15, local macOS, 10 cores, Node 26.6.0, Go 1.26.4), base de96a15 against candidate ee6038c built side by side, each test file run alone, interleaved base/candidate, one warm-up pair then 5 measured pairs (7 for coverage-scale), load average 1.8 to 3.0 at each run start (logs .build/evidence/t14-timing-*.log), medians in ms (base / candidate, ratio): test/coverage-scale.test.mjs 10,000-type dependency chain 898 (880-924) / 1,710 (1,658-1,729), 1.90; the three 5,000-field cases 1.00 to 1.02. test/large-source.test.mjs five-thousand-arm integer match 2,513 / 2,523, 1.00; three thousand distinct instantiations 807 (791-885) / 945 (933-999), 1.17; twenty thousand one-constructor types 667 (629-694) / 726 (720-785), 1.09; the other twelve cases 0.86 to 1.05. fn-linear-timing 20,000 parameters (measured figure): value 819 / 850, over 1,270 / 1,285, lambda 981 / 971, mismatch 280 / 297, partialFirst 785 / 736, partialAll 406 / 396, parenthesized 819 / 851, pipe 393 / 417 (0.94 to 1.06). fn-scale 5,000-parameter value phases 1,096 / 1,134, `go build` 4,260 / 4,425; generics phases 1,186 / 1,255, `go build` 2,558 / 2,534; 1,000 partial applications phases 93 / 119; 1,000-long arrow phases 130 / 116. fx-block 20,000 lets 635 / 645. fx-scale acceptance `go build` 252 / 254, the four runtime figures 0.99 to 1.00. fx008-scale 8,000 defers 1,040 / 1,089, 1,000 installations 468 / 480, 1,000 forwarding installations 324 / 338, 200 mark variables 116 / 127 (1.09). match-lift-timing ladder 564 / 577. text-scale 10,000 parts compile 528 / 553, `go build` 843 / 843; 1 MB literal and 100,000 escapes 1.01. r001-scale (no base equivalent, candidate only): compile 248, `go build` 822, test 4,082 and 1,585. Chain bisection (.build/evidence/t14-chain-bisect.log, three runs each alone, load average 3.5 to 5 after the gate): d36fcc4 922-943, 0a09142 (record types and row sorts) 1,312-1,330, bd766c4, a93b899 and c14d4b1 1,295-1,364, 61d55d4 (row sorts alternate both directions) 1,711-1,737, b800a18 1,679-1,690. Final gate on ee6038c (.build/evidence/t14-final-verify-1.log, exit 0): chain 2,495 ms under the parallel run. Bounds unchanged. R001 Task 15 (2026-10-15, local macOS, Node 26.6.0, Go 1.26.4): profiled (node --cpu-prof on the built output/) and fixed (commits 260edaf, 2ef868c, 52e82cb). Root causes: the 10,000-type chain spent about 710 ms per compile in Features.Resolve.Occurrences `declaredIndex` (a scan of every declared type per written type, run twice per program by Sorts); three thousand instantiations rebuilt and scanned the declared-type table per signature (Variables, Occurrences, about 80 ms per compile) and paid Ty's Ord instance building record-row dictionaries per nested comparison (Domain.Type.Core, about 11 ms per compile); twenty thousand types paid the row-sort walks (about 28 ms) and E017's ground tables, which a strict `where` built for every program (about 16 ms). Interleaved file runs (main de96a15, before bf4b8e7, candidate 52e82cb in the worktree; logs .build/evidence/t15-timing-coverage-scale-4.log and t15-timing-large-source-3.log; medians ms, main / before / candidate): 10,000-type chain 931 (913-945) / 1,710 (1,665-1,847) / 945 (933-966); three thousand instantiations 875 (770-922) / 968 (962-1,020) / 879 (867-1,116); twenty thousand types 660 (651-703) / 707 (691-724) / 684 (675-692). Control with the candidate also built in a copy under .build (t15-timing-coverage-scale-5.log, t15-timing-large-source-4.log): chain 911 / 1,683 / copy 932 / worktree 929; instantiations 841 / 1,002 / 881 / 861; twenty thousand types 680 / 704 / 665 / 698; `a wide redundant arm is the one reported` 61.7 / 63.9 / 63.4 / 70.9, the worktree run slower than the same commit in the copy (an environment effect of running from the worktree root, also seen in t15-timing-coverage-scale-*.log). r001-scale (t15-timing-r001-scale-2.log, before / candidate): compile 254 / 256, `go build` 721 / 726. The differential's harvest run (.build/evidence/t15-differential.log, 0 differences) printed 15 timing failures, test ms: 20,000-parameter Specialize value 1,718, over 2,284, lambda 2,167, mismatch 687, partialFirst 1,928, partialAll 1,684, parenthesized 3,939, pipe 2,977; 5,000-parameter value `go build` 15,114, 1,000-long arrow 1,768, generics 8,173; 20,000 let items 2,098; 8,000 defers 3,509; 1 MB literal 1,762; 10,000 interpolation parts 5,402. Bounds unchanged. Internal-error batch (2026-10-15, r001-followups, local macOS, Node 26.6.0, load average about 3-5): the differentials' harvest runs printed timing failures, test ms. ie-a-diff-2.log (13): 20,000-parameter Specialize value 1,793, over 2,620, lambda 2,402, mismatch 766, partialFirst 2,274, partialAll 1,216, parenthesized 6,577, pipe 820; 5,000-parameter value `go build` 15,821, generics 7,456; 20,000 let items 2,325; 1,000 installations 2,159; 1 MB literal 1,448. ie-b-diff-1.log (10): value 1,354, over 1,943, lambda 1,979, mismatch 777, partialFirst 1,725, partialAll 1,335, parenthesized 2,512, pipe 1,041; generics 7,112; 20,000 let items 1,913. ie-b-diff-2.log (13): value 1,498, over 2,146, lambda 2,172, mismatch 674, partialFirst 1,898, partialAll 1,369, parenthesized 2,205, pipe 862; 1,000-long arrow 1,454; generics 7,700; 20,000 let items 2,135; one fail across 1,000 handle frames 836; 8,000 defers 3,857. The full verifies of the same trees passed every bound (ie-a-verify-1.log, ie-b-verify-2.log and later). Bounds unchanged.
E014 Open Text v1 review notes (2026-10-13). (a) A line break in a nested literal inside a block’s hole reports Unterminated interpolation instead of the line-break error: Format.Lex.Origin lines 81 and 109 redirect whenever enclosing is set. Next: redirect only when the enclosing hole is single-line. (e) crash("a\nb") and crash("a\\nb") both report crash: a\nb; the report is not escaped. Evidence: whole-branch review of branch text, docs/progress.md “Text v1”.
E015 Open Text v1 review notes (2026-10-13). (b) Lexer naming and structure: Origin.lineBreak and Chars.lineBreak share a name; Origin mixes position arithmetic, error constructors and Mark; the Dedent mode is threaded through Origin; docs/architecture.md does not describe Lex.Run. Do this when CMT001 next touches the lexer. (f) scripts/differential.mjs is at 250 lines and src/Features/Check/Unify.purs at 245: split each before its next edit.
E016 Open, needs user approval Text v1 review notes (2026-10-13); any new user-facing text needs the user’s approval. (c) type Text = A gives “Expected a capitalized name”, the same pre-existing template as type Int = A; a better text. (d) "a" + "b" would benefit from a “use ++” hint.
E017 Done (R001 Task 2) A printable check misses type variables instantiated with functions (found 2026-10-14 while writing the R001 spec). fn show2(x: a): Text = show(x); fn main(): Text = show2(fn(n: Int) => n); is accepted by the checker and then fails go build with E_TOOL (cannot use waxwingLocal0 (variable of func type waxwingFun0) as string value). The printable rule (functions and handlers are not printable) is applied only to types known when the function is checked; nothing re-checks after specialization. Affects show, Console.print and crash (inferred: same path; verify each). Next: a specialization-time printable check at each print site’s ground type with the existing text Expected a printable value, found <t>; the R001 plan does this first, because records inherit the rule. Fixed: checkSites (src/Features/Specialize/Printable.purs) judges each show, Console.print and crash site at its ground type once specialization ends, with groundPrintable; a function or handler anywhere, also nested in data, is E_TYPE Expected a printable value, found <t> at the site (verified for all three sites by test/r001-printable.test.mjs). Regression row r001-print-ground (scripts/regression-r001.mjs): with the check removed the probe reaches E_TOOL. Differential against 356397c (6,894 harvested sources plus fuzz, match and mutation corpora): the only differences are this task’s eleven new test programs, E_TOOL at go build becoming this E_TYPE; no existing source changes.
E018 Open An unannotated lambda that reads a missing field reports a whole-function mismatch, not the missing-field text (R001 Task 5 review). Evidence (CLI, branch records): fn ap(f: { a: Int } -> Int): Int = f({ a: 1 }); fn main(): Int = ap(fn(x) => x.nope); is E_TYPE Expected { a: Int } -> Int, found { nope: _, ..._ } -> _ at the lambda, where an annotated parameter gives nope is not a field of { a: Int } at nope. Cause: lambdas are inferred first and then unified with the expected type (FN001 design §3), so the access sees a fresh record. Meta tails print as ..._ (existing _ convention). Next: check a lambda against a known expected arrow (propagate parameter types before the body) or classify the failure at the label; any new text needs the user’s approval.
E019 Open, needs user approval A lacks failure prints the meta tail it could not bind (R001 final review I3): fn k(y: { ...r }): { a: Int, ...r } = k(y); fn f(q: { a: Int }): Int = { let u = k(q); 1 }; is E_TYPE Expected { ..._ }, found { a: Int } (test/r001-unify.test.mjs, regression row r001-lacks), which reads as if any record should fit. It is the shape-mismatch template spec §6 allows when no two source occurrences can be named. Next: propose a lacks-specific wording (naming the label the tail may not hold) for the user’s approval, with exact-text tests; see E018 for the related lambda missing-field text.
E020 Open A mismatch can print a record-row argument as the unwritable Holder({}), and {} is not a type (fn f(x: {}): Int = 0; is E_SYNTAX Expected a type), so the printed form cannot be pasted back (R001 Task 13, 2026-10-15, branch records at 046089f; probe log .build/evidence/t13-backlog-probes.log). Evidence: type Holder(...r) = Holder({ x: Int, ...r }); fn f(h: Holder({ y: Bool })): Int = 0; fn main(): Int = f(Holder({ x: 1 })); is E_TYPE Expected Holder({ y: Bool }), found Holder({}) at Holder({ x: 1 }). Printer only; the accepted and rejected programs are right. Next: choose a writable printed form for an empty closed row argument (the user approves it), then fix the printer with exact-text tests.
T008 Done Fixed on fx008-impl (33f9854, e54d869; 2026-10-12; review docs/sdd/2026-10-09-effects-plan/review-t008-result.md, approved). Cause (profiled): not the feed graph — each settling consumption extended the shared target row by one label-free alias hop, so every walk crossed an N-hop chain (~14M boundRow at n=1,000). Fix: Features.Check.Aliases, union-find-style classes of alias hops in Subst; boundAt jumps to the class end. 1,000 installations 7.9 s → 0.5 s, 2,000 35 s → 1.0 s; defers and chain unchanged; differential 0 differences in every phase. Guards: ratio test (2,000/1,000 ≤ 3.2; 4.27 before), rows fx008-install-linear and fx008-alias-jump, property test test/aliases.test.mjs (~500k checks against a naive walk). Precondition documented: a bound meta is never rebound. Follow-up T009.
FX004 Done Task 5 closed these frontend validation gaps: duplicate effects, operations, effect and operation parameters, duplicate function/constructor names, and repeated/nonfinal row tails. Exact diagnostic/span regressions plus isolated restored-defect proofs are in test/fx-handler-check.test.mjs, test/regression.mjs, scripts/regression.mjs and .build/fx-handler-task5-verify5.log; see docs/progress.md and .superpowers/sdd/2026-10-09-effects-plan/task-5-report.md.
FX006 Planned FX008 note 2026-10-12 (design §4.6): any ctl clause makes its handler’s own mark M (may fail), since resumption control can abandon the cleanup; recorded in the effects spec as a future rule. General resume: ctl clauses and resume (reserved since FX001). Scope carried from the FX001 plan (Task 12 Step 3): continuation ownership (CF001 C6) and discard as a block exit reason; multi-shot defer unresolved (spec §3, CF001 C7); no capture across a foreign frame (I001); confining CPS without row-keyed specialization, since rows are erased (spec §4, “Revision of direction decision 1”). Design constraints from the user discussion of 2026-10-10: (1) Decide resumption multiplicity on language terms (semantics, typing, cleanup), not Go convenience (docs/plans/2026-10-08-language-direction.md, “Language before target”); goroutine-backed resume is an implementation option, never the reason for a rule. Current recommendation: resume at most once, enforced statically (OCaml 5 made the same choice; verify before citing). (2) Keep multi-shot revisitable as an additive extension. Multiplicity is declared per operation, one-shot by default (multi-shot spelling illustrative, e.g. ctl many), so a multi-shot operation is a row-visible label. Code without that label keeps its at-most-once guarantee; only code with it in scope needs new rules for defer, resources and state (an R0-style “performs no multi-shot X” restriction, or Koka-style finally/initially). A global switch to multi-shot is excluded: it would accept the same programs but silently break code correct under one-shot (a resource opened, an operation performed, the resource closed twice). (3) Reserve the multi-shot spelling with ctl/resume, so adding it later changes no existing program. (4) The defer rules (spec §2, §3), FX003 local-state semantics and CF001 one-shot reply slots state explicitly that they are defined for operations that resume at most once. (5) Regression: resuming an ordinary ctl operation twice is rejected, with a restored-defect proof. (6) Record for the decision: one-shot covers exceptions, state, generators, iterators, streams, async/await, coroutines and user schedulers; multi-shot adds nondeterminism and search, backtracking, probabilistic handlers and exhaustive test exploration of choice points. Without it these remain expressible through replay handlers (deterministic computations; roughly quadratic on deep search) or explicit List/Stream monads after K001/C001. (7) Concurrency semantics are target-independent: on Go, built-in concurrency (CF001, FX002) gets direct-style blocking from goroutines without continuations, but the language must not presume goroutines; another target (J001) may need continuations or CPS for the same semantics. User-defined schedulers and async libraries need ctl. Next: a design note settling (1) and (2) with the user before FX006 implementation planning. Extended by FX001 Task 12 (2026-10-12): ADR 010 records that row erasure revises direction decision 1, so confining CPS cannot rely on row-keyed specialization; the general-resume milestone must establish a conservative reachability analysis (can this specialized function, or a function value applied in it, reach a ctl operation?) or choose bounded row-aware re-specialization. Block exits today are normal completion, typed abort and recoverable defect; continuation discard (CF001 C6) is a future exit reason. Depends on CF001’s §10 list with FX002 and FX003. Gap found 2026-10-12 (FX001 Task 12 review): ctl/resume are reserved everywhere, but the dedicated E_SYNTAX General control (ctl/resume) is not supported yet appears only in expression position. CLI probes: resume(1) gives it; effect E { ctl op(): Int; }; gives Expected '}'; a handler clause ctl op() => 1 gives Expected an identifier. Next: the same dedicated diagnostic in effect-declaration and handler-clause positions, with tests.
FX002 Planned Concurrency in the effect system. Builds on CF001: first settle CF §10 items 1-8, including R0 for par, B1 and cancellation C1-C4, and apply CF §10 necessary changes 3, 5 and 6. Cancellation becomes a block exit reason (ADR 010: today only normal completion, typed abort and recoverable defect). No implementation authorized; CF001 awaits the user’s review.
FX003 Planned Local state and recording fakes (FX001 supports stateless fakes only; there is no mutable state). First B3: sharability as a transitive property of handler types plus the capture check (CF §10.3). Then design encapsulation, escape safety and backend representation, comparing Koka-style scoped var with parameterized handlers. Acceptance: recording database and logger doubles observable in tests. Depends on CF001’s §10 list.
FX005 Planned ctx elimination and cached lookup. Lookup is linear in installed handlers; Task 1’s hand-written Go took 11.8 s per 10^6 lookups at depth 10,000, and Task 11 measured Waxwing’s lookup at depth 100 at about 1.4x Task 1’s hand-written lookup at depth 100 (79.8 ms), not the depth-10,000 figure (docs/progress.md, “FX001 Task 11”). Caches are per task or immutable at publication, never written into a node reachable from another goroutine (CF §10.4). Settle before FX002. Next: a design note, then measurements against Task 11’s scale tests.
FX007 Open Re-scoped 2026-10-12 by FX008 (design §4.5, D5): the relaxation becomes nofail ...e, abort-free written row variables checked at instantiation; deferred until after FX008 merges. Relax FX001’s strict defer must not fail rule (spec §2, Task 8 review ruling 2026-10-09) with an internal lacks-Fail constraint on row variables, so cleanup may call effect-polymorphic callbacks (bracket idiom) and local lambdas also called outside the defer. Today both are rejected with E_EFFECT defer must not fail, but it may perform any effect of <r>. Requires constraint-carrying schemes and diagnostics; relaxation accepts strictly more programs. Next: a design note comparing caller-side constraint checking at instantiation with qualified row types; decide with the user. FX001 Task 11 evidence (2026-10-12): a deferred callback of an ambient or with Console row is rejected today (docs/findings.md, “FX001 Task 11: callback rows”), which the scenario baseline works around with with pure. ADR 010 (docs/adr/010-effects.md) records the strict rule, its accepted costs and FX008’s refinement; docs/language.md (Effects) states them for users.
FX008 Done (merged into main and pushed 2026-10-12) Hole in FX001’s strict defer must not fail rule (spec §3: “A deferred expression cannot end in a typed abort”), found by the Task 10 reference interpreter (2026-10-10) and confirmed with checked() by the Task 10 reviewer. The compiler accepts type E = E; effect Log { fn log(n: Int): Unit; }; fn main(): Unit with Console = handle with handler Log { log(n) => fail(E) } { defer log(1); print(2) } { fail(error: E) => print(9) }; (pinned in test/fx-oracle.test.mjs, a cleanup ending in a typed abort is an interpreter error): the deferred log(1) performs only Log, so the deferred row has no Fail label, but the handler clause it reaches fails with E; at run time the cleanup ends in a typed abort and Go exits 1 with fail(E): E, while the interpreter reports “typed abort escaped cleanup”. Cause: the rule inspects the deferred expression’s row, not what the handlers it reaches may raise; a clause runs in its with’s outer context, where Fail(E) is handled, so the clause type-checks. Workaround in place until FX008 Task 11: test/fx-gen-expr.mjs generated only fail-free random clauses; lifted on fx008-impl at 0a571d4. Next: decide the rule with the user — e.g. a handler clause reachable from a deferred operation must be fail-free (reject defer op when the innermost handler for op’s label was built with a row containing Fail not handled inside the clause), or treat handler construction under a defer-reachable scope as performing the clause’s labels — then add the rejection with a regression row and a differential generator case. Related: FX007 (the opposite direction, relaxing strictness). Scope widened 2026-10-10 (controller analysis): the hole is not limited to lexical handlers — any operation performed in cleanup may reach a failing clause of a handler installed by a caller in another function (fn f(): Unit with Log = { defer log(1); () } called under handler Log { log(n) => fail(E) }), so no check of the deferred expression’s own row can be sound. User decision 2026-10-10: do not weaken the language’s structure or design — rejected: delivering or converting a cleanup-raised abort at run time. User chose option 3 (2026-10-10): type-level tracking — the type system carries that performing L under a handler whose clauses may fail may abort, through rows and across calls, so the strict-defer check becomes sound without restricting effectful cleanup. Next: a reviewed design note (spec §2/§3 change), then implementation, before Tasks 11-12; afterwards lift Task 10’s generator restriction (fail-free random clauses). Design note 2026-10-10 (branch fx008): docs/plans/2026-10-10-abort-tracking-design.md recommends approach A, a nofail mark on label occurrences (handler installation gives the body’s label the handler’s mark; signatures write nofail L to rely on a handler not failing; clause own rows; per-declaration two-point propagation; marks erased, Go unchanged), with alternatives B (totality per effect) and C (inferred marks) and decisions D1-D5. Opus review (docs/sdd/2026-10-09-effects-plan/review-fx008-design-result.md): Accept with fixes, two Critical (solve must be seeded from every N mark; clause own rows need late consumption keeping Fail, to a fixpoint), applied in revision 1 with limitations L4 (equality merges through locals) and L5 (no mark polymorphism) and decisions D0-D7; re-review and round 3 (rereview-fx008-design-result.md) found a non-terminating late-consumption fixpoint (N1), fixed in revisions 2-3 by a shared-suffix rule and a copy map local to that step. The user shared outside advice (treated as advice only; note §12; D0-D7 open). Revision 4 rewrote the late-consumption step; a focused termination review (review-fx008-termination-result.md) refuted it (P3, P4, a duplicate-label hang, T1 clause rigid tails dropped) and gave a corrected procedure, adopted as revision 5 (per-key cycle weights, exit on any label-count change, clause-tail pass; fallback disqualified by four examples). It also found FX009 and FX010 in today’s checker. User decisions 2026-10-10 (note §11a): D0-D5 as recommended; D6 mark variables (nofail(k) L) and D7 directional checking included in FX008. Revision 6 (directional consumption with copies, per-installation frame marks, mark variables, path check) reviewed (review-fx008-rev6-result.md: no Critical; I1 consuming R lost inference). Revision 7 keeps R as today’s inference row (marks ignored) and adds an internal clause row C for marks (reviewed: review-fx008-rev7-result.md); revision 8 adds the user-chosen one-way hook keeping R in step with clause rows, and revision 9 applies its review (review-fx008-rev8-hook-result.md: F1 R≡R on shared clause tails, F2 cycle test), F1/F2 unreviewed; a probe gate is the first plan task. Next: the user’s written-spec approval, then a plan and test-first implementation; not authorized. Baseline rm -rf output && npm run verify on fx008 (2026-10-10, .build/fx008-baseline-verify.log): exit 0, 934/934 parallel, 29/29 serial, 33/33 regression proofs. Revision 10 (focused F1/F2 review: corrections T, F1′, S; re-check review-fx008-rev10-result.md found no substantive issue) approved by the user 2026-10-10, conditional on that review. Next: implementation plan (writing-plans), first task the probe gate (.build/fx008-probes/), then test-first implementation; FX001 Tasks 11-12 after. Merged record (2026-10-10): branch claude/optimistic-dijkstra-6i21jy recorded the same hole independently as an unverified hypothesis, from the draft article outline. Its row is folded in here and removed. Its accurate points: no check confined to the defer or its function can establish the rule, because any caller may install a failing handler; and the Task 8 review (review-task8-result.md: “Handler clauses and lambdas reach δ only through metas or rigid tails”) missed this case, which uses neither. Established on the current compiler (probes in .build/fx008-probes/facts/): the lexical form (the pinned program) and the cross-function form are both accepted. Without a pending abort, the cleanup’s fail(E) is not caught by the enclosing handle for E: the program exits 1 with stderr fail(E): E (the lexical form prints 2 first). With a pending abort fail(F) in the same block, the pending abort loses its recoverability and never reaches its handle: exit 1, stderr fail(F): F then cleanup failed: fail(E): E. Repair status: design approved (revision 10). Implementation plan docs/plans/2026-10-10-abort-tracking-plan.md approved by the user 2026-10-10. Setup done (worktree branch fx008-impl). Tasks 1-7 complete and reviewed on fx008-impl (through 66f1eca): probe gate; mark representation; nofail syntax; directional consumption; settling loop; tail watches and the restricted registry; clause own rows and the one-way hook (gain1 now accepted); installations with frame marks; the mark check (the pinned hole program is now rejected); notes for mark rejections; failing random clauses and the one-way soundness differential, 120 escapes over eight shapes all rejected; spec, CF001 and FX001 plan amendments (9a62014; 28 FX008 regression rows); T008 fixed (e54d869); whole-branch review (review-fx008-branch-result.md: ready after fixes, no Critical; 26 new adversarial programs sound) and its fixes (6213f56: base notes restored for programs without defer, unreachable paths are E_INTERNAL guards, nofail(k) tests). Remaining: the merge, with the user’s approval. Superseded remaining list: then the T008 fix (user decision 2026-10-11), then the whole-branch review. Merged (2026-10-12, with the user’s approval): fx008-impl into fx008 (dd1efd6), main’s INF001 docs merged (1331ae4), main fast-forwarded and pushed. The shipped rule is recorded in ADR 010 and docs/language.md (Effects). Open follow-ups: FX010, FX013-FX016 (limitations L1 with P5, L4, L6 and the handler-through-a-generic-function case), T009, T010, D002, E013.
FX009 Done Soundness defect in the shipped FX001 checker (found 2026-10-10 by the FX008 termination review): a Fail(a) with a rigid payload in a parameter row was accepted, and the deferred-key pairs meeting it were dropped, so type E = E; fn g(h: Int -> Unit with Fail(a)): Unit = h(1); fn main(): Unit with Console = g(fn(n: Int) => fail(E)); panicked at run time with no handler for Fail. User decisions 2026-10-10: a written Fail payload without a family key (type variable, function type, Handler type) is E_TYPE Fail needs a concrete error family at the written label, with a hint naming ...e, a concrete family or Result, in signatures (Resolve/Row.purs) and handle clauses (Resolve/HandlerExpression.purs, which gave E_INTERNAL before); Fail(Error(a)) and Fail(Unit) stay allowed; leftover set-aside pairs are rejected in the final settle (a minimal guard; about 50 probes reach no program that needs it). Branch fx009: c445bac, 33a3a62, f6cb6d0 (Sonnet implementer, Opus review and two re-reviews: docs/sdd/2026-10-09-effects-plan/review-fx009-result.md, rereview-fx009-result.md). Tests test/fx-fail-pairs.test.mjs (25, the rejections seen failing first); regression rows signature-fail, keyless-fail, clause-fail, nested-fail retargeted; verify exit 0, 958/958 parallel, 29/29 serial, 36 proofs.
FX010 Open Update 2026-10-11 (FX008 Task 7 diagnosis): the original program is now accepted on fx008-impl (soundly; the hook removed the R ≡ c link that formed the infinite row), but the defect remains: let z = fn(f) => { put(f); f(1) }; in the same setting still gives E_INTERNAL on both main and fx008-impl; use it as the reproducer. Internal error in the shipped FX001 checker (2026-10-10, FX008 termination review; reproduced): .build/fx008-probes/argbind-internal.wxw (a handler built in a lambda whose clause calls a parameter f, put(f) on an effect Cell(a), then put(fn(n: Int) => fail(E)) in a sibling lambda) gives E_INTERNAL Effect row consumption failed at with h { log(1) } instead of a user diagnostic; the reviewer attributes it to an infinite-row failure reported as internal. Next: minimize, identify the failing consumption, report it as the side-condition or occurs diagnostic, with a test seen failing first.
FX012 Open FX008 note 2026-10-12: settling round 1 still runs the unkeyed check (unchanged by FX008’s settle-order change); the fix remains to run it only in the final settle. Precision gap found by the FX009 re-review (2026-10-10, rereview-fx009-result.md Round 2): firstUnkeyed (Features.Check.Failure) also runs in the first settleKeys, before deferred rows are settled, so handle fail(x) { … } where x gets its type E only through a deferred put is rejected, on base and with FX009 alike; spec §2 says the key is decided after the body’s other constraints are solved. Next: a failing test with that program, then run the unkeyed check only in the final settle (as FX009 did for leftover pairs); coordinate with FX008’s settle-order change (§3.5).
FX011 Open Pre-existing defect in the FX001 checker, found by the FX008 revision 6 review (2026-10-10, scratchpad rev6/con.wxw): handler Console { … } gives E_INTERNAL Invalid handler effect instead of a user diagnostic (spec §1: user Console handlers wait for D001). Related: fn f(h: Handler(Console with pure)): Unit with Console = with h { print(1) } is accepted (con2.wxw), though no such value can be built. The FX009 implementer (2026-10-10) also saw handler Fail(E) {} give the same E_INTERNAL Invalid handler effect (not investigated). Next: a test-first E_HANDLER diagnostic (e.g. Console is handled only by the runtime) for both, and decide with the user whether Handler(Console …) types are rejected.
CF001 Proposed Concurrency-foundations design investigation, docs/plans/2026-10-09-concurrency-foundations-design.md (three Opus review rounds, accepted with fixes 2026-10-09; not authorized for implementation). Recommends a small fork-join subset (par let) with Fail-or-pure children, leftmost failure selection, child root wrappers relaying aborts at the join, bounded task budget, eager propagated discard, R0 ‘performs no X’ rule, and required runtime changes 5-6 (ctx mode + polls for par; discard panic kind vs cleanup). Settle-before-concurrency list includes FX005’s cached lookup keeping context nodes immutable. Next: user review. FX002, FX003 and FX006 depend on its §10 list (ADR 010 records the decisions it builds on; FX001 Task 12, 2026-10-12): settle items 1-8 before concurrency, and apply the §10 necessary changes 3, 5 and 6.
BK001 Proposed Go backend maintainability, docs/plans/2026-10-10-backend-syntax-proposal.md (not authorized; pending user review). Tier 1: Go-free plan records built totally, Signature/Callee so calls take ctx from the callee’s declaration, Go-correct string quoting, String helpers with explicit Lines/Inline layouts, raw only for fixed runtime templates. Tier 2 (opaque Expr/Stmt/Type/Name/Decl newtypes) optional, decided after step 2. Evidence: 607 concatenations in 20 modules; D1 frame order (4b80bd1); D2 key-0 defaults (4b80bd1; still Stage.purs:194 numberOf, Entry.purs:106-107 lookupOr); D3 unused arrow type (3a3d197); D7 unescaped names (Task 8 review; latent); D8 ctx agreement by convention (forward risk; no mismatch recorded). No library (structure gate allowlist); no gofmt at build time. Steps 0, 1, 6 before FX005/FX002 lowering; 2-5, 7 later; each byte-identical against bootstrap/*.go and an effect-mode emit corpus; 12 Format.Go regression needles re-targeted and re-proven. Accept: verify green, regression proofs 100%, no Format.Go module over 250 lines.
D002 Open Console frames generate no frame edges (src/Features/Check/Frames.purs guard, FX008 Task 9 review M1, 2026-10-11): unreachable today because Console has no user handler (FX011), but it would drop real outer-edge demands. Next: when D001 allows handling Console, decide whether Console frames generate edges, and remove or keep the guard with a test.
E013 Open FX008 Task 10 review (2026-10-12): a mark rejection whose fail-capable Plain comes from a type rather than a signature (a constructor field such as Box(Handler(Log with pure)), or a lambda argument’s parameter type) is rejected correctly but with zero notes. Next: add an origin note naming the type (its span) for paths starting at such a Plain, with a test pinning the text after the user approves it.
FX013 Open FX008 limitation L1, with P5 (2026-10-12; effects spec §2): marks nested inside data, constructor fields, operation signatures or a callback’s parameters are not opened (opening applies to function signatures only), so a handler stored in a field keeps its written mark; conservative (rejects more, never unsound). Next: decide with the user whether to open marks in fields once a program needs it.
FX014 Open FX008 limitation L4 (2026-10-12; pinned by probes rev6/l4b and rev7/merge2, both rejected): labels merged through one handler value’s R, or a residual merge through a shared tail, share a mark, so a fail-free use can be rejected because another use may fail. Conservative. Next: a precision design only if a real program hits it.
FX015 Open FX008 limitation L6 (2026-10-12): mark variables (nofail(k)) are not allowed in type or effect declarations; refused at resolution. Next: revisit with mark polymorphism in data if needed.
FX016 Open FX008 limitation from the Task 9 C1 ruling (user decision 2026-10-11/12): a handler passed through a generic function and installed from its result gives an M (may-fail) frame when the context’s rest is M or rigid, because the result’s clause row C gets a fresh tail unless the variable occurs in a parameter or the own row. Conservative. Next: revisit if a fail-free generic handler combinator is needed.
FX017 Done (diagnostic); subsumption stays with FX007 Pre-existing since FX001 Tasks 1-10, found by the FX001 whole-branch review (2026-10-12, probes in the scratchpad; outputs reproduced on fx001-finish cda990f): a callback parameter’s written row is not tracked against the caller’s row. By spec §2 a written with L means L plus the ambient row, so unifying it with a caller row that has another label fails the side condition (both rows end in the shared ambient tail), and spec §6 gives that its own message (<R1> and <R2> cannot be made equal: both end in ...<r>); the checker reports the wrong kind of headline instead. Probes: (1) effect L { fn l(): Int; }; effect M { fn m(): Int; }; fn a(q: Int -> Int with L, x: Int): Int with L + M = q(x); gives E_EFFECT a performs M, which its signature does not allow although the body never performs M. (2) fn a(q: Int -> Int with M, x: Int): Int with L + M = q(x) + l(); gives a performs L, which its signature does not allow. (3) Named tails, fn a(q: Int -> Int with L + ...e, x: Int): Int with M + L + ...e = q(x); gives a performs M, which its signature does not allow. Control: with L against with L is accepted. Effect: a written callback row must equal the caller’s row label for label, and the diagnostic contradicts the signature. Workaround (docs/language.md, Limits): write the callback’s row as the caller’s full row. Next: report it as the side condition, or as a dedicated message naming the callback, with exact-text tests; decide with FX007 whether the callback row should instead be subsumed by the caller’s (findings, “FX001 Task 11: callback rows” item 1). Approved text (user, 2026-10-12; dedicated message chosen over reusing the §6 shared-tail text), E_EFFECT at the call span, only when the leftover label belongs to the caller’s row and the called row lacks it (a callback performing more than the caller allows keeps <f> performs <L>, which its signature does not allow): headline <q> must allow every effect of <f> (<R>), but its row is <S>, with this function value in place of <q> when the callee is not a named parameter or local; and print labels only (`L + M`, `L`). Notes: `'s row is written here` at the callee's written row, `'s row is written here` at the caller's `with`. Hint: `write 's row as `with `; a callback's row must equal its caller's (FX017)`. A callee row with no labels (probe p4, `q: Int -> Int` in `with L`) reads `but its row has no labels` (user, 2026-10-12; the ambient row is not `pure`). Probe p4 also shows the current note `the signature of a does not allow L` pointing at `with L` itself. Probes: scratchpad fx017/p1-p6 (p5 and p6 are the genuine case and keep today's text). Done 2026-10-12 on branch fx017 (diagnostic only; the accepted and rejected program sets are unchanged, checked by scripts/differential.mjs): an application of a function value whose row is closed by a rigid tail and lacks a label of the enclosing function's own signature row, outside any `with` or `handle`, is reported with the approved text above (probes p1-p4), through `Features.Check.Callback`; the genuine case (p5, p6) and a call inside a `with` or `handle` (the current row then holds installed labels too) keep ` performs , which its signature does not allow`. The callee is named when it is a parameter of the enclosing function (LocalIds below the parameter count); a `let`-bound or lambda-bound callee prints `this function value` because the resolved syntax keeps no local's name. The callee note points at the parameter's `with`, or, for an unannotated arrow, at the parameter's name (the resolved syntax keeps no span for its type), reading `q's row is not written`; the caller note at the `with` of the signature. The Hinted form carries the hint after `; `. Regression row `fx-callback-row`, tests test/fx017-callback-row.test.mjs. Open question, not decided here: whether a callback's written row should instead be subsumed by its caller's (FX007, findings "FX001 Task 11: callback rows" item 1), and a callback passed as an argument to a higher-order function (`This function performs M, which its signature does not allow` at the argument, e4 in the report) keeps its old text.
FX018 Done (merged into main and pushed 2026-10-13 at 4445a83) Two callback-row mismatches that FX017 left on the old text (found while implementing FX017, 2026-10-12). (a) Inside with/handle the current row also holds installed labels, so a callback-row mismatch still reads <f> performs <L>, which its signature does not allow. Probe: effect L { fn l(): Int; }; effect M { fn m(): Int; }; fn a(q: Int -> Int with L, x: Int): Int with L + M = { let h = handler M { m() => 1 }; with h { q(x) } }; fn main(): Unit with Console = print(1); gives E_EFFECT a performs M, which its signature does not allow at q(x) with the note the signature of a does not allow M at with L + M. (b) Passing a callback whose row lacks a label of the expected parameter row: effect L { fn l(): Int; }; effect M { fn m(): Int; }; fn take(f: Int -> Int with L + M): Int with L + M = f(1); fn a(q: Int -> Int with L): Int with L + M = take(q); fn main(): Unit with Console = print(1); gives E_EFFECT This function performs M, which its signature does not allow at q, with no notes. Next, for both: dedicated text in the style of FX017, which needs the user’s approval of the wording first; subsumption itself stays with FX007. Wording chosen by the controller at the user’s request (2026-10-13, ‘select wording and fix’), in FX017’s style. Probes (2026-10-13): inside with h (M handled), q: with M + L + M is accepted and q: with L + M still fails, so the row q must equal is the row at the call (installed labels first, then the caller’s row); take(q) is accepted once q’s row is the parameter’s (with L + M). (a) E_EFFECT at the call: <q> must allow every effect at this call (<R>), but its row is <S> (but its row has no labels when S has none), R = the row at the call, installed labels first, labels only; notes: <q>'s row is written here / <q>'s row is not written (as FX017), <L> is handled here at each enclosing with/handle that installs a label of R, <f>'s row is written here; hint write <q>'s row as `with <R>`; a callback's row must equal the row where it is called (FX018) (R as written, named tail kept). (b) E_EFFECT at the argument: <q> must allow every effect of <g>'s parameter <p> (<R>), but its row is <S> (of the parameter it is passed to when the callee or parameter has no name), R = the parameter’s row, labels only; notes: <q>'s row is written here / is not written, <p>'s row is written here at the parameter’s with; hint write <q>'s row as `with <R>`; a function passed as an argument must have its parameter's row (FX018). Both: this function value and an anonymous hint (no ‘write …’ part: a function value's row must equal the row where it is called (FX018) / a function value passed as an argument must have its parameter's row (FX018)) under FX017’s rules (named only for single-stage parameters; payload rows holding a row; nested payload leftovers keep the old text). Done 2026-10-13 on branch fx018 (diagnostic only; differential against 0a23784 shows only the new-text programs): see docs/progress.md “FX018”. Rulings from checking: the write … hint of (b) is printed only when a simulation shows it fixes the call (a caller wider than the callee, or a constructor’s closed row, give the anonymous hint); inside a lambda body (a) keeps the old text. Known limits (review 2026-10-13): (M2) a hint is checked at its own call only (an (a) or FX017 hint, inside its own function only), so applying it can expose a truthful error elsewhere in the program, including a (b) error in a caller (fn b(p: Int -> Int with L): Int with L = a(p, 1) after a’s hint); (M5) when the argument and the parameter share a name, two f's row is written here notes appear (one at each); (M1) the global note cap drops is handled here notes past four; an (a) hint is written only for a callback read once in the function.
FX019 Open Found by the services-di rework (2026-10-13). A function that takes handler parameters and installs them cannot accept a logger whose clauses perform Console when the cleanup path needs nofail Log. With fn app(database: Handler(Database with pure), clock: Handler(Clock with pure), logger: Handler(nofail Log with Console)): Unit with Console = with database { with clock { with logger { … } } } the checker gives E_EFFECT app performs Clock, which its signature does not allow (notes nofail Log is handled here, the signature of app does not allow Clock); with the logger installed outermost it gives Expected nofail Log, found Log with the note this handler's clauses may perform any effect of ...; nofail Console on the type or the signature, and ...e on both, give the same; a closed type against app’s Console + ...e gives Console + ... and Console + ...e cannot be made equal: both end in .... Cause: the written clause row Console of a parameter’s handler type is closed, so installing it unifies with the rigid ambient tail of the installing function and the frame is may-fail (related to FX016). Workaround (the example): construct the handler with the named tail, Handler(nofail Log with Console + ...e), and install it in main. Next: a design question, whether a written handler-type row in a parameter should be closed or carry its own tail.
CMT001 Design needed Comments (user, 2026-10-13): // line comments for code, and doc comments, Rust-style /// attached to the following declaration (and possibly //! for module docs). Decide: block comments and whether they nest; how comments interact with text literals, # delimiters, ${…} holes and """ blocks (a // inside a literal is text); where a doc comment may attach (declarations, constructors, effect operations, record fields) and what a misplaced one is (error or warning); and that the lexer keeps comments as trivia with spans so the formatter (FMT001) can preserve them and doc tooling (DOC001) can read them. Prerequisite for FMT001 and DOC001. The Text lexer rework (2026-10-13 plan) leaves room for comments but does not add them.
G005 Design needed Uniform trailing commas (user, 2026-10-13: trailing commas, not Haskell-style leading commas). Today a trailing comma is accepted only after match arms; call arguments, parameters, type parameters, constructor fields and patterns reject it (add(1, 2,) is E_SYNTAX Expected an expression). Accept an optional trailing comma in every comma-separated list, including future records and handler clauses, with tests and unchanged meaning. Prerequisite for FMT001.
G006 Open A source near the JS stack limit compiles on one run and overflows on another in the same process (FX020 Task 5a paired trial, 2026-10-14, local macOS, Node 26.6.0): the baseline compiler threw RangeError: Maximum call stack size exceeded on two nested-handler-type sources from test/fx008-nesting.mjs (sha1 428a4826de4ecbda5f692e5dd2d9c0eb070de58b and b70055a10a31970b4859bb39507c998bedd02c02, Handler(Log with S(…)) near the G004 depth), while the candidate, the same compiler code loaded from a copy, emitted Go for both (go sha1 d2609d72… and 1fcf4b1f…). Likely cold versus optimised V8 frames; not established. The paired differential reports such a one-sided crash as an outcome difference and never retries it. Related: G004 (overflow instead of E_NESTING), G002 (parser stack capacity), E006 (Unify recursion). Next: reproduce in isolation by compiling each source cold and after warm-up in one process, record the stack depth reached, and fix by making the depth guard fire before the JS limit (G004) rather than by retrying. FX020 Task 6 (chunk 2 wrap, 2026-10-14, local macOS): the paired run hit it the other way round. The candidate threw RangeError on the same two sources (migrated text identical to the original) while the baseline emitted Go (log .build/fx020-task6-c2w-paired-stackflake.log). Isolated evidence: in a fresh process each source overflows on the first two compile calls and succeeds on the next ten, for the baseline tree and the candidate tree alike (twelve calls each, RE RE ok ok ok ok ok ok ok ok ok ok); node --stack-size=3000 makes the first call succeed for both. So the difference is V8 warm-up, not behaviour. Later Task 6 paired runs used node --stack-size=3000 scripts/fx020-paired.mjs; the c2w verdict in .build/fx020-task6-c2w-paired.log is from re-comparing the same harvest with that flag (no regeneration). The paired tools these runs used (scripts/fx020-paired.mjs and its siblings) were removed in FX020 Task 7 as one-off (git keeps them); the owner of the flake remains this row. R001 final fix wave (2026-10-15, local macOS, Node 26.6.0): the paired differential against 96e28f2 reported the candidate’s one-sided RangeError on the two longest Handler(Log with S(…)) sources (2,624 and 2,630 characters; reported in phase lex, the first phase of a crashed outcome). Isolated, both trees overflow in parse itself after warm-up: in one process repeating the six such sources, the first throw is at repetition 28 with parse alone and 26 with parse through specialize, for the base and the candidate alike (the wave does not touch the parser). A rerun of the harvested corpus with node --stack-size=3000 showed no such difference.
FMT001 Design needed Canonical formatter, gofmt-style: one style, no options (user, 2026-10-13). Layout, Prettier-style: an 80-column target; a construct that fits stays on one line; otherwise every element goes on its own line with a trailing comma and the closer on its own line; any pipe chain of two or more |> puts one stage per line; match arms, handler clauses and block items always one per line; two-space indentation. Text in ${…} holes, """ blocks and raw literals is never reflowed. Must preserve comments (CMT001) and be idempotent (formatting twice changes nothing), with a property test over generated programs that formatting preserves meaning (same checked IR). Depends on G005 and CMT001; editor integration with IDE001. Examples and docs follow it once it exists.
FX020 Done (merged into main 2026-10-14) Effect operation namespaces (user, 2026-10-13): every operation is qualified (Database.query, Console.print, Fail.fail); a function’s own row aliases and imports (with Database as D + Log { log, info as note }); lexical scoping (binders, parameters, imports, globals; no fallback), no row-dependent resolution; Console and Fail are built-in declarations; fail is no longer a keyword. Fixes two silent misbindings found 2026-10-13 (two effects declaring log; an operation and a function both named log). Design docs/plans/2026-10-13-effect-namespaces-design.md, ADR docs/adr/012-effect-namespaces.md, language docs/language.md (Effects, “Operation names”), evidence docs/progress.md (“FX020”), ledger .superpowers/sdd/2026-10-13-effect-namespaces-plan/progress.md. Regression rows fx020-first-declaration, fx020-function-wins, fx020-row-dependent. Approved by the user 2026-10-14: (1) the multi-candidate unbound-name hint wording, pinned in test/fx020-unbound.test.mjs by ruling R18, for a program declaring Audit before Log: Unbound function log; use Audit.log or Log.log, or import one with Audit { log } or Log { log } (three or more: A.log, B.log or C.log and A { log }, B { log } or C { log }); (2) the merge, fast-forward as c374b40. The ledger .superpowers/sdd/… is gone; rulings are summarised in docs/progress.md “FX020” and ADR 012. The editor grammar follows FX020 (housekeeping branch). Requirement kept on INF001: a way to alias or import without writing the whole row. Open follow-ups: FX021 (unreachable duplicate label), FX022 (hint text for a parameterised effect). Temporary gap: print and show stay compiler-provided until C001.
FX021 Open An import from a duplicated effect label that no perform can reach (e.g. State(Int) { get } + State(Bool) { get as g2 }) silently binds to the first label; consider a diagnostic (new user-facing text needs the user’s wording). Evidence (FX020 Task 3 review, test/fx020-imports-review.test.mjs): fn f(): Bool with State(Int) { get } + State(Bool) { get as g2 } = g2(); is E_TYPE Expected Bool, found Int at g2(); State(Int) { get as outer } + State(Int) { get as inner } both reach the first label (two handlers, inner returning 5, print 10). Next: decide whether duplicate user-effect labels in a row are themselves an error.
FX022 Done on branch fx020 (fix: hint writes effect parameters) The unbound-name hint omits an effect’s arguments, so for Fail it writes import it with Fail { fail }, which is not valid syntax as written (Fail(E) { fail } is; Box { put } likewise for a parameterised user effect). Found at the FX020 Task 7 review. Resolved 2026-10-14 (user approved): the import form writes the parameters, Fail(E) { fail } and Box(t) { put }; the qualified part stays bare. Tests test/fx020-unbound.test.mjs, ADR docs/adr/012-effect-namespaces.md (“The unbound-name hint”), report .superpowers/sdd/2026-10-13-effect-namespaces-plan/fx022-report.md.
FX023 Open The Fail.fail(E) call span stops before the ) (Fail.fail(E), unlike every other qualified call, which runs through its ): the old fail keyword span, kept and pinned at test/fx020-qualified.test.mjs (“a Fail span ends at its payload, as before”). Diagnostics only; Go output is unaffected. Found at the FX020 final review (2026-10-14). Next: decide whether to extend it to the ) (the pins would move).
FX024 Done (2026-10-15, branch records, R001 final fix wave) Cause: Resolve gave a type written as an effect label’s argument in a signature the signature’s ambient row, which is also the tail of that row; a caller with a label the callee lacks binds the tail, and so the payload’s row, to its own whole row, which no longer matches the payload the caller wrote (its ambient alone). Fix: a label’s arguments resolve without an ambient row everywhere (Features.Resolve.TypeRef bare, Resolve.purs signature rows, TypeArguments row arguments), as fields, operations, handler labels and handle clauses already did: an unannotated arrow or bare Handler(L) there is pure (ADR 010, docs/language.md). The reviewer’s arrow repros (z1, z2, z4, x1, a K((Int) -> Int) payload), the Handler repro below, its data analog and a State((Int) -> Int) analog now pass (test/fx024-label-arguments.test.mjs); regression row fx-label-argument-ambient (restored: E_EFFECT v performs Console, which its signature does not allow). Consequence: a function that raises an ambient callback in such a payload (fn work(f: (Int) -> Int): Int with Fail({ k: (Int) -> Int }) = Fail.fail({ k: f })) is now the existing E_EFFECT ... and pure cannot be made equal: both end in ...; write the callback with pure. One existing test program did this (test/fx-cleanup.test.mjs, the 5,000-arrow payload) and now builds its payload with crash. Original report: Spurious E_EFFECT when a Fail payload type carries a Handler (pre-existing; R001 Task 8 review, controller ruling: not fixed in R001). Repro: effect Log { fn log(n: Int): Unit; }; fn bad(): Int with Fail({ h: Handler(Log) }) = 1; fn mid(): Int with Fail({ h: Handler(Log) }) + Console = bad(); fn main(): Int = 0; is E_EFFECT mid performs Console, which its signature does not allow at bad() (note the signature of mid does not allow Console), though mid allows Console and bad performs nothing. The data analog type W(a) = W(a); with Fail(W(Handler(Log))) fails identically on main before records; a monomorphic type W = W(Handler(Log)) works. Hypothesis (not verified): the handler type’s own row inside the payload is matched against the function’s row. Next: reduce to the row operation that leaks (Features.Check.Failure, UnifyRow keying of a payload holding a row), fix, and add both repros as tests. Until then records of handlers cannot be Fail payloads across calls.
FX025 Done Pre-existing E_INTERNAL Missing row variable at offset 0 (line 1, column 1) for an unbound effect row variable passed to a declared type’s effect row parameter in a type declaration or a lambda annotation, where a user error is due (R001 Task 13, 2026-10-15, branch records at 046089f; probe log .build/evidence/t13-backlog-probes.log). Probes: type E(...e) = E((Int) -> Int with ...e); type W = W(E(...z)); and type E(...e) = E((Int) -> Int with ...e); fn f(x: Int): Int = { let g = fn(d: E(...e)) => 0; 0 }; (each with fn main(): Int = 0;). Raised at src/Features/Resolve/Row.purs:35. Next: report the unbound row variable at its ...z / ...e with an approved text (an unbound-name style message), with tests. Fixed on r001-followups (d0c9f8a; 2026-10-15): Row.resolveRow reports the existing E_UNBOUND Unbound type variable z (the text a record tail’s unbound variable gets) at the written row, which for a with row is the whole with ...z as for the other row sort errors, and at ...z for a declared type’s argument. Tests test/fx025-unbound-row.test.mjs, seen failing first (.build/evidence/ie-a-red-1.log); regression row fx025-unbound-row proved failing with the internal error restored.
FX026 Open, needs user approval Since FX024 a function raising an ambient callback in a label’s payload is rejected with the opaque row-equality text: fn w(f: (Int) -> Int): Int with Fail({ k: (Int) -> Int }) = Fail.fail({ k: f }); is E_EFFECT ... and pure cannot be made equal: both end in ... at Fail.fail({ k: f }), no notes (pinned in test/fx024-label-arguments.test.mjs, “the payload’s arrow is pure, as in a handle clause”; probe log .build/evidence/t13-backlog-probes.log). It names neither the callback nor the fix (with pure, or a shared written ...e, docs/language.md Effects). Next: propose wording naming the callback and the payload’s pure row, with a hint giving both fixes; implement after the user approves it.
FX027 Open A handler parameter installed in a function whose signature allows Console is reported as performing Console (R001 Task 13, 2026-10-15, branch records at 046089f; probe logs .build/evidence/t13-backlog-probes.log and t13-backlog-probes-handler.log): effect State(s) { fn get(): s; fn put(x: s): Unit; }; fn f(h: Handler(State(Int))): Int with Console = with h { State.get() }; is E_EFFECT f performs Console, which its signature does not allow at with h { State.get() }, notes State(Int) is handled here (same span) and the signature of f does not allow Console at with Console, which contradicts the signature. It fails without any call to f; the same f without with Console compiles and runs when main calls it. Cause, probably: FX017’s equal-row rule (the handler’s ambient row must equal f’s whole row) reported with the old headline, as FX018 (a) was. Next: confirm the cause, then report it in FX017/FX018 style with approved wording, or decide with FX007 whether the handler’s row should be subsumed.
FX028 Open FX024’s shared-...e alternative to with pure works only when the caller’s row equals the callee’s; a caller that adds a label is rejected, with a misleading or opaque message (R001 Task 13 review, 2026-10-15, branch records; labelled probes .build/evidence/t13-fix1-fx024-probes.mjs, logs t13-fix1-fx024-probes-2.log and, under the FX024 mutant, t13-fix1-fx024-probes-under-mutant.log). With say performing Console and fn w(f: (Int) -> Int with ...e): Int with Fail({ k: (Int) -> Int with ...e }) + ...e = Fail.fail({ k: f });: (P3) fn v(f: (Int) -> Int with ...e): Int with Console + Fail({ k: (Int) -> Int with ...e }) + ...e = w(f); is E_EFFECT v performs Console, which its signature does not allow at w(f), note the signature of v does not allow Console at v’s with, which contradicts the signature (the FX017 equal-row rule: w’s row Fail(...) + ...e must equal v’s, so the leftover Console is reported with the old headline, which FX017’s dedicated text does not cover here; the FX024 mutant gives the same). (P4, P5) closing the payload in a caller, fn v(): Int with Console + Fail({ k: (Int) -> Int with Console }) = w(say); (or Fail(...) + Console), is E_EFFECT Fail({ k: Int -> Int }) + Console and Console + Fail({ k: Int -> Int }) + ... cannot be made equal: both end in ... at w(say): binding e to the closed payload row closes w’s tail, and the printed rows drop the payload’s with Console; the FX024 mutant accepted both, so this rejection is new with FX024. Equal rows (P2) and with pure with an added label (P7) are accepted and run. Documented as a limit in ADR 010 and docs/language.md (Effects). Next: decide with FX007/FX017 whether the shared form should allow a caller’s extra labels; meanwhile report P3 in FX017’s style and P4/P5 with a text naming the payload’s row (wording needs the user’s approval), and print a payload arrow’s row.
FX029 Open, needs design Installing several handlers at once (user request, 2026-10-15): with database { with clock { with logger { print(report(1, 2)); print(report(3, 4)) } } } should be writable as with database + clock + logger { print(report(1, 2)); print(report(3, 4)) }. Direction (user, 2026-10-15): + joins independent handlers only, so its order never matters, like + in a row; a handler that depends on another is nested inside it, e.g. with dog + cat { with thingDependingOnDog { … } }. Independent means no two operands handle the same label and no operand’s clause row (its Handler(L with C) type) names a label another operand handles; otherwise the combined form is rejected with a hint to nest (wording needs approval). Earlier questions, now settled by that rule except (2)-(4): (1) order — whether the leftmost handler is the outermost, as in the nested form above, which matters when two handlers handle the same label or one handler’s clauses perform an effect another handles; (2) parsing — today with e1 + e2 { … } reads e1 + e2 as one expression, so + here must be part of the with form (rows already use + for labels, Console + Fail), not arithmetic on handler values; (3) whether the operands may be any handler expression (with handler L { … } + h { … }) or names only; (4) diagnostics for a non-handler operand. Next: a short design with these choices for the user’s approval, then desugaring to the nested form in parsing or resolution, tests seen failing first, and the depth probe if the parser changes.
T009 Open Remaining unoptimized alias shapes after T008 (review M7, 2026-10-12): chains of labelled extendRow hops are not compressed, and hops that cannot be merged (neither side a lone meta) are still walked one by one. Neither is known to matter in real programs. Next: probe the harvested corpus and the scale programs for chain lengths over these shapes; fix only if one grows with program size. Also (FX008 whole-branch review, user decision 2026-10-12: accepted as linear constant factors): 8,000 defers 1,056 ms vs 694 ms at 8fe4218; a chain of 1,000 installing functions 332 ms vs 83 ms.
T010 Open The FX008 soundness generator (test/fx-gen-failing.mjs) has no nofail(k) mark-variable shape (whole-branch review M4, 2026-10-12). Covered for now by fixed tests p01, p02, p11, q03 and controls (test/fx008-nofail-k.test.mjs, user decision). Next: add a callee-k shape (a nofail(k) callback under a caller’s failing and fail-free handlers) with a per-shape minimum and its sensitivity count.
T011 Open Record coverage gaps in the FX differential corpus (R001 Task 9 review). The census has no row for a generic record function used at two tails or for two label-set families active at once (both occur in samples: 289/351 and 348/351 programs at one seed), and all seven record rows trigger from one record frame, so they measure only how often the frame appears. The generator never produces records inside lambdas, open tails in declared types, records passed to handler operations, or type-variable payloads; the shrinker cannot replace a whole record expression. Next: add census rows for the two-tail and two-family cases with minimums, per-behaviour rows, and generator shapes for the missing constructs, each with a sensitivity check. Also (R001 final review, recommendation 4): generator shapes for lambda annotations over open signature tails and for handler type arguments (with handler State({ n: Int, ...r })); the differential would have found C1.
T012 Done (2026-10-15, R001 Task 13) Eight R001 regression rows match only their probe’s own failure label, so any mutant failure (a crash, an unrelated rejection) would satisfy the proof (R001 Task 11 review, 2026-10-14). Evidence: scripts/regression-r001.mjs rows r001-record-order (/label order/), r001-row-sort-propagation (/propagation/), r001-duplicate-field (/duplicate field/), r001-callee-to-caller (/callee to caller/), r001-settled-sort (/settled sort/), r001-caller-conflict (/caller conflict/), r001-sort-feedback (/feedback/) and r001-field-bound-tail (/rebound/); r001-print-ground and the five Task-10 rows already pin their symptom. Next: run each mutant in an isolated copy, record its actual output, tighten each message to label plus symptom (e.g. the wrong diagnostic text or acceptance), and re-prove with node scripts/regression.mjs <row>. Done: each mutant was run in an isolated copy (log .build/evidence/t13-mutant-outputs.log) and each message now pins its actual output: r001-record-order the unsorted E_TYPE Expected { b: Bool, a: Int }; r001-row-sort-propagation and r001-duplicate-field acceptance (ran); r001-callee-to-caller and r001-sort-feedback the clash reported against the wrong variable (s is an effect row, not a record, b is an effect row, not a record); r001-settled-sort and r001-caller-conflict E_INTERNAL Invalid type variable; r001-field-bound-tail Go’s cannot use build error. Re-proved with node scripts/regression.mjs <rows> (log .build/evidence/t13-regression-eight.log). The other rows added on records (r001-print-ground, the Task 10 and C1 rows, the R012 rows, fx-label-argument-ambient) already pinned their symptom.