@@ -463,6 +463,28 @@ A higher accepted invalidation or response atomically installs the new epoch,
463463clears older scope cache entries, supersedes affected work, and then advances
464464each subscribed session revision exactly once.
465465
466+ The epoch coordinator accepts one optional package-owned, receiver-free
467+ transition-preparation closure. For an accepted invalidation baseline or
468+ advance, and for a higher response, it snapshots the active revision audience,
469+ installs the epoch and notification sequence, and then calls that closure with
470+ a copied scope and the already-frozen epoch. The closure synchronously detaches
471+ incompatible work, cache entries, and epoch-specific gates and may return one
472+ receiver-free dispatch closure. The no-hook and no-dispatch paths allocate no
473+ transition wrapper state. Only after transition and session preparation
474+ has finished does the coordinator settle the producing response, invoke the
475+ transition dispatch, and dispatch session listeners. Provider aborts and work
476+ settlement therefore observe every prepared session revision, while listener
477+ code observes already-retired catalog work. Reentrant transition dispatch
478+ commands remain behind the current revision dispatch in the epoch FIFO.
479+ A missing or ` null ` dispatch is valid. A thrown transition preparation or
480+ non-function result is an internal contract failure and disposes the
481+ coordinator fail-closed. Dispatch is synchronously exact-return: it must return
482+ ` undefined ` . A throw or any other return value disposes the coordinator after
483+ state preparation and suppresses later revision listeners; an accidental
484+ Promise result receives best-effort detached rejection draining. Coordinator
485+ disposal revokes the preparation closure before external cleanup. The hook
486+ remains package-private and is not a provider or session extension point.
487+
466488A search that discovers a higher epoch supersedes itself instead of publishing
467489against its older captured revision. Pages and cache entries from different
468490epochs are never merged.
@@ -523,13 +545,21 @@ A thrown subscription or malformed returned cleanup value discards that buffer
523545and disables automatic invalidation for the live scope; explicit search
524546remains available.
525547The returned cleanup closure is itself untrusted: the service captures only a
526- function, calls it with ` this === undefined ` at most once, and isolates
527- malformed values, thrown cleanup, and rejected or hostile thenable results.
528- Detached cleanup settlement retains no coordinator state. A closure avoids a
529- structural TypeScript contract that would accept class instances or inherited
530- methods which the hostile runtime boundary could not safely validate. Dropping
531- the last owner removes the complete scope incarnation before cleanup. A later
532- join creates a new unobserved incarnation and may attempt a fresh subscription.
548+ function and calls it with ` this === undefined ` at most once. Its exact
549+ synchronous contract returns ` undefined ` ; TypeScript therefore rejects async
550+ cleanup functions rather than silently accepting them through ` void `
551+ assignability. A throw or any other return value quarantines the coordinator
552+ after the subscription is inert. Accidental ordinary Promise or thenable
553+ returns receive best-effort detached rejection draining, but a same-realm
554+ callback can always create a poisoned or independent unhandled rejection that
555+ no JavaScript library can retroactively contain. Legitimate asynchronous
556+ teardown must consume or report its own failure before the cleanup closure
557+ returns ` undefined ` . The valid ` undefined ` path returns without Promise
558+ allocation or a microtask. A closure avoids a structural TypeScript
559+ contract that would accept class instances or inherited methods which the
560+ hostile runtime boundary could not safely validate. Dropping the last owner
561+ removes the complete scope incarnation before cleanup. A later join creates a
562+ new unobserved incarnation and may attempt a fresh subscription.
533563
534564Malformed, duplicate, stale, or token-conflicting invalidations do not mutate
535565state and do not tear down an otherwise valid subscription. Every raw callback
0 commit comments