Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@

## Scope

Extend DTO discovery to descend into `[Factory]`-annotated types' public property graphs without treating the entity itself as a DTO. Today `WalkFactoryReturn` rejects a `[Factory]` root (correct — entities are preserved via DI registration) but returns before walking its properties, so a plain DTO reachable *only* as an entity property is never discovered and gets trimmed on the client. Consumer evidence from the zTreatment cut-over: `TreatmentBanner` (a record property on the `[Execute]`-opened `TreatmentContext` aggregate) and `DashboardContactResult` (a `List<T>` property on the `PatientSearchQuery` factory entity) both required manual LinkerConfig entries. The descent must reuse the same bucket-sort emission as TRIM-001 (Register vs PreserveType), share the visited-set for cycle safety across entity graphs (entities referencing entities, child lists), and skip entity-typed properties themselves while walking through them for DTO-typed leaves. This is the most design-open of the three plans — settle the walk's boundary rules (which factory-rooted types get their properties walked: all `[Factory]` types in the compilation, or only those reachable from factory method signatures) at draft time. Includes a publish-trimmed `RemoteFactory.TrimmingTests` case (DTO reachable only via entity property) and a `docs/trimming.md` update. Does NOT change entity preservation itself (already handled by `NeatooFactoryRegistrar` + DI registration).
Extend DTO discovery to descend into `[Factory]`-annotated types' public property graphs without treating the entity itself as a DTO. Today `WalkFactoryReturn` rejects a `[Factory]` root (correct — entities are preserved via DI registration) but returns before walking its properties, so a plain DTO reachable *only* as an entity property is never discovered and gets trimmed on the client. Consumer evidence from the zTreatment cut-over: `TreatmentBanner` (a record property on the `[Execute]`-opened `TreatmentContext` aggregate) and `DashboardContactResult` (a `List<T>` property on the `PatientSearchQuery` factory entity) both required manual LinkerConfig entries. The descent must reuse the same bucket-sort emission as TRIM-001 (Register vs PreserveType), share the visited-set for cycle safety across entity graphs (entities referencing entities, child lists), and skip entity-typed properties themselves while walking through them for DTO-typed leaves. This is the most design-open of the three plans — settle the walk's boundary rules (which factory-rooted types get their properties walked: all `[Factory]` types in the compilation, or only those reachable from factory method signatures) at draft time. While in the candidate checks, also tighten the pre-existing `ns.StartsWith("System")` prefix match to a segment match (`ns == "System" || ns.StartsWith("System.")`) — TRIM-001 code-review callout: a consumer namespace like `Systems.Domain` is currently excluded from preservation. Includes a publish-trimmed `RemoteFactory.TrimmingTests` case (DTO reachable only via entity property) and a `docs/trimming.md` update. Does NOT change entity preservation itself (already handled by `NeatooFactoryRegistrar` + DI registration).
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
**Plan #:** 004
**Date:** 2026-07-06
**Related Todo:** [../todo.md](../todo.md)
**Status:** In Progress
**Last Updated:** 2026-07-06
**Status:** Done
**Last Updated:** 2026-07-06 (closed: PR #68 merged, CI trimming step green on linux-x64)
**Plan-review opt-in:** No (test-infrastructure/CI wiring only; no public API, schema, or documented business-rule surface)
**Code-review opt-in:** No (no library behavior change; harness and workflow only)

Expand Down Expand Up @@ -54,7 +54,7 @@ Give the publish-trimmed harness enforceable pass/fail semantics and make CI run
## Acceptance

- [x] A deliberately-injected smoke-check failure makes the published trimmed exe exit non-zero; the all-green run exits 0. `[explicit-skip: harness-gate semantics — verified by one-off failure injection at the keyboard; the harness itself is the test]`
- [ ] CI publishes and runs the trimmed harness on every push/PR build, and the job fails when the harness fails. `[explicit-skip: CI wiring — verified by this plan's own workflow run]` *(pending first workflow run — triggers on PR to main or workflow_dispatch; user controls push)*
- [x] CI publishes and runs the trimmed harness on every push/PR build, and the job fails when the harness fails. `[explicit-skip: CI wiring — verified by this plan's own workflow run]` *(verified: PR #68 run 28817388916 — trimming step published linux-x64, marker grep passed, harness "All checks passed")*
- [x] Server-only marker absence in the published assembly is asserted by CI, not just documented in the README. `[explicit-skip: binary-inspection gate — workflow grep step]` *(grep logic verified locally against the win-x64 publish; CI asserts the linux-x64 artifact)*
- [x] `dotnet build` and `dotnet test` of `Neatoo.RemoteFactory.sln` remain green (net9.0 + net10.0). `[explicit-skip: build gate]`

Expand All @@ -80,7 +80,7 @@ Filled after implementation, before the Step 5 gate. All four Acceptance bullets
| Acceptance bullet (short) | Tier declared | Test method / evidence | Tier confirmed |
|---|---|---|---|
| Injected failure → non-zero exit; all-green → 0 | `[explicit-skip]` | Keyboard verification 2026-07-06: injected `failedChecks.Add(...)` → `dotnet run` exit 1; removed → exit 0; trimmed publish all-green → exit 0 | ✓ |
| CI publishes and runs the trimmed harness | `[explicit-skip]` | `build.yml` "Trimming verification" step; **pending first workflow run** (PR to main or workflow_dispatch) | ✗ pending |
| CI publishes and runs the trimmed harness | `[explicit-skip]` | `build.yml` "Trimming verification" step; verified by PR #68 workflow run 28817388916 (linux-x64, all checks passed) | |
| Marker absence asserted by CI | `[explicit-skip]` | `build.yml` grep step; logic verified locally against win-x64 publish (implementations absent, interface retention → TRIM-005) | ✓ |
| Solution build/test green | `[explicit-skip]` | `reviews/004-build.log` (0 errors), `reviews/004-test.log` (2254 passed, 0 failed, net9.0+net10.0) | ✓ |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# TRIM-006 — Incremental-generator caching regression test

**Plan #:** 006
**Status:** Draft
**Plan-review opt-in:** TBD at draft
**Code-review opt-in:** TBD at draft
**Related Todo:** [../todo.md](../todo.md)

## Scope

Add a driver-level regression test for the generator's incremental caching, closing the project-wide hole plan-review B1 (TRIM-001) exposed: the pipeline cache boundary lives on the transform-output records (`TypeInfo` / `TypeFactoryMethodInfo` / `MethodInfo`), and a non-`EquatableArray` field added there silently breaks caching for every consumer with **no failing test** — `DiagnosticTestHelper.RunGenerator` runs the generator exactly once and never asserts cached steps. The test should run the driver twice with `GeneratorDriverOptions`/`WithTrackingIncrementalGeneratorSteps`, apply an unrelated edit between runs, and assert the factory-generation steps report `Cached`/`Unchanged` — guarding all current and future transform-output fields (including TRIM-001's `DtoPreserveTypes`). Surfaced by the TRIM-001 test-review gate as pre-existing tech debt (2026-07-06). Does NOT change generator behavior.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# TRIM-001 Code Review (Step 5, opt-in) — 2026-07-06

**Reviewer:** code-reviewer agent, findings-only (no grade). Range `3752405..4151dc2` (bed0651 feat + 4151dc2 gate closure). Logs: `001-build.log`, `001-test.log` (grepped, not re-run).

**Result: no veto-tier findings.** Deliverable landed cleanly, shape verified correct.

## Verified

- **Plan-review B1:** `DtoPreserveTypes` is `EquatableArray<string>` on all three transform-output records (`TypeInfo`, `MethodInfo`, `TypeFactoryMethodInfo`); models correctly relax to `IReadOnlyList` (not cache keys).
- **Plan-review B2:** `WalkDtoGraph` buckets roots by ctor shape; `WalkEventRoot`'s root-always-Preserve rule fully retired, zero references remain; stale header comment fixed.
- **Emission placement:** all three registrars emit `PreserveType<T>()` unguarded alongside `Register<T>()` — matching Register's client/server-agnostic placement; no fourth site exists.
- **Semantics preserved:** rejection (structure or no-public-ctor) happens before `visited.Add`, matching prior behavior; the Register path is behaviorally identical to old `WalkFactoryReturn`; the new walk is a strict superset (records now descend).
- **Runtime parity:** Preserve bucket rule exactly matches `RecordBypassConverterFactory.CanConvert`; `PreserveType` deliberately does not populate the ctor registry.
- **Repo rules:** no reflection added; sacred tests untouched (additive-only harness changes); no DDD tutorial prose; build 0 errors (2 pre-existing WASM workload warnings); 2276 tests, 0 failed.
- **Plan-review A1 doc coherence:** no surviving sentence implies `PreserveType` is emitted nowhere; event-path removal sentence stays correctly scoped; CLAUDE-DESIGN/trimming.md/AllPatterns accurate to shipped behavior.

## Callout-tier findings

1. **Pre-existing:** `IsDtoStructureCandidate` excludes by `ns.StartsWith("System")` — a prefix match, so a consumer namespace like `Systems.Domain` would be silently excluded from both buckets (`DtoTypeWalker.cs:97`). Low confidence / negligible likelihood; unchanged by this plan (Constraints preserved exclusions intact). **Disposition:** routed to TRIM-002's draft-time scope — that plan already reworks the candidate checks at this exact seam (tighten to `ns == "System" || ns.StartsWith("System.")`).
2. **For the record only:** `record struct` trimmed round-trip has emission-side coverage only — already ACCEPTED-WITH-REASON at the test gate; no action.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# TRIM-001 Plan Review — 2026-07-06

**Reviewer:** plan-reviewer agent (two-pass: A = documented requirements, B = codebase)
**Verdict: APPROVED** — no veto-tier findings in either pass. Five callout-tier findings, all folded into the draft before implementation (see "Disposition").

---

## Pass A — vs. documented requirements

Docs consulted: `src/Design/CLAUDE-DESIGN.md` (FAQ ~295; DTO-registry section ~768-788), `src/Design/Design.Domain/FactoryPatterns/AllPatterns.cs` (~452-463), `docs/trimming.md` (243-306).

- **No veto findings.** The only documented-behavior change (record exclusion wording at `docs/trimming.md:267`, `CLAUDE-DESIGN.md:780`) is exactly the delta the parent todo's Goal and Acceptance Criterion 1 sanction, and Step 6 targets those sites.
- `CLAUDE-DESIGN.md:772` ("not in DI and not in the registry → `CreateObject` not set") stays accurate: `PreserveType<T>()` deliberately does not populate `TryCreate`; records are claimed by `RecordBypassConverterFactory` first.
- Callout A1: after this plan, `PreserveType<T>` is emitted again (by the factory-signature path) — Step 6 doc edits must leave no sentence implying it is emitted nowhere. `docs/trimming.md:300` (event pipeline removal) itself stays accurate — distinct path.
- Callout A2: the stale `FactoryEventHandlerPattern.cs` comments describing the *removed event-path* emission are already routed to TRIM-003 — keep the two doc deltas from colliding.

## Pass B — vs. codebase

Reality check passed: the gate (`DtoTypeWalker.cs:145`), dead `WalkEventRoot` (173-231, stale header 3-4), unemitted `PreserveType` (`DtoConstructorRegistry.cs:43`), and the three-renderer seam enumeration were all confirmed complete (no other `DtoReturnTypes` consumer exists).

- **B1 (key):** The incremental-cache boundary is the transform output `TypeInfo` (`FactoryGenerator.cs:19-31/54-64`) — `FactoryModelBuilder.Build` runs inside `RegisterSourceOutput`, so the three factory *models* are not cache keys. Thread the second bucket as `EquatableArray<string>` on `TypeInfo` (`Types.cs:71`) and `MethodInfo`/`TypeFactoryMethodInfo` (646/521); a plain `List`/`IReadOnlyList` field there would silently break incremental caching with **no failing test**.
- **B2 (key):** Do not copy `WalkEventRoot`'s root semantics — it forces the root into the PreserveType bucket regardless of ctor shape (`DtoTypeWalker.cs:196-197`, correct for event roots only). The factory-return root must bucket **by ctor shape**; a literal port would degrade parameterless class DTO returns to the reflection path under trimming. Only the *nested* bucket-sort (220-227) is the reusable shape.
- **B3:** `record struct` is the one shape where bucket rule and runtime detection diverge: Roslyn reports the synthesized parameterless ctor (→ Register bucket) but reflection `GetConstructors()` omits it (→ `RecordBypassConverterFactory` claims it). Benign (Register also carries DAM-All; bypass round-trips structs), but the "detection rule matches the bucket rule" parity claim isn't exact.
- **B4:** `NestedDtoDiscoveryTests.GetRegisteredDtoTypes` regex (`:24-26`) counts `Register<>` only; TS-010 (`:416`) and TS-014 (`:526`) count-assertions stay Register-only and remain green (fixtures are parameterless class DTOs). Anchor the PreserveType twin to `PreserveType<(.+?)>\(\)`.
- **B5:** Removing the ctor gate means property descent now enters record graphs — existing untrimmed targets (`InterfaceFactoryRecordTargets.cs`) will gain new `PreserveType<>` lines in their registrars. Additive, idempotent, intended; no test asserts their absence.

Infrastructure sweep: no snapshot/golden tests on generated text; `CombinationTestGenerator` unaffected; `init`-only properties are walked (`GetMethod != null`, `DtoTypeWalker.cs:248`); record `EqualityContract` filtered by the `Public` check (245); FQN rendering path identical to today's `Register<>` (nullable modifiers already stripped).

## Recommendations → Disposition

| # | Finding | Disposition |
|---|---------|-------------|
| B1 | Equatability lives on `TypeInfo`/`MethodInfo`, not models | Plan Framework Alignment + Step 2 corrected |
| B2 | Root buckets by ctor shape; only nested walk reusable | Plan Scope + Step 1 corrected |
| B3 | `record struct` bucket/detection divergence | Parity claim softened in Framework Alignment; keyboard note kept |
| B4 | Regex anchoring; TS-010/TS-014 stay Register-only | Added to plan Notes |
| A1 | No doc sentence may imply PreserveType unemitted | Added to plan Notes (Step 6 checklist) |

Calibration note per workflow: diagnoses adopted; prescriptions treated as advisory (all five were adopted as-is — they matched the code walk).
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# TRIM-001 Test Review (Step 5 Gate) — 2026-07-06

**Reviewer:** test-reviewer agent, two passes (initial + closure).
**Logs:** `001-build.log` (0 errors), `001-test.log` (final: 572+572 unit, 563+563 integration, 0 failed, net9.0+net10.0), `001-test-relay-rerun.log`, `001-publish.log`.
**Gate result: CLEARED** — no open must-cover or should-cover findings.

## Initial pass

Evidence map verified honest (all cited methods exist, assert what they claim, at declared tier; renderer coverage static/class/interface real; no vacuous assertions; no sacred tests touched — Program.cs/TrimTestCommands.cs additive only). Findings:

1. **should-cover (quality):** trimmed-harness negative control didn't isolate the return/nested shapes — `_ProcessRecord`'s constructed body could root the ctors (guarded-dead bodies are retained, per TRIM-005), making those checks potentially vacuous.
2. **should-cover (plan-related):** `record struct` bucket assignment untested (plan-review B3 edge).
3. **should-cover (tech-debt):** no incremental-cache regression test exists project-wide (plan-review B1) — nothing guards the `EquatableArray` requirement on transform-output fields.
4. **nice-to-have:** cross-method preserve-bucket dedupe; abstract/nullable record edges.

## Response and closure

| Finding | Disposition |
|---|---|
| Negative-control isolation | **CLOSED** — `_ProcessRecord` now returns `null` (no record constructed anywhere in the harness; discovery is signature-based). Two-stage control: v1 (constructed body, emission off) failed on the *parameter* shape — proving the return shape had been body-rooted, exactly as the reviewer suspected; v2 (null body, emission off) failed on the *return* shape itself (`NotSupportedException` on `TrimRecordResult`, exit 1). Emission restored → all green, exit 0. |
| `record struct` | **CLOSED** — `RecordStruct_LandsInRegisterBucket` pins Register-bucket assignment + Preserve-bucket absence. |
| Incremental-cache tech debt | **CLOSED via queue** — TRIM-006 stub + Index row (not absorbed into this plan). |
| Cross-method dedupe | **CLOSED** — `SameRecordFromTwoMethods_SinglePreserveTypeEmission`. |
| Abstract/nullable record edges | **ACCEPTED-WITH-REASON** — shared `IsDtoStructureCandidate`/`UnwrapType` gates already exercised for class DTOs (`NestedDtoDiscoveryTests` TS-005/TS-010/TS-011); low-risk. |
| Untrimmed `record struct` round-trip | **ACCEPTED-WITH-REASON** — runtime bypass-converter struct behavior is pre-existing and untouched by this plan. |

## Closing tier picture

- must-cover: none (never open).
- should-cover: none open.
- nice-to-have: dedupe added; two declines accepted with recorded reasons.
- tech-debt: queued as TRIM-006.

**Reviewer's closing note:** the Test Evidence map "now survives an independent read with no overreach"; the previously-unproven return/nested trimmed-harness controls are genuine. The gate's marquee catch — false trimmed-harness coverage that the self-authored evidence map could not see — is recorded in the plan's Amendments and the todo Discovery Log.

Also observed at this gate (unrelated to the plan): `RelayTimingTests.Relay_FiresAfterCallerSynchronousWriteOnContinuation` flaked once under parallel load (net9.0, TimeoutException), green in isolation and on both subsequent full runs — logged in the Discovery Log, flagged to the user, not queued in TRIM.
Loading
Loading