@substrat-run/kernel 0.139.0 → 0.140.0

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 (140) hide show
  1. package/dist/admin-log-ddl.d.ts +21 -0
  2. package/dist/admin-log-ddl.d.ts.map +1 -0
  3. package/dist/admin-log-ddl.js +32 -0
  4. package/dist/admin-log-ddl.js.map +1 -0
  5. package/dist/attachment-extractor.d.ts +12 -1
  6. package/dist/attachment-extractor.d.ts.map +1 -1
  7. package/dist/attachment-extractor.js +10 -5
  8. package/dist/attachment-extractor.js.map +1 -1
  9. package/dist/audit-outcome.d.ts +149 -0
  10. package/dist/audit-outcome.d.ts.map +1 -0
  11. package/dist/audit-outcome.js +233 -0
  12. package/dist/audit-outcome.js.map +1 -0
  13. package/dist/connect-links.d.ts +91 -0
  14. package/dist/connect-links.d.ts.map +1 -0
  15. package/dist/connect-links.js +189 -0
  16. package/dist/connect-links.js.map +1 -0
  17. package/dist/connect-state.d.ts +12 -3
  18. package/dist/connect-state.d.ts.map +1 -1
  19. package/dist/connect-state.js +8 -3
  20. package/dist/connect-state.js.map +1 -1
  21. package/dist/derived-object.d.ts +14 -0
  22. package/dist/derived-object.d.ts.map +1 -0
  23. package/dist/derived-object.js +2 -0
  24. package/dist/derived-object.js.map +1 -0
  25. package/dist/derived-objects.d.ts +108 -0
  26. package/dist/derived-objects.d.ts.map +1 -0
  27. package/dist/derived-objects.js +199 -0
  28. package/dist/derived-objects.js.map +1 -0
  29. package/dist/email-intent.d.ts +56 -0
  30. package/dist/email-intent.d.ts.map +1 -0
  31. package/dist/email-intent.js +71 -0
  32. package/dist/email-intent.js.map +1 -0
  33. package/dist/entity-edges.d.ts +6 -1
  34. package/dist/entity-edges.d.ts.map +1 -1
  35. package/dist/entity-edges.js +13 -0
  36. package/dist/entity-edges.js.map +1 -1
  37. package/dist/entity-grant-shape.d.ts +94 -0
  38. package/dist/entity-grant-shape.d.ts.map +1 -0
  39. package/dist/entity-grant-shape.js +329 -0
  40. package/dist/entity-grant-shape.js.map +1 -0
  41. package/dist/entity-grant.d.ts +45 -0
  42. package/dist/entity-grant.d.ts.map +1 -0
  43. package/dist/entity-grant.js +71 -0
  44. package/dist/entity-grant.js.map +1 -0
  45. package/dist/entity-state-reads.d.ts +4 -7
  46. package/dist/entity-state-reads.d.ts.map +1 -1
  47. package/dist/entity-state-reads.js +13 -57
  48. package/dist/entity-state-reads.js.map +1 -1
  49. package/dist/entity-state.d.ts +37 -10
  50. package/dist/entity-state.d.ts.map +1 -1
  51. package/dist/entity-state.js +80 -69
  52. package/dist/entity-state.js.map +1 -1
  53. package/dist/entity-trash.d.ts +245 -0
  54. package/dist/entity-trash.d.ts.map +1 -0
  55. package/dist/entity-trash.js +380 -0
  56. package/dist/entity-trash.js.map +1 -0
  57. package/dist/grant-scoped-read.d.ts +41 -0
  58. package/dist/grant-scoped-read.d.ts.map +1 -0
  59. package/dist/grant-scoped-read.js +100 -0
  60. package/dist/grant-scoped-read.js.map +1 -0
  61. package/dist/index.d.ts +31 -5
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +20 -4
  64. package/dist/index.js.map +1 -1
  65. package/dist/kernel-outbox.d.ts +14 -0
  66. package/dist/kernel-outbox.d.ts.map +1 -0
  67. package/dist/kernel-outbox.js +37 -0
  68. package/dist/kernel-outbox.js.map +1 -0
  69. package/dist/list-index.d.ts +11 -2
  70. package/dist/list-index.d.ts.map +1 -1
  71. package/dist/list-index.js +29 -21
  72. package/dist/list-index.js.map +1 -1
  73. package/dist/mail-sender.d.ts +78 -0
  74. package/dist/mail-sender.d.ts.map +1 -0
  75. package/dist/mail-sender.js +2 -0
  76. package/dist/mail-sender.js.map +1 -0
  77. package/dist/membership-executor.d.ts +0 -52
  78. package/dist/membership-executor.d.ts.map +1 -1
  79. package/dist/membership-executor.js +19 -59
  80. package/dist/membership-executor.js.map +1 -1
  81. package/dist/migration-digest.d.ts +161 -0
  82. package/dist/migration-digest.d.ts.map +1 -0
  83. package/dist/migration-digest.js +237 -0
  84. package/dist/migration-digest.js.map +1 -0
  85. package/dist/module-erasure.d.ts +156 -0
  86. package/dist/module-erasure.d.ts.map +1 -0
  87. package/dist/module-erasure.js +440 -0
  88. package/dist/module-erasure.js.map +1 -0
  89. package/dist/peer.d.ts +9 -1
  90. package/dist/peer.d.ts.map +1 -1
  91. package/dist/peer.js +29 -0
  92. package/dist/peer.js.map +1 -1
  93. package/dist/permission-checker.d.ts +6 -0
  94. package/dist/permission-checker.d.ts.map +1 -1
  95. package/dist/permission-checker.js.map +1 -1
  96. package/dist/permission-eval.d.ts +10 -1
  97. package/dist/permission-eval.d.ts.map +1 -1
  98. package/dist/permission-eval.js +51 -2
  99. package/dist/permission-eval.js.map +1 -1
  100. package/dist/platform-sweep.d.ts +1 -1
  101. package/dist/platform-sweep.d.ts.map +1 -1
  102. package/dist/platform-sweep.js +5 -0
  103. package/dist/platform-sweep.js.map +1 -1
  104. package/dist/scope-host.d.ts +301 -22
  105. package/dist/scope-host.d.ts.map +1 -1
  106. package/dist/scope-host.js +72 -5
  107. package/dist/scope-host.js.map +1 -1
  108. package/dist/search-index.d.ts +8 -0
  109. package/dist/search-index.d.ts.map +1 -1
  110. package/dist/search-index.js +51 -21
  111. package/dist/search-index.js.map +1 -1
  112. package/dist/spine-guard.d.ts +7 -0
  113. package/dist/spine-guard.d.ts.map +1 -1
  114. package/dist/spine-guard.js +54 -6
  115. package/dist/spine-guard.js.map +1 -1
  116. package/dist/spine-restore.d.ts.map +1 -1
  117. package/dist/spine-restore.js +5 -0
  118. package/dist/spine-restore.js.map +1 -1
  119. package/dist/sql-statements.d.ts +71 -0
  120. package/dist/sql-statements.d.ts.map +1 -0
  121. package/dist/sql-statements.js +191 -0
  122. package/dist/sql-statements.js.map +1 -0
  123. package/dist/subject-redaction.d.ts +9 -1
  124. package/dist/subject-redaction.d.ts.map +1 -1
  125. package/dist/subject-redaction.js.map +1 -1
  126. package/dist/system-switch-record.d.ts +3 -0
  127. package/dist/system-switch-record.d.ts.map +1 -1
  128. package/dist/system-switch-record.js +2 -0
  129. package/dist/system-switch-record.js.map +1 -1
  130. package/dist/table-ownership.d.ts +103 -0
  131. package/dist/table-ownership.d.ts.map +1 -0
  132. package/dist/table-ownership.js +285 -0
  133. package/dist/table-ownership.js.map +1 -0
  134. package/dist/timeline.d.ts +4 -4
  135. package/dist/timeline.js +4 -4
  136. package/dist/visible-continuation.d.ts +38 -0
  137. package/dist/visible-continuation.d.ts.map +1 -0
  138. package/dist/visible-continuation.js +162 -0
  139. package/dist/visible-continuation.js.map +1 -0
  140. package/package.json +2 -2
