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

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 (150) hide show
  1. package/README.md +49 -9
  2. package/REFERENCE.md +339 -22
  3. package/dist/agent/agent-history.d.ts +19 -3
  4. package/dist/agent/agent-history.d.ts.map +1 -1
  5. package/dist/agent/agent-history.js +41 -10
  6. package/dist/agent/agent-history.js.map +1 -1
  7. package/dist/agent/agent-live-read-runtime.d.ts +12 -1
  8. package/dist/agent/agent-live-read-runtime.d.ts.map +1 -1
  9. package/dist/agent/agent-live-read-runtime.js +5 -4
  10. package/dist/agent/agent-live-read-runtime.js.map +1 -1
  11. package/dist/browser/browser-server.d.ts +163 -0
  12. package/dist/browser/browser-server.d.ts.map +1 -1
  13. package/dist/browser/browser-server.js +579 -188
  14. package/dist/browser/browser-server.js.map +1 -1
  15. package/dist/browser/durable-subscription-bindings.d.ts +5 -0
  16. package/dist/browser/durable-subscription-bindings.d.ts.map +1 -1
  17. package/dist/browser/durable-subscription-bindings.js +226 -38
  18. package/dist/browser/durable-subscription-bindings.js.map +1 -1
  19. package/dist/browser/options.d.ts +5 -0
  20. package/dist/browser/options.d.ts.map +1 -1
  21. package/dist/bus/command-bus.d.ts +3 -0
  22. package/dist/bus/command-bus.d.ts.map +1 -1
  23. package/dist/bus/command-bus.js +69 -9
  24. package/dist/bus/command-bus.js.map +1 -1
  25. package/dist/bus/command-errors.d.ts +20 -4
  26. package/dist/bus/command-errors.d.ts.map +1 -1
  27. package/dist/bus/command-errors.js +47 -7
  28. package/dist/bus/command-errors.js.map +1 -1
  29. package/dist/bus/event-bus.d.ts +3 -0
  30. package/dist/bus/event-bus.d.ts.map +1 -1
  31. package/dist/bus/event-bus.js +130 -8
  32. package/dist/bus/event-bus.js.map +1 -1
  33. package/dist/context/bounded-context.d.ts +29 -11
  34. package/dist/context/bounded-context.d.ts.map +1 -1
  35. package/dist/context/bounded-context.js +147 -186
  36. package/dist/context/bounded-context.js.map +1 -1
  37. package/dist/entity/entity.d.ts +59 -0
  38. package/dist/entity/entity.d.ts.map +1 -1
  39. package/dist/entity/entity.js +60 -0
  40. package/dist/entity/entity.js.map +1 -1
  41. package/dist/handler/app-query-handler.d.ts +60 -0
  42. package/dist/handler/app-query-handler.d.ts.map +1 -0
  43. package/dist/handler/app-query-handler.js +23 -0
  44. package/dist/handler/app-query-handler.js.map +1 -0
  45. package/dist/handler/generated-registry-discovery.d.ts +6 -0
  46. package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
  47. package/dist/handler/generated-registry-discovery.js +71 -2
  48. package/dist/handler/generated-registry-discovery.js.map +1 -1
  49. package/dist/handler/handler-decorators.d.ts +8 -29
  50. package/dist/handler/handler-decorators.d.ts.map +1 -1
  51. package/dist/handler/handler-decorators.js +33 -210
  52. package/dist/handler/handler-decorators.js.map +1 -1
  53. package/dist/handler/spine-metadata-inventory.d.ts +99 -0
  54. package/dist/handler/spine-metadata-inventory.d.ts.map +1 -0
  55. package/dist/handler/spine-metadata-inventory.js +162 -0
  56. package/dist/handler/spine-metadata-inventory.js.map +1 -0
  57. package/dist/index.d.ts +5 -3
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +4 -2
  60. package/dist/index.js.map +1 -1
  61. package/dist/repository/repository.d.ts +44 -1
  62. package/dist/repository/repository.d.ts.map +1 -1
  63. package/dist/repository/repository.js +368 -88
  64. package/dist/repository/repository.js.map +1 -1
  65. package/dist/server/managed-server-application.d.ts +12 -11
  66. package/dist/server/managed-server-application.d.ts.map +1 -1
  67. package/dist/server/managed-server-application.js +41 -25
  68. package/dist/server/managed-server-application.js.map +1 -1
  69. package/dist/server/node-coordinator.d.ts +18 -5
  70. package/dist/server/node-coordinator.d.ts.map +1 -1
  71. package/dist/server/node-coordinator.js +554 -84
  72. package/dist/server/node-coordinator.js.map +1 -1
  73. package/dist/server/reserved-spine-rpc-paths.json +1 -0
  74. package/dist/server/server-environment.d.ts +17 -0
  75. package/dist/server/server-environment.d.ts.map +1 -1
  76. package/dist/server/server-environment.js +77 -16
  77. package/dist/server/server-environment.js.map +1 -1
  78. package/dist/server/server.d.ts +45 -0
  79. package/dist/server/server.d.ts.map +1 -1
  80. package/dist/server/server.js +111 -3
  81. package/dist/server/server.js.map +1 -1
  82. package/dist/services/app-query-execution.d.ts +53 -0
  83. package/dist/services/app-query-execution.d.ts.map +1 -0
  84. package/dist/services/app-query-execution.js +408 -0
  85. package/dist/services/app-query-execution.js.map +1 -0
  86. package/dist/services/query-reader.d.ts +32 -7
  87. package/dist/services/query-reader.d.ts.map +1 -1
  88. package/dist/services/query-reader.js +22 -2
  89. package/dist/services/query-reader.js.map +1 -1
  90. package/dist/services/read-request-status.d.ts +65 -0
  91. package/dist/services/read-request-status.d.ts.map +1 -0
  92. package/dist/services/read-request-status.js +151 -0
  93. package/dist/services/read-request-status.js.map +1 -0
  94. package/dist/services/spine-services.d.ts +36 -7
  95. package/dist/services/spine-services.d.ts.map +1 -1
  96. package/dist/services/spine-services.js +900 -168
  97. package/dist/services/spine-services.js.map +1 -1
  98. package/dist/services/trusted-reader-ingress.d.ts +92 -0
  99. package/dist/services/trusted-reader-ingress.d.ts.map +1 -0
  100. package/dist/services/trusted-reader-ingress.js +89 -0
  101. package/dist/services/trusted-reader-ingress.js.map +1 -0
  102. package/dist/spi/handler-registry.d.ts +2 -0
  103. package/dist/spi/handler-registry.d.ts.map +1 -1
  104. package/dist/spi/handler-registry.js +1 -1
  105. package/dist/spi/handler-registry.js.map +1 -1
  106. package/dist/stand/read-side-filter-engine.d.ts +46 -0
  107. package/dist/stand/read-side-filter-engine.d.ts.map +1 -0
  108. package/dist/stand/read-side-filter-engine.js +356 -0
  109. package/dist/stand/read-side-filter-engine.js.map +1 -0
  110. package/dist/stand/read-side-filter.d.ts +247 -0
  111. package/dist/stand/read-side-filter.d.ts.map +1 -0
  112. package/dist/stand/read-side-filter.js +39 -0
  113. package/dist/stand/read-side-filter.js.map +1 -0
  114. package/dist/stand/read-side-message.d.ts +66 -0
  115. package/dist/stand/read-side-message.d.ts.map +1 -0
  116. package/dist/stand/read-side-message.js +167 -0
  117. package/dist/stand/read-side-message.js.map +1 -0
  118. package/dist/stand/stand-direct-delivery.d.ts +161 -0
  119. package/dist/stand/stand-direct-delivery.d.ts.map +1 -0
  120. package/dist/stand/stand-direct-delivery.js +210 -0
  121. package/dist/stand/stand-direct-delivery.js.map +1 -0
  122. package/dist/stand/stand.d.ts +80 -42
  123. package/dist/stand/stand.d.ts.map +1 -1
  124. package/dist/stand/stand.js +410 -54
  125. package/dist/stand/stand.js.map +1 -1
  126. package/dist/stand/subscription-observer.d.ts.map +1 -1
  127. package/dist/stand/subscription-observer.js +103 -0
  128. package/dist/stand/subscription-observer.js.map +1 -1
  129. package/dist/stand/subscription-read-filters.d.ts +40 -0
  130. package/dist/stand/subscription-read-filters.d.ts.map +1 -0
  131. package/dist/stand/subscription-read-filters.js +91 -0
  132. package/dist/stand/subscription-read-filters.js.map +1 -0
  133. package/dist/stand/subscription-reader-binding.d.ts +20 -0
  134. package/dist/stand/subscription-reader-binding.d.ts.map +1 -0
  135. package/dist/stand/subscription-reader-binding.js +56 -0
  136. package/dist/stand/subscription-reader-binding.js.map +1 -0
  137. package/dist/stand/subscription-records.d.ts +30 -3
  138. package/dist/stand/subscription-records.d.ts.map +1 -1
  139. package/dist/stand/subscription-records.js +61 -16
  140. package/dist/stand/subscription-records.js.map +1 -1
  141. package/dist/stand/subscription-registry.d.ts +49 -14
  142. package/dist/stand/subscription-registry.d.ts.map +1 -1
  143. package/dist/stand/subscription-registry.js +150 -31
  144. package/dist/stand/subscription-registry.js.map +1 -1
  145. package/dist/stand/subscription-runtime.d.ts +7 -2
  146. package/dist/stand/subscription-runtime.d.ts.map +1 -1
  147. package/dist/stand/subscription-runtime.js +131 -25
  148. package/dist/stand/subscription-runtime.js.map +1 -1
  149. package/dist/tsconfig.tsbuildinfo +1 -1
  150. package/package.json +14 -14
