Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions docs/known-gaps.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
| Severity | Open |
|---|---|
<!-- @generated:gap-counts START (do not edit — `bun scripts/state.ts --write`) -->
| HIGH | 77 |
| MED | 201 |
| LOW | 86 |
| HIGH | 76 |
| MED | 203 |
| LOW | 87 |
| Nominal (spec-ahead-of-impl) | 7 |
<!-- @generated:gap-counts END -->

Expand All @@ -51,6 +51,17 @@

<!-- ⛑ S395-bryan filing — TWO entries, both PA-CONFIRMED BY EXECUTION on merged main `908a631c`, both surfaced by the #815 arc rather than by building anything. Both are the SAME ROOT as the entry #815 closed — the shared `bare-expr` handler under an active `tildeContext` — which is why #815 is correctly scoped as one position of a class and NOT the class. The dev agent declined the first of these deliberately rather than half-fix it, and that judgment is endorsed here: closing it touches the §32 tilde-accumulator semantics and the fix direction rests on a reading of §17.6.2 that has not been ruled. -->

<!-- ⛑ S395-bryan filing batch 2 — THREE entries from the #818 if-chain server-boundary arc. Two are PRE-EXISTING and were verified as such two-sided before filing (neither is introduced by #818); the third is a tooling-baseline debt whose measurement is RELAYED and labelled so, because `tsc` is not installed in this checkout and the types gate correctly refuses to pass without running. -->

### g-same-named-branch-declarations-bind-to-the-last-definition — two sibling `if=`/`else` branches declaring the SAME function name emit BOTH definitions but bind **every** call site to the LAST one, with zero diagnostics. Surfaced by the S239 pass on #818 and **PA-verified PRE-EXISTING, not introduced**: on merged main two *lone* `if=` divs each declaring `dup()` already emit `_scrml_dup_7` + `_scrml_dup_8` and route the calls to the last definition (measured 1 call to the first, 5 to the second). #818 brings the collapsed-chain shape to PARITY with that lone-`if=` oracle, which is its stated goal — so the chain now inherits the oracle's own defect rather than introducing one. ⛑ **The direction of the change at this shape is loud→silent**, and that is why it is filed rather than shrugged at: on base the chain produced a `ReferenceError` (nothing was defined at all); it now returns the WRONG branch's value silently. In the `server fn` / plain `function` mix the `if`-arm's call binds to the else-arm's CLIENT function while the emitted route goes unreferenced — a live POST endpoint with no caller. **No server body leaked in any duplicate-name shape tested** (`SERVER-SECRET` never reached `client.js`), so this is a correctness defect, not a confidentiality one. The real question underneath is whether a branch body is its own DECLARATION SCOPE — the same scoping question `g-if-chain-all-arms-run-at-module-init` asks about `${}` bodies, and it should be ruled once for both rather than twice. — `NEW S395-bryan (S239 finding on #818; PA-verified pre-existing on the lone-if= oracle)`; **MED**; open
<!-- @gap id=g-same-named-branch-declarations-bind-to-the-last-definition sev=MED status=open locus=compiler/src/codegen/collect.ts(collectFunctions registers by NAME with no per-branch scope, so the last registration wins; the same shape pre-exists for sibling lone-if= blocks, which is the oracle it was brought to parity with) prov=empirical:PA-two-sided-execution-two-lone-if-divs-on-merged-main-already-bind-all-calls-to-the-last-definition -->

