@spine-event-engine/server 2.0.0-snapshot.26 → 2.0.0-snapshot.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +56 -8
  2. package/REFERENCE.md +151 -39
  3. package/dist/agent/agent-live-ai-runtime.d.ts +1 -1
  4. package/dist/agent/agent-live-ai-runtime.js +1 -1
  5. package/dist/agent/agent-live-budget.d.ts +1 -1
  6. package/dist/agent/agent-live-budget.js +1 -1
  7. package/dist/agent/agent-live-read-runtime.d.ts +1 -1
  8. package/dist/agent/agent-live-read-runtime.js +1 -1
  9. package/dist/browser/browser-admission.d.ts +47 -0
  10. package/dist/browser/browser-admission.d.ts.map +1 -0
  11. package/dist/browser/browser-admission.js +200 -0
  12. package/dist/browser/browser-admission.js.map +1 -0
  13. package/dist/browser/browser-server.d.ts +14 -7
  14. package/dist/browser/browser-server.d.ts.map +1 -1
  15. package/dist/browser/browser-server.js +462 -129
  16. package/dist/browser/browser-server.js.map +1 -1
  17. package/dist/browser/index.d.ts +1 -1
  18. package/dist/browser/index.d.ts.map +1 -1
  19. package/dist/browser/options.d.ts +48 -2
  20. package/dist/browser/options.d.ts.map +1 -1
  21. package/dist/bus/command-bus.d.ts +6 -1
  22. package/dist/bus/command-bus.d.ts.map +1 -1
  23. package/dist/bus/command-bus.js +6 -1
  24. package/dist/bus/command-bus.js.map +1 -1
  25. package/dist/bus/event-bus.d.ts +6 -1
  26. package/dist/bus/event-bus.d.ts.map +1 -1
  27. package/dist/bus/event-bus.js +6 -1
  28. package/dist/bus/event-bus.js.map +1 -1
  29. package/dist/context/bounded-context.d.ts +30 -28
  30. package/dist/context/bounded-context.d.ts.map +1 -1
  31. package/dist/context/bounded-context.js +136 -82
  32. package/dist/context/bounded-context.js.map +1 -1
  33. package/dist/context/entity-inbox.d.ts +12 -17
  34. package/dist/context/entity-inbox.d.ts.map +1 -1
  35. package/dist/context/entity-inbox.js +106 -119
  36. package/dist/context/entity-inbox.js.map +1 -1
  37. package/dist/context/local-inbox-handoff.d.ts +13 -13
  38. package/dist/context/local-inbox-handoff.d.ts.map +1 -1
  39. package/dist/context/local-inbox-handoff.js +60 -32
  40. package/dist/context/local-inbox-handoff.js.map +1 -1
  41. package/dist/context/persistent-tenant-index.d.ts +81 -0
  42. package/dist/context/persistent-tenant-index.d.ts.map +1 -0
  43. package/dist/context/persistent-tenant-index.js +270 -0
  44. package/dist/context/persistent-tenant-index.js.map +1 -0
  45. package/dist/context/projection-handoff.d.ts +9 -9
  46. package/dist/context/projection-handoff.d.ts.map +1 -1
  47. package/dist/context/projection-handoff.js +31 -23
  48. package/dist/context/projection-handoff.js.map +1 -1
  49. package/dist/context/tenant-index.d.ts +7 -6
  50. package/dist/context/tenant-index.d.ts.map +1 -1
  51. package/dist/context/tenant-index.js +2 -188
  52. package/dist/context/tenant-index.js.map +1 -1
  53. package/dist/context/tenant-unknown-error.d.ts +2 -2
  54. package/dist/context/tenant-unknown-error.d.ts.map +1 -1
  55. package/dist/context/tenant-unknown-error.js +4 -1
  56. package/dist/context/tenant-unknown-error.js.map +1 -1
  57. package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
  58. package/dist/delivery/delivery-supervisor.js +12 -3
  59. package/dist/delivery/delivery-supervisor.js.map +1 -1
  60. package/dist/delivery/delivery.d.ts +4 -3
  61. package/dist/delivery/delivery.d.ts.map +1 -1
  62. package/dist/delivery/delivery.js +82 -34
  63. package/dist/delivery/delivery.js.map +1 -1
  64. package/dist/delivery/reception-observation.d.ts +3 -3
  65. package/dist/delivery/reception-observation.js +2 -2
  66. package/dist/index.d.ts +4 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +2 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/repository/command-routing.d.ts +1 -1
  71. package/dist/repository/command-routing.js +1 -1
  72. package/dist/repository/event-routing.d.ts +1 -1
  73. package/dist/repository/event-routing.js +1 -1
  74. package/dist/repository/repository.d.ts +23 -31
  75. package/dist/repository/repository.d.ts.map +1 -1
  76. package/dist/repository/repository.js +244 -218
  77. package/dist/repository/repository.js.map +1 -1
  78. package/dist/repository/state-update-routing.d.ts +1 -1
  79. package/dist/repository/state-update-routing.js +1 -1
  80. package/dist/runtime/signal-publisher.d.ts +23 -8
  81. package/dist/runtime/signal-publisher.d.ts.map +1 -1
  82. package/dist/runtime/signal-publisher.js +11 -8
  83. package/dist/runtime/signal-publisher.js.map +1 -1
  84. package/dist/server/node-coordinator.d.ts.map +1 -1
  85. package/dist/server/node-coordinator.js +220 -54
  86. package/dist/server/node-coordinator.js.map +1 -1
  87. package/dist/server/server-environment.d.ts +6 -5
  88. package/dist/server/server-environment.d.ts.map +1 -1
  89. package/dist/server/server-environment.js +13 -9
  90. package/dist/server/server-environment.js.map +1 -1
  91. package/dist/server/server.js +1 -0
  92. package/dist/server/server.js.map +1 -1
  93. package/dist/services/app-query-execution.js +2 -2
  94. package/dist/services/app-query-execution.js.map +1 -1
  95. package/dist/services/native-operation.d.ts +34 -0
  96. package/dist/services/native-operation.d.ts.map +1 -0
  97. package/dist/services/native-operation.js +77 -0
  98. package/dist/services/native-operation.js.map +1 -0
  99. package/dist/services/spine-services.d.ts +6 -0
  100. package/dist/services/spine-services.d.ts.map +1 -1
  101. package/dist/services/spine-services.js +128 -30
  102. package/dist/services/spine-services.js.map +1 -1
  103. package/dist/services/trusted-reader-ingress.d.ts +23 -5
  104. package/dist/services/trusted-reader-ingress.d.ts.map +1 -1
  105. package/dist/services/trusted-reader-ingress.js +18 -5
  106. package/dist/services/trusted-reader-ingress.js.map +1 -1
  107. package/dist/stand/stand.d.ts +1 -1
  108. package/dist/stand/stand.js +1 -1
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +16 -14
package/README.md CHANGED
@@ -44,7 +44,6 @@ Add generated entities with `BoundedContext.add(...)` and use
44
44
  `build()` to assemble the context and load generated repositories when configured. A
