Skip to content

Architectural Foundations Review

  • Date: 2026-05-09
  • Status: Working assessment (the new framing supersedes the 2026-05-07 strategy's measurement-gated Phase 2 framing)
  • Companion docs: Phase 1 Retrospective, Phase 1 Capabilities and Limits, 2026-05-07 Phase 2+3 Strategy (to be superseded)
  • Audience: anyone deciding what Phase 2 actually is, anyone evaluating whether the prototype is on track to become a production-grade reference, anyone proposing a new slice that would land on top of these foundations

Why this doc exists

The 2026-05-07 strategy doc made Phase 2 conditional on performance measurements. Five subsequent measurement points (slice-2-0 through slice-3-2) cleared every threshold; Phase 2 was deferred against strong evidence.

But the framing was wrong. Production-grade software isn't justified by measurement triggers — it's justified by being correct. Performance is one quality dimension among several; correctness, scalability, modularity, testability, maintainability, and design integrity are equally load-bearing. The SLICE-2.0 rubric measured store-side throughput; it never audited the framework against production-grade patterns.

This doc does that audit. The format mirrors the phase-1-capabilities-and-limits doc: a systematic inventory of what's already correct, what's partial, and what's missing — with concrete file/code references rather than abstract critique. Each gap maps to a candidate Phase 2 slice. The new Phase 2 (8 slices, not 4) is architectural-debt-driven, not performance-gated.

The prototype is positioned to become a reference for "what production-grade .NET 10 desktop industrial-control software looks like." That positioning is only earned if the architecture, design, and implementation are at the standard the documentation discipline has already reached. Today, several dimensions have not yet caught up.

Audit methodology

"Production-grade" here means the patterns and machinery a 2025-era .NET 10 desktop application would use if built from scratch today by an experienced team that cares about long-term maintainability. Concretely:

  • State management: single source of truth + pure reducers + selector-based subscriptions + memoized derived values. The Redux/Vuex/MobX pattern, adapted for .NET.
  • View layer: MVVM with source-generated INPC (CommunityToolkit.Mvvm or equivalent), compiled bindings where the platform supports them, declarative resource organization.
  • Domain purity: Domain layer has zero leakage of Application/Infrastructure concepts. Aggregates and value objects are explicit; domain events are first-class.
  • Error handling: Result<T, Error> for expected failures; exceptions reserved for genuinely exceptional cases. No silent fire-and-forget without compensating mechanisms.
  • Concurrency: async-all-the-way; no sync-over-async; channels and actors over locks where the workload calls for it.
  • Testing: unit + integration tiers (✓ already), plus property-based tests for state machines and mutation testing for coverage quality.
  • Code quality: Nullable reference types enabled project-wide; analyzers configured (.editorconfig + Roslyn rules); source generators replacing hand-written boilerplate.
  • Persistence: repository + migration patterns (✓ already), plus outbox pattern for reliable event publishing where it matters.
  • Observability: structured logs + metrics (✓ already), plus distributed tracing (OpenTelemetry) and health endpoints.

Each section below scores the codebase against this standard, with evidence.


1. State management

Correct today:

  • AppState is a single immutable record (Application/State/AppState.cs); the canonical store pattern (ADR-001).
  • AppStateStore.Update(Func<AppState, AppState> reducer) is a pure-reducer interface; no other component mutates AppState directly.
  • Lock-protected single-owner mutation; StateChanged fires outside the lock.
  • Caller-attribution via [CallerMemberName] + [CallerFilePath] (SLICE-2.0) gives traceability without sacrificing the reducer pattern.

Partial / gaps:

  • No selector-based subscriptions. Every StateChanged fires every subscriber, regardless of whether they care about the slice that changed. MainViewModel.Project(state) reassigns ~30 properties on every state change; ditto for command-guard re-evaluation via CommandManager.RequerySuggested. This is the originally-planned SLICE-2.4, but framed for the wrong reason (lock-wait under threshold) — the real reason is subscriber selectivity.
  • No memoized derived values. MainViewModel.IsMachineReady, MachineReadyLabel, etc. recompute on every state change even when the inputs haven't shifted.
  • State-shape invariants are not enforced in record constructors. Example: RunSummary.Pending (introduced in SLICE-3.2) uses EndedAtUtc = DateTimeOffset.MinValue as a placeholder; nothing prevents code from constructing a RunSummary { TerminalStatus = Completed, EndedAtUtc = DateTimeOffset.MinValue }.
  • Transition validation is in CommandGuards but not enforced reducer-side. A reducer could in theory write WorkflowState.Running on top of WorkflowState.Aborted without the guards catching it.

Candidate slices:

  • SLICE-2.4 (per-slice observables) — IObservable<ConnectionSlice>, IObservable<RunSlice>, etc., each with DistinctUntilChanged. Subscribers consume only what they care about. Re-framed exit gate: PropertyChanged events per StateChanged drops by ≥ 80% under a HighDefect or cassette capture.
  • SLICE-2.1 (slice AppState into sub-records) — ConnectionSlice, MotionSlice, RunSlice, etc. Reducers mutate one slice at a time. Real justification: blast-radius reduction. A LoadedRecipe change shouldn't force every subscriber that doesn't care about recipe state to re-read the whole AppState.

2. View / projection layer

Correct today:

  • MVVM separation between Presentation.ViewModels and App (XAML).
  • Diff-based collection projections in MainViewModel.RecentDefects (SLICE-3.1) and CassetteSlots (SLICE-3.2) preserve visual-element identity. Pattern is good — it just isn't generalized.

Partial / gaps:

  • INotifyPropertyChanged plumbing is hand-written. Every property in MainViewModel has a manual setter. Setters fire OnPropertyChanged unconditionally — no dirty-check. Today (2025-era .NET), the canonical pattern is CommunityToolkit.Mvvm's [ObservableProperty] source generator: declare the field, and the generator produces the setter, the dirty-check, the PropertyChanged event, and any [NotifyPropertyChangedFor] dependencies.
  • MainViewModel.Project(state) is the canonical anti-pattern. Sets ~30 properties unconditionally on every StateChanged event. Until dirty-checking lands, every binding re-evaluates on every state change.
  • WPF bindings are loose {Binding Foo} with no compiled-binding equivalent. WPF doesn't have x:Bind (that's WinUI/UWP), but strict DataContext typing + IDataErrorInfo discipline could approximate the safety. Currently a typo in a binding path silently produces blank UI.
  • Brushes and other resources are constructed in code, not declared. WaferToCanvas.BrushForSeverity returns Brushes.Red etc.; should be StaticResource lookup via theme. Same for CassetteSlotViewModel.StateBrush.
  • Accessibility is partial. AutomationProperties.AutomationId exists for FlaUI test targeting (SLICE-1.6 introduced 13 IDs; later slices added more). No AutomationProperties.Name / HelpText / LandmarkType for screen readers. Production accessibility audit hasn't happened.