### g-call-expression-interpolation-in-if-chain-branch-renders-empty — `${someFn()}` inside a collapsed `if=`/`else` branch **renders nothing**, at exit 0 with zero diagnostics, even for a plain TOP-LEVEL function that no if-chain walk touches. **PA-VERIFIED ON MERGED MAIN by reading the emitted artifact**, and it is a DIFFERENT class from the collector blindness #818 closed — it is an ORDERING defect in the boot sequence, not a collection one. Mechanism, read out of the emitted client JS: `_scrml_boot` resolves the target with `document.querySelector('[data-scrml-logic="…"]')` and calls `_scrml_render_value(el, _scrml_topHelper_N())` — but the branch content only enters the DOM in `_scrml_nav_rewire(document)`, which `_scrml_boot` invokes **afterwards**. `el` is null, the render is a no-op, and nothing reports it. ⛑ **A CELL interpolation in the identical position works**, so the defect is specific to call-expression interpolations, which is why it survived the #811/#818 class sweeps: those closed COLLECTION walks, and this needs the boot order (or a re-render hook) instead. The #818 conformance case deliberately asserts through the handler position rather than the rendered text for exactly this reason. — `NEW S395-bryan (surfaced as a deferred item by the #818 dispatch; PA-verified on merged main with a TOP-LEVEL fn, i.e. outside the fix's surface)`; **MED**; open
<!-- @gap id=g-call-expression-interpolation-in-if-chain-branch-renders-empty sev=MED status=open locus=compiler/src/codegen(the _scrml_boot emission: the querySelector+_scrml_render_value pair is emitted into boot ahead of the _scrml_nav_rewire(document) call that inserts branch content; the exact emitter was NOT traced) prov=empirical:PA-read-of-the-emitted-client-js-on-merged-main-boot-resolves-el-before-nav_rewire-inserts-the-branch -->

### g-types-check-baseline-never-refreshed-for-ast-if-chain — `bun scripts/types-gate.ts --check` is RED on pristine `main` with ~9 NEW diagnostics, most of them `TS7016 Could not find a declaration file for module './ast-if-chain.js'` across five existing consumers: the gate's baseline was never refreshed when that module landed at #805. #818 adds a sixth consumer and a tenth diagnostic of the identical class. A 4-line `compiler/src/ast-if-chain.d.ts` reportedly zeroes five at once. ⛑ **THE COUNTS HERE ARE RELAYED, NOT PA-MEASURED, AND THE ENTRY SAYS SO DELIBERATELY.** Two independent agents (the #818 dev agent and its S239 reviewer) concur on 9→10, but `tsc` is absent from this checkout's `node_modules` and `types-gate.ts` **correctly refuses to fall back to a PATH `tsc` or to skip** — *"a gate that passes without running is worse than no gate"*, its own words. So the PA could not execute the measurement and declined to land an unverified `.d.ts` on someone else's number. **Re-measure before acting.** `types:check` is not in the pre-commit hook and not a required cloud check, so this is debt, not a block. — `NEW S395-bryan (S239 finding on #818; RELAYED-UNVERIFIED by construction — the measuring tool is not installed here)`; **LOW**; open
<!-- @gap id=g-types-check-baseline-never-refreshed-for-ast-if-chain sev=LOW status=open locus=compiler/src/ast-if-chain.js(no sibling .d.ts; consumers import it from TS and take TS7016) prov=review:two-independent-agents-concur-on-9-to-10-but-PA-could-not-execute-tsc-is-not-installed-in-this-checkout -->

