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.Mvvmor 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:
AppStateis 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 mutatesAppStatedirectly.- Lock-protected single-owner mutation;
StateChangedfires 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
StateChangedfires 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 viaCommandManager.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) usesEndedAtUtc = DateTimeOffset.MinValueas a placeholder; nothing prevents code from constructing aRunSummary { TerminalStatus = Completed, EndedAtUtc = DateTimeOffset.MinValue }. - Transition validation is in
CommandGuardsbut not enforced reducer-side. A reducer could in theory writeWorkflowState.Runningon top ofWorkflowState.Abortedwithout the guards catching it.
Candidate slices:
- SLICE-2.4 (per-slice observables) —
IObservable<ConnectionSlice>,IObservable<RunSlice>, etc., each withDistinctUntilChanged. Subscribers consume only what they care about. Re-framed exit gate:PropertyChangedevents perStateChangeddrops by ≥ 80% under a HighDefect or cassette capture. - SLICE-2.1 (slice
AppStateinto sub-records) —ConnectionSlice,MotionSlice,RunSlice, etc. Reducers mutate one slice at a time. Real justification: blast-radius reduction. ALoadedRecipechange shouldn't force every subscriber that doesn't care about recipe state to re-read the wholeAppState.
2. View / projection layer
Correct today:
- MVVM separation between
Presentation.ViewModelsandApp(XAML). - Diff-based collection projections in
MainViewModel.RecentDefects(SLICE-3.1) andCassetteSlots(SLICE-3.2) preserve visual-element identity. Pattern is good — it just isn't generalized.
Partial / gaps:
INotifyPropertyChangedplumbing is hand-written. Every property inMainViewModelhas a manual setter. Setters fireOnPropertyChangedunconditionally — no dirty-check. Today (2025-era .NET), the canonical pattern isCommunityToolkit.Mvvm's[ObservableProperty]source generator: declare the field, and the generator produces the setter, the dirty-check, thePropertyChangedevent, and any[NotifyPropertyChangedFor]dependencies.MainViewModel.Project(state)is the canonical anti-pattern. Sets ~30 properties unconditionally on everyStateChangedevent. 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 havex:Bind(that's WinUI/UWP), but strictDataContexttyping +IDataErrorInfodiscipline 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.BrushForSeverityreturnsBrushes.Redetc.; should beStaticResourcelookup via theme. Same forCassetteSlotViewModel.StateBrush. - Accessibility is partial.
AutomationProperties.AutomationIdexists for FlaUI test targeting (SLICE-1.6 introduced 13 IDs; later slices added more). NoAutomationProperties.Name/HelpText/LandmarkTypefor 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.Applicationhas zero WPF references;Infrastructuredepends onApplicationabstractions only via interfaces. Confirmed by the SLICE-1.6 retirement (replaced FlaUI rig without touching Application or Domain).
Partial / gaps:
Domain.Contractshas at least one simulator leak.RunSummary.SimulatorProfileNameties 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 toOperatingProfileNameor move to a non-Domain payload).- No explicit aggregate boundaries.
RunSummary,Defect,Alarm,Frame, etc. are all records inDomain.Contractsat the same level. In DDD terms there are clear aggregates (a Run aggregates Defects + Alarms byRunId) but the relationship is implicit in queries rather than enforced in code. - No domain events.
WorkflowServiceraises events directly viaEventHandler<...>. A domain-event bus (IDomainEventPublisher+IDomainEventHandler<T>) would let the audit log (Phase 3.4) subscribe withoutWorkflowServiceknowing about it.
Candidate slices:
- SLICE-2.7 (domain purity audit) — sweep
Domain.Contractsfor any leakage (SimulatorProfileNameis the obvious target; verify no others). Add adotnet-formatrule 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;
CrashReporterproduces 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.GetRecipeAsyncreturnsnullon miss (acceptable).IMachineConnection.ConnectAsyncreturnsboolfor the failure path AND throws for SDK errors (mixed).SimulatorFaultInjector.InjectCriticalFaultthrows if the code is malformed. AResult<T, Error>(orOneOf<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.SafeAlarmHistoryAsyncandFramePipelineService.SafeDefectPersistAsyncswallow 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
OneOfor implementResult<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.BackgroundServicefor hosted pipelines._dispatcher.InvokeAsyncfor UI-thread marshaling (SLICE-1.2 frame, SLICE-3.1 wafer-map, SLICE-3.2 cassette).Interlockedfor_droppedCount,_disposedflags.
Partial / gaps / open questions:
AppStateStore.Updateuses aLock. Works at current load (lock-wait p95 = 0.5 µs across five captures). But the actor pattern (single-threaded consumer reading aChannel<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.
WorkflowServiceis 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=Performanceexcluded from CI green-bar). - Test fakes via
Stubs/folder (SLICE-1.6'sFakeUiDriver, SLICE-3.3'sFakeRunHistoryStore, etc.). - Roslyn tests for XAML attribute-name regression (
MainWindowAutomationIdRegressionTests). - Pester tests for PowerShell measurement helpers.
Partial / gaps:
- No property-based testing of state machines.
WorkflowServicehas invariants like "a run cannot transition from Faulted directly to Running" that are tested by hand-written examples. FsCheck against theWorkflowStatetransition 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_CompletesUnderOneSecondperf 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.propsis 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 usestring?deliberately); but I haven't verified every project + every file enables it. A single missingenableproduces a hole in the type-system safety story. .editorconfigand 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.dbfile 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).
AppMetricsmeter with 14+ counters and 2 histograms (post-SLICE-3.1 follow-up).CrashReporterfor unhandled exceptions.dotnet-counterscapture is well-documented and reproducible (runbook §3+).
Partial / gaps:
- OpenTelemetry tracing is partial.
Activityinstances aren't propagated through pipeline stages. A trace started by an operator click should follow the request throughWorkflowService→ pipelines → persistence; today each stage logs independently with no correlation ID. - No health endpoint or equivalent. Production deployments expect a
/healthor 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:
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.
The framework patterns reflect 2020-era .NET MVVM rather than 2025 best practices. Hand-written INPC,
EventHandler<T>overIObservable<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.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):
| Order | Slice | Effort | Impact |
|---|---|---|---|
| 1 | SLICE-2.5 Source-generated INPC | small | Establishes 2025 ViewModel pattern; removes hand-written boilerplate; dirty-checking as a side-effect; unblocks every future ViewModel |
| 2 | SLICE-2.7 Domain purity audit | small | Removes simulator leak from RunSummary; adds analyzer that prevents recurrence; hardens layered architecture |
| 3 | SLICE-2.4 Per-slice observables | medium | Addresses the Project anti-pattern at the producer end; reduces consumer-side projection cost; exit gate is PropertyChanged events per StateChanged |
| 4 | SLICE-2.6 Result types for expected failures | medium | Type-system-enforced error discipline; touches many call sites but mechanically straightforward |
| 5 | SLICE-2.8 Property-based tests for WorkflowService | small | FsCheck against state-machine invariants; ongoing discipline once added |
| 6 | SLICE-2.2 Immutable collections | small | Correctness guarantee (ImmutableArray<T> over IReadOnlyList<T>); small touch points |
| 7 | SLICE-2.1 Slice AppState into sub-records | medium | Blast-radius reduction; do after 2.4 reduces consumer fragility |
| 8 | SLICE-2.3 Data-plane lift-out (tags + frames) | large | Separation 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
- Phase 1 Retrospective (2026-05-03) — what Phase 1 shipped and the cross-slice performance picture
- Phase 1 Capabilities and Limits — the claims-table format this doc inherits
- 2026-05-07 Phase 2+3 Strategy — the framing this doc supersedes
- Roadmap Progress — per-slice status and session log
- Evolution Roadmap (2026-04-22) — the original five-phase plan
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.propsalready pinsCommunityToolkit.Mvvm 8.4.0.InspectionPrototype.Presentation.csprojalready references it.MainViewModelispublic sealed partial class MainViewModel : ObservableObjectand 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 insrc/is onMainViewModel.CurrentFrame, deliberately structured that way for theWriteableBitmapreference-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.SimulatorProfileName→OperatingProfileName(and the parallelActiveRunStatefield). - 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.cswith three reflection-based[Fact]tests:DomainAssembly_DoesNotReferenceUpperLayers— Domain assembly must not referenceApplication/Infrastructure/Presentation/App.DomainContracts_TypeNames_ContainNoForbiddenVocabulary— no Domain.Contracts type name may containSimulator,Sqlite,Wpf,ViewModel,Dapper, orFlaUI.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:
| Order | Slice | Effort | Status |
|---|---|---|---|
| — | — | Retired (already adopted) | |
| — | small | Completed | |
| 1 | SLICE-2.4 Per-slice observables | medium | Open |
| 2 | SLICE-2.6 Result types for expected failures | medium | Open |
| 3 | SLICE-2.8 Property-based tests for WorkflowService | small | Open |
| 4 | SLICE-2.2 Immutable collections | small | Open |
| 5 | SLICE-2.1 Slice AppState into sub-records | medium | Open |
| 6 | SLICE-2.3 Data-plane lift-out (tags + frames) | large | Open |
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.