@@ -12,7 +12,7 @@
12
12
  * the License.
13
13
  */
14
14
  import { clone, create, equals, ScalarType, toBinary, } from "@bufbuild/protobuf";
15
- import { AnySchema, Int32ValueSchema, Int64ValueSchema, StringValueSchema, } from "@bufbuild/protobuf/wkt";
15
+ import { AnySchema, Int32ValueSchema, Int64ValueSchema, StringValueSchema, TimestampSchema, } from "@bufbuild/protobuf/wkt";
16
16
  import { ValidationException, RejectionThrowable, Validate, TypeUrls, AnyPacker, Identifiers, StringifierRegistry, Time, } from "@spine-event-engine/core";
17
17
  import { CommandContextSchema, ActorContextSchema, CommandIdSchema, CommandSchema, EventContextSchema, EventIdSchema, EventSchema, MessageIdSchema, TenantIdSchema, RejectionEventContextSchema, VersionSchema, EntityOption_Kind, } from "@spine-event-engine/proto";
18
18
  import * as EntityLog from "@spine-event-engine/proto/generated/spine/system/server/entity_log_events_pb.js";
@@ -22,7 +22,7 @@ import { EventStore, StorageQueryPolicy, } from "@spine-event-engine/storage";
22
22
  import { TenantBoundary, eventStoreAccess, StoragePreparations, } from "@spine-event-engine/storage/provider";
23
23
  import { EntityCommitStorageFactories } from "@spine-event-engine/storage/provider";
24
24
  import { AgentHistoryStorageFactories, } from "@spine-event-engine/storage/provider";
25
- import { AgentHistoryEntrySchema, AgentInvocationTerminatedSchema, AgentModelSelectionChangedSchema as SelectionChangedSchema, AiModelKind, ModelRefSchema, ModelPreferenceSchema, ConversationIdSchema, } from "@spine-event-engine/proto/agent";
25
+ import { AgentHistoryEntrySchema, AgentInvocationTerminatedSchema, AgentModelSelectionChangedSchema as SelectionChangedSchema, AiModelKind, ModelRefSchema, ModelPreferenceSchema, ConversationRecordSchema, ConversationIdSchema, AgentHistoryCursorSchema, } from "@spine-event-engine/proto/agent";
26
26
  import { registryOptions } from "@spine-event-engine/ai/spi/runtime";