### g-bare-expr-in-if-arm-rebinds-tilde-context-corrupting-the-result-var — a bare-expression statement inside an if-as-expression arm mints a FRESH `_scrml_tilde_N` **and rebinds `tildeContext.var`**, so a following explicit `lift` writes the wrong variable and the binding stays `null`. **PA-CONFIRMED BY EXECUTION on merged main `908a631c`, and it survives #815.** `const label = if (@n > 0) { note("a") lift "pos" } else { lift "neg" }` compiles **exit 0, zero diagnostics** and emits `let _scrml_tilde_5 = _scrml_note_2("a");` then `_scrml_tilde_5 = "pos";` inside the then-arm, while the real result var `_scrml_tilde_4` keeps its `null` seed — so `label` is **always null on the true path**. ⛑ **AND THE SIBLING ARM LEAKS A GLOBAL.** With `else { lift "neg" }` the else-arm emits `_scrml_tilde_5 = "neg"`, a name `let`-declared INSIDE the then-block and therefore **not in scope** there. PA-verified the emitted client artifact is a **classic script** (`<script src=…>`, no `"use strict"`, no `type="module"`), so this is a silent **implicit global**, not a `ReferenceError`. ⛑ **A REVIEWER CLAIMED #815 REMOVES THIS `ReferenceError` CLASS; ADJUDICATED BY EXECUTION AND IT IS HALF TRUE** — with a SUGAR else (`else { "neg" }`) the else-arm correctly writes the outer `_scrml_tilde_4` and the class IS gone; with an explicit-`lift` else it is NOT, and the explicit-`lift` form is the one §17.6.1's canonical grammar documents. **This is the canonical production** `'{' statement* lift-stmt statement* '}'` — the shape §17.6.9 example 4 documents — and the corpus misses it on a technicality: `samples/compilation-tests/gauntlet-s19-phase2-control-flow/phase2-if-as-expr-intermediate-014.scrml` uses a `let`-decl as its intermediate statement, which does NOT rebind; only a bare EXPRESSION does. **Candidate governing sentence, PA-read not ruled:** §17.6.2 — *"Execution within an arm body is unrestricted: the arm body MAY contain variable declarations, **function calls**, server calls… Only one `lift` statement designates the result; **other statements are side effects or intermediate computation**."* If that governs, a bare call is normatively a side effect and NOT a result designator, the emitter contradicts it, and this is conformance restoration rather than a design fork. Direction is `semantics-changed`, so it is OUTSIDE the S385 4(b) class either way and owes a measured differential. **RULING OWED (bryan): accept the §17.6.2 reading, or rule the §32 question explicitly — does a bare expression inside an if-as-expression arm participate in the `~` accumulator?** — `NEW S395-bryan (surfaced by the dev agent during #815, declined by it deliberately, then PA-reproduced)`; **HIGH**; open
<!-- @gap id=g-bare-expr-in-if-arm-rebinds-tilde-context-corrupting-the-result-var sev=HIGH status=open locus=compiler/src/codegen/emit-logic.ts:1723,1882(the shared bare-expr handler: under an active tildeContext it emits `let _scrml_tilde_N = <expr>;` AND rebinds tildeContext.var — correct §32 pipeline semantics, wrong for a §17.6.2 arm body) prov=empirical:PA-two-sided-execution-on-merged-main-908a631c-then-arm-binds-null-and-explicit-lift-else-arm-writes-an-out-of-scope-name-in-a-non-strict-classic-script -->

Expand All @@ -60,8 +71,8 @@
### g-if-chain-all-arms-run-at-module-init — every arm of a mutually-exclusive `if=`/`else` chain executes its `${…}` body at module init, in source order, **last writer wins** — so a cell declared or written in the DEAD arm overwrites the live one. **PA-CONFIRMED BY EXECUTION.** `<div if=@open>${ <n>: number = 1 }…</div><div else>${ <n>: number = 2 }…</div>` with `@open = true` emits BOTH `_scrml_cs_reactive_set("n", 1)` and `_scrml_cs_reactive_set("n", 2)` at module init; `@n` is **2**, the dead arm's value, while the `if` arm is the one that mounts. Exit 0, zero diagnostics. SPEC §17.1.1 *Desugaring* is explicit that the chain desugars to `${ if (…) {…} else if (…) {…} else {…} }` — exactly one arm runs. ⚑ **THE ROOT IS PRE-EXISTING AND WIDER THAN THE CHAIN.** PA-verified on untouched `main`: a LONE `<div if=@open>${ <n>: number = 1 }…</div>` with `@open = **false**` STILL emits `reactive_set("n", 1)`. So "only the taken arm runs" has never held, and §17.1 calls `if=` sugar over `${ if(expr) { lift … } }`, which the lone shape already contradicts. ⚑ **Introduced-shape note, stated plainly:** before the S393 if-chain class fix the chain arms were DEAD (zero `reactive_set`), so this delta brings the chain into parity with the already-non-conforming lone shape rather than creating a new class — and **no currently-correct program regresses**, because anything depending on those statements was already broken. Corpus blast radius MEASURED ZERO (1 changed line across 2,198 files, and not this shape). **This is a RULING, not a fix:** closing it means deciding whether a markup `${…}` body is a FILE-SCOPE statement or a BRANCH-SCOPED one, and the answer changes lone `if=` too — which is why the landing branch did not absorb it. — `NEW S393-bryan (S239 re-review of the if-chain class fix; PA reproduced both the chain shape AND the pre-existing lone-shape root)`; **MED**; open
<!-- @gap id=g-if-chain-all-arms-run-at-module-init sev=MED status=open locus=searched:codegen/collect.ts:collectTopLevelLogicStatements,codegen/reactive-deps.ts — the collectors now reach every branch body and hoist each to module init; the SCOPING decision (file-scope vs branch-scope for a markup ${} body) has no single locus and is a ruling prov=empirical:PA-execution-both-arms-emit-reactive-set-last-wins-AND-the-lone-if-shape-fires-with-a-FALSE-condition-on-untouched-main -->