45
45
  context is single-tenant or multitenant by construction; select that mode
46
46
  before registering application handlers and storage.
47
-
48
47
  `build()` prepares the current model storage and awaits the first subscription
49
48
  registry pass. Set `createStorageStructures: false` or
50
49
  `startSubscriptionMonitoring: false` in `build({...})` to defer either phase
@@ -112,6 +111,15 @@ A failed handler leaves previous persisted state unchanged and
112
111
  suppresses produced output. An external HTTP request or other side effect may
113
112
  already have happened.
114
113
 
114
+ Repository-backed Command and Event posting resolves after recipient Inbox
115
+ writes. Delivery starts immediately and handles the stored row separately, so
116
+ successful posting does not mean the handler has completed. Restart durability
117
+ depends on the configured storage provider; custom dispatchers define their own
118
+ completion contract. A committed Entity awaits one outgoing handoff attempt for
119
+ handler-produced domain Commands and Events. A failed attempt is reported without
120
+ rerunning successful source handling. Internal diagnostic publication remains
121
+ best effort.
122
+
115
123
  Handler return declarations can use native unions for one of several results,
116
124
  or fixed tuples for several ordered results. For example,
117
125
  `CreateAccessGrant | ExtendAccessGrant` selects one Command, while
@@ -232,14 +240,36 @@ override. Single-tenant execution uses `SINGLE_TENANT`, even when the request
232
240
  omits a tenant field. A named tenant cannot query a single-tenant context: that