@@ -1,15 +1,18 @@
1
1
  import type { ModuleLog } from './module-log.js';
2
2
  import type { DeliveryRefusal } from './delivery-refusal.js';
3
3
  import type { ScopeRoleHolder } from './scope-role-admin.js';
4
- import type { OnBehalfOf, ExportReadInput, ExportedBatch, ImportBatch, ImportedEvent, ImportResult, ImportState, ImportCursorMove, ImportCursorMoved, AdminAction, BecomeCapabilityInput, CapabilityExchange, CapabilityFilter, CapabilityId, CapabilityPage, MintedCapability, ListPage, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, DelegatedReadRecord, OwnerTransferAudit, MemberChangeAudit, CopyMarkAudit, BindHostnameInput, AdminLogEntry, OpsFailureEntry, PlatformRequestDrainTotalsEntry, SweepRunEntry, SweepRunKind, SweepRunOutcome, ModelUsageEntry, ModelUsageSummary, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequest, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, PeerGrantsStatusEntry, SystemGrantsStatusEntry, SystemSwitchRecord, SystemSwitch, SystemSwitchResult, PeerCoverage, PeerSwitch, PeerSwitchResult, VerticalCaller, VerticalResolution, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ExportBreak, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, BindAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, IdentityMembership, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, RefusalFilter, RefusalRecord, PermissionDenial, Coverage, BeginImpersonationInput, ImpersonationFilter, ImpersonationSession, ImpersonationSessionId, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage, FreshnessSpec, EntityHistoryInput, EventFacetInput, EventCauseInput, EventEffectsInput, EffectsTree, InvocationEventsInput, InvocationEvents, DeadLettersInput, DeadLetter, LifecycleFlowInput, LifecycleFlowResult, OperationSeriesInput, OperationSeriesResult, CauseChain, EventFacetResult, HistoryEntry, ErrorCode, PlatformRequestFailureOrigin, IssueEntry, IssueStatus, IssueStatusInput, FindingEntry, FindingFilter, FindingRuleEntry, FindingRuleInput, FindingStatusInput, DeclaredMigration, CheckSubject } from '@substrat-run/contracts';
4
+ import type { OnBehalfOf, ExportReadInput, ExportedBatch, ImportBatch, ImportedEvent, ImportResult, ImportState, ImportCursorMove, ImportCursorMoved, AdminAction, BecomeCapabilityInput, CapabilityExchange, CapabilityFilter, CapabilityId, CapabilityPage, MintedCapability, ListPage, ConnectLink, ConnectLinkConsume, ConnectLinkFilter, ConnectLinkKey, ConsumeConnectLinkInput, MintConnectLinkInput, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, DelegatedReadRecord, OwnerTransferAudit, MemberChangeAudit, PeerBindingAudit, CopyMarkAudit, BindHostnameInput, AdminLogEntry, OpsFailureEntry, PlatformRequestDrainTotalsEntry, SweepRunEntry, SweepRunKind, SweepRunOutcome, ModelUsageEntry, ModelUsageSummary, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequest, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, PlatformOutcomeEvent, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, PeerGrantsStatusEntry, SystemGrantsStatusEntry, SystemSwitchRecord, SystemSwitch, SystemSwitchResult, PeerCoverage, PeerSwitch, PeerSwitchResult, VerticalCaller, VerticalResolution, PeerBinding, PeerInstanceResolution, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ExportBreak, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, BindAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, IdentityMembership, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, RefusalFilter, RefusalRecord, PermissionDenial, Coverage, BeginImpersonationInput, ImpersonationFilter, ImpersonationSession, ImpersonationSessionId, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage, FreshnessSpec, EntityHistoryInput, EventFacetInput, EventCauseInput, EventEffectsInput, EffectsTree, InvocationEventsInput, InvocationEvents, DeadLettersInput, DeadLetter, LifecycleFlowInput, LifecycleFlowResult, OperationSeriesInput, OperationSeriesResult, CauseChain, EventFacetResult, HistoryEntry, ErrorCode, PlatformRequestFailureOrigin, IssueEntry, IssueStatus, IssueStatusInput, FindingEntry, FindingFilter, FindingRuleEntry, FindingRuleInput, FindingStatusInput, DeclaredMigration, CheckSubject, EntityGrantShape } from '@substrat-run/contracts';
5
5
  import type { ConnectionUseOutcome } from './connector-calls.js';
