---
phase: 04-animation-flow-control
plan: 09
type: execute
wave: 7
depends_on:
  - "04-08"
files_modified:
  - .planning/phases/04-animation-flow-control/gap_analysis.py
  - .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md
autonomous: false
gap_closure: true
requirements:
  - ANIM-03

must_haves:
  truths:
    - "H1 vs H2 is discriminated with recorded evidence: 04-GAP-ANALYSIS.md states an explicit verdict citing which pre-declared interpretation rules (R1-R5) fired, backed by per-arm loss rates with confidence intervals, a Fisher exact shipped-vs-replica comparison, loss-vs-frame-burst clustering from the event data, the FPS trend, and the two-device comparison"
    - "Every statistic in the analysis is reproducible: gap_analysis.py recomputes it from the committed evidence files, and the interpretation rules were fixed in this plan BEFORE the analysis ran — the analysis applies them, it never invents or adjusts them"
    - "No UAT threshold, harness file, ROADMAP criterion, or src/ file is modified by this plan — where recalibration is the indicated route, the analysis contains PROPOSED amendment text clearly marked as awaiting an operator decision, nothing more"
    - statement: "The operator has recorded an explicit routing decision — fix / recalibrate / rerun / investigate — with the evidence in hand, captured verbatim in the 04-09 summary"
      verification: backstop
  artifacts:
    - path: ".planning/phases/04-animation-flow-control/gap_analysis.py"
      provides: "zero-dependency stats runner (Wilson CIs, Fisher exact, binomial probabilities, event-clustering) over the 04-08 evidence"
    - path: ".planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md"
      provides: "the decision brief: verdict, fired rules, evidence tables, route-specific consequences, and any proposed (not applied) amendment text"
  key_links:
    - from: ".planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md"
      to: ".planning/phases/04-animation-flow-control/04-GAP-INVESTIGATION.json"
      via: "every table recomputed from the committed evidence by gap_analysis.py"
      pattern: "gap_analysis.py"
    - from: ".planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md"
      to: ".planning/phases/04-animation-flow-control/04-GAP-SPIKE-FORENSICS.md"
      via: "the H2 calibration finding carried into the verdict"
      pattern: "spike 003 forensics"
---

<objective>
Turn the 04-08 evidence into a routed resolution of the ANIM-03 gap: apply the
pre-declared interpretation rules to the investigation data, state an explicit H1/H2
verdict in a decision brief, then BLOCK on the operator's routing decision. The
possible routes — a src/ fix plan, an operator-authored threshold recalibration, a
plain 04-06 re-run, or further investigation — have different consequences for
04-06 Task 2 and 04-07, which stay untouched and un-run until this decision lands.

The interpretation rules are fixed HERE, in this plan, before any analysis executes —
the same fixed-before-run discipline the UAT thresholds follow. The analysis may find
the rules inconclusive (that is an honest outcome routed to "investigate"), but it may
never bend them.

Purpose: ANIM-03's gate stays honest — the FAIL is explained with evidence and the
operator, not an executor, decides what the 0% criterion becomes (ROADMAP criterion 3).
Output: gap_analysis.py + 04-GAP-ANALYSIS.md + the operator's verbatim routing decision.
</objective>

