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