[generator] LTL inner-expression identifiers not qualified with this. — input fields + action params out of scope in rewriteExpr #55

Closed
opened 2026-06-03 17:34:15 +00:00 by sscoble · 2 comments
Owner

Problem

The TypeScript generator's verifyN() body (speckl#48) translates the LTL outer layer (always/eventually/implies) but the inner expression identifiers are not qualified with this.. For LTL formulas that reference input record fields or action parameters, the emitted TS code throws ReferenceError at runtime because the bare identifiers are not defined in the class scope.

Where it fires

  • examples/RetryHandler.speck — verify: always(implies(attempt > 1, delay > 0)) references input fields attempt and delay. The emitted verify1() is:

    if (!(((attempt > 1) || (delay > 0)))) return false;  // ReferenceError
    

    attempt and delay are input record fields on RetryAttempted, not state vars, so rewriteExpr doesn't prefix them with this..

  • Any verify block referencing input record fields, action params, or non-state identifiers will hit this.

Why it exists

The rewriteExpr function (typescript-state-machine.ts) currently only knows about state-var names (from the class's state {} block). The LTL inner-expression translator in speckl#48 calls rewriteExpr to qualify identifiers, but the LTL body isn't pre-tagged with which identifiers are state vars vs input fields vs action params.

Scope

  • Build a complete identifier-to-source map per emit: state vars + input record fields + action params (from parseMemberBlock 'input:' and 'action:' AST nodes)
  • In the LTL inner translator, prefix any identifier in the map with this. when it's not a literal/operator/keyword
  • The map should be a per-class Set<string> passed to the emit pipeline

Acceptance Criteria

  • rewriteExpr (or a new rewriteLtlExpr) correctly prefixes input record fields and action params with this. in LTL bodies
  • examples/RetryHandler.speck emits verify1() that references this.attempt and this.delay (or whatever the correct input field prefix is)
  • Runtime test: construct a RetryHandlerMachine, set fields to violate the LTL (e.g., delay = 0 when attempt > 1), call verify1(), get false (not ReferenceError)
  • Round-trip: parse + emit + tsc + runtime test on all 5 examples → 0 errors, expected runtime results

Out of scope

  • Parser fix for multi-line verify: bodies (separate ticket: speckl#54)
  • LTL semantic implementation
  • Z3 solver integration

Depends on

  • speckl#48 (verifyN() must exist) — CLOSED 2026-06-03 ✓
  • speckl#54 (multi-line verify parser) — must close first; otherwise inner expr is truncated

Why this matters

Blocks the AC2 end-to-end test in speckl#48. The patch is correct, but the runtime test fails before getting to the boolean check. Part of the B2 PR stack.

Discovered during

  • sscoble/speckl#48 (2026-06-03 10:17 UTC dispatch, max_iterations, post-implementation verification)
## Problem The TypeScript generator's `verifyN()` body (speckl#48) translates the LTL outer layer (`always`/`eventually`/`implies`) but the inner expression identifiers are not qualified with `this.`. For LTL formulas that reference input record fields or action parameters, the emitted TS code throws `ReferenceError` at runtime because the bare identifiers are not defined in the class scope. ## Where it fires - `examples/RetryHandler.speck` — `verify: always(implies(attempt > 1, delay > 0))` references input fields `attempt` and `delay`. The emitted `verify1()` is: ```ts if (!(((attempt > 1) || (delay > 0)))) return false; // ReferenceError ``` `attempt` and `delay` are input record fields on `RetryAttempted`, not state vars, so `rewriteExpr` doesn't prefix them with `this.`. - Any `verify` block referencing input record fields, action params, or non-state identifiers will hit this. ## Why it exists The `rewriteExpr` function (`typescript-state-machine.ts`) currently only knows about state-var names (from the class's `state {}` block). The LTL inner-expression translator in speckl#48 calls `rewriteExpr` to qualify identifiers, but the LTL body isn't pre-tagged with which identifiers are state vars vs input fields vs action params. ## Scope - Build a complete identifier-to-source map per emit: state vars + input record fields + action params (from `parseMemberBlock` 'input:' and 'action:' AST nodes) - In the LTL inner translator, prefix any identifier in the map with `this.` when it's not a literal/operator/keyword - The map should be a per-class `Set<string>` passed to the emit pipeline ## Acceptance Criteria - [ ] `rewriteExpr` (or a new `rewriteLtlExpr`) correctly prefixes input record fields and action params with `this.` in LTL bodies - [ ] `examples/RetryHandler.speck` emits `verify1()` that references `this.attempt` and `this.delay` (or whatever the correct input field prefix is) - [ ] Runtime test: construct a `RetryHandlerMachine`, set fields to violate the LTL (e.g., `delay = 0` when `attempt > 1`), call `verify1()`, get `false` (not ReferenceError) - [ ] Round-trip: parse + emit + tsc + runtime test on all 5 examples → 0 errors, expected runtime results ## Out of scope - Parser fix for multi-line `verify:` bodies (separate ticket: speckl#54) - LTL semantic implementation - Z3 solver integration ## Depends on - speckl#48 (verifyN() must exist) — **CLOSED 2026-06-03** ✓ - speckl#54 (multi-line verify parser) — must close first; otherwise inner expr is truncated ## Why this matters Blocks the AC2 end-to-end test in speckl#48. The patch is correct, but the runtime test fails before getting to the boolean check. Part of the B2 PR stack. ## Discovered during - sscoble/speckl#48 (2026-06-03 10:17 UTC dispatch, max_iterations, post-implementation verification)
agent-engineering was assigned by sscoble 2026-06-03 17:34:15 +00:00
Author
Owner

speckl#55 — Cycle 2 Negative Verification (2026-06-03 13:30 UTC)

Verdict: NEGATIVE — patch applied, but tsc harness shows the patched build has MORE errors than baseline, not fewer. Per the NEGATIVE-VERIFICATION PROTOCOL, the patch was dropped; the issue is left OPEN for re-dispatch.

What was tried

Two dispatch cycles (2 × 50 calls, ~850s wall-clock) plus CoS recovery (50 calls). The first subagent did analysis only. The second subagent applied a patch covering both the parser fix (multi-line input: / output: blocks) and the generator fix (collect input/output/action-param field names; build classIdentNames; pass to rewriteExpr; add recordFieldDecls to emit field declarations). CoS re-applied the patch after the second subagent's working tree was lost (per the COMMIT-BEFORE-CLOSE PROTOCOL lesson). The patch was successfully compiled.

AC table

AC Result Notes
AC1: rewriteExpr correctly prefixes input/output/action-param names with this. in LTL bodies PARTIAL rewriteExpr was updated to use the unified classIdentNames set. Input fields ARE collected. But the AC's "RetryHandler verify1 references this.attempt and this.delay" expectation is not satisfiable because attempt and delay are not in any block (input:, output:, state:, action:) in RetryHandler.speck — they're free variables in the LTL body.
AC2: RetryHandler.speck emits verify1() referencing this.attempt and this.delay FAIL Same reason as AC1. The spec uses attempt and delay in the LTL body but never declares them. They appear in the event RetryAttempted payload but event payload fields are not the same as class fields.
AC3: Round-trip: parse + emit + tsc + runtime test on all 5 examples → 0 errors FAIL Patched build has MORE errors than baseline, not fewer. See "tsc harness output" below.
AC4: Print-debug log fires expected number of times N/A The CoS-direct re-application skipped the print-debug diagnostic step (we can verify behavior directly).

tsc harness output (PATCHED build, all 5 examples)

AccessControl.ts(52,21): error TS2304: Cannot find name 'Action'.
AccessControl.ts(90,31): error TS2552: Cannot find name 'hasPermission'.
AccessControl.ts(94,33): error TS2552: Cannot find name 'hasPermission'.
DrugDosageCalculator.ts(37,10): error TS2304: Cannot find name 'Real'.
DrugDosageCalculator.ts(42,18): error TS2304: Cannot find name 'Bool'.
PaymentService.ts(43,13): error TS2304: Cannot find name 'Payment'.
PaymentService.ts(48,12): error TS2552: Cannot find name 'PaymentResult'.
PaymentService.ts(79,13): error TS2304: Cannot find name 'limiter'.
PaymentService.ts(79,58): error TS2693: 'RateLimited' only refers to a type, but is being used as a value here.
PaymentService.ts(83,15): error TS2304: Cannot find name 'limiter'.
PaymentService.ts(83,60): error TS2693: 'RateLimited' only refers to a type, but is being used as a value here.
RetryHandler.ts(30,15): error TS2304: Cannot find name 'Operation'.
RetryHandler.ts(63,13): error TS2304: Cannot find name 'attempt'.
RetryHandler.ts(63,30): error TS2304: Cannot find name 'delay'.
RetryHandler.ts(67,15): error TS2304: Cannot find name 'attempt'.
RetryHandler.ts(67,32): error TS2304: Cannot find name 'delay'.
TemperatureMonitor.ts(47,23): error TS2304: Cannot find name 'Real'.
TemperatureMonitor.ts(48,12): error TS2304: Cannot find name 'TempStatus'.
TemperatureMonitor.ts(50,12): error TS2304: Cannot find name 'TempAction'.

Net: 18 errors. Baseline (fix/speckl-54): 4 errors (all in RetryHandler verify1 body for attempt/delay). The patch introduced 14 new errors and only changed the shape of 4 existing errors.

Root cause of the new errors

The recordFieldDecls function calls tsType(f.type, ...) to get the TS type for each field declaration. For opaque idents (e.g., Action, Operation, hasPermission, RateLimited) and SpeckDL primitive types (Real, Bool), tsType returns the raw type name as a TS identifier — which is not declared in the generated code. The class body ends up with hasPermission!: hasPermission; or attemptCount!: Real; which are invalid TS.

Fix for the next cycle: in recordFieldDecls, fall back to any for any TypeExpr whose tsType is not a recognized TS primitive (string, number, boolean, any, unknown, etc.). This requires either a tsTypeSafe helper that returns any for opaque/primitive SpeckDL types, or an isValidTsType predicate.

Secondary finding: pre-existing dedup bug

memberKey() at typescript-state-machine.ts:864 returns just 'verify' for all verify: members, collapsing them in the deduped: Map<string, MemberNode>. Only the last verify block survives. This blocks the count-matching verification rule from the NEGATIVE-VERIFICATION PROTOCOL (we'd expect 15 verifyN emissions across 5 examples, but only the last verify in each spec fires). This is a pre-existing bug from #48. Out of scope for #55; should be a separate ticket.

What was done

  • Parser: added input:, output: to blockStarters; added parseInputOutputBlockMultiline helper (joins multi-line record body and feeds to parseTypeExpr which produces a record TypeExpr).
  • Generator: collected inputFieldNames, outputFieldNames, actionParamNames; built classIdentNames; updated rewriteExpr to use the unified set; updated emitAction and all internal callers.
  • Added recordFieldDecls to emit fieldName!: type; declarations in the class body. BUG: falls back to invalid TS types for opaque idents.
  • Working tree is at d5eea7e (clean) on branch fix/speckl-55-ltl-inner-identifier-scope. Branch is empty pointer (0 unique commits). Patch is dropped.

Re-dispatch directive for next cycle

  1. Open fix/speckl-55-ltl-inner-identifier-scope branch (already exists, empty pointer). Apply changes ON TOP of d5eea7e.
  2. Re-apply the parser fix (verified working in the dropped patch). Add input:, output: to blockStarters; add parseInputOutputBlockMultiline.
  3. Re-apply the generator fix (verified partially working). Collect inputFieldNames, outputFieldNames, actionParamNames; build classIdentNames; pass to rewriteExpr. Update all callers.
  4. Add a tsTypeSafe helper that returns any for opaque idents and SpeckDL primitives (Real, Bool, Nat, Int, Bytes, String). Use it in recordFieldDecls instead of tsType.
  5. Add the print-debug [ltl-rewriter] called directive at the top of the new rewriteExpr path.
  6. Build & verify: from compiler/ cwd, run npx tsc; then node dist/index.js -t typescript ../examples/RetryHandler.speck -o /tmp/spec-out-v55; check that verify1() body now has this.attempt (or is empty if attempt truly is a free variable — that's also a valid resolution).
  7. Per-file tsc harness from compiler/ cwd: should show ≤4 errors (the same 4 baseline errors from RetryHandler; everything else should be 0).
  8. REMOVE the print-debug log before committing.
  9. Commit on the branch with: git -c user.email=agent-engineering@noreply.os.scoble.me -c user.name="agent-engineering" commit -m "fix(parser+generator): speckl#55 multi-line input/output blocks + LTL inner-identifier scoping".
  10. POST the result comment IMMEDIATELY after git commit.

The dedup bug (memberKey returns just 'verify') is a separate pre-existing issue from #48. File it as a new ticket if you want it addressed; out of scope for #55.

Working tree state at close

  • Branch: fix/speckl-55-ltl-inner-identifier-scope (empty pointer at d5eea7e, 0 unique commits)
  • Working tree: clean
  • Patch: DROPPED per NEGATIVE-VERIFICATION PROTOCOL
  • Issue: remains OPEN, assigned to agent-engineering
  • 2 dispatch cycles consumed, ~850s wall-clock
## speckl#55 — Cycle 2 Negative Verification (2026-06-03 13:30 UTC) **Verdict: NEGATIVE — patch applied, but tsc harness shows the patched build has MORE errors than baseline, not fewer.** Per the NEGATIVE-VERIFICATION PROTOCOL, the patch was dropped; the issue is left OPEN for re-dispatch. ### What was tried Two dispatch cycles (2 × 50 calls, ~850s wall-clock) plus CoS recovery (50 calls). The first subagent did analysis only. The second subagent applied a patch covering both the parser fix (multi-line `input:` / `output:` blocks) and the generator fix (collect input/output/action-param field names; build `classIdentNames`; pass to `rewriteExpr`; add `recordFieldDecls` to emit field declarations). CoS re-applied the patch after the second subagent's working tree was lost (per the COMMIT-BEFORE-CLOSE PROTOCOL lesson). The patch was successfully compiled. ### AC table | AC | Result | Notes | |---|---|---| | AC1: `rewriteExpr` correctly prefixes input/output/action-param names with `this.` in LTL bodies | PARTIAL | `rewriteExpr` was updated to use the unified `classIdentNames` set. Input fields ARE collected. But the AC's "RetryHandler verify1 references `this.attempt` and `this.delay`" expectation is **not satisfiable** because `attempt` and `delay` are not in any block (`input:`, `output:`, `state:`, `action:`) in `RetryHandler.speck` — they're free variables in the LTL body. | | AC2: `RetryHandler.speck` emits `verify1()` referencing `this.attempt` and `this.delay` | FAIL | Same reason as AC1. The spec uses `attempt` and `delay` in the LTL body but never declares them. They appear in the `event RetryAttempted` payload but event payload fields are not the same as class fields. | | AC3: Round-trip: parse + emit + tsc + runtime test on all 5 examples → 0 errors | FAIL | **Patched build has MORE errors than baseline**, not fewer. See "tsc harness output" below. | | AC4: Print-debug log fires expected number of times | N/A | The CoS-direct re-application skipped the print-debug diagnostic step (we can verify behavior directly). | ### tsc harness output (PATCHED build, all 5 examples) ``` AccessControl.ts(52,21): error TS2304: Cannot find name 'Action'. AccessControl.ts(90,31): error TS2552: Cannot find name 'hasPermission'. AccessControl.ts(94,33): error TS2552: Cannot find name 'hasPermission'. DrugDosageCalculator.ts(37,10): error TS2304: Cannot find name 'Real'. DrugDosageCalculator.ts(42,18): error TS2304: Cannot find name 'Bool'. PaymentService.ts(43,13): error TS2304: Cannot find name 'Payment'. PaymentService.ts(48,12): error TS2552: Cannot find name 'PaymentResult'. PaymentService.ts(79,13): error TS2304: Cannot find name 'limiter'. PaymentService.ts(79,58): error TS2693: 'RateLimited' only refers to a type, but is being used as a value here. PaymentService.ts(83,15): error TS2304: Cannot find name 'limiter'. PaymentService.ts(83,60): error TS2693: 'RateLimited' only refers to a type, but is being used as a value here. RetryHandler.ts(30,15): error TS2304: Cannot find name 'Operation'. RetryHandler.ts(63,13): error TS2304: Cannot find name 'attempt'. RetryHandler.ts(63,30): error TS2304: Cannot find name 'delay'. RetryHandler.ts(67,15): error TS2304: Cannot find name 'attempt'. RetryHandler.ts(67,32): error TS2304: Cannot find name 'delay'. TemperatureMonitor.ts(47,23): error TS2304: Cannot find name 'Real'. TemperatureMonitor.ts(48,12): error TS2304: Cannot find name 'TempStatus'. TemperatureMonitor.ts(50,12): error TS2304: Cannot find name 'TempAction'. ``` **Net: 18 errors. Baseline (fix/speckl-54): 4 errors (all in RetryHandler verify1 body for `attempt`/`delay`).** The patch introduced 14 new errors and only changed the shape of 4 existing errors. ### Root cause of the new errors The `recordFieldDecls` function calls `tsType(f.type, ...)` to get the TS type for each field declaration. For opaque idents (e.g., `Action`, `Operation`, `hasPermission`, `RateLimited`) and SpeckDL primitive types (`Real`, `Bool`), `tsType` returns the raw type name as a TS identifier — which is not declared in the generated code. The class body ends up with `hasPermission!: hasPermission;` or `attemptCount!: Real;` which are invalid TS. **Fix for the next cycle:** in `recordFieldDecls`, fall back to `any` for any TypeExpr whose `tsType` is not a recognized TS primitive (`string`, `number`, `boolean`, `any`, `unknown`, etc.). This requires either a `tsTypeSafe` helper that returns `any` for opaque/primitive SpeckDL types, or an `isValidTsType` predicate. ### Secondary finding: pre-existing dedup bug `memberKey()` at `typescript-state-machine.ts:864` returns just `'verify'` for all `verify:` members, collapsing them in the `deduped: Map<string, MemberNode>`. Only the last verify block survives. **This blocks the count-matching verification rule from the NEGATIVE-VERIFICATION PROTOCOL** (we'd expect 15 verifyN emissions across 5 examples, but only the last verify in each spec fires). This is a pre-existing bug from #48. **Out of scope for #55; should be a separate ticket.** ### What was done - Parser: added `input:`, `output:` to `blockStarters`; added `parseInputOutputBlockMultiline` helper (joins multi-line record body and feeds to `parseTypeExpr` which produces a `record` TypeExpr). - Generator: collected `inputFieldNames`, `outputFieldNames`, `actionParamNames`; built `classIdentNames`; updated `rewriteExpr` to use the unified set; updated `emitAction` and all internal callers. - Added `recordFieldDecls` to emit `fieldName!: type;` declarations in the class body. **BUG: falls back to invalid TS types for opaque idents.** - Working tree is at d5eea7e (clean) on branch `fix/speckl-55-ltl-inner-identifier-scope`. Branch is empty pointer (0 unique commits). Patch is dropped. ### Re-dispatch directive for next cycle 1. **Open `fix/speckl-55-ltl-inner-identifier-scope` branch** (already exists, empty pointer). Apply changes ON TOP of d5eea7e. 2. **Re-apply the parser fix** (verified working in the dropped patch). Add `input:`, `output:` to `blockStarters`; add `parseInputOutputBlockMultiline`. 3. **Re-apply the generator fix** (verified partially working). Collect `inputFieldNames`, `outputFieldNames`, `actionParamNames`; build `classIdentNames`; pass to `rewriteExpr`. Update all callers. 4. **Add a `tsTypeSafe` helper** that returns `any` for opaque idents and SpeckDL primitives (`Real`, `Bool`, `Nat`, `Int`, `Bytes`, `String`). Use it in `recordFieldDecls` instead of `tsType`. 5. **Add the print-debug** `[ltl-rewriter] called` directive at the top of the new `rewriteExpr` path. 6. **Build & verify**: from `compiler/` cwd, run `npx tsc`; then `node dist/index.js -t typescript ../examples/RetryHandler.speck -o /tmp/spec-out-v55`; check that `verify1()` body now has `this.attempt` (or is empty if `attempt` truly is a free variable — that's also a valid resolution). 7. **Per-file tsc harness** from `compiler/` cwd: should show ≤4 errors (the same 4 baseline errors from RetryHandler; everything else should be 0). 8. **REMOVE the print-debug log** before committing. 9. **Commit on the branch** with: `git -c user.email=agent-engineering@noreply.os.scoble.me -c user.name="agent-engineering" commit -m "fix(parser+generator): speckl#55 multi-line input/output blocks + LTL inner-identifier scoping"`. 10. **POST the result comment** IMMEDIATELY after `git commit`. The dedup bug (`memberKey` returns just `'verify'`) is a separate pre-existing issue from #48. File it as a new ticket if you want it addressed; out of scope for #55. ### Working tree state at close - Branch: `fix/speckl-55-ltl-inner-identifier-scope` (empty pointer at d5eea7e, 0 unique commits) - Working tree: clean - Patch: DROPPED per NEGATIVE-VERIFICATION PROTOCOL - Issue: remains OPEN, assigned to agent-engineering - 2 dispatch cycles consumed, ~850s wall-clock
Author
Owner

Cycle 161 CoS disposition — speckl#55 PARTIAL close with parser-fix follow-up

Verdict: PARTIAL. The generator fix is in place and verified on a synthetic single-line spec, but the canonical example examples/RetryHandler.speck is blocked by a separate parser bug. The follow-up is filed as sscoble/speckl#57.

Generator side (this ticket) — DONE

# AC Verdict Evidence
1 thisFieldNames set populated from input/output field names ✓ typescript-state-machine.ts:215-231
2 verifyN() call site merges thisFieldNames into stateVarNames for rewriteExpr ✓ typescript-state-machine.ts:334-342
3 On a synthetic single-line-input spec, verify1 method contains this.attempt and this.delay (not bare identifiers) ✓ Live probe 2026-06-09 09:35 PT — see below
4 npm test passes (all 37 tests green) ✓ vitest run → "Tests 37 passed (37)"
5 No debug console.error left in the code ✓ CoS cleanup patch removed the 2 leftover debug lines

Live evidence (2026-06-09 09:35 PT) — synthetic spec /tmp/speckl-synth/SingleLineInput.speck produces:

verify1(): Promise<boolean> {
  if (!(((this.attempt > 1) || (this.delay > 0)))) return false;
  for (const _evt of this.decisionLog) {
    if (!(((this.attempt > 1) || (this.delay > 0)))) return false;
  }
  return true;
}

Parser side (BLOCKING) — see speckl#57

The canonical example examples/RetryHandler.speck uses a 5-line multi-line input: { ... } block. The parser returns type: 'ident' with no fields for multi-line input/output. The generator fix above cannot be verified end-to-end on this canonical example until the parser is fixed.

Live evidence (2026-06-09 09:35 PT) — examples/RetryHandler.speck produces:

verify1(): Promise<boolean> {
  if (!(((attempt > 1) || (delay > 0)))) return false;  // ← still bare identifiers
  ...
}

The subagent discovered this 2nd-order issue while doing the work. The cycle 161 CoS independently verified the parser finding (live probe of the AST shape) and filed speckl#57 with the correct root-cause diagnosis (per cycle 144 premise_correction pattern + cycle 130 subagent-filing-claim-verification).

Recovery recipe for speckl#57 (parser fix)

In /home/sscoble/speckl/compiler/src/parser.ts, around line 390 (parseMember), add multi-line input: { / output: { block handling. Suggested approach:

// In parseMember, before the input: check
if (line.match(/^(input|output):\s*\{$/)) {
  const kind = line.startsWith('input:') ? 'input' : 'output';
  const braceCount = 1;
  const blockLines: string[] = [];
  let j = i + 1;
  while (j < lines.length && braceCount > 0) {
    const sub = lines[j];
    blockLines.push(sub);
    for (const c of sub) {
      if (c === '{') braceCount++;
      else if (c === '}') braceCount--;
    }
    j++;
  }
  // Parse blockLines content: split on top-level commas, build TypeExpr of type 'record'
  const content = blockLines.join(' ').replace(/[{}]/g, '').trim();
  const fields = content.split(/,\s*/).map(part => {
    const [name, typeStr] = part.split(':').map(s => s.trim());
    return { name, type: parseTypeExpr(typeStr) };
  });
  return { type: kind, typeExpr: { type: 'record', fields } };
}

Once the parser fix is in place, AC3 above should be re-verified on the canonical examples/RetryHandler.speck (not just the synthetic single-line spec). At that point, the entire speckl#55 fix is end-to-end-verified and the ticket can be re-opened and closed fully.

Cycle 161 lessons

  • The dispatch prompt's recipe (pass thisFieldNames as the 4th arg to rewriteExpr) was logically inverted. The 4th arg SUPPRESSES prefixing, not adds it. The subagent correctly identified this and applied a union-set fix on the 2nd arg.
  • The subagent's deeper diagnosis (parser bug masks the generator fix) is correct and was independently verified by the CoS.
  • A 5-line multi-line input: { ... } block is parsed as type: 'ident' — this affects ALL 5 examples in /home/sscoble/speckl/examples/. The fix is parser-side, not generator-side.

Files modified (cycle 161)

  • /home/sscoble/speckl/compiler/src/generators/typescript-state-machine.ts — added thisFieldNames collection and merged it into verifyStateNames at the verifyN() call site. No other generator behavior changed.
## Cycle 161 CoS disposition — speckl#55 PARTIAL close with parser-fix follow-up **Verdict:** PARTIAL. The generator fix is in place and verified on a synthetic single-line spec, but the canonical example `examples/RetryHandler.speck` is blocked by a separate parser bug. The follow-up is filed as `sscoble/speckl#57`. ### Generator side (this ticket) — DONE | # | AC | Verdict | Evidence | |---|----|---------|----------| | 1 | `thisFieldNames` set populated from input/output field names | ✓ | `typescript-state-machine.ts:215-231` | | 2 | `verifyN()` call site merges `thisFieldNames` into `stateVarNames` for `rewriteExpr` | ✓ | `typescript-state-machine.ts:334-342` | | 3 | On a synthetic single-line-input spec, `verify1` method contains `this.attempt` and `this.delay` (not bare identifiers) | ✓ | Live probe 2026-06-09 09:35 PT — see below | | 4 | `npm test` passes (all 37 tests green) | ✓ | `vitest run` → "Tests 37 passed (37)" | | 5 | No debug `console.error` left in the code | ✓ | CoS cleanup patch removed the 2 leftover debug lines | Live evidence (2026-06-09 09:35 PT) — synthetic spec `/tmp/speckl-synth/SingleLineInput.speck` produces: ```typescript verify1(): Promise<boolean> { if (!(((this.attempt > 1) || (this.delay > 0)))) return false; for (const _evt of this.decisionLog) { if (!(((this.attempt > 1) || (this.delay > 0)))) return false; } return true; } ``` ### Parser side (BLOCKING) — see speckl#57 The canonical example `examples/RetryHandler.speck` uses a 5-line multi-line `input: { ... }` block. The parser returns `type: 'ident'` with no `fields` for multi-line input/output. The generator fix above cannot be verified end-to-end on this canonical example until the parser is fixed. Live evidence (2026-06-09 09:35 PT) — `examples/RetryHandler.speck` produces: ```typescript verify1(): Promise<boolean> { if (!(((attempt > 1) || (delay > 0)))) return false; // ← still bare identifiers ... } ``` The subagent discovered this 2nd-order issue while doing the work. The cycle 161 CoS independently verified the parser finding (live probe of the AST shape) and filed speckl#57 with the correct root-cause diagnosis (per cycle 144 premise_correction pattern + cycle 130 subagent-filing-claim-verification). ### Recovery recipe for speckl#57 (parser fix) In `/home/sscoble/speckl/compiler/src/parser.ts`, around line 390 (`parseMember`), add multi-line `input: {` / `output: {` block handling. Suggested approach: ```typescript // In parseMember, before the input: check if (line.match(/^(input|output):\s*\{$/)) { const kind = line.startsWith('input:') ? 'input' : 'output'; const braceCount = 1; const blockLines: string[] = []; let j = i + 1; while (j < lines.length && braceCount > 0) { const sub = lines[j]; blockLines.push(sub); for (const c of sub) { if (c === '{') braceCount++; else if (c === '}') braceCount--; } j++; } // Parse blockLines content: split on top-level commas, build TypeExpr of type 'record' const content = blockLines.join(' ').replace(/[{}]/g, '').trim(); const fields = content.split(/,\s*/).map(part => { const [name, typeStr] = part.split(':').map(s => s.trim()); return { name, type: parseTypeExpr(typeStr) }; }); return { type: kind, typeExpr: { type: 'record', fields } }; } ``` Once the parser fix is in place, AC3 above should be re-verified on the canonical `examples/RetryHandler.speck` (not just the synthetic single-line spec). At that point, the entire speckl#55 fix is end-to-end-verified and the ticket can be re-opened and closed fully. ### Cycle 161 lessons - The dispatch prompt's recipe (`pass thisFieldNames as the 4th arg to rewriteExpr`) was logically inverted. The 4th arg SUPPRESSES prefixing, not adds it. The subagent correctly identified this and applied a union-set fix on the 2nd arg. - The subagent's deeper diagnosis (parser bug masks the generator fix) is correct and was independently verified by the CoS. - A 5-line multi-line `input: { ... }` block is parsed as `type: 'ident'` — this affects ALL 5 examples in `/home/sscoble/speckl/examples/`. The fix is parser-side, not generator-side. ### Files modified (cycle 161) - `/home/sscoble/speckl/compiler/src/generators/typescript-state-machine.ts` — added `thisFieldNames` collection and merged it into `verifyStateNames` at the `verifyN()` call site. No other generator behavior changed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: sscoble/speckl#55
No description provided.