233
241
  fails before reading. See the [query contract](REFERENCE.md#handler-routing-and-operations)
234
242
  for the complete tenant rules and empty-result behavior.
235
- For a multitenant receiving context, explicitly register each accepted tenant
236
- through `context.tenantIndex.keep(tenantId)` or the configured directory.
237
- Provider-backed indexes may only confirm existing membership: fixed SQL catalogs
238
- cannot add tenants, and the Datastore index requires native namespace metadata.
239
- Use an application-managed `TenantIndex` to register tenants before provider
240
- data exists or to add tenants dynamically.
243
+ For a multitenant receiving Bounded Context, supply an application tenant directory and
244
+ register each accepted tenant through its `keep(tenantId)` method before sending
245
+ tenant-scoped work. The optional environment setting defaults to `SINGLE_TENANT`;
246
+ that mode creates no directory or provider catalog. A multitenant Bounded Context
247
+ requires an environment directory before assembly, even when `withTenantIndex()`
248
+ selects another directory for that Bounded Context. In one environment, a
249
+ single-tenant Identity Bounded Context still uses `SINGLE_TENANT` while a
250
+ multitenant Orders Bounded Context uses the configured directory.
251
+ Both public `tenantIndex` getters return `TenantIndexSetting`, so narrow against
252
+ `SINGLE_TENANT` before calling directory methods. A single-tenant Bounded Context always
253
+ returns the marker, even when its environment has a directory for other Bounded Contexts.
254
+ Provider catalogs remain available as storage-provider APIs, but environment
255
+ assembly does not infer membership from them.
256
+ `PersistentTenantIndex.create({ storageFactory, group })` stores registrations
257
+ through the selected factory. Remote providers can retain them across process
258
+ restarts; memory storage remains process-local. Its async factory prepares supported structures by
259
+ default; set `createMissingStructures: false` when the application prepares
260
+ the selected record family separately before data access. Close the index after its admitted work
261
+ finishes; it leaves the supplied storage factory open.
241
262
  Unknown tenants fail before reads or handlers run; input never registers them.
242
263
 
264
+ The [Orders application](https://github.com/SpineEventEngine/spine-ts/blob/master/examples/orders/README.md)
265
+ demonstrates this setup with memory, PostgreSQL, and Datastore. Its PostgreSQL
266
+ launch uses separate shared, Northwind, and Contoso databases, validates persisted
267
+ membership against configured routing, and prepares supported structures before
268
+ opening the listener. Orders focuses on tenant storage and has no sign-in UI.
269
+ The [Fieldnotes application](https://github.com/SpineEventEngine/spine-ts/blob/master/examples/fieldnotes/README.md)
270
+ demonstrates browser authentication and private data in a single-tenant Bounded
271
+ Context without a tenant index.
272
+
243
273
  Use `EntityQuery.all(...)` when every predicate must match and
244
274
  `EntityQuery.either(...)` when any branch may match.
245
275
 
@@ -411,6 +441,21 @@ and backend forwarding. Authenticated durable bindings are browser-host
411
441
  infrastructure, not native `Server` state. See the [browser/auth extension guide](https://github.com/SpineEventEngine/spine-ts/blob/master/docs/BROWSER_CLIENT_AUTH_EXTENSION_GUIDE.md)
412
442
  before exposing a backend to a browser.
413
443
 
444
+ For a standalone Gateway, call `BrowserServer.run` with a canonical
445
+ `backend.baseUrl` (or discovery), `origins`, `sessions`, `authorize`, `contexts`,
446
+ `clock`, and explicit `bindings`. Supply `credentials` with a
447
+ `BrowserCredentialReader` to select a browser auth engine. The reader and each
448
+ `authRoutes` entry declare `csrfHeaderNames`, `credentialCookieNames`, and
449
+ `stateCookieNames`; empty arrays state that a category is unused. The Gateway
450
+ validates those names before it starts listening, then checks raw headers before
451
+ calling the reader or a route. Preflight never calls the credential reader;
452
+ permitted origins receive credentialed CORS headers covering the configured CSRF
453
+ fields and Connect protocol headers. Connect stops waiting for a reader and
454
+ does not forward its late result when the request is cancelled or its deadline
455
+ passes. The reader receives no abort signal, so its underlying work may continue.
456
+ This setup does not construct a
457
+ `ServerEnvironment` or Bounded Context. Close the returned server at shutdown.
458
+
414
459
  ## Native reader admission
415
460
 
416
461
  Native authentication is configured through `ServerOptions.services.readerAdmission`.
@@ -423,7 +468,10 @@ public and supplies no authenticated principal or authority facts.
423
468
 
424
469
  Subscription activation and cancellation also check the admitted principal
425
470
  against the stored reader binding. A different principal cannot reuse a
426
- subscription even with the same ActorContext. See [native reader admission](REFERENCE.md#native-reader-admission)
471
+ subscription even with the same ActorContext. The host supplies an operation
472
+ signal before authentication and closes it after unary work or the complete
473
+ Activate stream. A direct authenticator can return a separately captured
474
+ `invalidated` signal for local logout and session expiry. See [native reader admission](REFERENCE.md#native-reader-admission)
427
475
  for adapter signatures, public admission, and managed application configuration.
428
476
 
429
477
  ## Limits
package/REFERENCE.md CHANGED
@@ -168,7 +168,7 @@ phase without implying that every Entity model has been prepared.
168
168
  awaits both model preparation and the first subscription reconciliation by
169
169
  default. Each option can be set to `false` independently. A deferred phase runs
170
170
  before the first valid Command, Event, query, subscription operation, or Inbox
171
- replay for the receiving context; lazy multitenant preparation selects only that
171
+ delivery for the receiving context; lazy multitenant preparation selects only that
172
172
  input's currently registered tenant. A failed lazy attempt rejects its current
173
173
  input and can be retried by a later input. Monitoring requires the shared
174
174
  subscription registry, so `startSubscriptionMonitoring: true` prepares that
@@ -208,20 +208,33 @@ contexts share one in-memory factory, including in production. Local and test
208
208
  environments default storage in memory and use the core registry only as a
209
209
  fallback; those defaults are not production configuration.
210
210
 
211
- `tenantIndex` selects a live application membership directory. When omitted,
212
- the environment uses its storage factory's native catalog where available, or
213
- a transient in-memory index for a custom factory without a catalog. The
214
- transient index lasts only for this environment instance. `contains()` checks
215
- membership without preparing storage; `keep()` makes a registration visible to
216
- later reads and may reject for a read-only catalog. PostgreSQL and MySQL
217
- catalogs expose only configured tenant entries: `keep()` is idempotent for
218
- those entries and rejects unknown IDs. A dynamic SQL tenant resolver does not
219
- register or enumerate tenants. Single-tenant contexts do not insert a synthetic
220
- member into this directory.
211
+ `tenantIndex` is optional and defaults to `SINGLE_TENANT`, a marker that creates
212
+ no directory and never consults a provider catalog. The environment getter
213
+ returns `TenantIndexSetting`, either that marker or an application-supplied
214
+ `TenantIndex`. Narrow against the marker before calling `contains()`, `keep()`,
215
+ `page()`, or `all()`. A multitenant Bounded Context requires a real environment
216
+ index before business storage opens. A single-tenant Bounded Context always returns the
217
+ marker, including inside a multitenant environment. Provider catalogs remain
218
+ separate storage-provider APIs and do not select application membership.
219
+
220
+ `PersistentTenantIndex.create({ storageFactory, group })` opens a separate,
221
+ storage-backed registration directory without constructing a Bounded Context or
222
+ `ServerEnvironment`. It creates and checks supported storage structures before
223
+ returning. Set `createMissingStructures: false` to skip preparation on creation
224
+ and on later reads or writes. A skipped directory needs structures prepared by
225
+ the application before data access; missing structures cause provider errors.
226
+ Memory storage remains process-local. The injected factory remains usable after the
227
+ index closes. `keep()` registers a complete `TenantId` atomically and retains
228
+ the first registration time; `contains()` reads its primary key. `page()` uses
229
+ finite keyset traversal with an abort signal and a continuation accepted only
230
+ by that index instance. `all()` traverses the complete current directory, so
231
+ its result can be large. Traversal does not promise a snapshot while other
232
+ writers register tenants. Registration does not provision tenant databases or
233
+ authorize users. The environment defaults to the explicit `SINGLE_TENANT` marker.
221
234
 
222
235
  Multitenant receiving contexts check current index membership before Command,
223
236
  Event, query, direct repository/history, and subscription work. Inbox persistence
224
- and later replay check separately, so removing a tenant prevents saved work from
237
+ and later delivery check separately, so removing a tenant prevents saved work from
225
238
  restoring it. Input does not call `keep()` implicitly. A confirmed missing member
226
239
  raises `TenantUnknownError` with the complete tenant ID and context name; native
227
240
  services return `NotFound` with a structured `spine.base.Error` detail whose type
@@ -249,8 +262,8 @@ Direct `repository.routeCommand()` and `repository.routeEvent()` calls return
249
262
  Promises and must be awaited. Commands select exactly one ID; Events and state
250
263
  updates may select any number of distinct IDs. After validation and stable
251
264
  deduplication, routes above 1,000 recipients emit one warning and retain every
252
- recipient. Inbox replay uses stored target IDs without rerouting. The legacy-named
253
- local `catchUpReadSide()` helper is unrelated to retry: it resets and replays
265
+ recipient. Inbox delivery uses stored target IDs without rerouting. The local
266
+ `catchUpReadSide()` helper is unrelated to retry: it resets and dispatches stored Events through
254
267
  the whole local read side, and is not Projection catch-up.
255
268
 
256
269
  ## Message-valued Entity IDs
@@ -282,7 +295,7 @@ const id: MessageId = create(CommandIdSchema, { uuid: "shipment-7" });
282
295
  ```
283
296
 
284
297
  Admission validates a message ID against the schema declared by the state
285
- field, including generated validation rules, before it is stored or replayed.
298
+ field, including generated validation rules, before it is stored or delivered.
286
299
  When default routing finds a message but the Entity ID type is primitive, a
287
300
  custom route returns the intended primitive ID.
288
301
 
@@ -596,9 +609,9 @@ repeated declarations fail closed. It is not an alternative routing mechanism.
596
609
  Configure logging through `ServerEnvironment` when assembling the server. The
597
610
  server installs that logger into its runtime components, where warnings and
598
611
  failures carry operation context. Durable facts belong in events or storage.
599
- Ordinary Aggregate, Process Manager, and Projection Inbox replay sends persisted
600
- accepted work through the normal handler path, so those handler effects must be
601
- replay-safe. An accepted Agent interaction is recorded by its receipt and is
612
+ Ordinary Aggregate, Process Manager, and Projection Inbox delivery sends persisted
613
+ accepted work through the normal handler path. Handler effects must tolerate
614
+ redelivery after an uncertain acknowledgement. An accepted Agent interaction is recorded by its receipt and is
602
615
  never re-executed after interruption.
603
616
 
604
617
  ## Context assembly and entities
@@ -607,9 +620,11 @@ Create a context with `BoundedContext.singleTenant(name)` or
607
620
  `BoundedContext.multitenant(name)`. Names are immutable. Use
608
621
  `ServerEnvironment.when(type).use({ storageFactory: factory })` before explicit
609
622
  environment setup to supply record/event storage to all contexts.
610
- `withTenantIndex(index)` selects a different live membership directory for one
611
- context; `context.tenantIndex` and `ServerEnvironment.instance().tenantIndex`
612
- return the configured objects. Closing a context does not close either index.
623
+ `withTenantIndex(index)` selects a different live membership directory for a
624
+ multitenant Bounded Context after its environment has a real index. The Bounded Context getter
625
+ returns its captured selection; single-tenant Bounded Contexts return `SINGLE_TENANT`.
626
+ Both getters have type `TenantIndexSetting` and require narrowing before index
627
+ methods. Closing a Bounded Context does not close the configured directory.
613
628
  The application imports its generated `spine-bootstrap.js` before registering
614
629
  classes with `add(EntityClass)`; `build()` then selects passive descriptions
615
630
  by exact constructor and performs assembly, including
@@ -911,6 +926,12 @@ work to stop using dependencies, then closes contexts. Caller-managed `start()`
911
926
  does not close global process facilities; close `ServerEnvironment.instance()`
912
927
  separately when used. The final run-managed `run()` close closes its environment.
913
928
 
929
+ Native QueryService accepts `BytesValue` equality and range operands for annotated scalar `bytes`
930
+ columns and permits ascending or descending byte ordering. The shared evaluator uses unsigned
931
+ lexicographic comparison, including shorter equal prefixes first. This TypeScript query capability
932
+ does not assert JVM JDBC byte-range compatibility. `QUERY_SERVICE_MAX_RESULTS` from core is the
933
+ current capacity of 1,000 returned and evaluated records for one native read, not a collection-size limit.
934
+
914
935
  `ServerOptions.appQueries` selects concrete generated `AppQueryHandler<Q, R>`
915
936
  instances by exact constructor metadata before listening. Duplicate Q routes
916
937
  or missing metadata fail startup. On direct app-node ingress,
@@ -1110,8 +1131,8 @@ export const scopedOnCreate =
1110
1131
  ```
1111
1132
 
1112
1133
  `Stand` registers entity state for point/list reads, updates current state, and
1113
- offers in-process subscriptions. The legacy-named local reset/replay helper
1114
- `catchUpReadSide(options?)` clears every registered Projection state and replays
1134
+ offers in-process subscriptions. The local read-side reset helper
1135
+ `catchUpReadSide(options?)` clears every registered Projection state and dispatches
1115
1136
  the whole stored event history through matching Projection subscribers; it does
1116
1137
  not append events. Single-tenant contexts reject a tenant option; multitenant
1117
1138
  contexts require one. It cannot target a Projection repository, entity IDs, or
@@ -1263,11 +1284,14 @@ transport responses independently.
1263
1284
 
1264
1285
  Configure app-node admission at `ServerOptions.services.readerAdmission`:
1265
1286
 
1266
- - `direct.authenticate({ service, method, value, headers, peer })` authenticates
1267
- a direct native request. It returns verified `{ principal, authorityFacts }`,
1268
- an empty object for deliberate public access, or `undefined` to deny access.
1287
+ - `direct.authenticate({ service, method, value, headers, peer, signal })` authenticates
1288
+ a direct native request. It returns `{ reader, invalidated? }` with verified
1289
+ `{ principal, authorityFacts }` in `reader`, `{ reader: {} }` for deliberate
1290
+ public access, or `undefined` to deny access. `signal` begins before
1291
+ authentication and ends on terminal unary or complete stream exit.
1269
1292
  The request value contains canonical Proto bytes; `peer` is the Connect
1270
- `HandlerContext` for that invocation.
1293
+ `HandlerContext` for that invocation. `invalidated` is a separate local
1294
+ AbortSignal captured by the host; it never enters reader facts or proofs.
1271
1295
  - `forwarded.verify(bytes, proof, peer)` verifies an assertion from a trusted
1272
1296
  forwarding peer. It must authenticate that peer and validate the supplied
1273
1297
  proof over the exact bytes. `BrowserServerOptions.forwardingProof.issue(bytes)`
@@ -1294,6 +1318,69 @@ child Servers with their verifier as well. Each hop verifies its incoming
1294
1318
  assertion and issues a new proof for the actual outgoing operation and payload.
1295
1319
  Loopback placement alone does not authenticate a forwarding peer.
1296
1320
 
1321
+ A standard session strategy accepts the host signal during `resolve` and
1322
+ returns an invalidation signal for local logout, its captured session deadline,
1323
+ and strategy close. A custom direct authenticator may omit invalidation, but
1324
+ then the host cannot stop an active stream on local logout or expiry. A
1325
+ successful unary request releases its operation even while the transport peer
1326
+ stays connected. Subscribe releases its operation after definition creation;
1327
+ Activate authenticates again and holds its operation through ACK and every
1328
+ update. Normal completion, host handler iterator return, cancellation, and host
1329
+ close stop the operation. Raw Connect clients must abort an explicit
1330
+ `AbortSignal` or call `SubscriptionService.Cancel` to stop remote stream work;
1331
+ returning from the client iterator alone does not signal transport cancellation.
1332
+ An active stream in another process receives no local signal.
1333
+
1334
+ ```ts
1335
+ import type { SignedSessions } from "@spine-event-engine/auth";
1336
+ import { Server, type ReaderAdmissionResult, type ServerOptions } from "@spine-event-engine/server";
1337
+
1338
+ /**
1339
+ * Configures direct native bearer admission with the local session strategy.
1340
+ *
1341
+ * @param sessions Strategy also used by the application's logout action.
1342
+ * @param contexts Application Bounded Contexts exposed by this Server.
1343
+ * @returns Server configured for native authentication.
1344
+ */
1345
+ function nativeServer(
1346
+ sessions: SignedSessions,
1347
+ contexts: NonNullable<ServerOptions["contexts"]>,
1348
+ ): Server {
1349
+ return new Server({
1350
+ contexts,
1351
+ services: {
1352
+ readerAdmission: {
1353
+ direct: {
1354
+ /**
1355
+ * Resolves the bearer credential within the host operation.
1356
+ *
1357
+ * @param request Native peer and lifetime facts.
1358
+ * @returns Verified reader and invalidation, or denial.
1359
+ */
1360
+ async authenticate(request): Promise<ReaderAdmissionResult | undefined> {
1361
+ const authorization = request.peer.requestHeader.get("authorization");
1362
+ const token = /^Bearer ([^\s,]+)$/i.exec(authorization ?? "")?.[1];
1363
+ if (token === undefined) return undefined;
1364
+ const session = await sessions.resolve(
1365
+ { kind: "bearer", value: token },
1366
+ request.signal,
1367
+ );
1368
+ if (session?.invalidated === undefined) return undefined;
1369
+ return { reader: { principal: session.principal }, invalidated: session.invalidated };
1370
+ },
1371
+ },
1372
+ },
1373
+ },
1374
+ });
1375
+ }
1376
+ ```
1377
+
1378
+ The admitted logout action checks `await sessions.logout(credential)` on that
1379
+ same strategy. It reports durable success only for `revoked`; `unavailable`
1380
+ and `expiryOnly` remain distinct. Application shutdown closes both Server and
1381
+ strategy. A custom authenticator still enforces its own credential and policy
1382
+ checks; a token in a header alone does not establish authority.
1383
+
1297
1384
  Authenticated forwarding now requires an issuer; configure both gateway and
1298
1385
  receiving node when updating an existing authenticated deployment. The native
1299
1386
  Server root still does not load the optional auth package. The forwarding ports
@@ -1305,6 +1392,11 @@ For `BrowserServer` from `@spine-event-engine/server/browser`, origins must be u
1305
1392
  fragment, or trailing slash. Requests without an allowed exact `Origin` receive
1306
1393
  403 before RPC handling. Allowed responses include credentialed exact-origin
1307
1394
  CORS, `Vary: Origin`, protocol request headers, and exposed gRPC status headers.
1395
+ RPC and auth-route preflight completes before credential extraction. The host
1396
+ allows configured CSRF headers alongside Connect and gRPC-Web protocol headers;
1397
+ actual responses, including permitted-origin terminal errors, retain exact-origin
1398
+ credentialed CORS. Callback responses cannot replace that host policy with a
1399
+ wildcard origin.
1308
1400
 
1309
1401
  Supplying `browser.backend.baseUrl` selects standalone mode. It is one canonical
1310
1402
  HTTP(S) origin for a Spine TS or JVM backend; the gateway never closes
@@ -1324,7 +1416,8 @@ re-query authoritative state after duplicate updates or generic loss notices.
1324
1416
  Each registration has one exact `GET` or `POST` canonical path, one method per
1325
1417
  path, exact canonical origins, finite request bytes, and a finite timeout. A
1326
1418
  missing Origin is rejected unless that route explicitly permits it for an OAuth
1327
- callback. Unknown paths, method/origin failures, body overflow, timeout, and
1419
+ callback and validates the protocol state in its handler.
1420
+ Unknown paths, method/origin failures, body overflow, timeout, and
1328
1421
  handler failure return fixed 404, 405, 403, 413, 504, and 500 responses without
1329
1422
  calling application code on rejected input. The 404 unknown-path result applies
1330
1423
  only after framework Origin admission. Auth admission is bounded to 64 active
@@ -1335,10 +1428,18 @@ transfer. Response bodies use `writeMaxBytes`; overflow returns 413 before any
1335
1428
  application status or headers are committed. Listener close ultimately aborts
1336
1429
  an active request through its disconnect signal.
1337
1430
 
1338
- With `cookies`, `OpaqueSessionCookies` performs strict bearer-first or
1339
- CSRF-protected cookie extraction. A strict rejection becomes an unusable
1340
- credential and never falls back to permissive header parsing. Without cookies,
1341
- the host accepts exactly one non-whitespace bearer token. Credentials and raw
1431
+ Every credential reader and auth route declares CSRF header names, primary
1432
+ credential-cookie names, and supporting state-cookie names. The host snapshots
1433
+ these declarations before startup, rejects repeated raw Authorization, Origin,
1434
+ configured CSRF headers, and selected cookies, and checks simultaneous primary
1435
+ credentials only for the active reader or selected route. Legal split Cookie
1436
+ fields and repeated unrelated cookies remain available to the reader.
1437
+ `OpaqueSessionCookies` provides a strict reader for a sealed opaque session or
1438
+ bearer credential, including CSRF checks; a primary cookie and bearer field
1439
+ together are rejected. The legacy `cookies` option cannot be combined with
1440
+ `credentials`. Without either, the host reads one valid Bearer field. A reader
1441
+ returns a credential, absence, or terminal rejection; a thrown read is terminal
1442
+ unavailability. Credentials and raw
1342
1443
  transport metadata do not reach the private backend. The application-supplied
1343
1444
  context resolver replaces caller actor and tenant facts before forwarding.
1344
1445
 
@@ -1381,11 +1482,21 @@ delivery, global update ordering, nor cluster-complete notification delivery.
1381
1482
  local durable-row abstraction. Inbox and shard storage require a single-tenant
1382
1483
  Delivery storage context shared by all tenants; the packed signal supplies
1383
1484
  the dispatch tenant. Read limits are 1–1000; work leases are
1384
- 1000–2147483647 ms. Framework replay supports command handling, projection
1385
- updates, and process-manager reactions. Callbacks are at-least-once/replay-safe:
1485
+ 1000–2147483647 ms. Framework delivery supports command handling, projection
1486
+ updates, process-manager reactions, and Aggregate Event reactions. Callbacks must tolerate redelivery:
1386
1487
  lost renewal can prevent stale finalization but cannot undo a callback already
1387
1488
  run. The package exposes no general raw worker callback API.
1388
1489
 
1490
+ Repository-backed Command and Event routing awaits recipient Inbox writes and
1491
+ notifies Delivery immediately. A successful post through this routing means
1492
+ recipient Inbox writes completed; it does not wait for handler execution or row
1493
+ acknowledgement. Restart durability depends on the configured storage provider.
1494
+ Custom dispatchers define their own completion contract. After an Entity commit, the
1495
+ source delivery awaits one outgoing bus handoff attempt through recipient Inbox
1496
+ acceptance. A failed publication is reported and contained without re-executing
1497
+ a successful source Command. Entity storage, recipient Inbox writes, and source
1498
+ acknowledgement remain separate operations.
1499
+
1389
1500
  Inbox reads return `InboxMessage` with a full-precision Protobuf `Timestamp` in
1390
1501
  `whenReceived`. `InboxMessageInput` requires a precise `Timestamp` for new
1391
1502
  receipts. Writes, acknowledgements, and exact removals use an `InboxMessage`
@@ -1441,7 +1552,8 @@ internally and persist their envelope in the target Entity Inbox before any
1441
1552
  handler runs. Projection input uses the same environment Delivery and shared
1442
1553
  Inbox, with its separate recipient identity.
1443
1554
 
1444
- Local posting waits for the result of that signal's delivery attempt. For an
1555
+ Local repository-backed posting waits for recipient Inbox writes and scheduling,
1556
+ while handler execution and row acknowledgement continue through Delivery. For an
1445
1557
  Agent, successful acceptance and Inbox acknowledgement let posting finish while
1446
1558
  the live handler continues. If Agent execution capacity is unavailable, the input
1447
1559
  stays pending and the local call reports the existing command-posting error.
@@ -1487,9 +1599,9 @@ scan delivers nothing; it does not restart after each productive page.
1487
1599
  Delivery compares pending rows with delivered rows in the current page
1488
1600
  and a process-local cache of the 1,000 most recent deliveries, then removes
1489
1601
  duplicates. The 30-second Inbox deduplication window controls how long delivered
1490
- rows remain available as duplicate evidence; it is not a replay-retention
1602
+ rows remain available as duplicate evidence; it is not a delivery-retention
1491
1603
  period. Persisted accepted rows follow their delivery lifecycle and may be
1492
- replayed according to that lifecycle after the deduplication window has elapsed.
1604
+ redelivered according to that lifecycle after the deduplication window has elapsed.
1493
1605
 
1494
1606
  Direct delivery uses the configured Inbox storage clock for both retained-row
1495
1607
  duplicate checks and early cleanup filtering. Remote delivery uses local client
@@ -1510,8 +1622,8 @@ reception monitor. A custom reception action that completes normally completes
1510
1622
  delivery and the enclosing stage acknowledges the row. An action that rejects
1511
1623
  propagates its error and leaves the row `TO_DELIVER`. `markDelivered()` does not
1512
1624
  mean domain processing succeeded.
1513
- Managed repository replay sends actual handler failures to the monitor while
1514
- storage and replay infrastructure errors propagate. A direct `onMessage` callback
1625
+ Managed repository delivery sends actual handler failures to the monitor while
1626
+ storage and delivery infrastructure errors propagate. A direct `onMessage` callback
1515
1627
  is treated as the endpoint. Handler effects and acknowledgement are separate,
1516
1628
  so a crash between them may redeliver.
1517
1629
  The framework adds no persisted attempts, quarantine, dead-letter, receipt, or
@@ -280,7 +280,7 @@ export declare class AgentLiveAiRuntime implements AgentAi {
280
280
  */
281
281
  private ticket;
282
282
  /**
283
- * Completes measured response bytes without replay state.
283
+ * Completes measured response bytes without recorded state.
284
284
  *
285
285
  * @param attempts Physical attempt state for this invocation.
286
286
  * @param audit Retained history audit for this invocation.
@@ -578,7 +578,7 @@ export class AgentLiveAiRuntime {
578
578
  return ticket;
579
579
  }
580
580
  /**
581
- * Completes measured response bytes without replay state.
581
+ * Completes measured response bytes without recorded state.
582
582
  *
583
583
  * @param attempts Physical attempt state for this invocation.
584
584
  * @param audit Retained history audit for this invocation.
@@ -13,7 +13,7 @@ export interface AgentPhysicalTicket {
13
13
  readonly maxOutputBytes: number | undefined;
14
14
  }
15
15
  /**
16
- * Counts one live invocation without durable replay or unacknowledged writes.
16
+ * Counts one live invocation without durable restart or unacknowledged writes.
17
17
  */
18
18
  export declare class AgentLiveBudget {
19
19
  #private;
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { AgentLiveFault } from "./agent-live-fault.js";
15
15
  /**
16
- * Counts one live invocation without durable replay or unacknowledged writes.
16
+ * Counts one live invocation without durable restart or unacknowledged writes.
17
17
  */
18
18
  export class AgentLiveBudget {
19
19
  limits;
@@ -7,7 +7,7 @@ import type { AgentHistoryPage } from "@spine-event-engine/storage/provider";
7
7
  import type { AgentHistoryScope } from "./agent-history.js";
8
8
  import { AgentLiveBudget } from "./agent-live-budget.js";
9
9
  /**
10
- * Sequences live Agent reads without durable snapshots or replay.
10
+ * Sequences live Agent reads without durable snapshots or recorded-operation restart.
11
11
  */
12
12
  export declare class AgentLiveReadRuntime {
13
13
  #private;
@@ -16,7 +16,7 @@ import { TypeUrls } from "@spine-event-engine/core";
16
16
  import { AgentHistoryEntrySchema } from "@spine-event-engine/proto/agent";
17
17
  import { AgentLiveBudget } from "./agent-live-budget.js";
18
18
  /**
19
- * Sequences live Agent reads without durable snapshots or replay.
19
+ * Sequences live Agent reads without durable snapshots or recorded-operation restart.
20
20
  */
21
21
  export class AgentLiveReadRuntime {
22
22
  scope;
@@ -0,0 +1,47 @@
1
+ import type * as http from "node:http";
2
+ import type { BrowserAuthRoute, BrowserCredentialAdmission } from "./options.js";
3
+ /**
4
+ * Immutable startup admission names and raw request inspection for one browser host.
5
+ */
6
+ export declare class BrowserAdmissionPolicy {
7
+ #private;
8
+ /**
9
+ * Captured duplicate-sensitive names for the active RPC credential reader.
10
+ */
11
+ readonly reader: BrowserCredentialAdmission;
12
+ /**
13
+ * Captured duplicate-sensitive names indexed by exact authentication route path.
14
+ */
15
+ readonly routes: ReadonlyMap<string, BrowserCredentialAdmission>;
16
+ /**
17
+ * Stores the validated reader and route names for one listener.
18
+ * @param reader Active RPC credential reader declaration.
19
+ * @param routes Authentication route declarations indexed by exact path.
20
+ */
21
+ private constructor();
22
+ /**
23
+ * Validates and copies every declaration before the first asynchronous startup step.
24
+ * @param reader Active RPC credential reader declaration.
25
+ * @param routes Validated route configurations indexed by pathname.
26
+ * @returns One immutable admission policy for startup and requests.
27
+ */
28
+ static create(reader: unknown, routes: ReadonlyMap<string, BrowserAuthRoute>): BrowserAdmissionPolicy;
29
+ /**
30
+ * Rejects ambiguous raw headers and returns an immutable duplicate-preserving view.
31
+ * @param request Incoming Node request before WHATWG normalization.
32
+ * @param active Reader or selected route declaration for primary conflict checks.
33
+ * @returns Raw header view or a terminal admission rejection.
34
+ */
35
+ inspect(request: http.IncomingMessage, active: BrowserCredentialAdmission): {
36
+ readonly kind: "admitted";
37
+ readonly headers: RawBrowserHeaders;
38
+ } | {
39
+ readonly kind: "rejected";
40
+ readonly reason: string;
41
+ };
42
+ }
43
+ /**
44
+ * Duplicate-preserving lowercase raw HTTP header values.
45
+ */
46
+ export type RawBrowserHeaders = Readonly<Record<string, string | readonly string[] | undefined>>;
47
+ //# sourceMappingURL=browser-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-admission.d.ts","sourceRoot":"","sources":["../../src/browser/browser-admission.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAEjF;;GAEG;AACH,qBAAa,sBAAsB;;IACjC;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAE5C;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;IAMjE;;;;OAIG;IACH,OAAO;IAaP;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CACX,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC5C,sBAAsB;IAMzB;;;;;OAKG;IACH,OAAO,CACL,OAAO,EAAE,IAAI,CAAC,eAAe,EAC7B,MAAM,EAAE,0BAA0B,GAEhC;QAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;KAAE,GAClE;QAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAW3D;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC"}