Candidate slices:

  • SLICE-2.5 (source-generated INPC) — adopt CommunityToolkit.Mvvm 8.x; convert every ViewModel to [ObservableProperty] + [RelayCommand] patterns. Includes dirty-check side-effect at no extra cost. Smallest high-leverage architectural change available today.
  • SLICE-2.4 addresses the Project anti-pattern by changing what fires the consumer-side projection in the first place (selectivity at the producer end, not just dirty-check at the consumer end).
  • A separate "UI hygiene" slice could fold theming, resource organization, and accessibility together; but realistically that's Phase 3.4-or-later territory once the UI surface stabilizes.

3. Domain layer purity

Correct today:

  • Layered architecture (Domain / Application / Infrastructure / Presentation) is genuinely separated. Application has zero WPF references; Infrastructure depends on Application abstractions only via interfaces. Confirmed by the SLICE-1.6 retirement (replaced FlaUI rig without touching Application or Domain).

Partial / gaps:

  • Domain.Contracts has at least one simulator leak. RunSummary.SimulatorProfileName ties the run-history record to the simulator concept. A real machine driving the workflow would never produce a "simulator profile name." This was added in SLICE-004 / SLICE-1.4 era when the simulator was the only runtime; now that Phase 4.1 (real SDK swap) is on the horizon, the leak should be removed (rename to OperatingProfileName or move to a non-Domain payload).
  • No explicit aggregate boundaries. RunSummary, Defect, Alarm, Frame, etc. are all records in Domain.Contracts at the same level. In DDD terms there are clear aggregates (a Run aggregates Defects + Alarms by RunId) but the relationship is implicit in queries rather than enforced in code.
  • No domain events. WorkflowService raises events directly via EventHandler<...>. A domain-event bus (IDomainEventPublisher + IDomainEventHandler<T>) would let the audit log (Phase 3.4) subscribe without WorkflowService knowing about it.

