Skip to content

SLICE-1.5.1: Automated Capture Follow-ups ​

Status: Superseded as of 2026-04-27, along with SLICE-1.5 itself. Items 1, 2, and 4 of this slice were specific to the retired automated rig (scenario-Home wiring, Capture-Measurements.ps1 partial-CSV protection, runbook §3a UI-binding caveat) and went away with the rig. Item 3 (the ObjectDisposedException at scenario shutdown / _disposed Interlocked guard on SimulatedTagSource) was a real DI double-disposal bug that survives in the codebase — the guard pattern was also applied to SimulatedCamera defensively. The replacement FlaUI rig (SLICE-1.6) shipped 2026-04-27. Read the rest of this spec for context only — do not implement against it.

Goal ​

Close the four findings from the SLICE-1.5 review before SLICE-1.2 begins capturing under the new automation. Items target a layering violation in the scenarios, an unenforced safety check in the orchestrator, a real shutdown defect surfaced by scenario mode, and a missing rationale in the runbook. None of the four are large; together they prevent compounding cost across the remaining Phase 1 slices.

Why This Slice ​

Every issue here either (a) will be copied into the next scenario the moment SLICE-1.2 lands, or (b) silently undermines the row already in the table. None block SLICE-1.5's exit gate, but all four become more expensive to fix once a second scenario class exists, a second row is captured, or a second operator becomes accustomed to the current behavior.

In Scope ​

1. Insert Home between runs in scenarios ​

DemoBaselineScenario.cs:119-123 (and the same block in MultiTagSoakScenario.cs) directly mutates AppState.WorkflowState to Ready after each run, bypassing WorkflowService and violating SLICE-1.5 acceptance criterion 9 ("scenarios call ICommand instances, not underlying methods").

The actual operator pattern is: after a run reaches Completed, the Start Run button disables (CanStart requires Idle / Ready), the operator clicks Home to return the workflow to Ready, then clicks Start Run for the next iteration. The scenario hack short-circuits the Home step rather than calling it. The runbook §4.1 step list is misleading on this point — its "Repeat step 6 continuously" instruction skips the implicit Home step that every manual operator performs.

The fix:

  • replace the state.Update(s with WorkflowState = Ready) block in both scenarios with ops.Home.Execute(null) followed by WaitForStateAsync(s => s.WorkflowState == Ready && s.IsMotionHomed) — the same wait the scenarios already use for the initial Home in the setup sequence
  • update the runbook §4.1 step list to make the inter-run Home explicit (insert a step "6a. Click Home. Wait until homing completes." between the existing "Click Start Run" and the "Repeat step 6 continuously" instruction); apply the same correction to §4.2
  • no CommandGuards changes; no new operator command; the scenario simply calls the command the manual flow always required

2. Enforce partial-CSV protection in the orchestrator ​

tools/Capture-Measurements.ps1:129-131 only emits Write-Warning on a non-zero app exit code, then proceeds to extract a row block from a possibly-truncated CSV. Tighten to match the original spec:

  • on $appExitCode -ne 0: rename $OutputCsv to ${OutputCsv}.partial, log the exit code with the path, exit 1
  • exception: a known-benign exit code list (currently containing only -532462766, the ObjectDisposedException from item 3) is allowed to proceed with a single Write-Warning. The list is a $KnownBenignExitCodes array near the top of the script so future entries are obvious. Once item 3 is fixed, the exception list collapses to empty and any non-zero code fails the script.

3. Investigate and fix the ObjectDisposedException at scenario-mode shutdown ​

