PHASE-003: entry-call tracking and framework-owned AfterCommit drain - #80
Merged
Conversation
Handlers registered with [FactoryEventHandler<T>] can now declare when they run relative to the factory operation that raised the event: - Immediate (default, unchanged): dispatched at Raise time, in the caller's transaction, observing staged state. - AfterFlush / AfterCommit: deferred into a per-scope queue and run when that phase drains, so read-only projections stop inheriting the in-transaction contract built for atomic write handlers. This plan lands the model, the registry phase, the queueing, and the drain primitive (IFactoryEventPhaseScheduler in the Internal namespace). The drain POINTS come next: entry-call tracking in PHASE-003, the consumer-facing coordinator in PHASE-004. Failure semantics key off the drain point rather than the phase: an in-transaction drain propagates handler exceptions so the caller can roll back; a post-completion drain logs (9003) and swallows, since a throw there can no longer roll anything back. A drain covers the requested phase and every earlier one, so nothing a handler enqueues mid-drain is silently dropped. FactoryEventHandlerAttribute<T> moved to its own file: FactoryAttributes.cs is linked into the netstandard2.0 generator, and compiling DispatchPhase there would duplicate a public runtime type. The generator matches the attribute by metadata name, so it never needed the type. Backward compatible: no phase argument means Immediate, the generator's existing two-argument RegisterHandler call is untouched, and no existing test was modified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ordering re-split recorded: PHASE-003 worked ahead of PHASE-002 (independent; riskiest first; RFEF blocked on it). Pre-flight Current State walked against the choke point, all three renderer seams, and the sync-method shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…draft amendment Failure path now clears explicitly at outermost exit (never drains); entry stays active through the drain (B-V3); interface-renderer walk corrected (inline guard, no split, TRIM item 20); drain pinned before the choke point's post-invoke cancellation check; pin-amendment set widened to six named tests; client-raise relay gap logged as deferred discovery (A-V1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (PHASE-003) Runtime: IFactoryEventPhaseScheduler gains depth-aware BeginEntryCall/ EndEntryCallAsync — drain (AfterCommit sweep, no token) at outermost success, explicit clear on failure; scheduler state now lock-guarded. Dispatcher queues phased handlers only while an entry call is active (9005 outside-entry log, 9006 failure-clear log). HandleRemoteDelegateRequest wraps delegate invocation as the remote entry — drain before the post-invoke cancellation check and relay collection. Generator: every Local* method splits into a non-async guarded wrapper routing through FactoryEntryCall (new) to a private Core — class, interface (split introduced; TRIM item 20 note updated), and static (DI lambda) legs. Sync non-Task factories block-drain only when pending (no-silent-loss). Tests: six pre-declared PHASE-001 pin amendments (intent restated under entry semantics; re-entrancy test re-pointed to discriminate on drain-join order), two TRIM-009 emission-shape pins amended to the new wrapper shape, new FactoryEntryCallTests + outside-entry/failure-clear dispatcher tests. 662x2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 new tests in FactoryEventPhaseEntryTests: HTTP + Logical entry drains with the method-done ordering discriminator, LocalSave nesting (local and under the choke point), AfterFlush-before-AfterCommit sweep at the entry, failure paths never running queued handlers, failure-then-success same-scope clear (A-V2 fixture), falsifiable forbidden-inner-call via the throwing interface-factory auth shape, handler-throw swallow with survivor, drained-handler events joining the same response's relay batch, client-raise entry semantics proven by 9001 logs, and post-success token cancellation not skipping the drain. Unit 662x2, integration 575x2, Design 86x2 — all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…og; 9005/9006 log-table rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AspForbidException success-shaped denial exercised end-to-end; concurrent- flows-share-entry-state semantics pinned as a documented limitation; interface renderer emission-shape pin added; nested-save inner-vs-outer discriminator; post-OCE entry-exit clear + double-End tolerance pin; relay-collection tests' entry-active premise restored; positive 9005 emission pin; caught-nested- failure and handler-invokes-factory re-entrancy pins; interface success-path and generated-sync-shape integration coverage. Unit 668x2, integration 579x2, Design 86x2 — 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…remise pin) Gate closed: unit 668x2, integration 579x2, Design 86x2 — 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…003 code review) V1: interface factories now emit a single-method registrar holder (NeatooInterfaceFactoryRegistrar_ prefix) and point the assembly attribute at it — the Local*Core split no longer sits in the DAM-roots-everything shape TRIM-009 measured as insufficient. C1: EndEntryCallAsync collapsed to one lock acquisition. C4: 9006 renamed FactoryEventPhaseDiscardedAtExit with a cause- neutral message. C9: FactoryEntryCall emitted global::-qualified. C3: sync block-drain deadlock caveat documented. C10: stale guard comments fixed; RFEF substrate + TRIM direction recorded in the Discovery Log. PHASE-003 Done. Unit 668x2, integration 579x2, Design 86x2 — 0 failures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes
AfterCommithandlers actually run: the framework tracks entry factory calls (depth-aware, per scope) and drains the phase queue when the outermost call completes successfully — uniformly for HTTP-dispatched[Remote]calls, direct server/Logical invocation, client-raised events, and all three factory patterns.What lands
BeginEntryCall/EndEntryCallAsync(success)/IsEntryCallActive; drain at the outermost success (sweepingAfterFlushfirst, no cancellation token); explicit clear on failure; lock-guarded state.FactoryEntryCall(new, public inInternal): the wrapper generated code routes through — null-tolerant, keeps every generated wrapper non-async.HandleRemoteDelegateRequestmarks the entry around delegate invocation and drains before the post-invoke cancellation check and relay collection, so events raised by drained handlers join the same response's relay batch.Local*method now splits into a non-async guarded wrapper → privateCore, across class, interface, and static legs. The interface leg gained the TRIM-009 registrar-holder fix so its new private cores are not DAM-rooted with their bodies.Decisions worth reviewer attention
CancellationToken.None; whether a post-completion drain should also swallow handlerOperationCanceledExceptionis handed to PHASE-004.Gates
Plan review (CONCERNS, 6 vetoes addressed pre-implementation), test review (two rounds), code review (1 veto — the interface registrar holder — fixed). Three deliberate wrong-implementations red-proofed the sharpest tests (
reviews/003-redproof.log). Suites: unit 668×2, integration 579×2 (+5 pre-existing skips), Design 86×2 — 0 failures.Sacred-test disclosure: beyond the six pre-declared PHASE-001 pins, two TRIM-009 emission-shape tests were amended (trimming intent re-asserted in the new shape) and two relay-collection tests that this change had silently weakened were restored. All listed in the plan's Test Evidence.
Docs:
docs/todos/PHASE-phased-event-dispatch/plans/003-aftercommit-entry-call-drain.md+reviews/003-*.🤖 Generated with Claude Code