Strong Slot Typing — Quality & Testing Report
For: Fable 5 design review
Scope: schema-0.2 strong slot typing (remote-control design §2.2) + the codex-review fixes
Date: 2026-07-02
Branch/commits: voice-capability-hierarchy → main — 2513a7d4, c6871ce3, cc84fa6a, 8d991170 (+ design docs 10ff8b52, a2d6d991)
1. What shipped
The slot_types facility was extended (not replaced) to make collect slots strongly typed, enforced at compile time (riffc L0-10) and runtime (validators):
- Unified
type:field — semantic names (enum,phone, …) never collide with JSON types (string, …), so onetype:carries both;{semantic_type: …}long-form still works. enumvalue domain —options(scalar or{value, label, aliases}), validated at load (non-empty), enforced at runtime (value ∈ options, alias→canonical).- Presentation —
label(screen),optional,widget(override, restyle-only per OD-R5), andreaskcanonized as the per-slot voice prompt (OD-R7). dateimplemented — extraction (_extract_calendar_dates) +IMPLEMENTED_TYPES, closing a pre-existing gap.- riffc L0-10 — an untyped collect slot is a hard build error.
2. Codex findings → fixes → evidence
Codex reviewed the design + first slice and found real gaps. All slice-level findings are closed with tests:
| # | Sev | Finding | Fix | Test evidence |
|---|---|---|---|---|
| 1 | HIGH | runtime ignored unified type: → {type: phone} accepted garbage |
resolve via effective_semantic_type at all 3 validation sites |
test_unified_type_enforced_at_runtime, test_unified_and_longform_agree_at_runtime |
| 2 | MED | {type: phone, semantic_type: email} silently resolved by precedence |
reject conflicting semantics at load | test_conflicting_semantics_rejected (3 forms) |
| 3 | HIGH | enum declared but not enforced at runtime |
enum_match membership check in validate_slot_value |
test_enum_accepts_and_canonicalizes (5), test_enum_rejects_out_of_domain (5), test_enum_rejects_injection_before_membership |
| 7 | HIGH | channel-bind unsound (design) | resolved by Fable 5 → §3.1 device-pairing (OD-R6) | design doc |
| 8 | MED | L0-11 undefined field-voice-prompt (design) | resolved → canonize reask (OD-R7) |
design doc |
Design findings #7/#8 are in Fable 5's court (recorded as OD-R6/OD-R7 §9). Surface-lane findings #4 (GoalSegment target_slots), #5 (enum option metadata), #6 (untyped-degrade) are assigned to the parallel surface session with the iter_slots(flow) directive.
3. Acceptance A5 coverage
A5:
riffcrejects a surface button naming a nonexistent token, a form field with an untyped slot, and a screen-only state with no voice path.
- Untyped-field leg — DONE & tested: L0-10 makes an untyped collect slot a hard error.
test_l0_10_untyped_synthetic_flow_errors(error-level, names the slot),test_l0_10_typed_synthetic_flow_compiles_clean. Corpus: 3 golden anchors emit 0 L0-10 errors; migration checklist enumerates the remaining 10 flows. - Token + screen-only legs are in the parallel surface slice (L0-11/L0-12).
4. Test inventory
| Suite | Tests | Focus |
|---|---|---|
test_slot_types_schema02.py |
16 | vocabulary, validation, unified-type + enum runtime, L0-10 |
test_slot_typing_adversarial.py |
53 | adversarial: every semantic via type:; enum alias/case/whitespace/injection/collision; conflicts in every form; runtime enforcement per semantic; widget override; date; L0-10 synthetic flows |
test_typed_slots_across_flows.py |
157 | cross-fleet: every typed slot resolves to an implemented type + validates type-correctly |
test_slot_type_ratchet.py |
2 | exact ratchet — no untyped behavioral slot silently added |
test_riffc_golden.py / test_riffc_all_flows.py |
3 + 60 | anchor census/checks locked; whole-corpus classification |
Adversarial highlights (the "under pressure" evidence): enum rejects an injection payload ("drain\x00; DROP TABLE") via the shape check before membership; alias collisions resolve deterministically by declaration order; conflicting type:/semantic_type: rejected; {type: phone} and {semantic_type: phone} proven byte-identical at runtime.
5. Regression discipline — every remaining failure is pre-existing
The full -k "validator or slot" suite has 11 failures + 1 error. Every one was verified failing on clean HEAD (41792be4, before any typing work) via isolated git worktree runs — pizza/scheduling/offer/fsm-debug scenario tests unrelated to typing. This slice:
- introduced zero new failures, and
- net-fixed one pre-existing failure (
apartment_intake:move_in_date=date) by implementingdate(test_typed_slots_across_flows: was 1-failed/150-passed → now 157/157).
Method note: each suspected regression was reproduced on a clean checkout before attribution — no failure was assumed mine or pre-existing without proof.
6. Honest caveats / open items
maincarries ~11 pre-existing test failures (pizza slot-extractor, austin acceptance/readback, scheduling offer loop) unrelated to typing — flagged, not owned by this slice.enumextraction is validation + membership only; free-speech→option extraction (constrained decode for collect) is a later slice — declaring an enum today validates a filled value but relies on the LLM/other extractors to propose it.- Surface-lane #4/#5/#6 (GoalSegment, enum metadata, untyped-degrade) are open in the parallel session; my L0-10 should adopt
iter_slotsonce it lands (single enumeration contract). - The I5 emission-location debt (state broadcast from transport, not engine bus) is separate and tracked in the console-map plan.
7. Verdict
Strong typing is real end-to-end: declared at load, enforced at compile (L0-10) and runtime (unified type: + enum membership), covered by 228 passing typing tests (schema02 16 + adversarial 53 + cross-fleet 157 + ratchet 2) including a 53-case adversarial suite, with a proven-zero-regression, net-positive impact on the corpus.