6
6
  import type { CapabilityVerbs } from './capability.js';
7
+ import type { AuditedOperationRef, AuditedOperationRow } from './audit-outcome.js';
7
8
  import { type EntityStateName } from '@substrat-run/contracts';
8
9
  import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
9
10
  import type { FindingPruneReport } from './findings.js';
10
11
  import type { SealedSecret } from './secret-box.js';
12
+ import type { OnSubjectErased } from './module-erasure.js';
11
13
  import type { SystemSwitchReassert, SystemSwitchReassertOptions, SystemSwitchRecordFilter } from './system-switch-record.js';
12
14
  import type { SearchHit, SearchOptions } from './search-index.js';
15
+ import type { GrantedEntitiesPage } from './grant-scoped-read.js';
13
16
  import type { EntityVersion } from './entity-version.js';
14
17
  import type { UndrainedEvents } from './outbox-event.js';
15
18
  import type { JobDriveReport, JobHandler, JobRun, JobRunFilter, StartJobRunInput } from './job-run.js';
@@ -40,6 +43,11 @@ export interface PageParams {
40
43
  * archive. Refused for an entity that declares no archive. The bin is `ctx.pageTrashed`.
41
44
  */
42
45
  readonly view?: Exclude<EntityStateName, 'trashed'>;
46
+ /**
47
+ * Also return each row's own cursor, as `rowCursors` (#2073) — asked for by `pageVisible`,
48
+ * which may stop partway through a page. Off by default, so an ordinary page is unchanged.
49
+ */
50
+ readonly rowCursors?: boolean;
43
51
  }
44
52
  /**
45
53
  * The scope-host contract — the adapter seam (§5.1 of the design doc).
@@ -90,6 +98,13 @@ export interface OperationContext {
90
98
  readonly tenantId: TenantId;
91
99
  readonly scopeId: ScopeId;
92
100
  readonly principal: PrincipalId;
101
+ /** Seal positions for one filtered list under this scope, caller and operation. */
102
+ pageContinuation(list: string, query: unknown): {
103
+ /** Read-only and copy walks cannot persist a hidden budget-stop position. */
104
+ writable?: boolean;
105
+ open(cursor: string): Promise<string>;
106
+ seal(position: string, hidden: boolean): Promise<string | null>;
107
+ };
93
108
  readonly sql: ScopedSql;
94
109
  /**
95
110
  * The operation's instant (#812) — the ONLY clock module code may read.
@@ -209,6 +224,18 @@ export interface OperationContext {
209
224
  * than returning a denial, and records none (#1642, `assertPermissionKey`).
210
225
  */
211
226
  check(permission: PermissionKey, entity?: EntityRef): Promise<Decision>;
227
+ /**
228
+ * Page entity ids reachable through this caller's live grants and declared parent edges.
229
+ * `all` means a node grant makes every entity of this type visible; `incomplete` means
230
+ * this checker cannot enumerate the path, so callers must use a checked full walk.
231
+ * An `ids` page is complete only after its cursor becomes null. Ids may repeat across
232
+ * pages on multiple parent paths: accumulate a Set, and do not treat page lengths as
233
+ * a count or total. The union of all pages is complete. Every id passes `ctx.check`.
234
+ */
235
+ grantedEntities(permission: PermissionKey, entityType: string, options?: {
236
+ limit?: number;
237
+ cursor?: string;
238
+ }): Promise<GrantedEntitiesPage>;
212
239
  /**
213
240
  * Find entities of one type by what a person typed (#827) — the read a picker
214
241
  * over 40 000 customers needs and `ctx.sql` cannot express without every
@@ -265,9 +292,9 @@ export interface OperationContext {
265
292
  *
266
293
  * **This does not check permission** — nothing on `ctx` does, and a paged read
267
294
  * is not an exception. The operation's own `assertAllowed` still comes first.
268
- * A read that filters per ROW after the fact (a portal walk) cannot use this at
269
- * all: a page of 20 filtered down to 3 is not a page, and the honest shape is an
270
- * over-fetch loop the handler owns.
295
+ * A read that filters per ROW after the fact (a portal walk) cannot hand this
296
+ * page on as it is: a page of 20 filtered down to 3 is not a page, and its cursor
297
+ * may be a refused row's. It walks this read with `pageVisible` instead (#2073).
271
298
  *
272
299
  * Throws `NotListable` for an entity no operation declared `paged.over` on,
273
300
  * `SortNotDeclared` for a `?sort=` outside the vocabulary, and
@@ -334,6 +361,12 @@ export interface OperationContext {
334
361
  * revives it and records that as one `entity.linked` spine event, since access resumes.
335
362
  *
336
363
  * Checks no permission: the operation does, in its own vocabulary.
364
+ *
365
+ * A parent in the TRASH (#119) is refused `not_found`: nothing new hangs off a binned
366
+ * entity, since it is gone from everyone's view until restored. A MISSING parent of a type that
367
+ * declares trash gets the identical refusal, so the two cannot be told apart. An ARCHIVED parent
368
+ * is allowed — archived is filed away and still readable, and whether one may still be added to
369
+ * is the vertical's rule, not the kernel's.
337
370
  */