Exit code -532462766 (0xE0434352 mapped — managed exception) fires after every scenario completes, originating from SimulatedTagSource.Dispose during host shutdown. It does not fire under interactive close. The investigation pass:

  • attach a debugger to --scenario Fake --duration 5 and capture the disposal stack
  • confirm whether the cause is shutdown ordering (a singleton being disposed twice, or before its dependents) or a missing guard inside SimulatedTagSource.Dispose
  • fix at the smallest reasonable scope (likely a _disposed flag inside SimulatedTagSource, or reordering the host's StopAsync sequence to drain consumers before disposing producers)
  • re-run --scenario DemoBaseline --duration 60 and confirm exit code 0

After the fix lands, remove -532462766 from the orchestrator's known-benign-exit-codes list (item 2) and from the §3a "App exit code" subsection of the runbook.

4. Add the UI-binding-regression caveat to runbook §3a ​

The §3a "When to fall back to the manual procedure" section lists partial slices, mid-scenario pauses, and observing UI live — but not the dispatcher-bypass rationale, which is the primary design tradeoff of the whole slice. Add one paragraph (or extend the bullet list) explicitly:

Use the manual §3 procedure when verifying a UI-binding regression. The automated scenarios call ICommand instances directly, so a binding that broke between XAML and the view-model (a typo in {Binding}, a mistyped CommandParameter, a CanExecute no longer raising CanExecuteChanged) will not surface in an automated capture even though the scenario reports success. For UI-affecting changes, run §3 manually as a spot-check before trusting §3a's row.

Position immediately after the existing fallback bullets in §3a.

Out of Scope ​

  • broader refactor of CommandGuards beyond what item 1 needs
  • new operator commands beyond what item 1's chosen option requires
  • new metrics or new measurement scenarios
  • a §3a-equivalent for SLICE-1.2's frame-payload scenario (that lands with SLICE-1.2 itself)
  • adding a recurring CI capture
  • replacing dotnet-counters with an in-process exporter
  • back-revising row 0a (the existing capture stands; if item 1's fix changes scenario timing materially, capture row 0b under the fixed code rather than rewriting 0a)

Acceptance Criteria ​

This slice is satisfied only if all of the following are true:

  1. DemoBaselineScenario and MultiTagSoakScenario no longer call state.Update directly. The post-run reset is performed via ops.Home.Execute(null) plus a WaitForStateAsync on WorkflowState == Ready && IsMotionHomed. A grep for state.Update under src/InspectionPrototype.Application/Scenarios/ returns no matches.
  2. Runbook §4.1 and §4.2 step lists explicitly include the inter-run Home step. The manual procedure on paper now matches what manual operators actually do.
  3. tools/Capture-Measurements.ps1 exits non-zero (and renames the CSV to .partial) on any non-zero app exit code outside the explicit $KnownBenignExitCodes allowlist. A unit-style smoke test (a fake app that exits 1) demonstrates the partial-rename path.
  4. --scenario Fake --duration 5 and --scenario DemoBaseline --duration 60 both exit with code 0 after item 3's fix. The orchestrator's $KnownBenignExitCodes array is empty (or its only remaining entries have a documented justification that does not include -532462766).
  5. Runbook §3a contains the UI-binding-regression caveat as a distinct paragraph or bullet, placed in the "When to fall back to the manual procedure" subsection.
  6. Row 0a is unchanged; its commit hash and CSV are not rewritten. The item 1 fix is expected to shift per-run cadence (inter-run Home time was missing under the old hack), so capture row 0b under --operator-delay 0 in the same PR and add a one-line note on row 0a marking row 0b as the new automated reference for SLICE-1.2 onward. Row 0b should land closer to row 0's runs.completed (≈41) than row 0a's (57) because the Home step is now in the loop.
  7. The full existing test suite still passes (currently 311 tests). Any test added in items 1 or 3 lifts the count.

Verification Notes ​

The implementation task for this spec must include verification for:

  • that no other code path under src/ calls state.Update to mutate WorkflowState directly outside of WorkflowService itself (a grep guard against future regressions of item 1)
  • that the --scenario Fake smoke run is reproducible from a clean checkout in under one minute (the simplest end-to-end signal that scenario mode works; item 3's fix should not slow it down)
  • that the partial-CSV path in item 2 produces a .partial file that is not picked up by a subsequent ConvertTo-MeasurementRow call — the orchestrator's failure should be loud, not quietly emit a row block from incomplete data
  • that the §3a addition in item 4 renders correctly under VitePress (the docs site uses VitePress per the recent fix vitepress commit)

Docs-first project memory for AI-assisted implementation.