27
27
  import { CommandValidationError } from "../bus/command-errors.js";
28
28
  import { SignalPublisher } from "../runtime/signal-publisher.js";
@@ -32,10 +32,12 @@ import { CommandRoutingInternals, } from "./command-routing.js";
32
32
  import { EventRoutingInternals } from "./event-routing.js";
33
33
  import { StateUpdateRoutingInternals, } from "./state-update-routing.js";
34
34
  import { RoutingDeclarations } from "./routing-declarations.js";
35
+ import { snapshotTrustedReader, } from "@spine-event-engine/core/spi/trusted-reader";
36
+ import { Code, ConnectError } from "@connectrpc/connect";
35
37
  import { EventDispatcherOriginSchemas } from "../bus/event-dispatcher-origin-schemas.js";
36
38
  import { receptionObservation } from "../delivery/reception-observation.js";
37
39
  import { commitFenced, withDeliveryCommitFence } from "./commit-fence.js";
38
- import { AgentHistoryReads } from "../agent/agent-history.js";
40
+ import { AgentHistoryReads, } from "../agent/agent-history.js";
39
41
  import { AgentLiveAiRuntime } from "../agent/agent-live-ai-runtime.js";
40
42
  import { AgentLiveAudit } from "../agent/agent-live-audit.js";
41
43
  import { AgentLiveBudget } from "../agent/agent-live-budget.js";
@@ -258,7 +260,14 @@ const RepositoryAgentReads = {
258
260
  : validated;
259
261
  const tenant = scope.tenantId === undefined ? undefined : RepositoryTenants.require(scope.tenantId);
260
262
  const context = RepositoryTenants.storageContextForTenant(runtime.context, tenant);
261
- return { runtime, context, id: copiedId };
263
+ const readSide = {
264
+ ...(scope.actorContext === undefined
265
+ ? {}
266
+ : { actorContext: clone(ActorContextSchema, scope.actorContext) }),
267
+ ...(scope.reader === undefined ? {} : snapshotTrustedReader(scope.reader)),
268
+ ...(scope.signal === undefined ? {} : { signal: scope.signal }),
269
+ };
270
+ return { runtime, context, id: copiedId, readSide };
262
271
  },
263
272
  /**
264
273
  * Returns a detached public history request before provider access.
@@ -269,7 +278,9 @@ const RepositoryAgentReads = {
269
278
  request(request) {
270
279
  return {
271
280
  pageSize: request.pageSize,
272
- ...(request.cursor === undefined ? {} : { cursor: request.cursor }),
281
+ ...(request.cursor === undefined
282
+ ? {}
283
+ : { cursor: clone(AgentHistoryCursorSchema, request.cursor) }),
273
284
  };
274
285
  },
275
286
  /**
@@ -313,22 +324,95 @@ const RepositoryAgentReads = {
313
324
  * @returns Detached history entries and opaque continuation.
314
325
  */
315
326
  async historyPage(repository, captured, view, request) {
327
+ this.checkCanceled(captured.readSide.signal);
316
328
  if (repositoryRuntimes.get(repository) !== captured.runtime)
317
329
  throw new Error("Agent read requires an active repository.");
318
330
  await captured.runtime.onAdmit(captured.context.tenantId);
319
- return RepositoryStorage.track(repository, async () => {
320
- const input = RepositoryStorage.entityStorageInput(repository, captured.context);
321
- const history = RepositoryStorage.openRepositoryAgentHistory(repository, captured.runtime.storageFactory, input);
322
- const page = await AgentHistoryReads.readBound({
323
- storage: history,
324
- entityId: captured.id,
325
- scope: repositoryAccess.agentHistoryScope(captured.context, input, captured.id),
326
- }, view, request);
327
- return {
328
- items: page.items,
329
- ...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
330
- };
331
- });
331
+ this.checkCanceled(captured.readSide.signal);
332
+ const page = await RepositoryStorage.track(repository, () => this.loadHistoryPage(repository, captured, view, request));
333
+ this.checkCanceled(captured.readSide.signal);
334
+ return page;
335
+ },
336
+ /**
337
+ * Rejects canceled public history reads even when no policy callback runs.
338
+ * @param signal Current caller cancellation, if supplied.
339
+ */
340
+ checkCanceled(signal) {
341
+ if (signal?.aborted)
342
+ throw new ConnectError("Read canceled.", Code.Canceled);
343
+ },
344
+ /**
345
+ * Reads a retained Agent page and transforms only its outward domain values.
346
+ *
347
+ * @param repository Registered Agent repository.
348
+ * @param captured Stable Entity, tenant, and reader facts.
349
+ * @param view Indexed history category.
350
+ * @param request Detached page request.
351
+ * @returns Visible entries and original continuation.
352
+ */
353
+ async loadHistoryPage(repository, captured, view, request) {
354
+ const input = RepositoryStorage.entityStorageInput(repository, captured.context);
355
+ const history = RepositoryStorage.openRepositoryAgentHistory(repository, captured.runtime.storageFactory, input);
356
+ const page = await AgentHistoryReads.readBound({
357
+ storage: history,
358
+ entityId: captured.id,
359
+ scope: repositoryAccess.agentHistoryScope(captured.context, input, captured.id),
360
+ }, view, request);
361
+ return this.visibleHistory(captured.runtime.stand, page, captured.id, view, request, captured.context.tenantId, captured.readSide);
362
+ },
363
+ /**
364
+ * Transforms returned Agent domain roots while retaining the original page cursor.
365
+ *
366
+ * @param stand Producing context's read policy.
367
+ * @param page Captured history page before outward transformation.
368
+ * @param entityId Agent identifier used for this call.
369
+ * @param view Requested history category.
370
+ * @param request Original page request.
371
+ * @param tenantId Effective tenant of this Agent.
372
+ * @param readSide Trusted in-process caller facts captured with the reader.
373
+ * @returns Visible entries with the provider's original continuation.
374
+ */
375
+ async visibleHistory(stand, page, entityId, view, request, tenantId, readSide = {}) {
376
+ const facts = {
377
+ operation: { kind: "history", history: { source: "agent", entityId, view, request } },
378
+ ...(tenantId === undefined ? {} : { effectiveTenant: tenantId }),
379
+ ...readSide,
380
+ };
381
+ const items = [];
382
+ for (const entry of page.items) {
383
+ const visible = await this.visibleEntry(stand, entry, facts);
384
+ if (visible !== undefined)
385
+ items.push(visible);
386
+ }
387
+ return { items, ...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }) };
388
+ },
389
+ /**
390
+ * Transforms one retained Agent entry without changing its wrapper or cursor.
391
+ *
392
+ * @param stand Producing context's outward read policy.
393
+ * @param entry Captured indexed history entry.
394
+ * @param facts Actual read operation and trusted invocation facts.
395
+ * @returns Visible entry, or undefined when policy removed its root.
396
+ */
397
+ async visibleEntry(stand, entry, facts) {
398
+ const item = entry.item;
399
+ let visible;
400
+ if (item.case === "conversationRecord") {
401
+ const value = await standAccess.filterState(stand, ConversationRecordSchema, item.value, facts);
402
+ if (value !== null)
403
+ visible = { case: "conversationRecord", value };
404
+ }
405
+ else if (item.case === "systemEvent" || item.case === "domainEvent") {
406
+ const value = await standAccess.filterEvent(stand, item.value, facts);
407
+ if (value !== null)
408
+ visible =
409
+ item.case === "systemEvent"
410
+ ? { case: "systemEvent", value }
411
+ : { case: "domainEvent", value };
412
+ }
413
+ return visible === undefined
414
+ ? undefined
415
+ : create(AgentHistoryEntrySchema, { ...entry, item: visible });
332
416
  },