Candidate slices:

  • SLICE-2.7 (domain purity audit) — sweep Domain.Contracts for any leakage (SimulatorProfileName is the obvious target; verify no others). Add a dotnet-format rule or analyzer that fails the build if Application/Infrastructure types are referenced from Domain. Small, high-discipline payoff.
  • A future slice could introduce explicit aggregates + domain events; defer until Phase 3.4 (audit) actively benefits from them.

4. Error handling

Correct today:

  • Three unhandled-exception handlers (Dispatcher / AppDomain / TaskScheduler) from SLICE-006; CrashReporter produces dump + banner; non-modal recovery.
  • Cancellation tokens propagate through async boundaries (verified in WorkflowService, SimulatedEncoderSource, FramePipelineService).

Partial / gaps:

  • Exception-as-control-flow for expected failures. IRecipeCatalog.GetRecipeAsync returns null on miss (acceptable). IMachineConnection.ConnectAsync returns bool for the failure path AND throws for SDK errors (mixed). SimulatorFaultInjector.InjectCriticalFault throws if the code is malformed. A Result<T, Error> (or OneOf<T, Error>) discipline would make the failure surface visible at the type system, prevent missed-catch defects, and enable monadic composition.
  • Fire-and-forget paths have no retry / dead-letter. WorkflowService.SafeAlarmHistoryAsync and FramePipelineService.SafeDefectPersistAsync swallow exceptions and log Warning. A SQLite-write failure in either path is silently lost. Production audit: this is data loss.
  • No transaction coordination across multiple stores. A run terminates, _historyStore.SaveAsync(summary) succeeds, then _alarmHistoryStore.MarkClearedAsync(...) fails — no rollback, no compensation. The outbox pattern is the canonical fix.

Candidate slices:

  • SLICE-2.6 (Result types for expected failures) — adopt OneOf or implement Result<T, Error>; convert SDK-boundary methods, recipe lookups, validation paths. Mechanical refactor; the type system enforces the new discipline.
  • A separate "outbox + reliability" slice would address the fire-and-forget data-loss surface. Probably belongs in Phase 4 once real persistence pressure justifies it.

5. Concurrency / async

Correct today:

  • Channel<T> for producer-consumer pipelines (frames, tags, encoder snapshots). DropOldest back-pressure semantics correct.
  • BackgroundService for hosted pipelines.
  • _dispatcher.InvokeAsync for UI-thread marshaling (SLICE-1.2 frame, SLICE-3.1 wafer-map, SLICE-3.2 cassette).
  • Interlocked for _droppedCount, _disposed flags.

Partial / gaps / open questions:

  • AppStateStore.Update uses a Lock. Works at current load (lock-wait p95 = 0.5 µs across five captures). But the actor pattern (single-threaded consumer reading a Channel<StateAction>) would be more composable, easier to test, and trivially deadlock-free. Whether to switch is an open architectural question — not a defect, but worth deciding consciously rather than by inertia.
  • No actor framework for stateful subsystems. WorkflowService is a stateful subsystem with implicit locking around _runCts, _terminationReason, etc. TPL Dataflow or a lightweight actor framework could express the state machine more declaratively. Same "is this needed?" question.
  • Sync-over-async risk unaudited. I haven't verified that no .Wait(), .Result, or .GetAwaiter().GetResult() lurks in the codebase. A grep + Roslyn analyzer enforcement would close this.

Candidate work:

  • An audit + analyzer for sync-over-async (small, mechanical).
  • The actor-vs-lock question is bigger and contested; defer to a strategic decision, not an automatic slice.

6. Testing

Correct today:

  • 553 unit tests; 5 acceptance tests; performance tests trait-filtered (Category=Performance excluded from CI green-bar).
  • Test fakes via Stubs/ folder (SLICE-1.6's FakeUiDriver, SLICE-3.3's FakeRunHistoryStore, etc.).
  • Roslyn tests for XAML attribute-name regression (MainWindowAutomationIdRegressionTests).
  • Pester tests for PowerShell measurement helpers.

