@@ -481,18 +481,105 @@ The service reference-counts one provider subscription per provider
481481configuration and scope, then fans invalidation out to subscribed sessions.
482482The installed callback closure supplies authenticated provider and scope
483483identity; the untrusted invalidation payload therefore contains only an epoch.
484- Subscription membership is installed atomically before a provider can call
485- back synchronously. A newly joining session captures an already-observed epoch
486- without a synthetic revision bump. Disposal removes membership before
487- unsubscribing or running external cleanup. Fifty sessions sharing a scope do
488- not create fifty provider subscriptions.
484+ Membership uses a two-phase prepare/commit protocol: the session stores its
485+ prepared lease before commit can invoke provider code. Commit installs the
486+ membership atomically before a provider can call back synchronously. Because a
487+ subscription callback is always a change event, a valid callback fired during
488+ ` subscribe ` advances that committed member after subscription installation
489+ succeeds; it is not reclassified as an initial snapshot. A later joining
490+ session captures an already-observed epoch without a synthetic revision bump.
491+ Disposal removes membership before unsubscribing or running external cleanup.
492+ Retirement also clears the membership's target and coordinator link before
493+ that cleanup, so a consumer retaining a disposed lease retains only inert,
494+ bounded membership metadata. The coordinator stores only the copied provider
495+ ID and captured subscription function; it does not retain the unused search
496+ function. Coordinator disposal revokes that subscription reference before
497+ external cleanup, so a consumer retaining the disposed coordinator handle does
498+ not retain the provider graph.
499+ Fifty sessions sharing a scope do not create fifty provider subscriptions.
500+ The coordinator reserves membership state and capacity before reading the
501+ revision target callback. A throwing or invalid callback getter rolls that
502+ reservation back with ` invalid-target ` unavailable evidence. Reentrant
503+ coordinator disposal instead reports ` disposed ` and leaves no reserved
504+ membership or captured callback behind.
489505
490506Each installed subscription has a service-owned incarnation identity captured
491507by its callback. The identity is revoked before external unsubscribe or
492508cleanup, and every callback checks it before entering the serialized epoch
493509gate. Cleanup from a retired incarnation is keyed to that identity and cannot
494510remove, mutate, or notify a reentrantly installed replacement.
495511
512+ The callback closes over a revocable cell rather than the service, scope state,
513+ or sessions. Retirement clears the cell before external cleanup, so a provider
514+ that retains an old callback retains only an inert bounded object. Synchronous
515+ callbacks are decoded into frozen epochs while ` subscribe ` is running; raw
516+ payloads and premature audience snapshots are not buffered. After the returned
517+ disposable is validated, the buffered epochs are staged together in FIFO
518+ order. Each accepted baseline or advance snapshots active membership and
519+ installs the epoch as one serialized step with no external call between those
520+ operations. A member joined during one event's dispatch therefore
521+ participates in a later pending event, but not the event already committed.
522+ A thrown subscription or malformed returned disposable discards that buffer
523+ and disables automatic invalidation for the live scope; explicit search
524+ remains available.
525+ The returned disposable is itself untrusted: the service captures one own
526+ enumerable data ` dispose ` closure, calls it with ` this === undefined ` at most
527+ once, and isolates malformed values and thrown cleanup. Dropping the last owner
528+ removes the complete scope incarnation before cleanup. A later join creates a
529+ new unobserved incarnation and may attempt a fresh subscription.
530+
531+ Malformed, duplicate, stale, or token-conflicting invalidations do not mutate
532+ state and do not tear down an otherwise valid subscription. Every raw callback
533+ still consumes the callback reset-window allowance. Exceeding that allowance
534+ revokes the incarnation and disables automatic invalidation before decoding
535+ further payloads. The conservative window resets from a zero-delay timer
536+ scheduled by the first callback. It intentionally makes no claim about formal
537+ JavaScript task or turn boundaries, while still preventing a provider from
538+ evading the allowance with a microtask chain. If hostile payload inspection
539+ synchronously reenters the same callback, the nested callback consumes that
540+ allowance but is ignored fail-closed; the coordinator does not retain the raw
541+ payload or invert callback order.
542+
543+ An epoch capture authenticates the exact provider configuration, scope
544+ incarnation, membership, observed epoch, and notification sequence. An
545+ equal-current response is publishable only if no accepted invalidation or
546+ higher response changed that sequence after capture. Otherwise it is
547+ superseded even when its epoch equals the newly current epoch. A capture is a
548+ single-use work token: submission claims it before queue admission, and replay
549+ is discarded even when the first submission was overloaded.
550+
551+ Epoch commands use one bounded, non-recursive FIFO drain. Its hard admission
552+ limit counts all work accepted during that drain, including commands already
553+ processed, so a one-at-a-time reentrant chain cannot evade the bound. For every
554+ accepted change, active membership is snapshotted and the epoch is immediately
555+ installed before revision preparation or any external listener runs. All
556+ affected session revisions then change before dispatch. Reentrant commands run
557+ only after the current change has reached every still-active member, preserving
558+ event order. The core does not coalesce revision events. The CodeMirror adapter
559+ may coalesce the resulting UI refresh, as described below. Admission fails
560+ before epoch mutation:
561+ an overflowing provider callback revokes automatic invalidation for that
562+ incarnation, while an overflowing response observation settles with closed
563+ overload evidence.
564+ Invalid, retired, replayed, disposed, and overflowing response submissions
565+ return closed settlement evidence without invoking a callback. Only an admitted
566+ FIFO command invokes its completion callback, so rejection cannot recursively
567+ reenter the coordinator's drain.
568+
569+ A revision target prepares its state change and returns a listener-dispatch
570+ closure. Returning ` null ` , returning a non-function, or throwing retires that
571+ membership. Retirement detaches state immediately, but hostile provider cleanup
572+ is deferred until every remaining target has prepared. A higher response epoch
573+ installed before preparation remains observed; if its submitting membership is
574+ retired during preparation, its response settles as retired while other
575+ successfully prepared members still dispatch. Coordinator disposal takes
576+ precedence over that retired evidence. The outermost cleanup barrier admits and
577+ invokes at most 1,024 captured disposers. If reentrant work attempts to admit a
578+ 1,025th cleanup, the coordinator quarantines itself immediately: all cells and
579+ memberships become inert and all not-yet-invoked disposer references are
580+ cleared without calling more provider code. Cleanup is never continued from a
581+ timer.
582+
496583The session exposes a disposable revision-change subscription for
497584service-originated changes:
498585
@@ -671,6 +758,12 @@ The initial checked limits are:
671758| Components per search path | 4 |
672759| Total catalog context | 16,384 UTF-16 units |
673760| Configured relation catalog providers | 1 |
761+ | Live catalog scopes per service | 128 |
762+ | Catalog memberships per service | 1,024 |
763+ | Catalog memberships per scope | 256 |
764+ | Raw subscription callbacks per reset window | 256 |
765+ | Catalog epoch admissions per drain | 1,024 |
766+ | Provider cleanups per outer barrier | 1,024 |
674767| Catalog results per search | 100 |
675768| Completion results after composition | 100 |
676769| Provider ID | 256 UTF-16 units |
@@ -815,19 +908,22 @@ Mixed cached assets fail the exact version check and retire the generation.
8159082 . Attach embedded regions to session open/update transactions atomically.
8169093 . Add the bounded partial-` SELECT ` query-site recognizer.
8179104 . Add the bounded CTE frame and visibility recognizer.
818- 5 . Add the service-owned catalog coordinator, subscriptions, cache,
819- in-flight sharing, cancellation, and provider contract suite.
820- 6 . Add the session completion method and deterministic composition.
821- 7 . Add the separate CodeMirror adapter and packed/browser fixtures.
822- 8 . Add the pinned packed/browser/runtime marimo fixture and 1/10/50-editor
911+ 5 . Add the service-owned scoped epoch/subscription coordinator and its hostile
912+ lifecycle contract suite. It does not invoke provider search or retain
913+ result pages.
914+ 6 . Add catalog search scheduling, cache, in-flight sharing, cancellation,
915+ retry gates, and availability observers over the proven epoch coordinator.
916+ 7 . Add the session completion method and deterministic composition.
917+ 8 . Add the separate CodeMirror adapter and packed/browser fixtures.
918+ 9 . Add the pinned packed/browser/runtime marimo fixture and 1/10/50-editor
823919 performance and leak evidence.
824- 9 . Validate both the in-memory/notebook and hierarchical remote provider
920+ 10 . Validate both the in-memory/notebook and hierarchical remote provider
825921 shapes, then stabilize the declarations and public export surface.
826- 10 . Design scoped parser semantics and protocol v2 against PostgreSQL and
922+ 11 . Design scoped parser semantics and protocol v2 against PostgreSQL and
827923 BigQuery corpora before any additional parser-derived scope feature
828924 consumes it.
829925
830- Breaking refinement remains allowed through step 8 . The provider and
926+ Breaking refinement remains allowed through step 9 . The provider and
831927completion types become stable only after the working vertical slice, reusable
832928provider contract suite, two materially different provider shapes, marimo
833929packed/browser integration, hostile decoding, cancellation, epoch and
0 commit comments