338
371
  link(child: EntityRef, parent: EntityRef): void;
339
372
  /**
@@ -346,6 +379,9 @@ export interface OperationContext {
346
379
  * move would make the child its own ancestor.
347
380
  * - `from` must be a live parent edge of `child`; `conflict` otherwise. Every other
348
381
  * parent a multi-parent entity has is left alone.
382
+ * - A `to` in the TRASH is refused `not_found`, as `link` refuses one (#119) — the same refusal a
383
+ * missing `to` of that type gets. `from` is not: moving a child out from under a binned
384
+ * parent is how it is rescued.
349
385
  * - `from` equal to `to` is a no-op: nothing written, nothing emitted.
350
386
  * - A `to` that is ALREADY a live parent of `child` is the way to detach one parent of a
351
387
  * multi-parent child (#2044): the `from` edge is tombstoned, the live `to` edge stays
@@ -876,9 +912,9 @@ export type MembershipChangeResult = {
876
912
  * It receives `HostAdmin`, not `ctx`: it acts with platform authority, which is
877
913
  * precisely what module code must never hold. Admin writes it makes through the `admin`
878
914
  * it is handed are stamped with the causing event's id (`causedBy`), so the split trail
879
- * joins. That `admin` is bound to this one event, never the host's own: a handler that
880
- * writes through the host instead — `host.attributed(…)` — passes `{ causedBy: event.id }`
881
- * itself (#2055).
915
+ * joins. That `admin` is bound to this one event, never the host's own (#2055). A handler
916
+ * that writes for a person attributes from it — `admin.attributed(onBehalfOf)` — which keeps
917
+ * the event (#2069); a view of the HOST knows nothing of the event.
882
918
  *
883
919
  * A handler that decides an event must never be effected RETURNS `refuseDelivery(reason)`
884
920
  * (#1184). The delivery is journaled terminal with the reason, never retried. A return
@@ -1491,6 +1527,11 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
1491
1527
  * nothing, and it reads as coverage. A bound operation with no entry is
1492
1528
  * allowed and means what it always meant — nothing was declared to parse.
1493
1529
  *
1530
+ * **It also carries the declared surface (#119).** `operationInputsOf` records, for the frozen
1531
+ * map it returns, every declared operation and the entity each addresses by id. The host DERIVES
1532
+ * its trash refusal from that — nothing else is handed over — and a module with a trashable
1533
+ * entity must pass that map as returned, with every operation it binds declared in it.
1534
+ *
1494
1535
  * Typed structurally rather than as `z.ZodType` so the kernel keeps its single
1495
1536
  * dependency and no zod version is pinned by the scope-host contract. The
1496
1537
  * shape is the whole surface the host uses: throw to refuse, return the value
@@ -1567,6 +1608,15 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
1567
1608
  * contribute the same name.
1568
1609
  */
1569
1610
  predicates?: Record<string, GuardPredicate>;
1611
+ /**
1612
+ * This module's own step in a subject erasure (#2068), for the rows its declared
1613
+ * `erasure: { subjects }` cannot reach — a link the row does not hold itself. Run by
1614
+ * `shredSubject` inside the erasure's one transaction, after the declared entities and
1615
+ * before the key is destroyed; a throw refuses the whole erasure and rolls it back.
1616
+ * Synchronous, idempotent, and handed a `sql` that reaches the module's own tables only
1617
+ * (`module-erasure.ts`). Requires `manifest.erasure`, whose `tables` is that reach.
1618
+ */
1619
+ onSubjectErased?: OnSubjectErased;
1570
1620
  }
1571
1621
  /**
1572
1622
  * Admin surface for enforcement input (design doc §4; control-plane.md §4.4).
@@ -1591,6 +1641,16 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
1591
1641
  * stay sync — they are code-time bookkeeping, not control-plane state.)
1592
1642
  */
