Skip to content

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

SliceTitleStatusNotes
001First Strong Vertical SliceCompleted
002Persistent Run HistoryCompleted
003JSON Recipe File ManagementCompleted
004Operational MaturityCompletedLast slice before the evolution roadmap landed.

Phase 0 — Foundations

SliceTitleStatusNotes
005CI and Quality GatesCompletedDelivered via GitHub Copilot agent session using prompt in TASK-005.
006Observability BaselineCompletedThree 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

SliceTitleStatusNotes
1.1Multi-Tag TelemetryCompletedPass 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.2Real frame payloadsCompleted (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.5Automated Measurement CaptureSuperseded (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.1Fix ObjectDisposedException; capture row 0bSuperseded (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.6FlaUI-driven Measurement CaptureCompleted (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.3Encoder-rate motionCompleted (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.4Storm & soak profilesCompleted (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.

SliceTitleStatusNotes
2.0Store allocation & contention profilingCompleted (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.1Slice AppState into sub-recordsDeferred (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.2Immutable collectionsDeferred (2026-05-07)SLICE-2.0 found no List<T> rebuild pressure (0.5% alloc share). Not triggered.
2.3Data-plane lift-outPartial / 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.4Per-slice observablesCompleted (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.5Source-generated INPCRetired (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.7Domain purity auditCompleted (2026-05-11)Renamed RunSummary.SimulatorProfileNameOperatingProfileName (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.

SliceTitleStatusNotes
3.3SQLite persistence + schema versioningCompleted (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.1Rich defect modelCompleted (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.2Wafer loop / cassette cadenceCompleted (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.4Identity + auditConditionalOperatorId 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. Scores AppState/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 via with{} (§14), RegisterTagsActiveProvider mechanism-name (§15), dual notification channels + null! workaround + MaxDiagnosticsEntries location (§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/ActiveRun is 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 current AppState store 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 from CLAUDE.md at time of approval.
  • Next: author the review doc (2026-05-??-appstate-design-review.md) — score current AppState design 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); migrated MainViewModel from monolithic Project(state) to 10 per-slice Subscribe<T> callbacks (Pass 2); added Get-SubscriberInvocationRate, Get-SubscriberToStoreRatio, Get-SubscriberSelectorDistribution helpers + extended ConvertTo-MeasurementRow to 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, commit 6d60698). 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.SimulatorProfileNameOperatingProfileName (and the parallel ActiveRunState field). Propagated through Domain / Application (WorkflowService) / Infrastructure (SqliteRunHistoryStore SQL + DTO + Dapper params) / Presentation (RunHistoryItemViewModel, MainViewModel, MainWindow.xaml grid column binding) / 5 test files / tools/Populate-SyntheticHistory.ps1. Added M005_rename_simulator_profile_column.sql (SQLite ALTER 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 three MigrationRunner* test files.
  • Added DomainLayerPurityTests (3 reflection-based [Fact] tests in new Architecture/ folder): forbidden-assembly references (Application / Infrastructure / Presentation / App), forbidden vocabulary fragments (Simulator / Sqlite / Wpf / ViewModel / Dapper / FlaUI) in Domain.Contracts type names and public member names.
  • Verification results. Vocabulary plant (added SimulatorTestPlant property to RunSummary) correctly failed DomainContracts_PublicMembers_ContainNoForbiddenVocabulary (the auto-generated get_SimulatorTestPlant accessor matched the forbidden Simulator fragment). 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, with DomainAssembly_DoesNotReferenceUpperLayers serving 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.0 is centrally pinned, MainViewModel is partial class : ObservableObject with 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.CanStart did not allow WorkflowState.Completed — cassette chaining required CanStart to return true after each wafer run transitions workflow to Completed; (2) SimulatedCassetteScheduler.RunWaferAsync guard inverted — StartRunAsync was never called because the guard checked !IsTerminalWorkflowState(Ready) = false before startup; (3) CassetteSoakFlaUi used WaitForTextAsync on a WPF Run element whose UIA Name property does not reliably return bound text — replaced with ClickByAutomationIdAsync("UnloadCassetteButton", 25min) which polls for the button enabled state.
  • Fixed BoolVis cross-assembly StaticResource crash: moved BooleanToVisibilityConverter from MainWindow.xaml Window.Resources to App.xaml Application.Resources.
  • Added Get-WafersCompletedCount, Get-CassetteWallClockSeconds helpers to tools/MeasurementExtraction.psm1; extended ConvertTo-MeasurementRow with -LotId parameter (metrics 33–34); fixed Get-SqliteCount missing-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, LotId LOT-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 (0 SqliteException Error 19 in 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.md and docs/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. Adds WaferId/LotId/CassetteSlot/CassetteState/WaferPhase to Domain. RunSummary gains nullable WaferId/LotId. RunTerminalStatus gains Pending (placed FIRST so default = Pending). M003 SQL adds wafer_id + lot_id columns + indexes; M004 audit-marks the FK-band-aid retirement. ICassetteScheduler interface + SimulatedCassetteScheduler implementation. WorkflowService.StartRunAsync(WaferId?, LotId?) overload writes a stub run_summary row with TerminalStatus = Pending before the run loop starts, then upserts at run end via the existing INSERT OR REPLACE. SqliteDefectStore.OpenAsync removes the explicit PRAGMA 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.Pending is FIRST enum member so default(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 in schema_version; (d) cassette mode runs in parallel-coexistence with direct mode — AppState.Cassette null = 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 via MultiTagSoakFlaUi); (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 but SimulatedCamera only streams during active scan motion — same constraint documented in slice-1-2-real-frame-payloads's frames.ingested shortfall. 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 a DefectStorm profile (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 Stopwatch timing in SafeDefectPersistAsync at LogDebug level; Release builds suppress Debug, so defect-persist p95 (ms) read in the row. Same observability pattern SLICE-2.0 uses for store-update lock-wait: a Histogram<double> counter is always-on regardless of log level. Landed: new AppMetrics.DefectPersistLatencyMicros Histogram (defect.persist.latency.micros, unit us); SafeDefectPersistAsync.Record(elapsed.TotalMicroseconds) on success; Get-DefectPersistP95Ms now accepts -Csv $rows (primary) with -LogPath retained 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 SqliteDefectStore filed as architectural follow-up. Pass 3 commit message documents that defects fail to persist with FK enforcement on (SQLite Error 19) because defects.run_id references run_summaries.run_id but the parent row only exists at run-end while defects are persisted fire-and-forget during the run. Fix shipped: explicit PRAGMA foreign_keys = OFF in SqliteDefectStore.OpenAsync(). Status: filed as a follow-up, not blocking. The schema still declares REFERENCES 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 stub run_summary row 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 create run_summary rows 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 Stopwatch timing to SafeDefectPersistAsync in FramePipelineService: 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 %, private script:Invoke-SqliteDistrib), Get-DefectPersistP95Ms (p95 from Defect persisted. ElapsedMs=… log pattern). Extended ConvertTo-MeasurementRow to 32 metrics; Export-ModuleMember updated.
  • Added 9 new Pester tests (40 total, all pass): Get-DefectsPersistedCount (delta logic), Get-DefectClassificationDistribution (4 cases), Get-DefectPersistP95Ms (4 cases). script:New-MinimalDefectsDb helper 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=Shared caused SqliteDefectStore to receive physical connections already having PRAGMA foreign_keys = ON (set by SqliteRunHistoryStore, SqliteAlarmHistoryStore, and MigrationRunner). Removing Foreign Keys=True from the connection string alone was insufficient; fix: explicit PRAGMA foreign_keys = OFF in SqliteDefectStore.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, commit 4bde4ed): 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): WaferToCanvas static helper, CanvasX/CanvasY from wafer-mm via (waferMm + 100) / 200 * canvasSize, MarkerBrush severity-coded (Minor=Yellow, Major=Orange, Critical=Red), DefectId.
  • Created WaferMapView UserControl (Presentation/Controls): 200×200 px wafer-disc Ellipse backdrop + ItemsControl with Canvas panel; ItemContainerStyle binds Canvas.Left/Canvas.Top; 8×8 px ellipse DataTemplate; DefectCountText AutomationId on count label; WaferMapView AutomationId on hosting element in MainWindow.xaml.
  • Updated MainViewModel: RecentDefects ObservableCollection<DefectViewModel> field + DefectCount int property; diff projection in Project(AppState) (prepend-new at index 0, reverse-loop remove evicted; ReferenceEquals guard for no-op updates).
  • Test project upgraded to net10.0-windows + UseWPF=true + Presentation project reference + explicit System.IO/System.Net.Http usings (net10.0-windows drops those from implicit set). Added WaferToCanvasTests (coord math, brush, edge cases) + MainViewModelDefectProjectionTests (diff logic: prepend, evict, no-op, window-cap, 200-iteration stress). AutomationId regression test extended with WaferMapView. 522/522 tests green.

2026-05-07 — TASK-3.1 Pass 1 complete (domain shapes + persistence + pipeline + AppState)

  • Created: Defect record (11 fields), BoundingBox, DefectClassification enum; deleted InspectionResult. M002 migration (defects table + 2 indexes + FK to run_summaries). IDefectStore + SqliteDefectStore (Dapper, SaveManyAsync batch via multi-row Dapper call). AppState.RecentDefects sliding-100 window; reset at WorkflowService.StartRunAsync. FramePipelineService upgraded: BuildSimulatedDefect, fire-and-forget SafeDefectPersistAsync, sliding window update.
  • New tests: MigrationRunnerM002Tests (3), SqliteDefectStoreTests (6), SqliteDefectStorePerformanceTests (1 + existing total perf=2), FramePipelineServiceDefectIntegrationTests (4), WorkflowServiceTests.StartRunAsync_ClearsRecentDefects (1). Updated MigrationRunnerTests to expect version=2. All 500 unit tests green; perf test 865 ms < 1000 ms ceiling. Commit f40db0c.
  • Optimization: SaveManyAsync changed from a C# foreach to Dapper multi-row ExecuteAsync(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.md and docs/tasks/TASK-3.1-implement-rich-defect-model.md. Spec scope: replace thin InspectionResult(SourceFrameId, HasDefect, DefectSummary, Severity) with rich Defect(DefectId, RunId, FrameId, BoundingBox, Classification, Confidence, Severity, WaferX, WaferY, ImageRef?). Add M002 migration creating defects table (FK to run_summaries) + 2 indexes. Implement IDefectStore + SqliteDefectStore (Dapper, mirrors SLICE-3.3 patterns; SaveManyAsync uses single-transaction batched inserts for 5K-defect performance). FramePipelineService.ProcessDefectsForFrame upgraded to produce + persist Defect via fire-and-forget. Add AppState.RecentDefects sliding-100 window; reset at WorkflowService.StartRunAsync. Add WaferMapView WPF UserControl rendering markers at wafer-mm coordinates, color-coded by severity (Minor=Gold, Major=Orange, Critical=Red). MainViewModel.RecentDefects is an ObservableCollection<DefectViewModel> updated via diff projection (not clear-and-rebuild).
  • Three Copilot passes: (1) domain shapes + M002 + persistence + pipeline upgrade + AppState reset + tests including SaveMany5000_CompletesUnderOneSecond perf test; (2) UI — WaferMapView UserControl + DefectViewModel + diff-based ObservableCollection projection + MainWindow.xaml integration + AutomationIds; (3) 30-min HighDefect capture verifying ≥ 5 000 defects + pagination + classification distribution + Phase 2 trigger assessment in phase-3-measurements.md.
  • Decisions in spec: (a) InspectionResult deleted (no external consumer beyond the pipeline service it lived inside); (b) RecentDefects sliding window cap = 100 in AppState (UI surface) — full per-run history accessible via IDefectStore.LoadByRunAsync for future "view all" UI; (c) per-defect fire-and-forget SaveAsync instead of buffered batched flusher — ships simpler path, revisit if measurement shows backlog; (d) RecentDefects reset 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 uses ObservableCollection diff 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: inline dotnet run pattern, single transaction, idempotent, 10 K synthetic run_summaries rows in ~5 s. Added 3 public helpers to tools/MeasurementExtraction.psm1: Get-RunsPersistedCount, Get-AlarmsPersistedCount, Get-RecentHistoryLoadP95 (+ private script:Get-SqliteCount via inline dotnet). Updated ConvertTo-MeasurementRow to accept -DatabaseBefore/-DatabaseAfter/-LogPath and emit 3 new rows (metrics 27–29). Added 3 new Pester Describe blocks; 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, commit fb5d518, 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. Extended ConvertTo-MeasurementRow to emit 26 rows (was 22 via Pass 2 additions: store.update rate, alloc share, lock-wait p95, top-caller). 9 new Pester tests appended to tests/Tools/MeasurementExtraction.Tests.ps1; also fixed pre-existing encoder test fixture (axis dim was in separate Tags column, not in Counter Name). Created docs/reviews/phase-2-measurements.md skeleton (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 the slice-2-0-store-profiling row, commit CSV evidence.

2026-05-08 — TASK-2.0 Pass 1 complete (AppStateStore instrumentation)

  • Added 3 new AppMetrics members (StoreUpdateCalls{caller}, StoreUpdateAllocBytes, StoreUpdateLockWaitMicros). Extended IAppStateStore.Update with [CallerMemberName] + [CallerFilePath] optional parameters. Rewrote AppStateStore.Update to record lock-wait via Stopwatch, alloc-delta via GC.GetAllocatedBytesForCurrentThread(), and call count with caller dimension. Updated all test fakes and 12 test files that construct new AppStateStore() to pass new AppMetrics().
  • Added AppStateStoreInstrumentationTests.cs (5 tests) and AppMetricsStoreUpdateDimensionTests.cs (6 tests). Build: 0 errors / 0 warnings. 471/471 xUnit tests pass. Commit 5f029cf.

2026-05-07 — SLICE-3.3 opened (SQLite persistence + schema versioning)

  • Wrote docs/specs/SLICE-2.0-store-allocation-profiling.md and docs/tasks/TASK-2.0-implement-store-allocation-profiling.md. Spec scope: instrument AppStateStore.Update with [CallerMemberName] + [CallerFilePath] parameters (compile-resolved at every callsite — no callsite changes), Stopwatch for lock-wait, GC.GetAllocatedBytesForCurrentThread() for alloc-delta. Three new AppMetrics members. One 30-minute MultiTag capture; row block under a new phase-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.Update allocations" / "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 MB superseded by working-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). Updated docs/reviews/phase-1-measurements.md (slice-1-4-soak-8h row block: added working-set steady-state drift (MB) = −2.7 row; original working-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.json FlakySdk Enabled true→false (criterion-16 reproducibility); FlakySdkDecorator timeout branch falls through to inner when caller CT not cancelled (spec fix); new regression test FlakySdk_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-set last − 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.md and docs/tasks/TASK-1.4-implement-storm-and-soak-profiles.md. Spec scope: add 7 storm-and-soak knobs to SimulatorProfile (DefectShowerEveryMs, DefectShowerDurationMs, AlarmBurstEveryMs, TelemetryDropoutChance, NetworkLatencyMeanMs, NetworkLatencyStddevMs, TimeCompressionFactor), a top-level Simulator:FlakySdk config 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 new AppMetrics counters — derivation reuses existing working_set and runs.faulted rows.
  • Task is structured as three Copilot passes: (1) profile-record fields + JSON binder + validators + Simulator:FlakySdk config-only + TelemetryDropoutChance in SimulatedTagSource + TimeCompressionFactor in SimulatedMachineConnection.ConnectAsync and SimulatedMotionController.InterpolateAsync per-tick wait + Gaussian network latency in ConnectAsync + appsettings.json (ChaosMonkey, Soak8h, FlakySdk block); (2) DefectShowerService + AlarmBursterService + FlakySdkDecorator (timeout-hang / ignore-cancellation / out-of-band-throw) + conditional DI wiring (decorator only when FlakySdk:Enabled == true) + MeasurementExtraction.psm1 helpers + Pester tests; (3) 30-min ChaosMonkey capture (criterion 11: every WorkflowService fault branch hit, log-evidence verified) + 8-hour Soak8h capture (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) FlakySdkOptions is global, not per-profile (per-profile is a follow-up); (b) FlakySdkDecorator wraps IMachineConnection only — IMotionController decorator is a follow-up since connect-side coverage is enough for the criterion-11 fault-branch evidence; (c) TimeCompressionFactor scales 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 = 0 under Soak8h is intentional — alarm bursts dominate run throughput and the soak's job is leak detection, not fault-path coverage; (e) TimeCompressionFactor: 1.0 on 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 so OnFaultInjected'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 (PowerShell Select-String recipe 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:
    1. bf32566SimulatorProfileHydrationService.StartAsync: 7 chaos fields were missing from the Select() projection (Pass 1 regression); all profiles had zero chaos knobs.
    2. 0f1596aMainWindow.xaml: added AutomationProperties.AutomationId="RecoverButton" (no AutomationId → FlaUI couldn't click Recover). MultiTagSoakFlaUi: added post-run Faulted→Idle wait loop before the inter-run Home click.
    3. 5462d42MultiTagSoakFlaUi: added retry-Home loop (3 attempts) with pre-retry fault-wait; fault fires ~every 45s so it can hit during the ~1s homing window.
    4. 2108272DemoBaselineFlaUi.SetupAsync: retry Connect up to 3 attempts with 1s gap; ChaosMonkey's ConnectionFailureProbability=0.3 fires on ConnectAsync and 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 to phase-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: EncoderStreamPipelineServiceTests asserts RecordingAppStateStore.UpdateCount == 0 after draining 10 snapshots; WinMmTimePeriodTests verifies acquire/dispose; SimulatedEncoderSourceTests verifies 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 to phase-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).WaitForNextTickAsync is not a real-time mechanism even with winmm!timeBeginPeriod(1) acquired. Per-tick scheduling overhead + per-tick producer work (~0.5 ms: two _motion._lock acquisitions + ImmutableArray.Builder<EncoderSample>(2) allocation + channel TryWrite + NoiseModelEvaluator.Evaluate per 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-AppState channel 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.md and docs/tasks/TASK-1.3-implement-encoder-rate-motion.md. Spec scope: introduce a 1 kHz IEncoderStream (per-axis EncoderSamples, bounded channel) alongside the existing 20 Hz IMotionController.PositionChanged UI feed. Producer reads commanded position from IMotionController.CurrentX/CurrentY, adds per-axis RandomWalkNoise, publishes EncoderSnapshot per tick. Pipeline service drains the channel and increments encoder.samples.ingested{axis=…} without writing to AppState — 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 + WinMmTimePeriod P/Invoke (winmm!timeBeginPeriod(1) — non-optional for criterion 7, since default Windows 15.6 ms tick caps at ~64 Hz) + validators + EncoderRate seed profile; (2) EncoderStreamPipelineService + axis-dim AppMetrics counters + Get-EncoderRatePerAxis extraction helper + 20-metric capture row; (3) 10-min EncoderRate capture under existing MultiTagSoakFlaUi (no new FlaUI scenario class), row block, runbook §4.4.
  • Decisions recorded in spec: encoder data stays out of AppState for 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 ship EncoderIntervalMs: 5 (200 Hz) so prior rows (0/0a/0b/slice-1-1/slice-1-2) stay reproducible; the encoder.x.counts / encoder.y.counts tags 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): IUiDriver interface + FakeUiDriver in-process test-double with ConcurrentDictionary, 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 via FakeUiDriver — all passing.
  • Added 13 AutomationProperties.AutomationId attrs to MainWindow.xaml (8 buttons, 2 combos, 3 text blocks); MainWindowAutomationIdRegressionTests (2 tests — note: XAML inline attached-property attr LocalName is "AutomationProperties.AutomationId" not just "AutomationId").
  • Added SimulatorProfilesOptionsBindingTests (4 tests) to guard the SLICE-1.2 silent-ignore bug; added FlaUI + Microsoft.Extensions.Configuration* to Directory.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, --scenario CLI parsing in App.xaml.cs, AddScenarios DI extension, tools/Capture-Measurements.ps1, the Move-PartialCsvOnFailure helper and its Pester tests. The shipped rig drove IOperatorCommands.ICommand instances 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.psm1 CSV-math helpers (ConvertTo-MeasurementRow, Get-GcPauseP95, Get-LohAllocRateAvg) — pure functions over dotnet-counters CSV, useful from manual §3. Kept: the _disposed Interlocked guards on SimulatedTagSource and SimulatedCamera (real DI double-disposal fix). Kept: rows 0a, 0b, slice-1-1-multi-tag-telemetry in phase-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 SimulatedTagSource and verified by AppMetricsTagDimensionTests); Pass 3 was just the capture + row block.
  • First MultiTag capture surfaced two latent orchestrator bugs: Capture-Measurements.ps1 launched the app with CWD = repo root, so appsettings.json never loaded and tags.active read 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 $worst was never updated. Fixed both (827a7ee): pass -WorkingDirectory (Split-Path $AppExe -Parent); treat all-tags-missing as exit 1 only 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.ps1 per-tag rate-check from exit 1 to Write-Warning + richer .txt artifact. 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 ObjectDisposedException at scenario shutdown: root cause was SimulatedTagSource registered under two service types (SimulatedTagSource + ITagStream); DI container disposed both at host teardown — two calls on the same instance. Fixed with Interlocked.Exchange(ref _disposed, 1) != 0 early-return guard (scope: SimulatedTagSource only).
  • 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.psm1 and Capture-Measurements.ps1 rate-check: dotnet-counters v9 never writes a CSV header row; fix re-imports with explicit -Header when first row has no Timestamp property.
  • 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 to phase-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.RecipeName was "standard-5pt-wafer-scan" (file slug) instead of "Standard 5-Point Wafer Scan" (catalog display name); (2) WorkflowService.StartRunAsync silently rejected the second run because _runCts was still non-null when the scenario re-entered the loop — fixed by clearing _runCts before the terminal _store.Update, so the lock is released before the Completed state 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 ObjectDisposedException disposal 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 ScenarioContext record (OperatorDelay); updated IScenario.RunAsync signature; wired operatorDelay through ScenarioRunner.RunAsync and App.OnStartup.
  • Implemented ScenarioStepHelper.WaitForStateAsync (event-driven, race-safe, descriptive timeout messages).
  • Implemented DemoBaselineScenario (connect → catalog → load recipe → home → run loop → teardown) and MultiTagSoakScenario (same, prepended ApplySimulatorProfile "MultiTag" step).
  • Registered both scenarios unconditionally in AddScenarios; both scenarios call state.Update(→ Ready) after Completed/Stopped to allow repeated runs.
  • Created tools/MeasurementExtraction.psm1 (ConvertTo-MeasurementRow — 16 metrics) and tools/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, FakeScenario in Application Scenarios layer; MainViewModelOperatorCommandsAdapter in 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: ScenarioRunner as transient, AddScenarios(includeFakes: Debugger.IsAttached) extension, IOperatorCommands → MainViewModelOperatorCommandsAdapter singleton.
  • 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 emitter Task per tag + snapshot publisher loop, ConcurrentDictionary per-tag cells, coalesce detection), TagStreamPipelineService (drains stream → LatestTagValues, coalesce → Warning diagnostics entry).
  • Deleted MachineTelemetry, ITelemetrySource, TelemetryPipelineService, SimulatedTelemetrySource, FakeTelemetrySource. Updated AppState (LatestTelemetry → ImmutableDictionary<string, TagSample> LatestTagValues). Rewired MainViewModel, InfrastructureServiceCollectionExtensions, ApplicationServiceCollectionExtensions.
  • Added FakeTagStream, TagStreamPipelineServiceTests (5 tests), MainViewModelTelemetryReadoutTests (6 tests); updated StreamingPipelineTests (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). Added Microsoft.Extensions.Options + .ConfigurationExtensions to 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 in InfrastructureServiceCollectionExtensions.
  • Added NoiseModelEvaluatorTests (6 tests) and SimulatorTagsValidatorTests (12 tests). All 269 tests green.
  • Commit b429f80. Old MachineTelemetry/ITelemetrySource path 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.md and docs/tasks/TASK-1.1-implement-multi-tag-telemetry.md. Spec scope: replace MachineTelemetry(Temp, Pressure) and ITelemetrySource with TagSample + TagDefinition registry, per-tag emitter loops, and AppState.LatestTagValues. Seed 50 tags via Simulator: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 AppState for 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 to docs/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.faulted all 0 (absent from CSV — expected on a clean baseline).
  • Fixed docs/runbook/capturing-measurements.md §5: dotnet-counters collect --format csv produces 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% from dotnet.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 the net10.0-windows TFM), .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 CrashReporter and non-modal banner, AppMetrics meter with seven counters wired into FramePipelineService / TelemetryPipelineService / WorkflowService, single-instance mutex. Runbook at docs/runbook/observability.md.
  • Wrote the measurement-capture procedure (docs/runbook/capturing-measurements.md) and the measurements table skeleton (docs/reviews/phase-1-measurements.md with row 0 template).
  • Introduced CLAUDE.md and 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:

  1. Edit the relevant row in the per-slice table above (status column + notes).
  2. Prepend a new dated block to the session log.
  3. Update the Current position block in CLAUDE.md to match.
  4. 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.

Docs-first project memory for AI-assisted implementation.