<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/ROADMAP.md
@.planning/phases/04-animation-flow-control/04-GAP-SPIKE-FORENSICS.md
@.planning/phases/04-animation-flow-control/04-GAP-INVESTIGATION.json
@.planning/phases/04-animation-flow-control/04-08-SUMMARY.md
@.planning/phases/04-animation-flow-control/04-06-PLAN.md
@.planning/phases/04-animation-flow-control/04-07-PLAN.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Compute the discriminating statistics and write the decision brief (04-GAP-ANALYSIS.md)</name>
  <files>.planning/phases/04-animation-flow-control/gap_analysis.py, .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md</files>
  <read_first>
    - .planning/phases/04-animation-flow-control/04-GAP-INVESTIGATION.json — the per-arm aggregates this task analyses
    - .planning/phases/04-animation-flow-control/04-GAP-INVESTIGATION-EVENTS.jsonl — the per-query/per-frame events for the clustering analysis
    - .planning/phases/04-animation-flow-control/04-GAP-SPIKE-FORENSICS.md — the settled calibration finding and the enumerated spike-vs-shipped mechanical differences
    - .planning/phases/04-animation-flow-control/04-08-SUMMARY.md — any recorded instrument deviations that qualify the evidence
  </read_first>
  <action>
    First build gap_analysis.py in the phase dir: a stdlib-only (zero-dependency
    project) analysis runner that reads the two evidence files and prints every table
    the brief embeds, so each number is reproducible by rerunning one command. It must
    implement: (a) Wilson score 95% intervals (closed form, z=1.96) for each arm's
    pooled loss; (b) rule-of-three upper bounds (3/n) for any zero-loss arm; (c) exact
    two-sided Fisher test via math.comb hypergeometric tails comparing shipped-arm vs
    replica-arm pooled losses; (d) P(zero losses in n | p) = (1-p)**n where p is the
    pooled 04-06 UAT rate from the forensics doc; (e) event clustering over the JSONL:
    for every shipped/sweep-arm query, derive the gate-outstanding value at query send
    time (latest preceding frame event), the count of non-gated frames inside the query
    window [t, t+2.0 s], and the time since the last non-gated frame — then report each
    of those three, split by outcome ok vs lost (medians and counts); (f) a loss-vs-FPS
    table over 10/15/20; (g) a primary-vs-fallback device table; (h) the control-arm
    ambient floors. ruff check + format and pyright strict clean; commit before running
    it with prefix "feat(04-09):" via git commit -s.

    Then write 04-GAP-ANALYSIS.md (Australian English) embedding gap_analysis.py's
    verbatim output and applying these PRE-DECLARED interpretation rules — fixed now,
    before analysis; the brief must cite which rules fired and may not add new ones:

    - R1 (ambient floor): any control arm shows pooled loss > 0 with n >= 100 →
      single-shot queries are lossy on this network even without streaming → a 0%
      criterion is unattainable independent of the gate → supports recalibration.
    - R2 (sampling artefact confirmed): the replica arm (spike 003 methodology, n >=
      150) shows pooled loss >= 2% → spike 003's 0/50 was a small-sample draw from a
      lossy distribution → H2 confirmed; combined with R4's answer, route to
      recalibration (floor) — there is no methodology delta to fix.
    - R3 (mechanical delta — H1-fixable): the replica arm holds 0 losses over >= 150
      queries while the shipped arm shows >= 2% on the same device in the same session
      (Fisher p < 0.05) → a real difference between the spike arm and the shipped path
      exists → H1-fixable candidate. The brief must then rank the forensics doc's
      enumerated mechanical differences (per-packet event-loop-yielding sends vs
      synchronous burst; DeviceConnection vs raw-socket prober) by what the clustering
      data supports, as the input to a follow-up fix plan.
    - R4 (load saturation — H1-floor): using pooled per-FPS loss rates (10 and 15 FPS
      from the sweep arm, ~90 queries each; 20 FPS from the shipped arm, ~180 queries):
      pooled loss is monotonically non-decreasing across 10 → 15 → 20 FPS, AND the
      10 FPS pooled rate is strictly below its own rule-of-three bound (3/n at 10 FPS,
      ≈ 3.3% at n ≈ 90; pooled losses = 0 satisfies this trivially), AND the 20 FPS
      pooled rate is at or above that same bound → the loss at 20 FPS is the device
      saturating near its ~20 msg/s handling limit → an unfixable floor at the ANIM-03
      operating point → supports recalibration with the measured floor.
    - R5 (non-reproduction): every shipped arm round on both devices shows 0 losses →
      the 04-06 FAIL does not reproduce → route to a plain 04-06 re-run, unchanged.
    - Otherwise, or where rules conflict beyond the combinations above: verdict
      "ambiguous", route to further investigation with the specific open question named.

    Required brief sections: "## Evidence inventory" (files + commits + any 04-08
    deviations), "## Statistical tables" (gap_analysis.py output verbatim), "## Rules
    fired", "## Verdict" (explicit H1-fixable / H1-floor / H2 / non-reproduction /
    ambiguous), "## Consequences per route" (what each of fix / recalibrate / rerun /
    investigate would concretely entail — including that recalibrate means an
    OPERATOR-authored amendment to ROADMAP success criterion 3, REQUIREMENTS ANIM-03
    wording, and the harness MAX_QUERY_LOSS_PCT constant, followed by a 04-06 re-run
    under the amended criterion; and that fix means a TDD src/ change plan at the
    04-01..04-04 standard followed by a 04-06 re-run), and — only if the verdict
    supports recalibration — "## Proposed amendment (NOT applied — operator decision
    required)" with exact replacement wording for ROADMAP criterion 3 that states the
    evidence-derived threshold and cites the evidence files by name. Do NOT edit
    ROADMAP.md, REQUIREMENTS.md, uat_ack_stream.py or src/ in this task under any
    verdict (D4-01's tuning constants likewise stay untouched — this plan interprets,
    it does not retune).

    Commit the brief with prefix "docs(04-09):" via git commit -s.
  </action>
  <verify>
    <automated>uv run python .planning/phases/04-animation-flow-control/gap_analysis.py >/dev/null && uv run ruff check .planning/phases/04-animation-flow-control/gap_analysis.py && grep -q "## Verdict" .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md && grep -q "## Rules fired" .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md</automated>
  </verify>
  <acceptance_criteria>
    - gap_analysis.py runs clean against the committed evidence (exit 0) with only stdlib imports; ruff and pyright clean
    - 04-GAP-ANALYSIS.md contains all six required sections; every number in its tables appears in gap_analysis.py's output
    - The verdict is exactly one of: H1-fixable, H1-floor, H2, non-reproduction, ambiguous — and cites the fired rules R1-R5 by name
    - If the verdict supports recalibration, the proposed amendment section is present, marked NOT applied, and quotes exact proposed ROADMAP criterion-3 wording; git diff shows ROADMAP.md, REQUIREMENTS.md, uat_ack_stream.py and src/ unmodified by this plan
    - If the verdict is H1-fixable, the mechanical-difference ranking cites specific clustering numbers, ready to seed a fix plan
  </acceptance_criteria>
  <done>The gap has an evidence-backed, reproducible verdict under rules fixed before the analysis ran, and the operator has a decision brief in which every route's consequences are spelled out.</done>
