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.ps1partial-CSV protection, runbook §3a UI-binding caveat) and went away with the rig. Item 3 (theObjectDisposedExceptionat scenario shutdown /_disposedInterlocked guard onSimulatedTagSource) was a real DI double-disposal bug that survives in the codebase — the guard pattern was also applied toSimulatedCameradefensively. 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.
- Status: Superseded by SLICE-1.6 (FlaUI capture, shipped)
- Date: 2026-04-25
- Depends on: SLICE-1.5: Automated Measurement Capture
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 withops.Home.Execute(null)followed byWaitForStateAsync(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
CommandGuardschanges; 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$OutputCsvto${OutputCsv}.partial, log the exit code with the path, exit 1 - exception: a known-benign exit code list (currently containing only
-532462766, theObjectDisposedExceptionfrom item 3) is allowed to proceed with a singleWrite-Warning. The list is a$KnownBenignExitCodesarray 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 5and 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
_disposedflag insideSimulatedTagSource, or reordering the host'sStopAsyncsequence to drain consumers before disposing producers) - re-run
--scenario DemoBaseline --duration 60and 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
ICommandinstances directly, so a binding that broke between XAML and the view-model (a typo in{Binding}, a mistypedCommandParameter, aCanExecuteno longer raisingCanExecuteChanged) 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
CommandGuardsbeyond 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-counterswith 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:
DemoBaselineScenarioandMultiTagSoakScenariono longer callstate.Updatedirectly. The post-run reset is performed viaops.Home.Execute(null)plus aWaitForStateAsynconWorkflowState == Ready && IsMotionHomed. A grep forstate.Updateundersrc/InspectionPrototype.Application/Scenarios/returns no matches.- 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.
tools/Capture-Measurements.ps1exits non-zero (and renames the CSV to.partial) on any non-zero app exit code outside the explicit$KnownBenignExitCodesallowlist. A unit-style smoke test (a fake app that exits 1) demonstrates the partial-rename path.--scenario Fake --duration 5and--scenario DemoBaseline --duration 60both exit with code0after item 3's fix. The orchestrator's$KnownBenignExitCodesarray is empty (or its only remaining entries have a documented justification that does not include-532462766).- 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.
- 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 0in 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'sruns.completed(≈41) than row 0a's (57) because the Home step is now in the loop. - 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/callsstate.Updateto mutateWorkflowStatedirectly outside ofWorkflowServiceitself (a grep guard against future regressions of item 1) - that the
--scenario Fakesmoke 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
.partialfile that is not picked up by a subsequentConvertTo-MeasurementRowcall — 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 vitepresscommit)