Skip to content

feat(generator): per-assembly event-preservation registrar — subscribe-only events survive trimming (TRIM-003+007) - #71

Merged
keithdv merged 9 commits into
mainfrom
TRIM-007-event-preservation-emission
Jul 13, 2026
Merged

feat(generator): per-assembly event-preservation registrar — subscribe-only events survive trimming (TRIM-003+007)#71
keithdv merged 9 commits into
mainfrom
TRIM-007-event-preservation-emission

Conversation

@keithdv

@keithdv keithdv commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Two plans of the TRIM todo in one PR: TRIM-003's verification (deliberately red until this fix) and TRIM-007's fix that turns it green.

TRIM-003 (verification): a FactoryEventBase-derived record whose only client-side reference is a generic Subscribe<TEvent> call site — the real consumer shape — loses its constructor under PublishTrimmed=true. The type resolves (runtime [FactoryEvent] scan works) but deserialization throws the DeserializeNoConstructor-shaped failure: inherited [DynamicallyAccessedMembers] does not flow to derived types under ILLink. The v1.4.0 "annotation covers every descendant" story was wrong, and the old smoke test couldn't see it (it constructs its event directly). Evidence triplet: red trimmed / green untrimmed / green with call-site DAM.

TRIM-007 (fix, per user decision — generator emission): a fourth incremental pipeline branch discovers every concrete, accessible FactoryEventBase descendant declared in a compilation (records-only CreateSyntaxProvider — descendants carry no attribute), walks it with the shared bucketed WalkDtoGraph (root + nested property graph), and emits one per-assembly NeatooEventPreservationRegistrar riding the existing assembly-level [NeatooFactoryRegistrar] discovery (whose DAM-annotated Type parameter roots the registrar under full trimming). Declaring an event is now sufficient — no handler, no subscription, no consumer annotation, no LinkerConfig entry. Nested reference types in event records stop being a documented manual case.

Key design points from review: accessibility gate (private/protected/file-scoped nested event records are skipped — the repo's own private test events would otherwise break every consumer build), FQN base matching, ordinally-sorted deterministic output, no emission when no events.

Verification

  • TRIM-003's subscribe-only harness check green in the pure consumer shape incl. a nested record; negative control (pipeline disabled → identical failure, exit 1)
  • 10 new EventPreservationDiscoveryTests (buckets, nested walk, abstract intermediates, accessibility gate, generics, FQN decoy, dedupe, determinism, no-events)
  • Docs corrected end to end — trimming.md, factory-events.md, CLAUDE-DESIGN.md (incl. the now-inverted v1.4.0 supersession narrative, with history note), FactoryEventBase.cs, Design pattern files, skill references, IL2026 justification
  • 595+595 unit, 561+561 integration, 0 failed; trimmed harness all checks passed

Reviews (in docs/todos/TRIM-dto-trimming-preservation-gaps/reviews/)

  • 007-plan-review.md: CONCERNS → veto (accessibility gate) folded before implementation
  • 007-test-review.md: gate CLEARED
  • 007-code-review.md: 3 veto doc findings (surviving DAM overpromises outside the anchor list) — all fixed in-branch

Test plan

  • Full solution build/test green both TFMs (build doubles as the accessibility-gate proof)
  • Trimmed harness green locally, negative control verified
  • This PR's CI run exercises the trimming gate on linux-x64

🤖 Generated with Claude Code

keithdv and others added 9 commits July 6, 2026 16:35
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… trimmed client

The subscribe-only consumer shape (event record referenced only as the
generic argument of an aggregator's Subscribe<TEvent> call) fails on a
publish-trimmed client: the type survives via the runtime [FactoryEvent]
scan, but its ctor is stripped — inherited [DynamicallyAccessedMembers]
on FactoryEventBase does not flow to derived types under ILLink.

Triplet evidence: red trimmed (pure shape), green untrimmed, green
trimmed with DAM on the subscribe generic parameter (validating the
Raise<T>-style call-site fix mechanism).

The harness check is intentionally red on this branch; TRIM-007 owns the
fix, the doc corrections (current docs overpromise automatic trimming
safety), and turning this check green before merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ction

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ity gate)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-007)

TRIM-003 proved the inherited [DynamicallyAccessedMembers] on
FactoryEventBase does not preserve derived event records under ILLink —
a subscribe-only event's ctor was stripped on the trimmed client (the
DeserializeNoConstructor production symptom).

A fourth incremental pipeline branch (CreateSyntaxProvider — descendants
carry no attribute of their own) discovers every concrete, accessible
FactoryEventBase descendant, walks it with the shared bucketed
WalkDtoGraph (root + nested property graph), and emits one per-assembly
NeatooEventPreservationRegistrar riding the existing assembly-level
[NeatooFactoryRegistrar] discovery. Accessibility gate skips
private/protected/file-scoped nested event records (the generated file
could not legally reference them — RemoteFactory.UnitTests declares such
records and now proves the gate at build time). Buckets are ordinally
sorted for deterministic output.

- TRIM-003's red subscribe-only harness check is now green in the pure
  consumer shape, including a nested record property; negative control
  verified (pipeline disabled -> identical failure, exit 1)
- 8 new EventPreservationDiscoveryTests
- Docs corrected end to end: trimming.md event section (incl. the
  now-inverted v1.4.0 supersession narrative and the nested section,
  now automatic), FactoryEventBase.cs comment, CLAUDE-DESIGN.md event
  paragraphs with history note, stale Design.Domain/Design.Tests
  comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y, stale DAM comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kill, and comments (TRIM-007 code review)

Code review found the falsified 'inherited DAM preserves descendants'
claim surviving in FactoryEventRelayPattern.cs (Design source of truth),
docs/factory-events.md, the EventSubscribeOnlySmokeTest class summary,
both RemoteFactory skill reference files, the FactoryEventTypeRegistry
IL2026 suppression justification, and a harness comment. All corrected
to the generator-emitted per-assembly registrar story.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@keithdv
keithdv merged commit f67622b into main Jul 13, 2026
2 checks passed
@keithdv
keithdv deleted the TRIM-007-event-preservation-emission branch July 13, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant