Roadmap Progress
Detailed companion to the "Current position" block in CLAUDE.md. This file records per-slice status and a running session log so any session, on any machine, can reconstruct how the repo arrived at its current state.
CLAUDE.md is the five-line summary. This file is the full record.
Per-slice status
Legend:
- Proposed — spec and task files written; implementation has not started
- In Progress — implementation has begun but not merged
- Completed — implementation merged and all acceptance criteria met
- Blocked — waiting on an external decision; row lists the blocker
Earlier slices (SLICE-001 through SLICE-004) predate the evolution roadmap and are recorded below for completeness. Later phases will append rows as their slices are opened.
Pre-roadmap slices
| Slice | Title | Status | Notes |
|---|---|---|---|
| 001 | First Strong Vertical Slice | Completed | |
| 002 | Persistent Run History | Completed | |
| 003 | JSON Recipe File Management | Completed | |
| 004 | Operational Maturity | Completed | Last slice before the evolution roadmap landed. |
Phase 0 — Foundations
| Slice | Title | Status | Notes |
|---|---|---|---|
| 005 | CI and Quality Gates | Completed | Delivered via GitHub Copilot agent session using prompt in TASK-005. |
| 006 | Observability Baseline | Completed | Three Copilot passes: logging, unhandled exceptions + crash log, metrics + mutex. |
Phase 0 exit gate: met. Demo baseline (row 0) captured on 2026-04-23 against commit 7ecef05; CSV at docs/captures/demo-baseline-2026-04-23.csv, all 16 metrics filled in phase-1-measurements.md.
Phase 1 — Simulator to scale
| Slice | Title | Status | Notes |
|---|---|---|---|
| 1.1 | Multi-Tag Telemetry | Completed | Pass 1 complete (b429f80): domain shapes, noise models, 50-tag seed config, startup validator, noise + validator tests. Pass 2 complete (45de45b): ITagStream, SimulatedTagSource, TagStreamPipelineService, AppState.LatestTagValues, MainViewModel rewire, old MachineTelemetry path deleted. Pass 3: per-tag metrics already wired (samples.ingested/samples.coalesced with tag.name dimension, tags.active gauge); 30-min MultiTag soak captured (174 runs, all 50 tags in samples.ingested; CSV span 5811 s due to 63-min mid-capture system-sleep event, NOT a code hang — verified by zero events in the gap window); slice-1-1-multi-tag-telemetry row appended; criterion 7 amended (per-tag accuracy documented not gated; with scenario-duration normalization: ≤5 Hz tags within ±2%, 10–50 Hz tags 60–90% of configured, ≥100 Hz tags cap near 64 Hz from default Windows 15.6 ms timer tick). |
| 1.2 | Real frame payloads | Completed (2026-04-27) | Passes 1 & 2 complete (Frame.{Width,Height,BytesPerPixel}, SimulatorProfile.{FrameWidth,FrameHeight,BytesPerPixel}, HighFrameRate seed profile, SimulatedCamera real byte[] allocation + gradient fill, MainViewModel.CurrentFrame WriteableBitmap binding, Get-GcPauseP95 / Get-LohAllocRateAvg extraction helpers + Pester tests). Pass 3 captured 2026-04-27 under SLICE-1.6 FlaUI rig: 8 154 frames ingested, 0 dropped, 0 faulted, gen-2 GC = 2 713, LOH alloc = 1 MB/s, p95 pause = 11.76 ms. frames.ingested (8 154) below criterion 17 500 — model mismatch (SimulatedCamera streams only during active run motion, not continuously while connected); filed as follow-up, not a pipeline defect. |
| 1.5 | Automated Measurement Capture | Superseded (2026-04-27) | Originally completed (f64173f): IScenario / ScenarioRunner / --scenario CLI / Capture-Measurements.ps1 / MainViewModelOperatorCommandsAdapter etc. Retired in favor of UI-Automation-driven capture (planned SLICE-1.6, FlaUI); the bypass-the-UI design tradeoff that the spec called out turned out to matter more in practice. Code, tests, tooling, and --scenario flag removed. Rows 0a, 0b, and slice-1-1-multi-tag-telemetry stay in the measurements table as historical evidence. |
| 1.5.1 | Fix ObjectDisposedException; capture row 0b | Superseded (2026-04-27) | Items 1, 2, 4 of the spec went away with the SLICE-1.5 retirement. Item 3 — _disposed Interlocked guard on SimulatedTagSource (DI double-disposal fix from 2b25476) — survives in the codebase, plus the matching guard on SimulatedCamera from 9867bc1. The headerless-CSV fix in MeasurementExtraction.psm1 also survives (manual §3 captures still use it). Row 0b stays. |
| 1.6 | FlaUI-driven Measurement Capture | Completed (2026-04-27, pass 3/3) | Pass 1 (eca5c9e): InspectionPrototype.UiDriver (IUiDriver / FakeUiDriver), InspectionPrototype.AcceptanceTests (FlaUI 5.0.0, FlaUiDriver, 5 smoke tests), 13 AutomationIds in MainWindow.xaml, 2 XAML-regression + 4 JSON-binding tests. Pass 2 (5f39a9d): DemoBaselineFlaUi + MultiTagSoakFlaUi scenarios, FakeUiDriver scenario unit tests, Capture-Measurements.ps1 rewrite (full orchestrator with -AppendToTable, -Profile, -SliceTag, -AllowDirty). Pass 3: XAML ActiveSimulatorProfileText fix (StringFormat → split TextBlocks), SIMULATOR_PROFILE env-var in MultiTagSoakFlaUi, -Profile param in script; SLICE-1.2 Pass 3 captured; runbook §3b added; SLICE-1.5/1.5.1 banners updated; SLICE-1.2 closed. |
| 1.3 | Encoder-rate motion | Completed (2026-04-30, criterion 7 amended) | Pass 1 (3cec4d8): EncoderAxis/EncoderSample/EncoderSnapshot, IEncoderStream, SimulatedEncoderSource, WinMmTimePeriod P/Invoke, Simulator:Encoder config + validators, SimulatorProfile.EncoderIntervalMs, EncoderRate seed profile, +47 tests. Pass 2 (1ba005a, fix-ups 385b118/736afac): EncoderStreamPipelineService (channel drain, axis-dim metrics, no AppState write — verified by recording-store test), AppMetrics.EncoderSamplesIngested/EncoderSamplesCoalesced, Get-EncoderRatePerAxis extraction, 20-metric ConvertTo-MeasurementRow output. Pass 3 (this session, no new commit yet): 10-min EncoderRate capture (docs/captures/slice-1-3-encoder-rate-2026-04-30.csv, 613 s span). Receiver rate 656.6 Hz on both axes vs original criterion 980-1020 Hz — Windows PeriodicTimer + timeBeginPeriod(1) ceiling, not a code defect. Criterion 7 amended to documented-not-gated (mirrors SLICE-1.1's per-tag-rate amendment). Architectural goal met: runs.faulted=0, frames.dropped=0, tags.active=50, encoder counters reachable through non-AppState channel at hundreds of Hz × 2 axes. Follow-up filed: encoder-cadence remediation. |
| 1.4 | Storm & soak profiles | Completed (2026-05-03, criterion 12 amended) | Passes 1–3 complete. ChaosMonkey row: 491 runs.started, 37 faulted, criterion-11 verified (all four fault branches, 39 injected/39 cleared/37 recovered/120 defect-shower). Soak8h row: 28 809 s, 5 109 runs, working-set steady-state drift = −2.7 MB (avg(last 60 min) = 232.7 MB minus avg(min 5-60) = 235.4 MB) — passes amended criterion 12 by a wide margin. Original last − first = 186.5 MB was entirely the process startup ramp (47.5 → 230.9 MB in 29 s), confirmed by direct CSV inspection at 14 timepoints across 8 h. Same amendment pattern as SLICE-1.1 / SLICE-1.3 criterion 7. Pre-flight commit 018bf29; capture+rows commit e3e4977. |
Phase 1 exit gate: met on 2026-05-03, see rows slice-1-4-chaos-monkey and slice-1-4-soak-8h of the measurements table. Criterion 12 amended on the same date (working-set steady-state drift metric replaced the original last − first metric); same amendment pattern as SLICE-1.1's criterion 7 (per-tag rate accuracy) and SLICE-1.3's criterion 7 (encoder receiver rate). Steady-state drift over 8 h = −2.7 MB, well within the 50 MB ceiling.
Phase 2 — Store under pressure
Phase 1's evidence — frames.dropped near zero, runs.faulted bounded, working-set steady-state flat over 8 h, GC pause p95 stable at 7.9–12.4 ms across all rows — does not, on its face, motivate any of the originally planned Phase 2 slices (2.1 store-slicing, 2.2 immutable collections, 2.3 data-plane lift-out, 2.4 per-slice observables). The roadmap §3 explicitly says "If the app survives this run beautifully, Phase 2 is deferred. If not, we know exactly which slice of the store to attack first." The app did survive beautifully — but AppStateStore.Update allocation-share and lock-wait are unmeasured. Phase 2 opens with a measurement-first slice.
| Slice | Title | Status | Notes |
|---|---|---|---|
| 2.0 | Store allocation & contention profiling | Completed (2026-05-07) | All 3 passes complete. 30-min MultiTag capture: alloc share 0.5%, lock-wait p95 0.4 µs, top caller WorkflowService.OnPositionChanged 46.4% / TagStreamPipelineService.ExecuteAsync 46.4%. Phase 2 deferred entirely — all three rubric gates clear. Row in phase-2-measurements.md. |
| 2.1 | Slice AppState into sub-records | Deferred (2026-05-07) | SLICE-2.0 measured alloc share = 0.5% (gate: ≥10%). Not triggered. Re-evaluate if a future Phase 3/4 slice pushes alloc share above the gate. |
| 2.2 | Immutable collections | Deferred (2026-05-07) | SLICE-2.0 found no List<T> rebuild pressure (0.5% alloc share). Not triggered. |
| 2.3 | Data-plane lift-out | Partial / Deferred (2026-05-07) | Encoder stream already lifted (SLICE-1.3). Tag stream (46.4% of calls) below 50% trigger; and with 0.5% alloc share, lifting it would save < 0.25% of total alloc rate. Not triggered. |
| 2.4 | Per-slice observables | Completed (2026-05-11) | subscriber-to-store ratio = 1.12 (exit gate ≤ 4.0; −94.4% from ~20.0 pre-slice est.). Commits: 53965cf / a6e8a13 / 6d60698. Row in phase-2-measurements.md. 55 Pester tests pass. |
| 2.5 | Source-generated INPC | Retired (2026-05-11) | The 2026-05-09 review's §2 claimed MainViewModel used hand-written INPC; current code already inherits from CommunityToolkit.Mvvm.ObservableObject with 45+ [ObservableProperty] fields. Package CommunityToolkit.Mvvm 8.4.0 is centrally pinned. Slice retired as no-op. See addendum on 2026-05-09 review. |
| 2.7 | Domain purity audit | Completed (2026-05-11) | Renamed RunSummary.SimulatorProfileName → OperatingProfileName (Domain + Application + Infrastructure + Presentation + tests + tools). M005 column-rename migration. Added DomainLayerPurityTests (3 reflection-based [Fact] tests for forbidden-assembly references + forbidden-vocabulary in type names + member names in Domain.Contracts). 558 tests. Commits 483192a (spec/task), 2c0b36c (Pass 1). |
Phase 3 — New functionality
Phase 3 opens in parallel with conditional Phase 2 per the 2026-05-07 strategy doc. The original roadmap reserved Phase 3 for "out-of-scope" budget reasons; the strategy revision reorders Phase 3 as the source of real-world architectural pressure that Phase 2 refactors can be triggered against.
| Slice | Title | Status | Notes |
|---|---|---|---|
| 3.3 | SQLite persistence + schema versioning | Completed (2026-05-07, pass 3/3) | Pass 1 (239fdca): M001_initial_schema.sql, MigrationRunner, SqliteRunHistoryStore, SqliteAlarmHistoryStore, JsonImportService, SqliteTestHelper, 44 unit tests. Pass 2 (fb5d518): IAlarmHistoryStore, WorkflowService fire-and-forget alarm persistence, HistoryHydrationService log format with timing, SqlitePersistenceOptions, performance test (10K-row load < 200 ms: 1 ms in test, 89.9 ms in live 30-min capture). Pass 3: Populate-SyntheticHistory.ps1, 3 extraction helpers, 3 Pester tests (31 total, all pass), 30-min MultiTag capture with 10K pre-populated rows: runs.persisted=3 072, alarms.persisted=0, recent-history-load p95=89.9 ms (criterion met). Phase-2 gates remain clear. |
| 3.1 | Rich defect model | Completed (2026-05-08, pass 3/3) | Pass 1 (f40db0c): Defect record + BoundingBox + DefectClassification, M002 migration, IDefectStore + SqliteDefectStore, AppState.RecentDefects sliding-100 window, FramePipelineService upgraded. 500 tests green; perf 865 ms. Pass 2 (98a9494): DefectViewModel (WaferToCanvas, severity brush), WaferMapView UserControl (200×200 px disc + ItemsControl canvas markers), MainViewModel.RecentDefects ObservableCollection<DefectViewModel> diff projection, MainWindow.xaml integration, 22 new tests. 522 tests green. Pass 3: SafeDefectPersistAsync Debug timing; 3 new extraction helpers + ConvertTo-MeasurementRow extended to 32 metrics; 9 new Pester tests (40 total); runbook §5.3; FK fix (PRAGMA foreign_keys = OFF in SqliteDefectStore.OpenAsync); 30-min HighDefect soak: 1 406 defects persisted, 0 FK warnings, all 5 classifications represented. Phase-2 gates: alloc share 0.3%, lock-wait p95 0.3 µs — still clear. |
| 3.2 | Wafer loop / cassette cadence | Completed (2026-05-08, pass 3/3) | Spec + task written. Composes SLICE-3.3 + SLICE-3.1 + workflow state machine: WaferId/LotId/CassetteState records, M003 (cassette columns), M004 (FK-band-aid retirement audit), ICassetteScheduler + SimulatedCassetteScheduler, WorkflowService.StartRunAsync(WaferId?, LotId?) overload with stub-row-at-run-start pattern (RunTerminalStatus.Pending). Closes the SLICE-3.1 FK band-aid (SqliteDefectStore removes PRAGMA foreign_keys = OFF). Pass 3 fixed 3 bugs: CommandGuards.CanStart (add Completed), RunWaferAsync guard logic, CassetteSoakFlaUi Run-element completion detection. BoolVis moved to App.xaml. Capture: slice-3-2-cassette-cadence-2026-05-08.csv, LotId LOT-20260508-150713, 25 wafers, 255.8 s, 0 FK errors. Phase 2 gates: alloc share 0.3%, lock-wait p95 0.5 µs — deferred (5th clear point). |
| 3.4 | Identity + audit | Conditional | OperatorId attached to every RunSummary and state-changing event; role-gated diagnostics panel. |
Phase 4 — Real-world edges
Per the 2026-05-07 strategy: 4.1 (first real SDK swap) considered for parallel execution with later Phase 3 slices since the simulator-vs-real-hardware gap is the largest unresolved confidence question. 4.2 / 4.3 remain post-Phase 3.
Session log
One entry per session that moved the plan forward. Newest first. Keep each entry to ~5 lines.
2026-05-12 — AppState design review authored against the 2026-05-12 criteria
- New doc:
2026-05-12-appstate-design-review.md. ScoresAppState/AppStateStore/Subscribe<T>against criteria 1–12 with file/line evidence. Scorecard: 6 Pass, 1 Pass-by-accident, 4 Partial, 2 Fail. - Headline: the "JS-frontend-shaped + over-abstracted" hypothesis does not hold up against the code; the implementation is markedly leaner than the 2026-05-09 doc's Redux-shaped target. The real gaps are (a) subscriber-dispatch concurrency in
AppStateStore.Update(§1 — state can regress at subscribers under concurrent writers), (b) operational forensics for a fab deployment — no on-demand state dump (§6b Fail), no E10 projection (§7 Fail), no durable state-transition log (§5 Partial), and (c) a small set of housekeeping items (F-1 through F-4 + diagnostics-as-state in §12). - Section E added (criteria 13–16, craftsmanship and domain fit) after the user flagged that the original 12 criteria covered behavior + cost but not code quality / domain-language fit. New scorecard: 7 Pass + 1 Pass-by-accident + 6 Partial + 2 Fail across 16 criteria. Section E findings: simulator vocabulary leak in
AppState.SelectedSimulatorProfile/SimulatorProfileCatalog(§13), physically-impossible state combinations constructible viawith{}(§14),RegisterTagsActiveProvidermechanism-name (§15), dual notification channels +null!workaround +MaxDiagnosticsEntrieslocation (§16). - 14 ranked recommendations with mapping back to criteria. Items 1, 2, 3, 8, 9, 10, 11, 12, 13, 14 are mechanical and could land in a single "SLICE-2.9: AppState design + craftsmanship follow-ups." Items 4, 5, 6, 7 (durable transition log + E10 projector + service-mode snapshot command) are larger and likely warrant their own Phase 3/4 slice.
- Anti-recommendations section explicitly rejects: actions/middleware/reducer tables, splitting AppState, replacing the lock with a Channel, DevTools-style panels, persisting full AppState across crashes. Discriminated-union refactor of
WorkflowState/ActiveRunis filed as an option but not recommended for current scope. - Next: user reviews findings. If accepted, author the smallest-cost slice first (subscriber-dispatch serialization — single file edit) or open a new spec for the durable-transition-log + E10 projector pair if the fab-deployment use case is in scope for Phase 3/4.
2026-05-12 — AppState design review criteria drafted and approved
- New doc:
2026-05-12-appstate-design-criteria.md. 11 criteria across 4 groups (correctness under domain constraints, operational fitness, cost of carrying the abstraction, honest scope) for evaluating the currentAppStatestore design against production-ready industrial-desktop expectations rather than JS-frontend pattern fidelity. - Motivation: the 2026-05-09 Architectural Foundations doc endorsed a Redux/Vuex/MobX-shaped target ("single source of truth + pure reducers + selector-based subscriptions + memoized derived values") as the production-grade standard. This criteria doc challenges whether that target imports ceremony that doesn't earn its keep in a WPF desktop driving real hardware, and locks the criteria before the review writes findings.
- Status: criteria approved; review itself not yet started. Out of scope for the review: performance (already covered), view layer (separate concern), per-field placement decisions.
- VitePress sidebar updated (
docs/.vitepress/config.mts) to include the new doc under Reviews. Not yet linked fromCLAUDE.mdat time of approval. - Next: author the review doc (
2026-05-??-appstate-design-review.md) — score currentAppStatedesign row-by-row against criteria 1–11 with file/line evidence; propose smallest fixes per failure, not pattern swap. Findings may reshape SLICE-2.6 / 2.8 / 2.1 / 2.2 scoping.
2026-05-11 — TASK-2.4 closed (pass 3/3); SLICE-2.4 Completed
- SLICE-2.4 closed. Added
IAppStateStore.Subscribe<T>+Subscription<T>(Pass 1); migratedMainViewModelfrom monolithicProject(state)to 10 per-sliceSubscribe<T>callbacks (Pass 2); addedGet-SubscriberInvocationRate,Get-SubscriberToStoreRatio,Get-SubscriberSelectorDistributionhelpers + extendedConvertTo-MeasurementRowto 29 metrics + 7 Pester tests (Pass 3). Commits:53965cf(Pass 1),a6e8a13(Pass 2),6d60698(Pass 3 tooling). - 30-min HighDefect FlaUI capture (
slice-2-4-per-slice-observables-2026-05-11.csv, 1810 s, exit 0, commit6d60698). subscriber-to-store ratio = 1.12 (pre-slice est. ~20.0, −94.4% reduction). Exit gate ≤ 4.0: met. Top selector:SubscribeStagePosition(59.8%); ratio > 1.0 explained by multi-slice workflow transitions. - 55 Pester tests (all pass, +7 from this slice). 568 xUnit tests (all pass). 0 build warnings.
- Runbook §5.2 added (Subscriber-invocation profiling — SLICE-2.4, HighDefect profile); old §5.2 → §5.3, §5.3 → §5.4. Phase-2-measurements row appended as first row (ahead of slice-2-0).
- Next: re-audit remaining Phase 2 candidates (SLICE-2.6 / 2.8 / 2.2 / 2.1 / 2.3) against current code before authoring any of them. Reordered candidate queue: SLICE-2.6 → 2.8 → 2.2 → 2.1 → 2.3. Phase 3 continuation (SLICE-3.4 or SLICE-4.1) also in scope.
2026-05-11 — TASK-2.7 closed; Phase 2 SLICE-2.5 retired, SLICE-2.7 shipped
- SLICE-2.7 closed. Renamed
RunSummary.SimulatorProfileName→OperatingProfileName(and the parallelActiveRunStatefield). Propagated through Domain / Application (WorkflowService) / Infrastructure (SqliteRunHistoryStoreSQL + DTO + Dapper params) / Presentation (RunHistoryItemViewModel,MainViewModel,MainWindow.xamlgrid column binding) / 5 test files /tools/Populate-SyntheticHistory.ps1. AddedM005_rename_simulator_profile_column.sql(SQLiteALTER TABLE … RENAME COLUMN; existing rows preserve values). Migration runner picks up M005 automatically via existing manifest-resource pattern. Migration-count assertions (4 → 5) updated in threeMigrationRunner*test files. - Added
DomainLayerPurityTests(3 reflection-based [Fact] tests in newArchitecture/folder): forbidden-assembly references (Application/Infrastructure/Presentation/App), forbidden vocabulary fragments (Simulator/Sqlite/Wpf/ViewModel/Dapper/FlaUI) inDomain.Contractstype names and public member names. - Verification results. Vocabulary plant (added
SimulatorTestPlantproperty toRunSummary) correctly failedDomainContracts_PublicMembers_ContainNoForbiddenVocabulary(the auto-generatedget_SimulatorTestPlantaccessor matched the forbiddenSimulatorfragment). Assembly-reference plant (added<ProjectReference>from Domain → Application) blocked at build time by circular-dependency detection — so the architecture test is correct-by-construction on this codebase, withDomainAssembly_DoesNotReferenceUpperLayersserving as belt-and-suspenders coverage for hypothetical non-cyclic scenarios (e.g., a future split-out sub-assembly that doesn't already depend on Domain). - SLICE-2.5 retired as no-op. The 2026-05-09 review's §2 claim that
MainViewModel's INPC was hand-written is incorrect when measured against current code:CommunityToolkit.Mvvm 8.4.0is centrally pinned,MainViewModelispartial class : ObservableObjectwith 45+[ObservableProperty]fields, other ViewModels are immutable projections. Addendum to the 2026-05-09 review records this so the doc remains trustworthy for the remaining 6 candidates. - 558 tests (555 + 3 new architecture). 0 build warnings. Commits:
483192a(spec + task),2c0b36c(Pass 1 rename + M005), Pass 2 (this entry — DomainLayerPurityTests + close-out docs). - Next: re-audit the remaining 6 Phase 2 candidates (SLICE-2.1 / 2.2 / 2.3 / 2.4 / 2.6 / 2.8) against current code before authoring any of them. The SLICE-2.5 staleness proves the review-doc's §1–§9 should be sanity-checked first. Reordered candidate queue: SLICE-2.4 → 2.6 → 2.8 → 2.2 → 2.1 → 2.3.
2026-05-09 — TASK-3.2 Pass 2 completed (cassette UI panel + MainViewModel projection)
- CassetteSlotViewModel, CassetteStatusPanel.xaml+cs, MainViewModel cassette projection (IsCassetteLoaded, CassetteLotIdText, CassetteCurrentSlotText, CassetteCurrentPhaseText, DiffCassetteSlots), 4 cassette commands, MainWindow.xaml buttons + panel hosting, FakeCassetteScheduler, MainViewModelCassetteProjectionTests. 553 tests (6 new). Commit 07f7b42.
2026-05-08 — TASK-3.2 Pass 3 complete; SLICE-3.2 closed
- Fixed 3 bugs blocking the cassette capture: (1)
CommandGuards.CanStartdid not allowWorkflowState.Completed— cassette chaining requiredCanStartto returntrueafter each wafer run transitions workflow toCompleted; (2)SimulatedCassetteScheduler.RunWaferAsyncguard inverted —StartRunAsyncwas never called because the guard checked!IsTerminalWorkflowState(Ready)= false before startup; (3)CassetteSoakFlaUiusedWaitForTextAsyncon a WPFRunelement whose UIANameproperty does not reliably return bound text — replaced withClickByAutomationIdAsync("UnloadCassetteButton", 25min)which polls for the button enabled state. - Fixed
BoolViscross-assemblyStaticResourcecrash: movedBooleanToVisibilityConverterfromMainWindow.xamlWindow.ResourcestoApp.xamlApplication.Resources. - Added
Get-WafersCompletedCount,Get-CassetteWallClockSecondshelpers totools/MeasurementExtraction.psm1; extendedConvertTo-MeasurementRowwith-LotIdparameter (metrics 33–34); fixedGet-SqliteCountmissing-table handling (returns 0 for WAL-mode before-snapshots). 4 new Pester tests (44 total, all pass). - 25-wafer cassette FlaUI capture (
slice-3-2-cassette-cadence-2026-05-08.csv, LotIdLOT-20260508-150713, 255.8 s wall-clock, 0 FK errors, exit 0).wafers.completed = 25,cassette.wall-clock = 255.8 s— both exit-gate criteria met. FK band-aid retirement verified (0SqliteException Error 19in log). - Phase-2 gates: alloc share 0.3%, lock-wait p95 0.5 µs — 5th consecutive measurement point below both gates; Phase 2 remains deferred. SLICE-3.2 → Completed. Next: SLICE-3.4 (identity + audit) or SLICE-4.1 (first real SDK swap). Commit
3e4539a.
2026-05-09 — TASK-3.2 Pass 1 completed (cassette scheduler + stub-row + FK retirement)
- Domain shapes (WaferPhase, WaferId, LotId, CassetteSlot, CassetteState), RunTerminalStatus.Pending, RunSummary WaferId/LotId, M003+M004 migrations, ICassetteScheduler, SimulatedCassetteScheduler, WorkflowService stub-row overload, AppState.Cassette, SimulatorProfile wafer timing fields, DI registration. 547 tests (22 new). Commit 1de2e3a.
2026-05-08 — SLICE-3.2 opened (wafer loop / cassette cadence)
- Wrote
docs/specs/SLICE-3.2-cassette-cadence.mdanddocs/tasks/TASK-3.2-implement-cassette-cadence.md. Spec scope: compose SLICE-3.3 + SLICE-3.1 + workflow state machine into a cassette scheduler driving Load → Align → Run → Unload phases for 25 wafers per cassette. AddsWaferId/LotId/CassetteSlot/CassetteState/WaferPhaseto Domain.RunSummarygains nullableWaferId/LotId.RunTerminalStatusgainsPending(placed FIRST so default = Pending). M003 SQL addswafer_id+lot_idcolumns + indexes; M004 audit-marks the FK-band-aid retirement.ICassetteSchedulerinterface +SimulatedCassetteSchedulerimplementation.WorkflowService.StartRunAsync(WaferId?, LotId?)overload writes a stubrun_summaryrow withTerminalStatus = Pendingbefore the run loop starts, then upserts at run end via the existing INSERT OR REPLACE.SqliteDefectStore.OpenAsyncremoves the explicitPRAGMA foreign_keys = OFF. Cassette UI: 5×5 slot grid, 4 new MainWindow buttons. - Three Copilot passes: (1) domain shapes + M003 + M004 + scheduler + WorkflowService stub-row + FK retirement + tests including FK orphan-row integrity test (the verification of band-aid removal); (2) cassette UI panel + 4 buttons + diff projection in MainViewModel; (3) 25-wafer cassette capture under Soak8h profile + row block + runbook §5.4 + Phase 2 trigger assessment.
- Decisions in spec: (a)
RunTerminalStatus.Pendingis FIRST enum member sodefault(enum) = Pending(defensive — partial RunSummary constructions default to "not yet terminal"); (b) stub-row insert is fire-and-forget (mirrors alarm-history pattern); (c) the M004 migration is a no-op SQL but records the FK-retirement audit trail inschema_version; (d) cassette mode runs in parallel-coexistence with direct mode —AppState.Cassettenull = direct mode, non-null = cassette mode; (e) single-wafer faults log Warning + advance to next slot (cassette-level fault tolerance); (f) capture in Pass 3 is manual UI-driven (cassette scheduler not reachable viaMultiTagSoakFlaUi); (g) cassette mode is the highest workflow-state-transition rate the prototype has measured — strongest Phase 2 trigger candidate yet. - This slice closes the FK band-aid filed as item 3 of the 2026-05-08 follow-ups entry below. The architectural fix (stub-row at run start) is what makes FK enforcement viable; the band-aid retirement is a code-only change inside this slice.
- Added the SLICE-3.2 row to the Phase 3 progress table above (status: Proposed). No code yet.
2026-05-08 — Follow-ups from TASK-3.1 Pass 3 capture review
Three follow-ups identified in the post-capture review of slice-3-1-rich-defect-model. The first two landed in this same session; the third is filed as future work.
(1) Criterion 9 amended (≥ 5 000 → ≥ 1 000 defects). SLICE-3.1's spec required 5 000 defects in 30 min under HighDefect; capture produced 1 406. Math: HighDefect's
DefectProbabilityPerFrame = 0.6×SimulatedCamera's active-run-only frame rate of ~1.27 fps × 1 811 s ≈ 1 372 defects expected. The 5 000 target presumed continuous frame production butSimulatedCameraonly streams during active scan motion — same constraint documented inslice-1-2-real-frame-payloads'sframes.ingestedshortfall. Amended: SLICE-3.1 spec status →Completed (2026-05-08, criterion 9 amended). Architectural intent ("rich defect persistence works under sustained high-defect load with pagination") is met by 1 406 just as well as it would be by 5 000. Same pattern as SLICE-1.1 / 1.3 / 1.4 criterion amendments. To produce 5 000+ would require aDefectStormprofile (HighDefect's defect probability + HighFrameRate's 33 ms frame interval) or a longer capture; both filed as Phase 3.2+ follow-ups if needed.(2) Defect-persist p95 promoted from Debug log line to a Histogram counter. Pass 3 added
Stopwatchtiming inSafeDefectPersistAsyncat LogDebug level; Release builds suppress Debug, sodefect-persist p95 (ms)read—in the row. Same observability pattern SLICE-2.0 uses for store-update lock-wait: aHistogram<double>counter is always-on regardless of log level. Landed: newAppMetrics.DefectPersistLatencyMicrosHistogram (defect.persist.latency.micros, unitus);SafeDefectPersistAsync.Record(elapsed.TotalMicroseconds)on success;Get-DefectPersistP95Msnow accepts-Csv $rows(primary) with-LogPathretained as a fallback for Debug-build captures. 4 new Pester tests cover the CSV path; existing 4 log-path tests still pass. Future §5.3 captures will populate the metric.(3) FK band-aid in
SqliteDefectStorefiled as architectural follow-up. Pass 3 commit message documents that defects fail to persist with FK enforcement on (SQLite Error 19) becausedefects.run_idreferencesrun_summaries.run_idbut the parent row only exists at run-end while defects are persisted fire-and-forget during the run. Fix shipped: explicitPRAGMA foreign_keys = OFFinSqliteDefectStore.OpenAsync(). Status: filed as a follow-up, not blocking. The schema still declaresREFERENCES run_summaries(run_id)but no FK is enforced at runtime. The application invariant ("defects are only created for an active run") holds today; orphan rows are theoretically possible if a run faults/aborts in some untested edge case. Preferred fix: insert a stubrun_summaryrow at run start (TerminalStatus = Pending); UPDATE at run end. Smallest behavioral change; preserves the schema's documented intent. Alternative: M003 migration drops the FK declaration. Defer until SLICE-3.2 (cassette cadence) lands — the wafer-loop scheduler will createrun_summaryrows at known points anyway, which is the natural place to add the stub-row pattern.
2026-05-08 — TASK-3.1 Pass 3 complete; SLICE-3.1 closed
- Added
Stopwatchtiming toSafeDefectPersistAsyncinFramePipelineService:LogDebug("Defect persisted. ElapsedMs={ElapsedMs:F2}.")on success; p95 extractable from log in Debug/Development builds. - Added 3 new helpers to
tools/MeasurementExtraction.psm1:Get-DefectsPersistedCount(delta from DB snapshots, try/catch for missing-table on first run),Get-DefectClassificationDistribution(GROUP BY classification with %, privatescript:Invoke-SqliteDistrib),Get-DefectPersistP95Ms(p95 fromDefect persisted. ElapsedMs=…log pattern). ExtendedConvertTo-MeasurementRowto 32 metrics;Export-ModuleMemberupdated. - Added 9 new Pester tests (40 total, all pass):
Get-DefectsPersistedCount(delta logic),Get-DefectClassificationDistribution(4 cases),Get-DefectPersistP95Ms(4 cases).script:New-MinimalDefectsDbhelper added to test scaffold. - Added runbook §5.3 (Rich defect model capture — SLICE-3.1, HighDefect profile). Registered scenarios table updated.
- FK fix root cause identified and resolved:
Cache=SharedcausedSqliteDefectStoreto receive physical connections already havingPRAGMA foreign_keys = ON(set bySqliteRunHistoryStore,SqliteAlarmHistoryStore, andMigrationRunner). RemovingForeign Keys=Truefrom the connection string alone was insufficient; fix: explicitPRAGMA foreign_keys = OFFinSqliteDefectStore.OpenAsync(). Verified with smoke test (43 defects in 60 s, 0 FK warnings), then full 30-min soak. - 30-min HighDefect FlaUI capture (
slice-3-1-rich-defect-model-2026-05-07.csv, 1811 s, exit 0, commit4bde4ed):defects.persisted = 1 406, distribution = Scratch 21.3% / Discoloration 20.6% / Unknown 19.9% / Particle 19.2% / Crack 19.0%, 0 FK warnings. All 5 classifications represented with roughly uniform frequency. - Phase-2 gates: alloc share 0.3%, lock-wait p95 0.3 µs — still clear, no Phase 2 trigger. SLICE-3.1 → Completed. Next: SLICE-3.2 (wafer loop / cassette cadence) or SLICE-3.4 (identity + audit).
2026-05-08 — TASK-3.1 Pass 2 complete (WaferMapView UI overlay)
- Created
DefectViewModel(Presentation/ViewModels):WaferToCanvasstatic helper,CanvasX/CanvasYfrom wafer-mm via(waferMm + 100) / 200 * canvasSize,MarkerBrushseverity-coded (Minor=Yellow, Major=Orange, Critical=Red),DefectId. - Created
WaferMapViewUserControl (Presentation/Controls): 200×200 px wafer-discEllipsebackdrop +ItemsControlwithCanvaspanel;ItemContainerStylebindsCanvas.Left/Canvas.Top; 8×8 px ellipse DataTemplate;DefectCountTextAutomationId on count label;WaferMapViewAutomationId on hosting element in MainWindow.xaml. - Updated
MainViewModel:RecentDefects ObservableCollection<DefectViewModel>field +DefectCountint property; diff projection inProject(AppState)(prepend-new at index 0, reverse-loop remove evicted;ReferenceEqualsguard for no-op updates). - Test project upgraded to
net10.0-windows+UseWPF=true+ Presentation project reference + explicitSystem.IO/System.Net.Httpusings (net10.0-windows drops those from implicit set). AddedWaferToCanvasTests(coord math, brush, edge cases) +MainViewModelDefectProjectionTests(diff logic: prepend, evict, no-op, window-cap, 200-iteration stress). AutomationId regression test extended withWaferMapView. 522/522 tests green.
2026-05-07 — TASK-3.1 Pass 1 complete (domain shapes + persistence + pipeline + AppState)
- Created:
Defectrecord (11 fields),BoundingBox,DefectClassificationenum; deletedInspectionResult. M002 migration (defectstable + 2 indexes + FK torun_summaries).IDefectStore+SqliteDefectStore(Dapper,SaveManyAsyncbatch via multi-row Dapper call).AppState.RecentDefectssliding-100 window; reset atWorkflowService.StartRunAsync.FramePipelineServiceupgraded:BuildSimulatedDefect, fire-and-forgetSafeDefectPersistAsync, sliding window update. - New tests:
MigrationRunnerM002Tests(3),SqliteDefectStoreTests(6),SqliteDefectStorePerformanceTests(1 + existing total perf=2),FramePipelineServiceDefectIntegrationTests(4),WorkflowServiceTests.StartRunAsync_ClearsRecentDefects(1). UpdatedMigrationRunnerTeststo expect version=2. All 500 unit tests green; perf test 865 ms < 1000 ms ceiling. Commitf40db0c. - Optimization:
SaveManyAsyncchanged from a C#foreachto Dapper multi-rowExecuteAsync(sql, IEnumerable<DefectRow>)— reduced 5K-insert time from ~1164 ms to 865 ms.
2026-05-07 — SLICE-3.1 opened (rich defect model)
- Wrote
docs/specs/SLICE-3.1-rich-defect-model.mdanddocs/tasks/TASK-3.1-implement-rich-defect-model.md. Spec scope: replace thinInspectionResult(SourceFrameId, HasDefect, DefectSummary, Severity)with richDefect(DefectId, RunId, FrameId, BoundingBox, Classification, Confidence, Severity, WaferX, WaferY, ImageRef?). Add M002 migration creatingdefectstable (FK torun_summaries) + 2 indexes. ImplementIDefectStore+SqliteDefectStore(Dapper, mirrors SLICE-3.3 patterns;SaveManyAsyncuses single-transaction batched inserts for 5K-defect performance).FramePipelineService.ProcessDefectsForFrameupgraded to produce + persistDefectvia fire-and-forget. AddAppState.RecentDefectssliding-100 window; reset atWorkflowService.StartRunAsync. AddWaferMapViewWPF UserControl rendering markers at wafer-mm coordinates, color-coded by severity (Minor=Gold, Major=Orange, Critical=Red).MainViewModel.RecentDefectsis anObservableCollection<DefectViewModel>updated via diff projection (not clear-and-rebuild). - Three Copilot passes: (1) domain shapes + M002 + persistence + pipeline upgrade + AppState reset + tests including
SaveMany5000_CompletesUnderOneSecondperf test; (2) UI —WaferMapViewUserControl +DefectViewModel+ diff-based ObservableCollection projection +MainWindow.xamlintegration + AutomationIds; (3) 30-min HighDefect capture verifying ≥ 5 000 defects + pagination + classification distribution + Phase 2 trigger assessment inphase-3-measurements.md. - Decisions in spec: (a)
InspectionResultdeleted (no external consumer beyond the pipeline service it lived inside); (b)RecentDefectssliding window cap = 100 in AppState (UI surface) — full per-run history accessible viaIDefectStore.LoadByRunAsyncfor future "view all" UI; (c) per-defect fire-and-forgetSaveAsyncinstead of buffered batched flusher — ships simpler path, revisit if measurement shows backlog; (d)RecentDefectsreset at run start (not run end) so the wafer-map keeps the most recent run's defects visible until the next run begins; (e) Dapper INSERT OR REPLACE upsert pattern matches SLICE-3.3; (f) MainViewModel usesObservableCollectiondiff projection (preserves visual-element identity for unchanged entries) instead of clear-and-rebuild — load-bearing for performance under high defect rate; (g) WaferToCanvas helper static, dimension-coupled to 200 mm wafer × 400 px canvas (WaferRangeMm = 100,CanvasSizePx = 400); (h) Pass 3 capture uses HighDefect (DefectProbabilityPerFrame=0.6) — produces ~12 defects/sec, well above the 5 000-in-30-min criterion. - Added the SLICE-3.1 row to the Phase 3 progress table above (status: Proposed). No code yet.
2026-05-07 — TASK-3.3 Pass 3 complete; SLICE-3.3 closed
- Created
tools/Populate-SyntheticHistory.ps1: inlinedotnet runpattern, single transaction, idempotent, 10 K syntheticrun_summariesrows in ~5 s. Added 3 public helpers totools/MeasurementExtraction.psm1:Get-RunsPersistedCount,Get-AlarmsPersistedCount,Get-RecentHistoryLoadP95(+ privatescript:Get-SqliteCountvia inline dotnet). UpdatedConvertTo-MeasurementRowto accept-DatabaseBefore/-DatabaseAfter/-LogPathand emit 3 new rows (metrics 27–29). Added 3 new PesterDescribeblocks; all 31 tests pass. Runbook §5.2 added. - 30-min MultiTag FlaUI capture (
slice-3-3-sqlite-persistence-2026-05-07.csv, 1812 s, exit 0, commitfb5d518, 10K pre-populated rows): 210 runs.started, 0 faulted, frames.dropped 0, working-set peak 241.8 MB, gc-pause-p95 7.49 ms. - Persistence metrics (from DB snapshots + log):
runs.persisted = 3 072(210 real + 2 862 synthetic to top up to 10K),alarms.persisted = 0(MultiTag, no chaos — expected),recent-history-load p95 = 89.9 ms(10K-row hydration; < 200 ms criterion met). - Phase-2 gates: alloc share 0.3%, lock-wait p95 0.2 µs — still clear, no Phase 2 trigger. SLICE-3.3 → Completed. Next: SLICE-3.1.
2026-05-07 — TASK-2.0 Pass 3 complete (capture + Phase 2 deferred); SLICE-2.0 Completed
- 30-min MultiTag FlaUI capture:
slice-2-0-store-profiling-2026-05-07.csv(1812 s, 209 runs, 0 faulted, exit 0). Key store-side numbers: alloc share 0.5%, lock-wait p95 0.4 µs, top callers WorkflowService.OnPositionChanged 46.4% / TagStreamPipelineService.ExecuteAsync 46.4% (77 125 total calls, 42.6/s). All three Phase-2 rubric gates clear (alloc < 10%, lock-wait < 100 µs, no dominant data-plane caller > 50%). - Phase 2 deferred entirely. SLICE-2.1, 2.2, 2.3 (tag/frame), 2.4 all marked Deferred with measured evidence in the row Notes section. Store is healthy — no refactoring justified.
- Row committed to
docs/reviews/phase-2-measurements.md. Runbook §4.7+ placeholder replaced by §4.7+ deferral note + §5.1 (Phase 2 store-profiling scenario). SLICE-2.0 → Completed. CLAUDE.md updated: next action is TASK-3.3 Pass 1.
2026-05-08 — TASK-2.0 Pass 2 complete (measurement-extraction tooling)
- Added 4 store-side helpers to
tools/MeasurementExtraction.psm1:Get-StoreUpdateRate,Get-StoreUpdateAllocShare,Get-StoreUpdateLockWaitP95,Get-StoreUpdateCallerDistribution. ExtendedConvertTo-MeasurementRowto emit 26 rows (was 22 via Pass 2 additions: store.update rate, alloc share, lock-wait p95, top-caller). 9 new Pester tests appended totests/Tools/MeasurementExtraction.Tests.ps1; also fixed pre-existing encoder test fixture (axis dim was in separateTagscolumn, not inCounter Name). Createddocs/reviews/phase-2-measurements.mdskeleton (Conventions + 26-metric fixed set; placeholder row for Pass 3). 26/26 Pester pass. - Commit
82a46eb. SLICE-2.0 Pass 3 remains: run the 30-min capture, fill theslice-2-0-store-profilingrow, commit CSV evidence.
2026-05-08 — TASK-2.0 Pass 1 complete (AppStateStore instrumentation)
- Added 3 new
AppMetricsmembers (StoreUpdateCalls{caller},StoreUpdateAllocBytes,StoreUpdateLockWaitMicros). ExtendedIAppStateStore.Updatewith[CallerMemberName]+[CallerFilePath]optional parameters. RewroteAppStateStore.Updateto record lock-wait viaStopwatch, alloc-delta viaGC.GetAllocatedBytesForCurrentThread(), and call count with caller dimension. Updated all test fakes and 12 test files that constructnew AppStateStore()to passnew AppMetrics(). - Added
AppStateStoreInstrumentationTests.cs(5 tests) andAppMetricsStoreUpdateDimensionTests.cs(6 tests). Build: 0 errors / 0 warnings. 471/471 xUnit tests pass. Commit5f029cf.
2026-05-07 — SLICE-3.3 opened (SQLite persistence + schema versioning)
- Wrote
docs/specs/SLICE-2.0-store-allocation-profiling.mdanddocs/tasks/TASK-2.0-implement-store-allocation-profiling.md. Spec scope: instrumentAppStateStore.Updatewith[CallerMemberName]+[CallerFilePath]parameters (compile-resolved at every callsite — no callsite changes),Stopwatchfor lock-wait,GC.GetAllocatedBytesForCurrentThread()for alloc-delta. Three newAppMetricsmembers. One 30-minuteMultiTagcapture; row block under a newphase-2-measurements.md; runbook §5.1 (Phase-2 captures). - Why measurement-first: the roadmap §3 ("if the app survives beautifully, Phase 2 is deferred") points at the right discipline. Phase 1 evidence shows the app does survive — but the originally planned 2.1/2.2/2.3/2.4 slices have exit gates phrased as "≥ 40% reduction in
AppStateStore.Updateallocations" / "lock-wait stays under a measured threshold." Without baselines for those, opening any of them commits to a refactor whose payoff cannot be quantified. SLICE-2.0 produces the baselines. - Decision rubric recorded in spec:
alloc share < 10%→ defer 2.1/2.2;lock-wait p95 < 100 µs→ defer 2.4; top caller dominates (>50%) → 2.3 lift-out for that caller; otherwise → 2.1 opens with measured baseline. Deferral is a first-class outcome — saves weeks of work the roadmap hadn't budgeted for if the architecture is already healthy. - Added the SLICE-2.0 row to the new Phase 2 progress table above (status: Proposed). Added the 2.1/2.2/2.3/2.4 rows with Conditional status pending SLICE-2.0's recommendation. No code changes yet.
2026-05-03 — Criterion 12 amended; Phase 1 exit gate met
- Criterion-12 measurement-methodology amendment. Original metric
working-set growth = last − first ≤ 50 MBsuperseded byworking-set steady-state drift = avg(last 60 min) − avg(minutes 5-60) ≤ 50 MB. The 50 MB ceiling itself is unchanged; only the measurement window. Same amendment pattern as SLICE-1.1 criterion 7 (per-tag rate accuracy → Windows scheduling reality) and SLICE-1.3 criterion 7 (encoder receiver rate → Windows timer-resolution ceiling). - Direct CSV inspection of
docs/captures/slice-1-4-soak-8h-2026-05-02.csv(sampled at 14 timepoints from t=0 to t=8h) confirmed the 186.5 MB original-metric reading is entirely the process startup ramp: working-set rose from 47.5 MB to 230.9 MB at t=29 s (full ramp under 30 seconds — WPF + 50 tag emitters + encoder pipeline + JIT). It then held a stable sawtooth between 224 MB and 240 MB for the remaining 7.5 hours. Window analysis: avg(min 5-30) = 235.4 MB, avg(h 4-5) = 234.0 MB, avg(last 60 min) = 232.7 MB. Steady-state drift = −2.7 MB over 8 h. p99 = 238.1 MB; one transient max = 246.0 MB. No leak. - Updated
docs/specs/SLICE-1.4-storm-and-soak-profiles.md(criterion 12 amendment text + status banner → Completed). Updateddocs/reviews/phase-1-measurements.md(slice-1-4-soak-8h row block: addedworking-set steady-state drift (MB) = −2.7row; originalworking-set growth (MB) 186.5 ⚠retained for traceability with ‡ footnote pointing to amendment; rewrote criterion-12 analysis Notes paragraph). Updated CLAUDE.md current-position. Replaced the deferred-exit-gate banner under the Phase 1 section heading with the met-on-2026-05-03 banner. - Phase 1 exit gate met 2026-05-03. All five Phase-1 slices (1.1, 1.2, 1.3, 1.4, 1.6) closed; SLICE-1.5/1.5.1 superseded.
2026-05-03 — TASK-1.4 Pass 3 closed; SLICE-1.4 complete (criterion-12 gap filed)
- Pre-flight commit
018bf29:appsettings.jsonFlakySdkEnabledtrue→false (criterion-16 reproducibility);FlakySdkDecoratortimeout branch falls through to inner when caller CT not cancelled (spec fix); new regression testFlakySdk_TimeoutBranch_WhenNotCancelled_FallsThroughToInner. 460 unit + 5 acceptance tests pass. - Soak8h capture:
docs/captures/slice-1-4-soak-8h-2026-05-02.csv(28 809 s, exit 0). 5 109 runs.started, 5 109 runs.completed, 0 faulted, 2 frames.dropped, working-setlast − first= 186.5 MB. Time-series shape: 47.5 MB → 237 MB in first 30 min (startup ramp), then stable sawtooth 228–238 MB for remaining 7.5 h. No monotonic growth. Criterion-12 (≤ 50 MB) fails as measured due to startup-ramp conflation; real steady-state variation ≤ 10 MB. Criterion-12 measurement-methodology follow-up filed. - ChaosMonkey row block + Soak8h row block appended to
phase-1-measurements.md; runbook §4.5 (ChaosMonkey) + §4.6 (Soak8h) added; §4.5+ placeholder replaced with §4.7+ Phase-2 placeholder; §3b registered-scenarios table updated. - Phase 1 exit gate deferred pending criterion-12 amendment. Docs+CSVs commit
e3e4977.
2026-05-01 — TASK-1.4 Pass 3 (partial): ChaosMonkey capture + criterion-11 verified; four FlaUI fixes
- Wrote
docs/specs/SLICE-1.4-storm-and-soak-profiles.mdanddocs/tasks/TASK-1.4-implement-storm-and-soak-profiles.md. Spec scope: add 7 storm-and-soak knobs toSimulatorProfile(DefectShowerEveryMs,DefectShowerDurationMs,AlarmBurstEveryMs,TelemetryDropoutChance,NetworkLatencyMeanMs,NetworkLatencyStddevMs,TimeCompressionFactor), a top-levelSimulator:FlakySdkconfig block (4 fields), two new profiles (ChaosMonkey,Soak8h), three new services (DefectShowerService+IDefectShowerSchedule,AlarmBursterService,FlakySdkDecorator<IMachineConnection>), and two derived measurement-extraction helpers (Get-WorkingSetGrowthMb,Get-FaultCyclesCount). No newAppMetricscounters — derivation reuses existingworking_setandruns.faultedrows. - Task is structured as three Copilot passes: (1) profile-record fields + JSON binder + validators +
Simulator:FlakySdkconfig-only +TelemetryDropoutChanceinSimulatedTagSource+TimeCompressionFactorinSimulatedMachineConnection.ConnectAsyncandSimulatedMotionController.InterpolateAsyncper-tick wait + Gaussian network latency inConnectAsync+appsettings.json(ChaosMonkey, Soak8h, FlakySdk block); (2)DefectShowerService+AlarmBursterService+FlakySdkDecorator(timeout-hang / ignore-cancellation / out-of-band-throw) + conditional DI wiring (decorator only whenFlakySdk:Enabled == true) +MeasurementExtraction.psm1helpers + Pester tests; (3) 30-minChaosMonkeycapture (criterion 11: everyWorkflowServicefault branch hit, log-evidence verified) + 8-hourSoak8hcapture (criterion 12: working-set growth ≤ 50 MB) + 22-metric row blocks + runbook §4.5 (ChaosMonkey) + §4.6 (Soak8h) + Phase-1 exit-gate banner. - Decisions recorded in spec: (a)
FlakySdkOptionsis global, not per-profile (per-profile is a follow-up); (b)FlakySdkDecoratorwrapsIMachineConnectiononly —IMotionControllerdecorator is a follow-up since connect-side coverage is enough for the criterion-11 fault-branch evidence; (c)TimeCompressionFactorscales only simulator-internal idle waits (connect delay, motion-tick wait), NOT producer rates (tags / frames / encoder), so the data plane stays representative under compression; (d)AlarmBurstEveryMs = 0underSoak8his intentional — alarm bursts dominate run throughput and the soak's job is leak detection, not fault-path coverage; (e)TimeCompressionFactor: 1.0on both new profiles for the exit-gate captures (real-time keeps the data plane representative); (f) the alarm-burster's fault-code pool is round-robin soOnFaultInjected's "already active" duplicate-code branch is also exercised by the wrap-around; (g) the criterion-11 fault-branch coverage is verified by log inspection (PowerShellSelect-Stringrecipe in runbook §4.5), not by counter values, since counters only say how many faults occurred, not which workflow state they hit. - Added the SLICE-1.4 row to the Phase 1 table above (status: Proposed). No code changes yet.
2026-05-01 — TASK-1.4 Pass 3 (partial): ChaosMonkey capture + criterion-11 verified; four FlaUI fixes
- Four FlaUI / service fixes required before the capture could run:
bf32566—SimulatorProfileHydrationService.StartAsync: 7 chaos fields were missing from theSelect()projection (Pass 1 regression); all profiles had zero chaos knobs.0f1596a—MainWindow.xaml: addedAutomationProperties.AutomationId="RecoverButton"(no AutomationId → FlaUI couldn't click Recover).MultiTagSoakFlaUi: added post-run Faulted→Idle wait loop before the inter-run Home click.5462d42—MultiTagSoakFlaUi: added retry-Home loop (3 attempts) with pre-retry fault-wait; fault fires ~every 45s so it can hit during the ~1s homing window.2108272—DemoBaselineFlaUi.SetupAsync: retry Connect up to 3 attempts with 1s gap; ChaosMonkey'sConnectionFailureProbability=0.3fires onConnectAsyncand the single-attempt code threw immediately on failure.
- ChaosMonkey 30-min capture:
docs/captures/slice-1-4-chaos-monkey-2026-05-01.csv(1807 s, exit 0). Row: 491 runs.started, 453 runs.completed, 37 runs.faulted, 37 fault-cycles, frames.ingested 10 469, encoder-rate 199.9 Hz, gc-pause-p95 10.28 ms, working-set peak 225.3 MB. - Criterion-11 log evidence (
app-20260501.log): 39 fault-injected, 39 fault-cleared, 37 recovery-completed, 120 defect-shower entries. All branches hit. ✅ - Soak8h capture deferred to tonight by user. Remaining work for this session: run
-DurationSeconds 28800 -Profile Soak8h, verify criterion-12 (growth ≤ 50 MB), append both row blocks tophase-1-measurements.md, write runbook §4.5/§4.6, final commit. - 459 unit tests pass.
2026-04-30 — TASK-1.3 Pass 3 capture + criterion-7 amendment; SLICE-1.3 closed
- Build green (0 warnings); 373 tests pass (368 unit + 5 acceptance, +47 vs the SLICE-1.6 baseline). All load-bearing tests in place:
EncoderStreamPipelineServiceTestsassertsRecordingAppStateStore.UpdateCount == 0after draining 10 snapshots;WinMmTimePeriodTestsverifies acquire/dispose;SimulatedEncoderSourceTestsverifies cadence in a 1-second integration test. - Pass 3 capture:
docs/captures/slice-1-3-encoder-rate-2026-04-30.csv(613 s, 84 507 CSV rows). Headline numbers: encoder-rate-x = 656.6 Hz, encoder-rate-y = 656.6 Hz, frames.dropped = 0, runs.faulted = 0, tags.active = 50, gen-2 = 50, LOH-alloc-rate = 23 KB/s, gc-pause-p95 = 7.9 ms, working-set peak = 223.3 MB. 20-metric row block appended tophase-1-measurements.md; runbook §4.4 added; runbook §3b registered-scenarios table updated. - Receiver-rate gap: 656.6 Hz vs original criterion 980-1020 Hz. Diagnosis:
PeriodicTimer(1 ms).WaitForNextTickAsyncis not a real-time mechanism even withwinmm!timeBeginPeriod(1)acquired. Per-tick scheduling overhead + per-tick producer work (~0.5 ms: two_motion._lockacquisitions +ImmutableArray.Builder<EncoderSample>(2)allocation + channelTryWrite+NoiseModelEvaluator.Evaluateper axis) drives the effective tick to ~1.52 ms. Same Windows-timer-resolution family of constraints that SLICE-1.1 amended its criterion 7 for; SLICE-1.3 criterion 7 amended on the same pattern (documented-not-gated). Architectural goal — encoder data plane reachable through non-AppStatechannel at hundreds of Hz without destabilizing the workflow state machine — is satisfied by the row block evidence. - Pass 1/Pass 2 commits already landed (
3cec4d8,1ba005a,385b118,7ee3ff3,736afac). This session's edits: spec criterion 7 amendment, row block, runbook §4.4 + §3b table row, CLAUDE.md, this entry, follow-ups section. Single commit covers the docs + the captured CSV.
2026-04-30 — SLICE-1.3 opened (encoder-rate motion)
- Wrote
docs/specs/SLICE-1.3-encoder-rate-motion.mdanddocs/tasks/TASK-1.3-implement-encoder-rate-motion.md. Spec scope: introduce a 1 kHzIEncoderStream(per-axisEncoderSamples, bounded channel) alongside the existing 20 HzIMotionController.PositionChangedUI feed. Producer reads commanded position fromIMotionController.CurrentX/CurrentY, adds per-axisRandomWalkNoise, publishesEncoderSnapshotper tick. Pipeline service drains the channel and incrementsencoder.samples.ingested{axis=…}without writing toAppState— that bypass is the slice's load-bearing design (preview of Phase 2 / SLICE-2.3 data-plane lift-out). - Task is structured as three Copilot passes: (1) shapes + producer +
WinMmTimePeriodP/Invoke (winmm!timeBeginPeriod(1) — non-optional for criterion 7, since default Windows 15.6 ms tick caps at ~64 Hz) + validators +EncoderRateseed profile; (2)EncoderStreamPipelineService+ axis-dim AppMetrics counters +Get-EncoderRatePerAxisextraction helper + 20-metric capture row; (3) 10-minEncoderRatecapture under existingMultiTagSoakFlaUi(no new FlaUI scenario class), row block, runbook §4.4. - Decisions recorded in spec: encoder data stays out of
AppStatefor this slice (the inverse of SLICE-1.1's "telemetry stays in AppState" decision — encoder rate is high enough that store-coalesce would dominate); existing profiles shipEncoderIntervalMs: 5(200 Hz) so prior rows (0/0a/0b/slice-1-1/slice-1-2) stay reproducible; theencoder.x.counts/encoder.y.countstags in the multi-tag registry are unrelated to the new encoder stream despite naming overlap (called out explicitly in the spec). - Added the SLICE-1.3 row to the Phase 1 table above (status: Proposed). No code changes yet.
2026-04-27 — TASK-1.6 Pass 2 & Pass 3 complete; SLICE-1.6 and SLICE-1.2 closed
Pass 2 (5f39a9d): DemoBaselineFlaUi + MultiTagSoakFlaUi FlaUI scenario [Fact]s; FakeUiDriver-based unit tests for both; Capture-Measurements.ps1 full rewrite (~245 lines) with -AppendToTable, -Profile, -SliceTag, -AllowDirty, VSTestTestCaseFilter Category!=Capture guard. 326 tests, 0 failures. Pass 3 (874946d): XAML ActiveSimulatorProfileText fix (StringFormat=Active: {0} → split TextBlocks so FlaUI reads the raw value); SIMULATOR_PROFILE env-var in MultiTagSoakFlaUi.cs; -Profile param wire-through in Capture-Measurements.ps1. 10-min HighFrameRate soak captured via FlaUI rig (exit 0): 8 154 frames ingested, 0 dropped, 0 faulted, gen-2 GC = 2 713, LOH alloc-rate ~1 MB/s, gc-pause-p95 = 11.76 ms, alloc-rate +326× vs row 0b. frames.ingested (8 154) below criterion 17 500 — model mismatch, not a pipeline defect (filed as follow-up). Runbook §3b added (FlaUI rig, prerequisites, registered scenarios, fallback guidance); §4.3 added. SLICE-1.5/1.5.1/TASK-1.5/TASK-1.5.1 banners updated "planned" → "shipped 2026-04-27". TASK-1.2 and SLICE-1.2 closed. SLICE-1.6 spec status → Completed.
2026-04-28 — TASK-1.6 Pass 1 complete (FlaUI scaffolding, AutomationIds, regression tests)
- Created
src/InspectionPrototype.UiDriver(net10.0, no WPF/FlaUI deps):IUiDriverinterface +FakeUiDriverin-process test-double withConcurrentDictionary, click log, 10 ms poll loop. - Created
tests/InspectionPrototype.AcceptanceTests(net10.0-windows, FlaUI.Core + FlaUI.UIA3 5.0.0):FlaUiDriver(UIA3,Application.Attach, 100 ms poll, IDisposable), 5 smoke tests viaFakeUiDriver— all passing. - Added 13
AutomationProperties.AutomationIdattrs toMainWindow.xaml(8 buttons, 2 combos, 3 text blocks);MainWindowAutomationIdRegressionTests(2 tests — note: XAML inline attached-property attrLocalNameis"AutomationProperties.AutomationId"not just"AutomationId"). - Added
SimulatorProfilesOptionsBindingTests(4 tests) to guard the SLICE-1.2 silent-ignore bug; added FlaUI +Microsoft.Extensions.Configuration*toDirectory.Packages.props. - Commit
eca5c9e. 319 + 5 = 324 tests, 0 failures. Next: TASK-1.6 Pass 2 (scenarios + PowerShell orchestrator).
2026-04-27 — Retired the automated capture rig (SLICE-1.5 / SLICE-1.5.1 → Superseded)
- Removed ~1700 lines of code and tests for the headless-scenario approach:
src/InspectionPrototype.Application/Scenarios/directory entirely,MainViewModelOperatorCommandsAdapter,--scenarioCLI parsing inApp.xaml.cs,AddScenariosDI extension,tools/Capture-Measurements.ps1, theMove-PartialCsvOnFailurehelper and its Pester tests. The shipped rig droveIOperatorCommands.ICommandinstances directly, bypassing the XAML binding layer — that design tradeoff was called out in SLICE-1.5's spec but in practice misses the UI-binding regressions a real capture should catch. Replacement direction: SLICE-1.6 (FlaUI) — write the spec when ready. - Kept:
tools/MeasurementExtraction.psm1CSV-math helpers (ConvertTo-MeasurementRow,Get-GcPauseP95,Get-LohAllocRateAvg) — pure functions overdotnet-countersCSV, useful from manual §3. Kept: the_disposedInterlocked guards onSimulatedTagSourceandSimulatedCamera(real DI double-disposal fix). Kept: rows 0a, 0b, slice-1-1-multi-tag-telemetry inphase-1-measurements.md— historical evidence with provenance noted. - Runbook §3a rewritten to a sleep-disable powercfg recipe + retirement pointer;
Implemented by:lines stripped from §4.1 / §4.2; manual §3 is the supported capture path until SLICE-1.6 lands. - TASK-1.2 status: Passes 1 & 2 still hold (Frame fields, HighFrameRate profile, real byte[] payloads, MainViewModel.CurrentFrame, GC-pause/LOH-alloc extraction helpers). Pass 3 deferred — slice-1-2-real-frame-payloads row will land under SLICE-1.6 or via manual §3.
2026-04-26 — TASK-1.1 Pass 3 complete (slice-1-1-multi-tag-telemetry row captured); SLICE-1.1 Completed
- Per-tag metrics work was already in place (wired into
SimulatedTagSourceand verified byAppMetricsTagDimensionTests); Pass 3 was just the capture + row block. - First MultiTag capture surfaced two latent orchestrator bugs:
Capture-Measurements.ps1launched the app with CWD = repo root, soappsettings.jsonnever loaded andtags.activeread 0 throughout (latent in row 0a/0b too — Demo doesn't depend on tags). Per-tag rate-check declared PASS when all 50 tags were missing because$worstwas never updated. Fixed both (827a7ee): pass-WorkingDirectory (Split-Path $AppExe -Parent); treat all-tags-missing asexit 1only when count equals expected. - Re-captured under fixed orchestrator: CSV span 5811 s, 174 runs, all 50 tags in
samples.ingested. The 5811 s vs 1800 s configured mismatch is a 63-min mid-capture system-sleep event (laptop slept; process resumed where it left off — verified by zero log events and zero CSV rows in the 11:17:59→12:21:41 window). Earlier same-day run (10:12–10:42) finished cleanly in 30 min for 174 runs. NOT a code hang. Captures must run on a sleep-disabled session:powercfg /change standby-timeout-ac 0. Filed as runbook caveat, not a code follow-up. - Per-tag accuracy with scenario-duration normalization (1800 s, since emitters pause during sleep): ≤5 Hz tags within ±2%; 10 Hz hits ~9.17 Hz (-8%); 50 Hz hits ~32 Hz (-36%); ≥100 Hz tags cap near 64 Hz from the default Windows 15.6 ms timer tick. Original criterion 7 (±2% per tag, all bands) is unachievable on Windows for tags configured below the 15.6 ms tick boundary regardless of code quality; medium-rate tags (10–50 Hz) are subject to scheduling overhead with 50 concurrent emitters. Amended criterion 7 to document-not-gate. Filed as follow-up: simulator emit-rate accuracy under concurrent load.
- Demoted
Capture-Measurements.ps1per-tag rate-check fromexit 1toWrite-Warning+ richer.txtartifact. Row block + 50-tag rate distribution committed; CLAUDE.md and roadmap-progress updated. SLICE-1.1 → Completed.
2026-04-26 — TASK-1.5.1 Pass 3 complete (ObjectDisposedException fix; row 0b captured)
- Eliminated
ObjectDisposedExceptionat scenario shutdown: root cause wasSimulatedTagSourceregistered under two service types (SimulatedTagSource+ITagStream); DI container disposed both at host teardown — two calls on the same instance. Fixed withInterlocked.Exchange(ref _disposed, 1) != 0early-return guard (scope:SimulatedTagSourceonly). - Added
SimulatedTagSourceDisposalTests(2 regression tests); 317 total tests green. - Emptied
$KnownBenignExitCodes; removed §3a "App exit code" subsection from runbook — no longer needed. - Fixed headerless-CSV bug in
MeasurementExtraction.psm1andCapture-Measurements.ps1rate-check: dotnet-counters v9 never writes a CSV header row; fix re-imports with explicit-Headerwhen first row has noTimestampproperty. - Captured row 0b:
docs/captures/demo-baseline-automated-row-0b-2026-04-26.csv(601 s, 44 runs, telemetry 4.95 Hz, exit code 0). Appended row 0b block and Notes tophase-1-measurements.md. - Commit
f520969. SLICE-1.5.1 exit gate met. Next: SLICE-1.2 (real frame payloads).
2026-04-25 — TASK-1.5 Pass 3 complete (automated capture, row 0a, runbook §3a)
- Fixed two bugs blocking the automated capture: (1)
DemoBaselineScenario.RecipeNamewas"standard-5pt-wafer-scan"(file slug) instead of"Standard 5-Point Wafer Scan"(catalog display name); (2)WorkflowService.StartRunAsyncsilently rejected the second run because_runCtswas still non-null when the scenario re-entered the loop — fixed by clearing_runCtsbefore the terminal_store.Update, so the lock is released before theCompletedstate becomes observable. - Ran 10-minute automated capture: 57 runs in 598 s, telemetry rate 4.93 Hz (+2.1% vs row 0 baseline of 4.83 Hz — within ±5%). App exited with known
ObjectDisposedExceptiondisposal crash (pre-existing, non-blocking). - Appended row 0a to
phase-1-measurements.md(automated-capture reference; headless mode means memory/GC numbers are not comparable to row 0 — noted in row 0a notes). - Added §3a (automated capture command) to
capturing-measurements.md; added "Implemented by:" lines to §4.1 and §4.2. - Commit
f64173f. SLICE-1.5 exit gate met. Next: SLICE-1.2 (real frame payloads).
2026-04-25 — TASK-1.5 Pass 2 complete (DemoBaseline + MultiTagSoak + PowerShell orchestrator)
- Added
ScenarioContextrecord (OperatorDelay); updatedIScenario.RunAsyncsignature; wiredoperatorDelaythroughScenarioRunner.RunAsyncandApp.OnStartup. - Implemented
ScenarioStepHelper.WaitForStateAsync(event-driven, race-safe, descriptive timeout messages). - Implemented
DemoBaselineScenario(connect → catalog → load recipe → home → run loop → teardown) andMultiTagSoakScenario(same, prepended ApplySimulatorProfile "MultiTag" step). - Registered both scenarios unconditionally in
AddScenarios; both scenarios callstate.Update(→ Ready)after Completed/Stopped to allow repeated runs. - Created
tools/MeasurementExtraction.psm1(ConvertTo-MeasurementRow — 16 metrics) andtools/Capture-Measurements.ps1(build → launch → dotnet-counters → extract → per-tag rate check → optional AppendToTable). - Added 5 new scenario tests (
DemoBaselineScenarioTests,MultiTagSoakScenarioTests); 311 total green, 0 warnings. - Commit
3318928. Pass 3 (smoke-test capture + measurements row) is next.
2026-04-25 — TASK-1.5 Pass 1 complete (IScenario abstraction, runner, CLI routing)
- Created
IScenario,IOperatorCommands,ScenarioRunner,ScenarioCliArgs,FakeScenarioin Application Scenarios layer;MainViewModelOperatorCommandsAdapterin Presentation Scenarios layer. - Wired CLI routing in
App.OnStartup: TryParse after mutex guard, scenario mode runs headless (no window), exits with runner's code; interactive mode unchanged. - DI:
ScenarioRunneras transient,AddScenarios(includeFakes: Debugger.IsAttached)extension,IOperatorCommands → MainViewModelOperatorCommandsAdaptersingleton. - Added 14 new tests (
ScenarioCliArgsTests,ScenarioRunnerTests); 306 total green, 0 warnings. - Commit
d851a85. Pass 2 (DemoBaselineScenario, MultiTagSoakScenario, PowerShell orchestrator) is next.
2026-04-24 — TASK-1.1 Pass 2 complete (producer + pipeline + AppState swap + delete old path)
- Created
TagSnapshot,ITagStream(bounded snapshot stream abstraction),SimulatedTagSource(one emitterTaskper tag + snapshot publisher loop,ConcurrentDictionaryper-tag cells, coalesce detection),TagStreamPipelineService(drains stream →LatestTagValues, coalesce → Warning diagnostics entry). - Deleted
MachineTelemetry,ITelemetrySource,TelemetryPipelineService,SimulatedTelemetrySource,FakeTelemetrySource. UpdatedAppState(LatestTelemetry →ImmutableDictionary<string, TagSample> LatestTagValues). RewiredMainViewModel,InfrastructureServiceCollectionExtensions,ApplicationServiceCollectionExtensions. - Added
FakeTagStream,TagStreamPipelineServiceTests(5 tests),MainViewModelTelemetryReadoutTests(6 tests); updatedStreamingPipelineTests(tests 3+4). 280 tests green (+11 vs Pass 1). - Commit
45de45b. Pass 3 (per-tag metrics, AppMetrics counters, diagnostics panel, phase-1 measurements) is next.
2026-04-24 — TASK-1.1 Pass 1 complete (domain shapes + 50-tag seed + validator)
- Created domain types:
TagQuality,TagSample,TagDefinition,NoiseModel(4 sealed variants: SineNoise, DriftNoise, RandomWalkNoise, StepNoise),NoiseModelEvaluator(pure-function, Box-Muller Gaussian, ref-state per-tag). - Created infra config types:
NoiseOptions,SimulatorTagOptions,SimulatorTagsOptions;SimulatorTagsValidator(IValidateOptions, ValidateOnStart). AddedMicrosoft.Extensions.Options+.ConfigurationExtensionsto packages and Infrastructure csproj. - Updated
appsettings.json: 50-tag seed (8 interval groups, all 4 noise variants, reserved tags present) + MultiTag profile (TelemetryIntervalMs: 50). Also added MultiTag to PostConfigure fallback inInfrastructureServiceCollectionExtensions. - Added
NoiseModelEvaluatorTests(6 tests) andSimulatorTagsValidatorTests(12 tests). All 269 tests green. - Commit
b429f80. OldMachineTelemetry/ITelemetrySourcepath untouched; two pipelines coexist as planned.
2026-04-24 — SLICE-1.1 opened (multi-tag telemetry)
- Wrote
docs/specs/SLICE-1.1-multi-tag-telemetry.mdanddocs/tasks/TASK-1.1-implement-multi-tag-telemetry.md. Spec scope: replaceMachineTelemetry(Temp, Pressure)andITelemetrySourcewithTagSample+TagDefinitionregistry, per-tag emitter loops, andAppState.LatestTagValues. Seed 50 tags viaSimulator:Tags. - Task is structured as three Copilot passes: (1) domain shapes + 50-tag seed + validator, (2) producer/pipeline/AppState swap + UI adapter + delete old types, (3) per-tag metrics dimensions + §4.2 runbook scenario + 30-min CSV capture for the measurements row.
- Added the SLICE-1.1 row to the Phase 1 table above (status: Proposed). No code changes yet.
- Decision recorded in spec: telemetry data stays in
AppStatefor this slice (lift-out is Phase 2 / SLICE 2.3, deliberately deferred until measurements justify it).
2026-04-23 — Demo baseline (row 0) captured; Phase 0 exit gate met
- Ran scenario §4.1 against commit
7ecef05; 687 s capture (10-min scenario + warm-up/cool-down) written todocs/captures/demo-baseline-2026-04-23.csv. - Filled all 16 metrics in row 0 of
phase-1-measurements.md. Headline numbers: 41 runs completed in 687 s, 492 frames ingested, 3 315 telemetry events, 203 MB working-set peak, 0.99% avg CPU, 4.69% peak CPU.frames.dropped,telemetry.coalesced,runs.faultedall 0 (absent from CSV — expected on a clean baseline). - Fixed
docs/runbook/capturing-measurements.md§5:dotnet-counters collect --format csvproduces long-format (Timestamp/Provider/Counter Name/Counter Type/Mean/Increment), not wide-format. Replaced extraction table with correct OTel-style counter names and added a PowerShell script that sums Rate counters and computes CPU% fromdotnet.process.cpu.time+dotnet.process.cpu.count. - Phase 0 exit gate is now met; Phase 1 can be opened.
2026-04-22 — Phase 0 planning and execution
- Produced three reviews: production-readiness audit, canonical-AppState review, and the five-phase evolution roadmap (all under
docs/reviews/). - Opened SLICE-005 (CI + quality gates), SLICE-006 (observability baseline), and the matching TASK files.
- Copilot agent session completed TASK-005.
Directory.Build.props,Directory.Packages.props(with Microsoft.Extensions.* bumped from 9.0.4 → 10.0.x to match thenet10.0-windowsTFM),.editorconfig,.github/workflows/ci.yml, solution file. All 216 tests green. - Copilot agent session completed TASK-006 in three passes. Serilog + diagnostics-timeline bridge, three unhandled-exception handlers with
CrashReporterand non-modal banner,AppMetricsmeter with seven counters wired intoFramePipelineService/TelemetryPipelineService/WorkflowService, single-instance mutex. Runbook atdocs/runbook/observability.md. - Wrote the measurement-capture procedure (
docs/runbook/capturing-measurements.md) and the measurements table skeleton (docs/reviews/phase-1-measurements.mdwith row 0 template). - Introduced
CLAUDE.mdand this progress file for cross-session continuity. - Outstanding for Phase 0: capture demo baseline (row 0) and commit the CSV + filled row.
How to update this file
When a session finishes a slice, changes its status, or produces a measurement row:
- Edit the relevant row in the per-slice table above (status column + notes).
- Prepend a new dated block to the session log.
- Update the Current position block in
CLAUDE.mdto match. - Commit all three edits (slice code, this file,
CLAUDE.md) in the same commit so the next session sees a consistent snapshot.
If a phase's exit gate is met, add a one-line note under that phase's section in the per-slice table ("Phase N exit gate: met on YYYY-MM-DD, see row N.x of measurements table") so the exit event is not buried in the session log.