</task>

<task type="checkpoint:decision" gate="blocking">
  <name>Task 2: Operator routing decision — what does the ANIM-03 gate become?</name>
  <decision>How to resolve the ANIM-03 UAT gap given the 04-GAP-ANALYSIS.md verdict: fix the implementation, recalibrate the criterion, re-run the UAT unchanged, or investigate further</decision>
  <context>
    04-06 FAILed its fixed 0% concurrent-query-loss threshold twice (pooled ~3.4%;
    every other threshold passed comfortably). The forensics settled that spike 003's
    0% came from a single 50-query round that could not resolve rates below ~6%, and
    the 04-08 hardware arms measured whether the residual loss is mechanically fixable,
    a device floor, or non-reproducing. Per the phase UAT rules, changing a threshold
    is exclusively an operator decision recorded with evidence — no executor may make
    it. 04-06 Task 2 (visual verdict) and all of 04-07 (Ceiling, ANIM-04) remain
    pending and must not run until this decision routes them. Present the operator with
    04-GAP-ANALYSIS.md's Verdict, Rules fired, and Consequences-per-route sections
    (and the Proposed amendment section if present) before asking.
  </context>
  <options>
    <option id="fix">
      <name>fix — H1-fixable route</name>
      <pros>Keeps the 0% target; closes a real mechanical gap between the spike arm and the shipped path; the clustering evidence seeds a concrete TDD fix plan (04-10+) at the 04-01..04-04 standard, then 04-06 re-runs unchanged</pros>
      <cons>Only defensible if the analysis verdict is H1-fixable; src/ churn on a 100%-covered layer; another hardware UAT cycle afterwards</cons>
    </option>
    <option id="recalibrate">
      <name>recalibrate — operator-authored criterion amendment</name>
      <pros>Honest if the verdict is H2 or H1-floor: the criterion tracks what the hardware can actually do (still a ~4x improvement over the 14.6% blind-fire baseline); amendment text is pre-drafted with evidence citations</pros>
      <cons>Relaxes the headline 0% claim; requires the operator to amend ROADMAP criterion 3, REQUIREMENTS ANIM-03 and the harness constant (a follow-up plan applies the operator-approved wording), then 04-06 re-runs under the amended criterion</cons>
    </option>
    <option id="rerun">
      <name>rerun — non-reproduction route</name>
      <pros>Correct if the investigation could not reproduce the loss (R5): the FAIL was environmental/transient; 04-06 re-runs byte-identical</pros>
      <cons>Only defensible under R5; if the loss was real but intermittent, this defers the problem to the next FAIL</cons>
    </option>
    <option id="investigate">
      <name>investigate — evidence ambiguous</name>
      <pros>Honest when rules conflict or discrimination failed; the brief names the specific open question for a targeted follow-up investigation plan</pros>
      <cons>ANIM-03, 04-06 Task 2 and 04-07 stay blocked; another hardware session</cons>
    </option>
  </options>
  <read_first>
    - .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md — the brief the operator decides from (present its Verdict, Rules fired and Consequences sections verbatim)
  </read_first>
  <action>
    Present the four options with the brief's verdict and the route the analysis
    supports, then BLOCK until the operator responds. Record the operator's decision
    and rationale VERBATIM in the 04-09 summary. This plan applies nothing: whatever
    the route, the implementing work (fix plan, amendment application, 04-06 re-run
    scheduling) happens in follow-up planning — surface the exact next command in the
    summary (route fix or investigate: /gsd-plan-phase 4 --gaps with the brief as
    evidence; route recalibrate: operator amendment then 04-06 re-run; route rerun:
    /gsd-execute-phase 4 resuming 04-06). Also record explicitly that 04-06 Task 2 and
    04-07 remain gated on this route landing. Never edit ROADMAP.md, REQUIREMENTS.md,
    uat_ack_stream.py or src/ here, even if the operator picks recalibrate — the
    decision is recorded now, applied by the follow-up under its own plan.
  </action>
  <verify>
    <human-check>Operator has read the decision brief and given an explicit routing decision (fix / recalibrate / rerun / investigate) with rationale, recorded verbatim</human-check>
  </verify>
  <acceptance_criteria>
    - The operator's decision and rationale appear verbatim in the 04-09 summary, alongside the brief's verdict they were given
    - The summary names the concrete next step for the chosen route and restates that 04-06 Task 2 and 04-07 are gated on it
    - No file outside the summary is modified by this task
  </acceptance_criteria>
  <done>The ANIM-03 gap has an operator-decided route recorded with evidence in hand — the threshold's fate was decided by the operator, never by an executor.</done>
  <resume-signal>Type one of: "fix", "recalibrate", "rerun", or "investigate" — optionally with rationale. Your words are recorded verbatim as the routing decision; the implementing work happens in the follow-up the summary names.</resume-signal>