### g-collect-functions-branch-decl-vs-server-boundary-routing — closing `collect.ts`'s `collectFunctions` if-chain blind spot **emits a `server fn` BODY into `client.js`**, so the obvious fix trades a loud `ReferenceError` for a silent server-code-in-client-bundle leak. **Two-sided, both agent-executed with a control and PA-corroborated on the shipped build.** As it stands, a `function` declared inside an `if=`/`else` branch is **never defined** while its call sites still emit **unmangled** — lone `if=` gives `function _scrml_helper_5(){…}` + 4 mangled calls; `if=`/`else` gives **0 definitions and 4 bare `helper()` calls**, i.e. `ReferenceError` at exit 0. But closing that walk alone emitted `function _scrml_zzload_7() { return 1; }` into `client.js` with **no `server.js` produced at all** (control: body-in-client 1 with the hunk, 0 without, 0 on base) — because that walk feeds the CLIENT function emitter while the server-boundary routing walk is blind in its own separate way. **DELIBERATELY REVERTED AND FILED; the shipped build has body-in-client 0, PA-verified.** ⚑ A **LEAK GUARD** test ships with it (`g-if-chain-branch-cell-never-wired.test.js`) that goes RED if anyone closes `collectFunctions` without first closing the routing walk — re-review re-applied the descent in a scratch copy and confirmed the guard reds. **Fix direction: the two walks must close in the SAME arc, routing first.** Note the guard's second assertion (`not.toContain("server fn")`) is vacuous — only the emitted-body string bites, and it is whitespace-sensitive. — `NEW S393-bryan (if-chain class dispatch walked into it and backed out)`; **HIGH**; open
<!-- @gap id=g-collect-functions-branch-decl-vs-server-boundary-routing sev=HIGH status=open locus=compiler/src/codegen/collect.ts(collectFunctions — blind to if-chain; closing it alone leaks a server fn body into client.js because the server-boundary ROUTING walk is separately blind) prov=empirical:agent-executed-control-body-in-client-1-with-the-hunk-0-without-0-on-base-reverted-and-leak-guarded -->
### g-collect-functions-branch-decl-vs-server-boundary-routing — closing `collect.ts`'s `collectFunctions` if-chain blind spot **emits a `server fn` BODY into `client.js`**, so the obvious fix trades a loud `ReferenceError` for a silent server-code-in-client-bundle leak. **Two-sided, both agent-executed with a control and PA-corroborated on the shipped build.** As it stands, a `function` declared inside an `if=`/`else` branch is **never defined** while its call sites still emit **unmangled** — lone `if=` gives `function _scrml_helper_5(){…}` + 4 mangled calls; `if=`/`else` gives **0 definitions and 4 bare `helper()` calls**, i.e. `ReferenceError` at exit 0. But closing that walk alone emitted `function _scrml_zzload_7() { return 1; }` into `client.js` with **no `server.js` produced at all** (control: body-in-client 1 with the hunk, 0 without, 0 on base) — because that walk feeds the CLIENT function emitter while the server-boundary routing walk is blind in its own separate way. **DELIBERATELY REVERTED AND FILED; the shipped build has body-in-client 0, PA-verified.** ⚑ A **LEAK GUARD** test ships with it (`g-if-chain-branch-cell-never-wired.test.js`) that goes RED if anyone closes `collectFunctions` without first closing the routing walk — re-review re-applied the descent in a scratch copy and confirmed the guard reds. **Fix direction: the two walks must close in the SAME arc, routing first.** Note the guard's second assertion (`not.toContain("server fn")`) is vacuous — only the emitted-body string bites, and it is whitespace-sensitive. **RESOLVED S395-bryan via #818 (`c4c55c50`)**, in the mandated order: the RI server-boundary walk (`route-inference.ts` `collectFileFunctions` + `collectWorkerBodyFunctionIds`) FIRST, `collectFunctions` second, same arc. **PA-verified on MERGED main, two-sided:** body-in-client `0` with `server.js` produced and the body inside it; the plain-fn symptom goes 0 definitions / 2 bare calls -> 1 definition / 0 bare calls. Three-way control reproduced the positive case exactly — the codegen half ALONE still emits body-in-client `1` with no `server.js`. ⛑ **The root, which explains why the order is the fix:** `emit-server.ts` and the client emitter read ONE collector; the client emitter omits a `server fn` body ONLY because RI already claimed it. ⛑ **A second walk not in the brief was closed too** — `collectWorkerBodyFunctionIds`; left blind it falls out of lockstep and false-fires `E-ROUTE-001` on a branch-declared fn in a worker body, i.e. blast radius the fix itself would have created. Differential 7408 artifacts byte-identical, 0 differing — EXPLAINED by an AST census: 17 of 1914 corpus sources carry a collapsed if-chain and **zero** declare a function inside a branch. **The leak guard is hardened, four defects not three** (whitespace-normalized body check; a structural fetch-stub-name check; real server-only markers replacing the vacuous `not.toContain("server fn")`; and a POSITIVE limb — it was ABSENCE-ONLY, so the base column satisfied it by making the function vanish). — `NEW S393-bryan (if-chain class dispatch walked into it and backed out)`; **HIGH**; resolved S395
<!-- @gap id=g-collect-functions-branch-decl-vs-server-boundary-routing sev=HIGH status=resolved locus=compiler/src/codegen/collect.ts(collectFunctions — blind to if-chain; closing it alone leaks a server fn body into client.js because the server-boundary ROUTING walk is separately blind) prov=empirical:agent-executed-control-body-in-client-1-with-the-hunk-0-without-0-on-base-reverted-and-leak-guarded -->

### g-timer-in-if-chain-branch-never-starts — a `<timer>` declared inside an `if=`/`else` chain branch is never started: `_scrml_timer_start` count **0** on the chain shape vs **1** on the lone-`if=` oracle. **PRE-EXISTING and byte-identical before/after the S393 if-chain class fix** — a sibling of the same class living in the LIFECYCLE emitter rather than in `symbol-table.ts` / `collect.ts` / `reactive-deps.ts`. Verified no module-init body leak in either chain shape (timer nested in a branch, and timer AS the branch element). **RELAYED from the S239 re-review; not PA-reproduced.** — `NEW S393-bryan (S239 re-review of the if-chain class fix)`; **MED**; open
<!-- @gap id=g-timer-in-if-chain-branch-never-starts sev=MED status=open locus=searched:the-lifecycle-emitter-that-owns-_scrml_timer_start — not traced; the class is the same if-chain descent blind spot prov=review:S239-re-review-RELAYED-pre-existing-byte-identical-across-the-delta -->
Expand Down
Loading
Loading