1593
1643
  export interface HostAdmin {
1644
+ /**
1645
+ * This same admin, with every row written through it also naming `onBehalfOf` (#977, #2069),
1646
+ * and keeping whatever this one already carries — for the `admin` an executor handler is
1647
+ * handed, the event (`causedBy`). A handler that writes for a person attributes from that
1648
+ * `admin`, so the cause comes along by construction and there is no option to forget.
1649
+ *
1650
+ * Optional so an admin that predates it still satisfies the interface; a caller that finds it
1651
+ * absent writes through the admin it holds, unattributed to a person.
1652
+ */
1653
+ attributed?(onBehalfOf: OnBehalfOf): HostAdmin;
1594
1654
  defineRole(actor: PlatformActorId, tenantId: TenantId, role: RoleDefinition): Promise<void>;
1595
1655
  /**
1596
1656
  * Every role the directory holds, ordered by (tenantId, key).
@@ -1622,6 +1682,57 @@ export interface HostAdmin {
1622
1682
  */
1623
1683
  unassignRole(actor: PlatformActorId, assignment: RoleAssignment): Promise<void>;
1624
1684
  grant(actor: PlatformActorId, grant: CapabilityGrant): Promise<void>;
1685
+ /**
1686
+ * Give a person a vertical's declared entity-grant SHAPE on one entity (#2071): every key in
1687
+ * `permissions`, plus the marker that makes them a holder of the shape, so a key the shape
1688
+ * gains later reaches them at the next {@link reconcileEntityGrantShapes}. What a vertical
1689
+ * calls where it used to `grant` its `ENTITY_GRANTS` keys one at a time. It brings back a key
1690
+ * or marker a revoke tombstoned, as `grant` does, and leaves a live row as it is, expiry
1691
+ * included, as `ctx.grant` does. Audited as `grantEntityShape`.
1692
+ */
1693
+ grantEntityShape(actor: PlatformActorId, grant: {
1694
+ principalId: PrincipalId;
1695
+ node: {
1696
+ tenantId: TenantId;
1697
+ scopeId: ScopeId;
1698
+ };
1699
+ entity: EntityRef;
1700
+ permissions: readonly PermissionKey[];
1701
+ grantedBy: PrincipalId;
1702
+ }): Promise<void>;
1703
+ /**
1704
+ * Top every holder of each declared shape up to the shape as it is now (#2071): a key the
1705
+ * shape gained is granted to each person holding the shape's marker on an entity of that
1706
+ * type. Never a key revoked from that person there (its K-21 tombstone stays). A key merely
1707
+ * dropped from the shape is left in place; a key the shape declares `retired` (#2082) is taken
1708
+ * back from every holder instead, its row tombstoned, once per scope — including a direct grant
1709
+ * of that key to the holder on the same entity, which is the same row. Until it is done on a scope, a
1710
+ * shape that declares a `holder` is also backfilled: each person its holder relationship names
1711
+ * as the owner of an entity (`'self'`, or the vertical's own column) is marked if they hold a
1712
+ * key of the shape there. Never inferred from key sets, so a grant delegated on someone else's
1713
+ * record is never marked. Without a `holder`, there is no backfill.
1714
+ *
1715
+ * Only shapes declared `bootstrap: true` are reconciled — those given with
1716
+ * {@link grantEntityShape} on a person's own record. A SHARING shape, reached through
1717
+ * `ctx.grant` (todo's `list`), is skipped whole: reconciling one would mark every person
1718
+ * something was fully shared with as a holder.
1719
+ *
1720
+ * Bounded: `batch` rows of work (default 500, an integer from 1 to 5000, or `validation_failed`)
1721
+ * per scope transaction, backfill included, repeated until a pass finishes — so it is safe
1722
+ * on a large scope, and finishes on a re-run if interrupted. Each (person, entity) topped up is
1723
+ * an `entity.grants-topped-up` event on the entity, and each one retired keys were taken from an
1724
+ * `entity.grants-retired` event. Audited as `reconcileEntityGrantShapes` when it changed
1725
+ * anything. Returns how many (person, entity) it topped up, and how many it retired keys from.
1726
+ */
1727
+ reconcileEntityGrantShapes(actor: PlatformActorId, node: {
1728
+ tenantId: TenantId;
1729
+ scopeId: ScopeId;
1730
+ }, shapes: readonly EntityGrantShape[], opts?: {
1731
+ batch?: number;
1732
+ }): Promise<{
1733
+ toppedUp: number;
1734
+ retired: number;
1735
+ }>;
1625
1736
  /** Grant to an organization (portal customers); members reach it via membership tuples. */
1626
1737
  /**
1627
1738
  * Grant a permission to a CONNECTION (#97) — how a connector is allowed to
@@ -2308,6 +2419,17 @@ export interface HostAdmin {
2308
2419
  * per-call path, before any staff member is involved.
2309
2420
  */
2310
2421
  resolveVerticalInstance(tenantId: TenantId, vertical: string): Promise<VerticalResolution>;
2422
+ /** Resolve for one caller at execution time. An explicit stale binding refuses without fallback. */
2423
+ resolvePeerInstance(tenantId: TenantId, callerScopeId: ScopeId, vertical: string): Promise<PeerInstanceResolution>;
2424
+ /** Read the caller's explicit choice, if any. Tenant-scoped and recorded as an admin read. */
2425
+ peerBinding(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId, vertical: string): Promise<PeerBinding | undefined>;
2426
+ /** Every explicit target choice this calling scope currently holds. */
2427
+ peerBindings(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId): Promise<PeerBinding[]>;
2428
+ /** Bind or clear this caller's target. Both endpoints are checked in the directory at write. */
2429
+ setPeerBinding(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId, vertical: string, targetScopeId: ScopeId | null): Promise<{
2430
+ changed: boolean;
2431
+ previous: ScopeId | null;
2432
+ }>;
2311
2433
  /**
2312
2434
  * Persist a tenant. Idempotent on the id — re-creating an existing tenant is a
2313
2435
  * no-op, not an error (control-plane.md §4.1). `status` starts `active` and
@@ -2952,8 +3074,11 @@ export interface HostAdmin {
2952
3074
  * the audit metadata — the rotate-side analogue of `createdBy` on create
2953
3075
  * (§3.5.1). Omitted ⇒ the effecting actor stands alone, the platform-driven
2954
3076
  * refresh path.
3077
+ *
3078
+ * `expiresAt`: a date replaces the connection's expiry, `null` clears it (the credential
3079
+ * that had one was replaced by one that has none), and omitted keeps it.
2955
3080
  */
2956
- updateConnectionSecret(actor: PlatformActorId, id: ConnectionId, secret: ConnectionSecret, expiresAt?: string, opts?: {
3081
+ updateConnectionSecret(actor: PlatformActorId, id: ConnectionId, secret: ConnectionSecret, expiresAt?: string | null, opts?: {
2957
3082
  rotatedBy?: string;
2958
3083
  }): Promise<void>;
2959
3084
  /**
@@ -2963,6 +3088,37 @@ export interface HostAdmin {
2963
3088
  * constraint ignores revoked rows.
2964
3089
  */
2965
3090
  revokeConnection(actor: PlatformActorId, id: ConnectionId): Promise<void>;
3091
+ /**
3092
+ * A vertical's MAILED connect link (connections.md §3.5.4) — the row that makes a consent
3093
+ * round single-use and revocable when it lives days rather than minutes. Beside the
3094
+ * connections because it is their front door: the round it starts ends in
3095
+ * `createConnection` (or a rotation) for the same tenant and scope.
3096
+ *
3097
+ * Every verb below is keyed by (tenant, scope): a link of another scope or tenant is
3098
+ * `undefined` / `unknown`, never a refusal that admits it exists. Expiry is judged against
3099
+ * the host's own clock. Mint, revoke, consume and restore are each audited — metadata
3100
+ * only, though nothing here is secret: the credential does not exist until a round is
3101
+ * consumed, and it never touches this row.
3102
+ */
3103
+ mintConnectLink(actor: PlatformActorId, input: MintConnectLinkInput): Promise<ConnectLink>;
3104
+ getConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<ConnectLink | undefined>;
3105
+ /** Newest first. `outstandingOnly`: outstanding AND unexpired — the links that still open. */
3106
+ listConnectLinks(actor: PlatformActorId, filter: ConnectLinkFilter): Promise<ConnectLink[]>;
3107
+ /**
3108
+ * Outstanding → revoked. Idempotent: a revoked or used link is answered as it stands and
3109
+ * nothing is written. `undefined` when the scope holds no such link.
3110
+ */
3111
+ revokeConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<ConnectLink | undefined>;
3112
+ /**
3113
+ * Outstanding and unexpired → used, recording what the consent attached. Atomic: of two
3114
+ * concurrent consumes exactly one answers `ok`. Otherwise the typed reason.
3115
+ */
3116
+ consumeConnectLink(actor: PlatformActorId, input: ConsumeConnectLinkInput): Promise<ConnectLinkConsume>;
3117
+ /**
3118
+ * Used → outstanding, the best-effort undo after the credential store failed. Only a
3119
+ * used, unexpired link moves; `false` for anything else, a revoked link included.
3120
+ */
3121
+ restoreConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<boolean>;
2966
3122
  /**
2967
3123
  * Open the credential for one (tenant, vertical, provider[, account]) — the
2968
3124
  * connector's read, and the only path in the system that yields plaintext.
@@ -3263,6 +3419,27 @@ export interface HostAdmin {
3263
3419
  * cannot be written.
3264
3420
  */
3265
3421
  recordMemberChange(actor: PlatformActorId, entry: MemberChangeAudit): Promise<void>;
3422
+ /** One intent or outcome of a tenant's peer-instance binding change (#1720). */
3423
+ recordPeerBindingChange(actor: PlatformActorId, entry: PeerBindingAudit): Promise<void>;
3424
+ /**
3425
+ * Close one audited change whose intent has no outcome (#2064): in ONE transaction, read the
3426
+ * intent `intentId` names, and if no outcome row exists for its operation, write an `unknown`
3427
+ * outcome carrying `error` and an ops-failure row (`audit.<action>`, stage `outcome-unknown`)
3428
+ * for the staff digest. Answers `true` when it wrote them, `false` when an outcome was already
3429
+ * there, so concurrent callers settle an operation once. Throws `not_found` for an id that is
3430
+ * not an audited-change intent. See `audit-outcome.ts` for why a later real outcome still wins.
3431
+ */
3432
+ settleUnrecordedOutcome(actor: PlatformActorId, input: {
3433
+ intentId: string;
3434
+ error: string;
3435
+ }): Promise<boolean>;
3436
+ /**
3437
+ * Every admin-log row of the given audited operations (#2064), read through the operation-id
3438
+ * index in bounded batches — one statement per `AUDITED_OPERATIONS_BATCH` ids, never a scan of
3439
+ * the log. Only rows of the exact (action, operation, tenant, scope) asked about are returned.
3440
+ * Reading the audit trail is itself recorded, once per call.
3441
+ */
3442
+ auditedOperations(actor: PlatformActorId, refs: readonly AuditedOperationRef[]): Promise<AuditedOperationRow[]>;
3266
3443
  /**
3267
3444
  * Stamp `drainedAt` on every not-yet-drained access row up to and including
3268
3445
  * `upToId`, marking them shipped to Tier 2. Returns how many rows moved.
@@ -3908,10 +4085,19 @@ export interface ScopeHost {
3908
4085
  * Optional so a host that predates it still satisfies the interface; a transport
3909
4086
  * that finds it absent writes unattributed rows, which is what every row was before.
3910
4087
  *
3911
- * `options.causedBy` (#2055) is the event whose effect the view's writes are (K-22): an
3912
- * executor handler passes its own event's id, because the host stamps nothing ambiently —
3913
- * a field set around the handler's `await` would also stamp every other admin call the
3914
- * host served meanwhile. The `admin` a handler is handed already carries it.
4088
+ * A view of the host knows nothing of any event: an executor handler attributes from the
4089
+ * `admin` it is handed instead (`HostAdmin.attributed`), which keeps its `causedBy`.
4090
+ */
4091
+ attributed?(onBehalfOf: OnBehalfOf): ScopeHost;
4092
+ /**
4093
+ * The same view, its rows also naming `options.causedBy` as the event they are the effect
4094
+ * of (K-22, #2055).
4095
+ *
4096
+ * @deprecated since #2069: an executor handler attributes from the `admin` it is handed —
4097
+ * `admin.attributed(onBehalfOf)` — which carries its event by construction. This form needs
4098
+ * the event passed again, and a handler that forgets writes `causedBy` NULL with nothing
4099
+ * failing. It stays because it shipped, and because it is how the membership executor still
4100
+ * attributes on a host whose handed admin predates `HostAdmin.attributed`.
3915
4101
  */
3916
4102
  attributed?(onBehalfOf: OnBehalfOf, options?: {
3917
4103
  causedBy?: string;
@@ -4618,6 +4804,11 @@ export interface ScopeHost {
4618
4804
  lastError?: string | null;
4619
4805
  /** WHO refused (#841). Omitted by a caller too old to attribute — stored as NULL. */
4620
4806
  failure?: PlatformRequestFailure | null;
4807
+ /**
4808
+ * #2102: an outcome event written into the scope with the settle, in its transaction,
4809
+ * and only when this settle moves the row out of `pending` (`settlePlatformRequestIn`).
4810
+ */
4811
+ event?: PlatformOutcomeEvent | null;
4621
4812
  }): Promise<void>;
4622
4813
  /**
4623
4814
  * Live reads (#938) — a subscription to this scope's change feed, or absent.
@@ -4671,10 +4862,12 @@ export interface ScopeHost {
4671
4862
  * changed at 14:02 is information about that row, so "the body was empty" is not a
4672
4863
  * defence: the filter runs whether or not there is a payload to withhold.
4673
4864
  *
4674
- * A subscription may be narrowed `within` one entity (#1853), which only removes frames;
4675
- * the one exception to the per-principal check is a `within` the vertical built with
4676
- * `vouchedWithin`, whose subscriber is sent bare `LiveNudge` frames and nothing that
4677
- * names an entity.
4865
+ * A subscription may be narrowed `within` one entity (#1853), which only removes frames.
4866
+ * Two built roots replace the per-row check with one about the ROOT, and their subscribers
4867
+ * are sent bare `LiveNudge` frames that name no entity: `checkedWithin`, whose principal must
4868
+ * pass a stated key on the root at the handshake and again on every pass that has something
4869
+ * to send (#938), and `vouchedWithin`, which the vertical asserts once and nothing re-checks
4870
+ * (#1853). Which to use is on `checkedWithin`.
4678
4871
  *
4679
4872
  * **Generic over the runtime's request and response, because the kernel names
4680
4873
  * neither.** This package has one dependency and no DOM or workers lib
@@ -4727,10 +4920,51 @@ export interface LiveReadSurface<Req extends LiveUpgradeRequest = LiveUpgradeReq
4727
4920
  * the same trust it already extends in naming `principal`, and the scope's walk is
4728
4921
  * the whole filter. Such a subscriber receives `LiveNudge` frames only, which name
4729
4922
  * no event type and no entity.
4923
+ * - **A `checkedWithin(…)` value replaces the per-row check with one on the root**
4924
+ * (#938). The principal must pass the stated key on the root, at the handshake (a
4925
+ * `403` otherwise) and again on every pass that has a row beneath it to announce,
4926
+ * so a grant withdrawn, or a root moved out from under it, closes the socket
4927
+ * instead of nudging it. `LiveNudge` frames only, as for a vouched root.
4928
+ *
4929
+ * **Freshness (Codex #2077 r5).** A decision is reused within a pass only while the
4930
+ * scope has had no write and nothing it holds has reached its `expires_at` since the
4931
+ * decision. It is never reused while the scope reads its permissions from the directory.
4932
+ * Both are checked against the clock immediately before each send. A change that lands
4933
+ * while a check is still being evaluated can let that one frame through. Frames name
4934
+ * nothing the subscriber could not read when checked, and the next frame, pass and poll
4935
+ * see the change: a push is a hint at most one evaluation stale.
4936
+ */
4937
+ within?: EntityRef | VouchedWithin | CheckedWithin;
4938
+ /**
4939
+ * When the credential that proved `principal` stops being valid (ISO 8601), so the
4940
+ * socket never outlives it (#938). Pass the session's or the bearer's expiry
4941
+ * (`AuthSubject.expiresAt`). A handshake at or past it is refused, and the scope closes
4942
+ * the socket (`1008`) on its first pass at or past it, before sending anything on that
4943
+ * pass. Absent: the socket lives until either end closes it, as before.
4730
4944
  */
4731
- within?: EntityRef | VouchedWithin;
4945
+ expiresAt?: string;
4732
4946
  }): Promise<Res>;
4733
4947
  }
4948
+ /**
4949
+ * The close codes a live socket ends with, so a client can tell "try again" from "poll".
4950
+ *
4951
+ * - `1008` (policy): the subscriber may no longer watch what it subscribed to — a
4952
+ * `checkedWithin` gate refused, or the session that opened it ended. A reconnect meets
4953
+ * the handshake's own refusal.
4954
+ * - `4429`: this principal already holds `LIVE_SOCKETS_PER_PRINCIPAL` sockets on the scope.
4955
+ * Not a reason to retry: the client should poll, which it does anyway, and stop asking.
4956
+ */
4957
+ export declare const LIVE_CLOSE: {
4958
+ readonly revoked: 1008;
4959
+ readonly tooMany: 4429;
4960
+ };
4961
+ /**
4962
+ * How many live sockets one principal may hold on one scope (#938). A tab holds one per
4963
+ * feed (the desk's, and one per open conversation in ticket0's portal), and each socket is
4964
+ * work on every post-commit pass, so a principal opening more is a cost on everybody else
4965
+ * writing to the scope. Eight covers several tabs; past it a socket is closed `4429`.
4966
+ */
4967
+ export declare const LIVE_SOCKETS_PER_PRINCIPAL = 8;
4734
4968
  /** The brand only `vouchedWithin` can apply — a literal cannot type-check as one. */
4735
4969
  declare const vouchedBrand: unique symbol;
4736
4970
  /**
@@ -4762,6 +4996,50 @@ export declare function vouchedWithin(entity: EntityRef, opts: {
4762
4996
  }): VouchedWithin;
4763
4997
  /** Was this value built by `vouchedWithin`? A host asks before it drops the principal's check. */
4764
4998
  export declare function isVouchedWithin(value: unknown): value is VouchedWithin;
4999
+ /** The brand only `checkedWithin` can apply. */
5000
+ declare const checkedBrand: unique symbol;
5001
+ /**
5002
+ * A `within` root the PRINCIPAL must be allowed `permission` on, re-checked for as long
5003
+ * as the socket is open (#938). Built only by `checkedWithin`.
5004
+ */
5005
+ export interface CheckedWithin {
5006
+ readonly [checkedBrand]: true;
5007
+ readonly entity: EntityRef;
5008
+ readonly permission: PermissionKey;
5009
+ }
5010
+ /**
5011
+ * Watch `entity` and everything beneath it, for as long as the subscriber may
5012
+ * `permission` on `entity` itself.
5013
+ *
5014
+ * For a subscriber whose grant reaches the ROOT but not each row under it the way a
5015
+ * `liveTargets` key would: ticket0's portal customer holds `conversation:read-own` on
5016
+ * their contact, which reaches the conversation's public thread, while the per-row key
5017
+ * on a message is the staff read. Rooted at an entity whose subtree is exactly what that
5018
+ * key's read returns, the walk is the row filter and the key is the gate.
5019
+ *
5020
+ * The gate is asked at the handshake (`403` if it refuses) and again, once per socket,
5021
+ * on every post-commit pass that has a row beneath the root to announce. A refusal or a
5022
+ * check that throws closes the socket (`1008`) and sends nothing on that pass, so a
5023
+ * revoked grant, a principal who is gone, or a root relinked out of the grant's reach
5024
+ * ends the subscription instead of nudging it, and the client's reconnect meets the
5025
+ * handshake's `403`. A pass with nothing beneath the root asks nothing, so an idle
5026
+ * subscription costs no checks.
5027
+ *
5028
+ * **`checkedWithin` or `vouchedWithin`?** Use `checkedWithin` whenever the subscriber is
5029
+ * a principal with a grant that reaches the root: authority leaves with the grant. Use
5030
+ * `vouchedWithin` only when there is no such grant to ask, as for ticket0's widget
5031
+ * visitor, who holds a session token rather than a principal of their own. A vouched
5032
+ * socket is asserted once, so a token revoked mid-socket keeps receiving content-free
5033
+ * nudges until the socket closes. That is known and accepted for the widget: the nudge
5034
+ * names nothing, and every re-read it causes is checked again.
5035
+ *
5036
+ * Both send `LiveNudge` frames only: the subscriber may not read each row, so it is
5037
+ * never told which one changed. Only rows of a type some module declared in
5038
+ * `liveTargets` are announced, as for every other subscriber.
5039
+ */
5040
+ export declare function checkedWithin(entity: EntityRef, permission: string): CheckedWithin;
5041
+ /** Was this value built by `checkedWithin`? */
5042
+ export declare function isCheckedWithin(value: unknown): value is CheckedWithin;
4765
5043
  /**
4766
5044
  * Is this request asking to be upgraded to a WebSocket?
4767
5045
  *
@@ -4822,12 +5100,13 @@ export interface LiveChange {
4822
5100
  readonly at: string;
4823
5101
  }
4824
5102
  /**
4825
- * What a vouched subscriber is told (#1853): something beneath its root changed, and
4826
- * nothing else.
5103
+ * What a vouched or checked subscriber is told (#1853, #938): something beneath its root
5104
+ * changed, and nothing else.
4827
5105
  *
4828
- * No event type and no entity, deliberately. A vouched subscriber holds no read on the
4829
- * entities its frames are about — the vertical vouched for the ROOT, and the scope
4830
- * cannot know which rows under it the vertical's own read would show. Naming the type
5106
+ * No event type and no entity, deliberately. Such a subscriber holds no read on the
5107
+ * entities its frames are about — the vertical vouched for the ROOT, or the principal was
5108
+ * checked on the root alone, and the scope cannot know which rows under it the vertical's
5109
+ * own read would show. Naming the type
4831
5110
  * or the id would tell it what it never asked to read. The client re-reads, as it does
4832
5111
  * on a `LiveChange`.
4833
5112
  */