Partial / gaps:

  • No property-based testing of state machines. WorkflowService has invariants like "a run cannot transition from Faulted directly to Running" that are tested by hand-written examples. FsCheck against the WorkflowState transition graph would generate thousands of random transition sequences and assert invariants automatically — surfaces edge cases the example tests miss.
  • No mutation testing. Stryker.NET would mutate the production code (flip booleans, change operators, etc.) and report which tests still pass with the mutation. Mutations that survive identify untested branches. The prototype's test count is high but coverage quality is unmeasured.
  • No load test for the SQLite write path under cassette + chaos. SLICE-3.3 has a SaveMany5000_CompletesUnderOneSecond perf test; equivalent for sustained mixed-workload (run + defect + alarm inserts concurrently for an hour) doesn't exist.

Candidate slices:

  • SLICE-2.8 (property-based tests for WorkflowService) — add FsCheck; one suite that asserts state-machine invariants on randomly-generated transition sequences. Small, ongoing-discipline payoff.
  • Mutation testing is a tooling decision, not a slice. Worth running once and addressing gaps as they surface.

7. Code quality

Correct today:

  • Warnings-as-errors enforced by Directory.Build.props.
  • Centralized package versions in Directory.Packages.props.
  • Directory.Build.props is the single source for shared MSBuild config.

Partial / gaps:

  • Nullable reference types — needs project-wide audit. Some files clearly have <Nullable>enable</Nullable> (the records use string? deliberately); but I haven't verified every project + every file enables it. A single missing enable produces a hole in the type-system safety story.
  • .editorconfig and Roslyn analyzers — needs audit. Are IDE0/IDE1 rules treated as errors? CA rules? StyleCop? Without a documented analyzer baseline, code-style drift is inevitable.
  • No source generators. INPC is the obvious target (covered by SLICE-2.5 above); Options-validation generators (Microsoft.Extensions.Options.SourceGeneration) and builder-pattern generators are lower-priority but follow the same logic.

Candidate work:

  • Audit + tighten .editorconfig + analyzer rules. Small, high-leverage; should land before any big refactor so the refactor itself meets the new bar.
  • SLICE-2.5 covers source-generators for INPC.

8. Persistence / data architecture

Correct today:

  • M001 → M004 schema migrations with audit trail in schema_version. Migration runner is idempotent + transactional.
  • Repository pattern: IRunHistoryStore, IDefectStore, IAlarmHistoryStore, all with Dapper-backed SQLite implementations.
  • FK enforcement re-enabled via the stub-row pattern (SLICE-3.2).
  • Pagination and bulk-insert (SaveManyAsync) discipline.
  • WAL mode for concurrent reader/writer behavior.

Partial / gaps:

  • No outbox pattern. Defects persisted fire-and-forget; if the SQLite write fails mid-run, the defect is lost from both AppState.RecentDefects (cleared at next run start) and the database. An outbox table + replay-on-startup would close this. Probably overkill at simulator load; could become important under real-machine fault scenarios.
  • No backup / restore tooling. The inspection.db file is the entire data store; copying it is the entire backup procedure. Acceptable today; production deployments need versioned backup + restore.

Candidate work:

  • Outbox pattern: defer until measurement or a real-world fault surfaces the data-loss case.
  • Backup tooling: defer to Phase 4.3 (packaging + deployment).

9. Observability

Correct today:

  • Structured logging via Serilog with rolling-file sink; UI sink for diagnostics timeline (SLICE-006).
  • AppMetrics meter with 14+ counters and 2 histograms (post-SLICE-3.1 follow-up).
  • CrashReporter for unhandled exceptions.
  • dotnet-counters capture is well-documented and reproducible (runbook §3+).

Partial / gaps:

  • OpenTelemetry tracing is partial. Activity instances aren't propagated through pipeline stages. A trace started by an operator click should follow the request through WorkflowService → pipelines → persistence; today each stage logs independently with no correlation ID.
  • No health endpoint or equivalent. Production deployments expect a /health or in-process equivalent that tools can poll to detect "is the inspection app responsive?" outside of dotnet-counters.

Candidate work:

  • OpenTelemetry tracing — useful but not blocking; defer until a Phase 4 integration justifies the cost.
  • Health-check endpoint — defer to Phase 4.3 (packaging).

Cross-cutting observations

