Revision 0.9 adds section 20, the cadence and event subsystems contract, and carries two corrections the implementation forced. Section 6.1 now states that a duration is the authored literal or a non-negative finite number already in milliseconds, since a DurationSpec may be the resolved output of a ValueSpec or a bounded TimeSpec, with the one documented exception of an automation track's `at`, which 19.1 keeps literal-only so that point ordering stays decidable at import. Section 20.11 documents the rejection of an undeclared input name in an event action's `with` map as ERR_UNKNOWN_FIELD — the section's own convention for that shape of error, replacing an invented code that appeared nowhere in the registry. The review record is committed with the code it describes: the two code triages that found these defects, the reconciliation plan that sequenced the fixes, and a follow-up debt record listing what was deliberately left open — the unchecked JSON Schema artifact, degenerate path arcs, post-effect transient allocation, the window-traffic fixture's per-copy wrap bounds, and the unstated `ownership: "persistent"` value on a sound action. None of the five blocks phase 6; all five are written down rather than dropped. Devlog entries are backfilled for the two milestones that had none: phase 3c slice 2, the audio lifecycle and voice ceilings, and slice 4d, the renderer core. The implementation status summary now reflects the reconciled state rather than the in-flight one. 231 tests pass. tools/verify-spec-contract.py reports 46 declared diagnostic codes with every used code resolving and its two long-standing unresolved cross-references unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01ShxxFqFmCUDQnQvFNm4TKy
5.7 KiB
Follow-up debt after the pre-Phase 6 reconciliation
Status: current as of the reconciliation commits landing on main (2026-09-06). This is the written-down remainder that 00-pre-phase6-reconciliation.md §2 asks for: findings from the two triages that were not dropped silently, recorded here so Phase 6 can start without losing them.
What was closed
Every Tier-1 finding of both triages is fixed in the code that shipped in these commits, and each is covered by a test that drives the production path rather than a duplicate of it:
| Finding | Source | Closed by |
|---|---|---|
F1 + F2 — invented ERR_SOUND_USAGE_MISMATCH, and the permitsAction short-circuit that let a scenario-only sound play from a manual action |
03-phase5-triage.md |
actions.js now checks allowedUsage.includes(callerUsage) against the caller's own context and raises ERR_UNSUPPORTED_TARGET per §20.2.2/§20.12. permitsAction is deleted. |
F3 — the second invented code, ERR_UNKNOWN_PARAMETER |
03-phase5-triage.md |
Resolved as the rename option, not the drop: the check raises ERR_UNKNOWN_FIELD, the section's own convention, and §20.11 documents the behavior as normative surface. |
| F4 + F5 — Traces 7–10 certifying a duplicate scheduler that disagreed with the shipping one on recency semantics | 03-phase5-triage.md |
Traces 7–10 now drive CadenceSubsystem.update()/advance() with a seeded RNG, as Traces 11–14 already did. triggerSound() and calculateEligiblePool() are deleted rather than reconciled. |
| C1 — production render path never created compositing surfaces; the first masked group permanently deactivated the visual subsystem | 02-triage.md |
VisualSubsystem.render passes a real createSurface factory (OffscreenCanvas, falling back to a detached canvas element), per R2's fix rather than R3's guard-and-skip. |
C2 — system-level behaviors arrays skipped semantic validation, so six classes of invalid document failed at activation instead of import |
02-triage.md |
validateBehaviors runs at system scope during import with options.fields set from the system context. |
The two items §2 asked to be promoted because Phase 6 will exercise them are also closed: C20 (remove on an unknown instance ID now returns refused, not executed) and C18 (a resolved non-integer count or burst count now raises ERR_TYPE_MISMATCH at the boundary instead of being rounded). Both touch the spawn/cleanup accounting the scenario director's ownership work depends on.
C5 — the one open design question — was decided by the user rather than guessed: a DurationSpec accepts the authored duration literal or a non-negative finite number already in milliseconds. The numeric form exists because a DurationSpec may be the resolved output of a ValueSpec or a bounded TimeSpec. Runtime (types.js), validator (visual-validation.js), and specification (§6.1) now agree, with the single documented exception of an automation track's at, which §19.1 keeps literal-only so point ordering stays decidable at import.
Beyond the reconciliation plan's required set, the working tree also carries fixes for C3, C4, C6, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C19, B1, S1, and P3.1. These were verified present in the source before the reconciliation commits; they are not separately re-certified here.
What remains open
None of the following blocks Phase 6. Each is real, and each should be picked up rather than forgotten.
- C7 — the JSON Schema is not a checked artifact. The schema has no consumer: nothing in
src/,tools/, ortest/validates against it, so it can drift from the validator without any test noticing (P3.5 makes the same point from the other direction). The concrete fix is to validate the exhibit fixtures against the schema in CI; until then, treatvalidator.jsas the single source of truth and the schema as documentation. - P3.2 — degenerate path arcs. A coincident-endpoint
arcforcesacos(0)and emits a phantom bulge; a negative radius is drawn mirrored rather than raisingERR_OUT_OF_BOUNDS. Early-return on coincident endpoints and check raw radii beforeMath.abs. - P3.3 — post-effect transient memory. Each blur pass allocates full-frame copies at up to 4096 device pixels, with no tie-in to the §19.5 pass budget. The device radius is now capped at 128, which bounds the work but not the allocation. Tiling the blur is the real fix.
- P3.4 — the
window-trafficchallenge fixture. Per-copywrapbounds are ±30px around each copy's own origin, so cars cycle in place instead of crossing the window. Either use the window rect as bounds, or document the cell-stutter as intended. The 4g suite only asserts finiteness and equality, so it passes either way. - F6 —
ownership: "persistent"on a sound action. The schema andcadence-validation.jsaccept it; §20.12 documents only"performance"and"scenario". This is probably deliberate carry-over from the §10.1 ownership vocabulary, but it is currently unstated. One line in §20.12 settles it either way — and it is worth settling before Phase 6, which is where scenario ownership becomes load-bearing.
Still user-observed, unchanged by this reconciliation
Carried forward verbatim from the project status, because nothing automated can close them: the slice 4h / GC6 hardware measurement (every §19.5 aggregate stays provisional until trace 21 of §19.7 runs on recorded hardware), the PRD 130 visual challenge judged on a real display, Phase 3's audible acceptance — no sound has yet been heard from a production build — and Phase 1's direct-file two-fixture restart observation.