</task>

</tasks>

## Artifacts this plan produces

| Artifact | Kind | Location |
|----------|------|----------|
| `gap_analysis.py` — reproducible stdlib-only stats runner over the 04-08 evidence | Analysis script (phase dir, never shipped) | `.planning/phases/04-animation-flow-control/gap_analysis.py` |
| `04-GAP-ANALYSIS.md` — decision brief: verdict, fired rules, tables, consequences per route, proposed (unapplied) amendment text where applicable | Decision brief | `.planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md` |
| Operator routing decision (verbatim) + next-step routing | Human decision record | `.planning/phases/04-animation-flow-control/04-09-SUMMARY.md` |

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Analysis → committed evidence | Read-only consumption of 04-08 evidence files; no device contact in this plan |
| Operator ← decision brief | The brief is the sole input to a criterion-affecting human decision |

## STRIDE Threat Register

| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-04-18 | Repudiation | Verdict + routing decision | medium | mitigate | Interpretation rules R1-R5 fixed in the plan before analysis; every statistic reproducible via gap_analysis.py; operator decision recorded verbatim |
| T-04-19 | Tampering | UAT thresholds / ROADMAP criterion 3 | high | mitigate | Hard prohibition: this plan edits no threshold, no ROADMAP/REQUIREMENTS text, no harness, no src/ — recalibration exists only as clearly-marked proposed text awaiting the operator; acceptance criteria verify via git diff |
| T-04-20 | Information disclosure | Evidence files (LAN IPs of test devices) | low | accept | Private-range IPs of the operator's own test devices, already present throughout phase artefacts |

No packages are installed by this plan (zero-dependency project) — no supply-chain gate applies.
</threat_model>

<verification>
- gap_analysis.py reproduces every number in the brief from the committed evidence
- 04-GAP-ANALYSIS.md verdict cites fired rules from the pre-declared R1-R5 set only
- Operator decision recorded verbatim; ROADMAP.md, REQUIREMENTS.md, uat_ack_stream.py and src/ untouched by this plan
- The summary names the follow-up route and restates the 04-06 Task 2 / 04-07 gating
</verification>

<success_criteria>
- H1 vs H2 discriminated with recorded, reproducible evidence under rules fixed before analysis, and the ANIM-03 routing decision made by the operator with that evidence in hand — thresholds untouched, gap closed by decision rather than by silent adjustment
</success_criteria>

<output>
Create `.planning/phases/04-animation-flow-control/04-09-SUMMARY.md` when done, recording:
the verdict and fired rules, the operator's verbatim routing decision and rationale, the
named follow-up step, the 04-06 Task 2 / 04-07 gating note, and commit hashes.
</output>