Three patterns span multiple dimensions:

  1. The repo's documentation, measurement, and incremental-shipping discipline is genuinely strong. Specs, tasks, design notes, retrospectives, capabilities-and-limits — all consistently maintained. This part of "production-grade" is already there. The framework patterns are what need to catch up.

  2. The framework patterns reflect 2020-era .NET MVVM rather than 2025 best practices. Hand-written INPC, EventHandler<T> over IObservable<T>, exception-based control flow, lock-protected stores. None is wrong; all have known better-modern alternatives. Source generators and CommunityToolkit.Mvvm have largely supplanted what the prototype uses.

  3. Architectural choices (canonical store, layered architecture, bounded streaming, fire-and-forget audit paths) are correct, but they lack supporting machinery. A canonical store without selectors is half-correct; a layered architecture without a domain-purity guard analyzer is enforcement-by-vigilance; fire-and-forget without an outbox is convenience masquerading as correctness. The right slices add the missing machinery without rewriting the choices.


Slice prioritization

Eight slices, ordered by leverage (impact ÷ effort + dependency unlocks):

OrderSliceEffortImpact
1SLICE-2.5 Source-generated INPCsmallEstablishes 2025 ViewModel pattern; removes hand-written boilerplate; dirty-checking as a side-effect; unblocks every future ViewModel
2SLICE-2.7 Domain purity auditsmallRemoves simulator leak from RunSummary; adds analyzer that prevents recurrence; hardens layered architecture
3SLICE-2.4 Per-slice observablesmediumAddresses the Project anti-pattern at the producer end; reduces consumer-side projection cost; exit gate is PropertyChanged events per StateChanged
4SLICE-2.6 Result types for expected failuresmediumType-system-enforced error discipline; touches many call sites but mechanically straightforward
5SLICE-2.8 Property-based tests for WorkflowServicesmallFsCheck against state-machine invariants; ongoing discipline once added
6SLICE-2.2 Immutable collectionssmallCorrectness guarantee (ImmutableArray<T> over IReadOnlyList<T>); small touch points
7SLICE-2.1 Slice AppState into sub-recordsmediumBlast-radius reduction; do after 2.4 reduces consumer fragility
8SLICE-2.3 Data-plane lift-out (tags + frames)largeSeparation of concerns; do after 2.1 establishes the slicing pattern

Slices 1, 2, 5 are small enough to land in 1-2 Copilot passes each. Slices 3, 4, 6 are medium (full 3-pass cycle). Slices 7, 8 are large refactors; do them last because they benefit from the patterns established earlier.

A reasonable Phase 2 cadence: ship 1, 2, 5 in parallel/sequentially first (foundational establishment), then 3, 4 (correctness work), then 6, 7, 8 (architectural refactors). Phase 3.4 and Phase 4.x can land in parallel as long as they adopt the new patterns introduced by 1, 2, 5.


What this doc does NOT decide

  • Vendor SDK selection for Phase 4.1. Out of scope; depends on real-machine availability.
  • Specific UI design choices (theming, accessibility hierarchy details, layout). Out of scope; defer to a UI-design slice when the surface stabilizes.
  • Whether the actor pattern should replace the lock-protected store. Open architectural question; deserves its own discussion, not a default decision.
  • Backup / restore / DR strategy. Phase 4.3 territory.
  • The order in which Phase 3.4 (identity + audit) and Phase 4.1 (real SDK swap) should land relative to Phase 2. Per the 2026-05-07 strategy, both are parallel candidates; that framing still works under this doc's reframing because Phase 2's foundational slices (2.5, 2.7, 2.4) unblock cleaner Phase 3.4 / 4.1 work without requiring sequential ordering.

What replaces the 2026-05-07 strategy

The 2026-05-07 strategy doc made Phase 2 conditional on measurement triggers. With this reframing, Phase 2 becomes mandatory for architectural correctness, prioritized by the table above. The measurement-first principle from the original roadmap remains useful for validation (every slice still produces a row block) but stops being the gating mechanism for whether Phase 2 happens at all.

A new strategy doc — 2026-05-09-architecture-first-strategy.md (TBD) — should supersede the 2026-05-07 one and codify this. Until then, this doc is the working reference.

Companion docs


Addendum (2026-05-11): SLICE-2.5 retired, SLICE-2.7 closed

Two updates to this doc's working state, recorded here rather than editing the audit sections above so the audit-as-of-2026-05-09 remains intact for historical reference.

SLICE-2.5 (source-generated INPC) retired as no-op