333
417
  /**
334
418
  * Returns one typed category from the full Proto history oneof.
@@ -483,6 +567,9 @@ export const repositoryAccess = Object.freeze({
483
567
  }
484
568
  return schemas;
485
569
  },
570
+ /**
571
+ * Returns the AI registry configured for this repository, if present.
572
+ */
486
573
  agentConfiguration(repository) {
487
574
  const ai = repositoryAiConfigurations.get(repository);
488
575
  return ai === undefined ? {} : { ai };
@@ -512,6 +599,9 @@ export const repositoryAccess = Object.freeze({
512
599
  ...read,
513
600
  }));
514
601
  },
602
+ /**
603
+ * Reads one tenant-admitted Agent history page through retained history storage.
604
+ */
515
605
  async agentHistoryPage(repository, entityId, request, tenantId) {
516
606
  if (repository.entityFamily !== "agent")
517
607
  throw new TypeError("Agent history requires an Agent repository.");
@@ -531,7 +621,7 @@ export const repositoryAccess = Object.freeze({
531
621
  });
532
622
  },
533
623
  /**
534
- * Computes the opaque cursor scope for a registered Agent history read.
624
+ * Returns the opaque cursor scope for a registered Agent history read.
535
625
  */
536
626
  agentHistoryScope(context, input, entityId) {
537
627
  return {
@@ -1170,7 +1260,7 @@ class AggregateCommandExecution {
1170
1260
  this.#publishCommandDispatch(intake.route.entityId);
1171
1261
  let produced;
1172
1262
  try {
1173
- produced = await this.#invokeAssignee(loaded.entity, intake.assignee.handler, intake.message, EntityInvocation.commandHandlerContext(this.#command));
1263
+ produced = await this.#invokeAssignee(loaded.entity, intake.assignee.handler, intake.message, EntityInvocation.commandHandlerContext(this.#command), this.#command.context?.actorContext);
1174
1264
  }
1175
1265
  catch (error) {
1176
1266
  return this.#postAssigneeRejection(intake, error);
@@ -1227,26 +1317,45 @@ class AggregateCommandExecution {
1227
1317
  * @param handler Assignee declaration to invoke.
1228
1318
  * @param message Decoded Command message.
1229
1319
  * @param context Copied Command context for the handler.
1320
+ * @param actorContext Actor metadata of the Command signal.
1230
1321
  * @returns The handler's raw result after a successful commit.
1231
1322
  */
1232
- async #invokeAssignee(entity, handler, message, context) {
1233
- transactionalEntityAccess.start(entity);
1323
+ async #invokeAssignee(entity, handler, message, context, actorContext) {
1324
+ const releaseActor = entityHistoryAccess.bindActor(entity, actorContext);
1325
+ let started = false;
1234
1326
  try {
1235
- const produced = await EntityInvocation.invokeEntityMethod(entity, handler.methodName, message, handler.parameterCount, context);
1236
- const signals = this.#support.normalizeProducedSignals(produced);
1237
- if (signals.length === 0)
1238
- throw receptionObservation.recordPostHandlerFailure(new Error("Repository aggregate command handlers must return at least one event."));
1239
- RepositoryHandlers.requireDeclaredOutputs(handler, signals);
1240
- const commit = await commitFenced(entity, (current) => transactionalEntityAccess.commit(current, true));
1241
- if (commit.status === "rejected") {
1242
- throw EntityInvocation.rejectedTransition(commit.validation.error);
1243
- }
1244
- return produced;
1327
+ transactionalEntityAccess.start(entity);
1328
+ started = true;
1329
+ return await this.#invokeAndCommitAssignee(entity, handler, message, context);
1245
1330
  }
1246
1331
  catch (error) {
1247
- transactionalEntityAccess.rollback(entity);
1332
+ if (started)
1333
+ transactionalEntityAccess.rollback(entity);
1248
1334
  throw error;
1249
1335
  }
1336
+ finally {
1337
+ releaseActor();
1338
+ }
1339
+ }
1340
+ /**
1341
+ * Checks a declared Aggregate result and commits its accepted transition.
1342
+ *
1343
+ * @param entity Current Aggregate draft.
1344
+ * @param handler Declared Command handler.
1345
+ * @param message Decoded domain Command.
1346
+ * @param context Detached signal context supplied to the handler.
1347
+ * @returns Raw handler result after commit.
1348
+ */
1349
+ async #invokeAndCommitAssignee(entity, handler, message, context) {
1350
+ const produced = await EntityInvocation.invokeEntityMethod(entity, handler.methodName, message, handler.parameterCount, context);
1351
+ const signals = this.#support.normalizeProducedSignals(produced);
1352
+ if (signals.length === 0)
1353
+ throw receptionObservation.recordPostHandlerFailure(new Error("Repository aggregate command handlers must return at least one event."));
1354
+ RepositoryHandlers.requireDeclaredOutputs(handler, signals);
1355
+ const commit = await commitFenced(entity, (current) => transactionalEntityAccess.commit(current, true));
1356
+ if (commit.status === "rejected")
1357
+ throw EntityInvocation.rejectedTransition(commit.validation.error);
1358
+ return produced;
1250
1359
  }
1251
1360
  /**
1252
1361
  * Binds each produced result to the target and pre-dispatch version.
@@ -1421,16 +1530,23 @@ class AggregateEventExecution {
1421
1530
  async #invokeHandlers(entityId, loaded, intake) {
1422
1531
  const eventContext = EntityInvocation.eventHandlerContext(this.#event);
1423
1532
  const events = [];
1424
- transactionalEntityAccess.start(loaded.entity);
1533
+ const releaseActor = entityHistoryAccess.bindActor(loaded.entity, this.#runtime.signalMetadata.originFromEvent(this.#event).actorContext);
1534
+ let started = false;
1425
1535
  try {
1536
+ transactionalEntityAccess.start(loaded.entity);
1537
+ started = true;
1426
1538
  await this.#invokeReactors(entityId, loaded, intake, eventContext, events);
1427
1539
  await this.#commitEntity(loaded.entity, events.length > 0);
1428
1540
  return Object.freeze(events);
1429
1541
  }
1430
1542
  catch (error) {
1431
- transactionalEntityAccess.rollback(loaded.entity);
1543
+ if (started)
1544
+ transactionalEntityAccess.rollback(loaded.entity);
1432
1545
  throw error;
1433
1546
  }
1547
+ finally {
1548
+ releaseActor();
1549
+ }
1434
1550
  }
1435
1551
  /**
1436
1552
  * Invokes each reactor, checks its declared output, and collects bound Events.
@@ -1734,20 +1850,26 @@ class ProjectionEventExecution {
1734
1850
  * @returns Completion after the subscriber transaction commits.
1735
1851
  */
1736
1852
  async #invokeSubscribers(entity, subscribers, packedMessage) {
1737
- transactionalEntityAccess.start(entity);
1853
+ const releaseActor = entityHistoryAccess.bindActor(entity, this.#runtime.signalMetadata.originFromEvent(this.#event).actorContext);
1854
+ let started = false;
1738
1855
  try {
1856
+ transactionalEntityAccess.start(entity);
1857
+ started = true;
1739
1858
  for (const subscriber of subscribers) {
1740
1859
  await this.#invokeSubscriber(entity, subscriber, packedMessage);
1741
1860
  }
1742
1861
  const commit = await commitFenced(entity, (current) => transactionalEntityAccess.commit(current));
1743
- if (commit.status === "rejected") {
1862
+ if (commit.status === "rejected")
1744
1863
  throw EntityInvocation.rejectedTransition(commit.validation.error);
1745
- }
1746
1864
  }
1747
1865
  catch (error) {
1748
- transactionalEntityAccess.rollback(entity);
1866
+ if (started)
1867
+ transactionalEntityAccess.rollback(entity);
1749
1868
  throw error;
1750
1869
  }
1870
+ finally {
1871
+ releaseActor();
1872
+ }
1751
1873
  }
1752
1874
  /**
1753
1875
  * Invokes one Event subscriber and checks its raw return value.
@@ -1773,22 +1895,40 @@ class ProjectionEventExecution {
1773
1895
  * @returns Completion after the subscriber transaction commits.
1774
1896
  */
1775
1897
  async #invokeStateSubscribers(entity, subscribers, state) {
1776
- transactionalEntityAccess.start(entity);
1898
+ const releaseActor = entityHistoryAccess.bindActor(entity, this.#runtime.signalMetadata.originFromEvent(this.#event).actorContext);
1899
+ let started = false;
1777
1900
  try {
1778
- for (const subscriber of subscribers) {
1779
- const context = EntityInvocation.eventHandlerContext(this.#event);
1780
- const result = await EntityInvocation.invokeEntityMethod(entity, subscriber.handler.methodName, state, subscriber.handler.parameterCount, context);
1781
- if (result !== undefined)
1782
- throw receptionObservation.recordPostHandlerFailure(new Error(`Projection subscriber "${subscriber.handler.methodName}" must not return signals.`));
1783
- }
1901
+ transactionalEntityAccess.start(entity);
1902
+ started = true;
1903
+ await this.#runStateSubscribers(entity, subscribers, state);
1784
1904
  const commit = await commitFenced(entity, (current) => transactionalEntityAccess.commit(current));
1785
1905
  if (commit.status === "rejected")
1786
1906
  throw EntityInvocation.rejectedTransition(commit.validation.error);
1787
1907
  }
1788
1908
  catch (error) {
1789
- transactionalEntityAccess.rollback(entity);
1909
+ if (started)
1910
+ transactionalEntityAccess.rollback(entity);
1790
1911
  throw error;
1791
1912
  }
1913
+ finally {
1914
+ releaseActor();
1915
+ }
1916
+ }
1917
+ /**
1918
+ * Delivers one state update to each declared Projection subscriber.
1919
+ *
1920
+ * @param entity Projection currently in its transaction.
1921
+ * @param subscribers Subscribers selected for the source state.
1922
+ * @param state Decoded source Entity state.
1923
+ * @returns Completion after all subscribers accept the state.
1924
+ */
1925
+ async #runStateSubscribers(entity, subscribers, state) {
1926
+ for (const subscriber of subscribers) {
1927
+ const context = EntityInvocation.eventHandlerContext(this.#event);
1928
+ const result = await EntityInvocation.invokeEntityMethod(entity, subscriber.handler.methodName, state, subscriber.handler.parameterCount, context);
1929
+ if (result !== undefined)
1930
+ throw receptionObservation.recordPostHandlerFailure(new Error(`Projection subscriber "${subscriber.handler.methodName}" must not return signals.`));
1931
+ }
1792
1932
  }
1793
1933
  /**
1794
1934
  * Restores or creates a Projection and binds its storage history.
@@ -1867,7 +2007,11 @@ const ProcessManagerQueries = Object.freeze({
1867
2007
  * @returns Detached Projection state snapshots.
1868
2008
  */
1869
2009
  async load(stand, registeredSchema, plan, destination, query, schema) {
1870
- const results = await QueryReader.read(stand, registeredSchema, plan, destination, 10_000, query);
2010
+ const results = await QueryReader.read(stand, registeredSchema, plan, destination, 10_000, query, {
2011
+ operation: { kind: "query", query },
2012
+ ...(query.context === undefined ? {} : { actorContext: query.context }),
2013
+ ...(destination === undefined ? {} : { effectiveTenant: destination }),
2014
+ });
1871
2015
  return Object.freeze(results.map((result) => clone(schema, result.state)));
1872
2016
  },
1873
2017
  });
@@ -2503,7 +2647,7 @@ class AgentLiveRunner {
2503
2647
  * @returns The handler bound to current protected history and AI capabilities.
2504
2648
  */
2505
2649
  bind(entity, facade, reads, actor) {
2506
- const history = AgentHistoryReads.withReadInterceptor(entity, (scope, request, live, maxBytes) => reads.history(scope, request, live, maxBytes));
2650
+ const history = AgentHistoryReads.withReadInterceptor(entity, (scope, request, live, maxBytes, present) => reads.history(scope, request, live, maxBytes, present));
2507
2651
  let query;
2508
2652
  try {
2509
2653
  query = ProcessManagerQueries.bind(entity, this.runtime, actor, this.tenantId, (schema, request, live) => reads.query(schema, request, live));
@@ -3170,17 +3314,18 @@ class ProcessManagerEventExecution {
3170
3314
  const commands = [];
3171
3315
  const events = [];
3172
3316
  const releaseQuery = ProcessManagerQueries.bind(entity, this.#runtime, this.#runtime.signalMetadata.originFromEvent(this.#event).actorContext, tenantId);
3173
- transactionalEntityAccess.start(entity);
3317
+ let started = false;
3174
3318
  try {
3319
+ transactionalEntityAccess.start(entity);
3320
+ started = true;
3175
3321
  await this.#invokeEventHandlers(entityId, entity, intake, eventContext, events, commands);
3176
3322
  await this.#commitEntity(entity, events.length > 0);
3177
- return Object.freeze({
3178
- commands: Object.freeze(commands),
3179
- events: Object.freeze(events),
3180
- });
3323
+ const emitted = { commands: Object.freeze(commands), events: Object.freeze(events) };
3324
+ return Object.freeze(emitted);
3181
3325
  }
3182
3326
  catch (error) {
3183
- transactionalEntityAccess.rollback(entity);
3327
+ if (started)
3328
+ transactionalEntityAccess.rollback(entity);
3184
3329
  throw error;
3185
3330
  }
3186
3331
  finally {
@@ -3758,22 +3903,50 @@ const RepositoryEntities = {
3758
3903
  throw new Error("Agent history storage is required.");
3759
3904
  const lifetime = RepositoryStorage.lifetime(repository);
3760
3905
  const runtime = repositoryRuntimes.get(repository);
3761
- AgentHistoryReads.bind(entity, {
3906
+ if (runtime === undefined)
3907
+ throw new Error("Agent history requires its active runtime.");
3908
+ AgentHistoryReads.bind(entity, this.agentHistoryBinding(repository, entity, entityId, input, history, runtime, lifetime));
3909
+ },
3910
+ /**
3911
+ * Captures retained Agent history admission and outward filtering in one binding.
3912
+ *
3913
+ * @param repository Repository tracking the active runtime.
3914
+ * @param entity Restored Agent instance.
3915
+ * @param entityId Typed Agent identifier.
3916
+ * @param input Retained storage scope.
3917
+ * @param history Indexed Agent history handle.
3918
+ * @param runtime Current context services.
3919
+ * @param lifetime Repository storage lifetime.
3920
+ * @returns History binding with an invocation-time actor snapshot.
3921
+ */
3922
+ agentHistoryBinding(repository, entity, entityId, input, history, runtime, lifetime) {
3923
+ return {
3762
3924
  storage: history,
3763
3925
  entityId,
3764
3926
  onAdmit: async (onRead) => {
3765
- if (runtime === undefined || repositoryRuntimes.get(repository) !== runtime)
3927
+ if (repositoryRuntimes.get(repository) !== runtime)
3766
3928
  throw new Error("Agent history requires its active runtime.");
3767
3929
  await runtime.onAdmit(input.context.tenantId);
3768
3930
  return RepositoryStorage.track(repository, onRead, lifetime);
3769
3931
  },
3770
- scope: {
3771
- context: input.context.name,
3772
- tenant: JSON.stringify(input.context.tenantId ?? null),
3773
- repository: input.sourceType.typeName,
3774
- entity: input.id.key(entityId),
3775
- },
3776
- });
3932
+ captureActor: () => entityHistoryAccess.currentActor(entity),
3933
+ filterPage: (page, view, request, actorContext) => RepositoryAgentReads.visibleHistory(runtime.stand, page, entityId, view, request, input.context.tenantId, actorContext === undefined ? {} : { actorContext }),
3934
+ scope: this.agentHistoryScope(input, entityId),
3935
+ };
3936
+ },
3937
+ /**
3938
+ * Returns the exact retained Agent repository and typed ID for cursor validation.
3939
+ * @param input Captured Agent storage scope.
3940
+ * @param entityId Typed Agent identifier.
3941
+ * @returns Stable scope encoded in history cursors.
3942
+ */
3943
+ agentHistoryScope(input, entityId) {
3944
+ return {
3945
+ context: input.context.name,
3946
+ tenant: JSON.stringify(input.context.tenantId ?? null),
3947
+ repository: input.sourceType.typeName,
3948
+ entity: input.id.key(entityId),
3949
+ };
3777
3950
  },
3778
3951
  /**
3779
3952
  * Restores an Entity from Stand or creates a fresh instance at Version zero.
@@ -6310,7 +6483,7 @@ const RepositoryStorage = {
6310
6483
  };
6311
6484
  Object.freeze(RepositoryStorage);
6312
6485
  /**
6313
- * Internal repository history internals operations.
6486
+ * Provides internal repository history operations.
6314
6487
  */
6315
6488
  const RepositoryHistoryInternals = {
6316
6489
  /**
@@ -6326,30 +6499,89 @@ const RepositoryHistoryInternals = {
6326
6499
  bindEntityHistory(repository, entity, storage, entityId, schema, tenantId) {
6327
6500
  const lifetime = RepositoryStorage.lifetime(repository);
6328
6501
  const runtime = repositoryRuntimes.get(repository);
6329
- const onAdmit = async () => {
6330
- if (runtime === undefined || repositoryRuntimes.get(repository) !== runtime)
6331
- throw new Error("Repository history requires its active runtime.");
6332
- await runtime.onAdmit(tenantId);
6333
- };
6502
+ if (runtime === undefined)
6503
+ throw new Error("Repository history requires its active runtime.");
6504
+ const onAdmit = () => this.admitHistory(repository, runtime, tenantId);
6334
6505
  const { stateCache, eventCache } = this.boundHistoryCaches(storage, entityId, schema);
6335
6506
  entityStateHistoryCaches.set(entity, stateCache);
6336
6507
  entityHistoryAccess.bind(entity, {
6337
- stateAt: async (time) => {
6338
- await onAdmit();
6339
- return this.readBoundStateAt(repository, lifetime, storage, entityId, time);
6340
- },
6341
- states: async (depth) => {
6342
- await onAdmit();
6343
- return this.readBoundStates(repository, lifetime, schema, stateCache, depth);
6344
- },
6345
- events: async (depth) => {
6346
- await onAdmit();
6347
- return this.readBoundEvents(repository, lifetime, eventCache, depth);
6348
- },
6508
+ ...this.historyReaders({
6509
+ repository,
6510
+ lifetime,
6511
+ stand: runtime.stand,
6512
+ storage,
6513
+ schema,
6514
+ entityId,
6515
+ tenantId,
6516
+ entity,
6517
+ onAdmit,
6518
+ stateCache,
6519
+ eventCache,
6520
+ }),
6349
6521
  stateMaintenance: this.boundStateMaintenance(repository, lifetime, storage.states, onAdmit),
6350
6522
  eventMaintenance: this.boundEventMaintenance(repository, lifetime, storage.events, onAdmit),
6351
6523
  });
6352
6524
  },
6525
+ /**
6526
+ * Admits retained history provider work only while repository membership remains current.
6527
+ *
6528
+ * @param repository Repository whose runtime must remain current.
6529
+ * @param runtime Captured context runtime.
6530
+ * @param tenantId Bound tenant when multitenant.
6531
+ * @returns Completion after membership admission.
6532
+ */
6533
+ async admitHistory(repository, runtime, tenantId) {
6534
+ if (repositoryRuntimes.get(repository) !== runtime)
6535
+ throw new Error("Repository history requires its active runtime.");
6536
+ await runtime.onAdmit(tenantId);
6537
+ },
6538
+ /**
6539
+ * Captures each protected history call's actor and request before admission awaits.
6540
+ *
6541
+ * @param scope Bound repository, caches, and admission work.
6542
+ * @returns State and Event read callbacks for the Entity.
6543
+ */
6544
+ historyReaders(scope) {
6545
+ return {
6546
+ stateAt: (time) => this.historyStateAt(scope, time),
6547
+ states: (depth) => this.historyStates(scope, depth),
6548
+ events: (depth) => this.historyEvents(scope, depth),
6549
+ };
6550
+ },
6551
+ /**
6552
+ * Captures an Entity's state-at request and actor before membership awaits.
6553
+ * @param scope Bound history resources.
6554
+ * @param time Caller-selected historical time.
6555
+ * @returns Historical state, if retained and visible.
6556
+ */
6557
+ async historyStateAt(scope, time) {
6558
+ const actor = entityHistoryAccess.currentActor(scope.entity);
6559
+ const captured = clone(TimestampSchema, time);
6560
+ await scope.onAdmit();
6561
+ return this.readBoundStateAt(scope.repository, scope.lifetime, scope.stand, scope.storage, scope.schema, scope.entityId, captured, scope.tenantId, actor);
6562
+ },
6563
+ /**
6564
+ * Captures an Entity state-history depth and actor before membership awaits.
6565
+ * @param scope Bound history resources.
6566
+ * @param depth Number of prior states requested.
6567
+ * @returns Visible historical states in retained order.
6568
+ */
6569
+ async historyStates(scope, depth) {
6570
+ const actor = entityHistoryAccess.currentActor(scope.entity);
6571
+ await scope.onAdmit();
6572
+ return this.readBoundStates(scope.repository, scope.lifetime, scope.stand, scope.schema, scope.stateCache, scope.entityId, depth, scope.tenantId, actor);
6573
+ },
6574
+ /**
6575
+ * Captures an Entity Event-history depth and actor before membership awaits.
6576
+ * @param scope Bound history resources.
6577
+ * @param depth Number of prior Events requested.
6578
+ * @returns Visible retained Event envelopes.
6579
+ */
6580
+ async historyEvents(scope, depth) {
6581
+ const actor = entityHistoryAccess.currentActor(scope.entity);
6582
+ await scope.onAdmit();
6583
+ return this.readBoundEvents(scope.repository, scope.lifetime, scope.stand, scope.eventCache, scope.entityId, depth, scope.tenantId, actor);
6584
+ },
6353
6585
  /**
6354
6586
  * Creates state and Event caches over retained history ports.
6355
6587
  *
@@ -6406,38 +6638,86 @@ const RepositoryHistoryInternals = {
6406
6638
  *
6407
6639
  * @param repository Registered repository.
6408
6640
  * @param lifetime Captured repository storage lifetime.
6641
+ * @param stand Producing context's outward read policy.
6409
6642
  * @param storage Retained Entity read port.
6643
+ * @param schema Historical state schema.
6410
6644
  * @param entityId Captured Entity identifier.
6411
6645
  * @param time Historical instant.
6646
+ * @param tenantId Effective tenant of the bound Entity.
6647
+ * @param actorContext Actor metadata of an active handler, when present.
6412
6648
  * @returns Historical state, if present.
6413
6649
  */
6414
- readBoundStateAt(repository, lifetime, storage, entityId, time) {
6415
- return RepositoryStorage.track(repository, async () => this.cloneHistoryState(await storage.states.stateAt(entityId, time)), lifetime);
6650
+ readBoundStateAt(repository, lifetime, stand, storage, schema, entityId, time, tenantId, actorContext) {
6651
+ return RepositoryStorage.track(repository, async () => {
6652
+ const state = this.cloneHistoryState(await storage.states.stateAt(entityId, time));
6653
+ if (state === undefined)
6654
+ return undefined;
6655
+ const visible = await standAccess.filterState(stand, schema, state, {
6656
+ operation: { kind: "history", history: { source: "entity-state-at", entityId, time } },
6657
+ ...(tenantId === undefined ? {} : { effectiveTenant: tenantId }),
6658
+ ...(actorContext === undefined ? {} : { actorContext }),
6659
+ });
6660
+ return visible ?? undefined;
6661
+ }, lifetime);
6416
6662
  },
6417
6663
  /**
6418
6664
  * Reads a complete bound state-history page within repository admission.
6419
6665
  *
6420
6666
  * @param repository Registered repository.
6421
6667
  * @param lifetime Captured repository storage lifetime.
6668
+ * @param stand Producing context's outward read policy.
6422
6669
  * @param schema State schema used to unpack rows.
6423
6670
  * @param cache Bound state-history cache.
6671
+ * @param entityId Captured Entity identifier.
6424
6672
  * @param depth Requested history depth.
6673
+ * @param tenantId Effective tenant of the bound Entity.
6674
+ * @param actorContext Actor metadata of an active handler, when present.
6425
6675
  * @returns Frozen historical states.
6426
6676
  */
6427
- readBoundStates(repository, lifetime, schema, cache, depth) {
6428
- return RepositoryStorage.track(repository, async () => this.freezeHistoryStates((await cache.read(depth)).map((record) => EntityRecords.unpack(schema, record).state)), lifetime);
6677
+ readBoundStates(repository, lifetime, stand, schema, cache, entityId, depth, tenantId, actorContext) {
6678
+ return RepositoryStorage.track(repository, async () => {
6679
+ const states = this.freezeHistoryStates((await cache.read(depth)).map((record) => EntityRecords.unpack(schema, record).state));
6680
+ const visible = [];
6681
+ for (const state of states) {
6682
+ const next = await standAccess.filterState(stand, schema, state, {
6683
+ operation: { kind: "history", history: { source: "entity-states", entityId, depth } },
6684
+ ...(tenantId === undefined ? {} : { effectiveTenant: tenantId }),
6685
+ ...(actorContext === undefined ? {} : { actorContext }),
6686
+ });
6687
+ if (next !== null)
6688
+ visible.push(Object.freeze(next));
6689
+ }
6690
+ return Object.freeze(visible);
6691
+ }, lifetime);
6429
6692
  },
6430
6693
  /**
6431
6694
  * Reads a complete bound Event-history page within repository admission.
6432
6695
  *
6433
6696
  * @param repository Registered repository.
6434
6697
  * @param lifetime Captured repository storage lifetime.
6698
+ * @param stand Producing context's outward read policy.
6435
6699
  * @param cache Bound Event-history cache.
6700
+ * @param entityId Captured Entity identifier.
6436
6701
  * @param depth Requested history depth.
6702
+ * @param tenantId Effective tenant of the bound Entity.
6703
+ * @param actorContext Actor metadata of an active handler, when present.
6437
6704
  * @returns Frozen historical Events.
6438
6705
  */
6439
- readBoundEvents(repository, lifetime, cache, depth) {
6440
- return RepositoryStorage.track(repository, async () => this.freezeHistoryEvents(await cache.read(depth)), lifetime);
6706
+ readBoundEvents(repository, lifetime, stand, cache, entityId, depth, tenantId, actorContext) {
6707
+ return RepositoryStorage.track(repository, async () => {
6708
+ const events = this.freezeHistoryEvents(await cache.read(depth));
6709
+ const visible = [];
6710
+ for (const event of events) {
6711
+ const next = await standAccess.filterEvent(stand, event, {
6712
+ operation: { kind: "history", history: { source: "entity-events", entityId, depth } },
6713
+ ...(tenantId === undefined ? {} : { effectiveTenant: tenantId }),
6714
+ ...(actorContext === undefined ? {} : { actorContext }),
6715
+ });
6716
+ if (next !== null)
6717
+ visible.push(Object.freeze(next));
6718
+ }
6719
+ return Object.freeze(visible);
6720
+ }, lifetime);
6441
6721
  },
6442
6722
  /**
6443
6723
  * Creates a history cache that serializes reads and extends prior results.