§2 ("View / projection layer") claimed MainViewModel's INPC plumbing was hand-written and proposed adopting CommunityToolkit.Mvvm as SLICE-2.5. Measured against current code on 2026-05-11:

  • Directory.Packages.props already pins CommunityToolkit.Mvvm 8.4.0.
  • InspectionPrototype.Presentation.csproj already references it.
  • MainViewModel is public sealed partial class MainViewModel : ObservableObject and applies [ObservableProperty] to 45+ fields.
  • Partial-method hooks (OnSelectedRecipeChanged, OnSelectedSimulatorProfileNameChanged) are already used for change-driven side-effects.
  • Other ViewModels (AlarmViewModel, DiagnosticsEntryViewModel, RunHistoryItemViewModel, DefectViewModel, CassetteSlotViewModel) are immutable read-only projections — no INPC at all, created fresh per state change.
  • The only hand-written OnPropertyChanged() call in src/ is on MainViewModel.CurrentFrame, deliberately structured that way for the WriteableBitmap reference-change path (one property, not slice-worthy).

The work SLICE-2.5 proposed is already in production; the slice is retired rather than authored. The §2 audit needs re-reading with this correction in mind — the dirty-check side-effect of source-gen INPC is already delivered, so the Project(state) anti-pattern remains worth addressing but for subscriber selectivity reasons (SLICE-2.4's domain), not for consumer-side dirty checking reasons.

SLICE-2.7 (domain purity audit) closed

The §3 leak (RunSummary.SimulatorProfileName) was real. SLICE-2.7:

  • Renamed RunSummary.SimulatorProfileNameOperatingProfileName (and the parallel ActiveRunState field).
  • Propagated through Application / Infrastructure / Presentation / App XAML / tests / tools.
  • Applied M005_rename_simulator_profile_column.sql (ALTER TABLE … RENAME COLUMN; existing rows preserve values).
  • Added tests/InspectionPrototype.Tests/Architecture/DomainLayerPurityTests.cs with three reflection-based [Fact] tests:
    • DomainAssembly_DoesNotReferenceUpperLayers — Domain assembly must not reference Application / Infrastructure / Presentation / App.
    • DomainContracts_TypeNames_ContainNoForbiddenVocabulary — no Domain.Contracts type name may contain Simulator, Sqlite, Wpf, ViewModel, Dapper, or FlaUI.
    • DomainContracts_PublicMembers_ContainNoForbiddenVocabulary — same fragment list, applied to public member names.

The forbidden-vocabulary list is editable in one place (DomainLayerPurityTests.cs); future reviews can extend it as new layer names emerge. The vocabulary plant verification (added SimulatorTestPlant property) correctly caused DomainContracts_PublicMembers_ContainNoForbiddenVocabulary to fail — the auto-generated get_SimulatorTestPlant accessor matched the forbidden Simulator fragment. The reference plant (adding <ProjectReference> from Domain to Application) is unfalsifiable at test time on this codebase because cyclic project references are blocked at build time; DomainAssembly_DoesNotReferenceUpperLayers remains 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).

Updated leverage table

The original 8-slice table is reduced to 6 remaining candidates, reordered with SLICE-2.5 retired and SLICE-2.7 shipped:

OrderSliceEffortStatus
SLICE-2.5 Source-generated INPCRetired (already adopted)
SLICE-2.7 Domain purity auditsmallCompleted
1SLICE-2.4 Per-slice observablesmediumOpen
2SLICE-2.6 Result types for expected failuresmediumOpen
3SLICE-2.8 Property-based tests for WorkflowServicesmallOpen
4SLICE-2.2 Immutable collectionssmallOpen
5SLICE-2.1 Slice AppState into sub-recordsmediumOpen
6SLICE-2.3 Data-plane lift-out (tags + frames)largeOpen

A re-audit of the remaining six candidates against current code is advisable before authoring any of them — the SLICE-2.5 staleness above is the proof-of-existence that the audit's §1–§9 should be sanity-checked against code before each new spec is opened. Specifically:

  • §1 State management: the "no selector-based subscriptions" claim is plausibly still true (SLICE-2.4 territory) but MainViewModel.Project(state) already benefits from CTK.Mvvm's source-generated dirty-check, so the framing "30 properties reassign unconditionally" needs verifying against the actual [ObservableProperty] setter behavior.
  • §4 Error handling, §5 Concurrency, §6 Testing, §7 Code quality, §8 Persistence, §9 Observability were authored without exhaustive code spot-checks; treat as hypotheses, not verified state.

Docs-first project memory for AI-assisted implementation.