@substrat-run/kernel 0.137.0 → 0.138.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 (69) hide show
  1. package/dist/delivery-refusal.d.ts +21 -0
  2. package/dist/delivery-refusal.d.ts.map +1 -0
  3. package/dist/delivery-refusal.js +21 -0
  4. package/dist/delivery-refusal.js.map +1 -0
  5. package/dist/index.d.ts +18 -14
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +13 -9
  8. package/dist/index.js.map +1 -1
  9. package/dist/job-run.d.ts +309 -48
  10. package/dist/job-run.d.ts.map +1 -1
  11. package/dist/job-run.js +388 -80
  12. package/dist/job-run.js.map +1 -1
  13. package/dist/membership-executor.d.ts +97 -0
  14. package/dist/membership-executor.d.ts.map +1 -0
  15. package/dist/membership-executor.js +246 -0
  16. package/dist/membership-executor.js.map +1 -0
  17. package/dist/membership-fence.d.ts +57 -0
  18. package/dist/membership-fence.d.ts.map +1 -0
  19. package/dist/membership-fence.js +90 -0
  20. package/dist/membership-fence.js.map +1 -0
  21. package/dist/operation-series.d.ts +58 -0
  22. package/dist/operation-series.d.ts.map +1 -0
  23. package/dist/operation-series.js +63 -0
  24. package/dist/operation-series.js.map +1 -0
  25. package/dist/peer.d.ts +13 -8
  26. package/dist/peer.d.ts.map +1 -1
  27. package/dist/peer.js +13 -14
  28. package/dist/peer.js.map +1 -1
  29. package/dist/permission-eval.d.ts +39 -1
  30. package/dist/permission-eval.d.ts.map +1 -1
  31. package/dist/permission-eval.js +111 -18
  32. package/dist/permission-eval.js.map +1 -1
  33. package/dist/read-only-sql.d.ts +2 -26
  34. package/dist/read-only-sql.d.ts.map +1 -1
  35. package/dist/read-only-sql.js +13 -9
  36. package/dist/read-only-sql.js.map +1 -1
  37. package/dist/scope-copy.d.ts +18 -0
  38. package/dist/scope-copy.d.ts.map +1 -1
  39. package/dist/scope-copy.js +18 -0
  40. package/dist/scope-copy.js.map +1 -1
  41. package/dist/scope-host.d.ts +236 -9
  42. package/dist/scope-host.d.ts.map +1 -1
  43. package/dist/scope-host.js +41 -0
  44. package/dist/scope-host.js.map +1 -1
  45. package/dist/spine-restore.d.ts +7 -2
  46. package/dist/spine-restore.d.ts.map +1 -1
  47. package/dist/spine-restore.js +13 -5
  48. package/dist/spine-restore.js.map +1 -1
  49. package/dist/system-switch-record.d.ts +277 -77
  50. package/dist/system-switch-record.d.ts.map +1 -1
  51. package/dist/system-switch-record.js +322 -105
  52. package/dist/system-switch-record.js.map +1 -1
  53. package/dist/system-switch.d.ts +110 -14
  54. package/dist/system-switch.d.ts.map +1 -1
  55. package/dist/system-switch.js +92 -17
  56. package/dist/system-switch.js.map +1 -1
  57. package/dist/timeline.d.ts +23 -0
  58. package/dist/timeline.d.ts.map +1 -1
  59. package/dist/timeline.js +30 -0
  60. package/dist/timeline.js.map +1 -1
  61. package/dist/ulid.d.ts +5 -0
  62. package/dist/ulid.d.ts.map +1 -1
  63. package/dist/ulid.js +9 -0
  64. package/dist/ulid.js.map +1 -1
  65. package/dist/vertical-events.d.ts +7 -1
  66. package/dist/vertical-events.d.ts.map +1 -1
  67. package/dist/vertical-events.js +14 -0
  68. package/dist/vertical-events.js.map +1 -1
  69. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { substratError } from '@substrat-run/contracts';
1
2
  /**
2
3
  * The textual gate in front of the scope SQL console (`HostAdmin.queryScope`, #219).
3
4
  *
@@ -46,10 +47,13 @@ const FORBIDDEN = new Set([
46
47
  'savepoint',
47
48
  'release',
48
49
  ]);
50
+ /** The gate's refusal: the caller's mistake (`validation_failed`), under the `read-only console:` prefix. */
51
+ const refuse = (reason) => substratError('validation_failed', `read-only console: ${reason}`);
52
+ const SINGLE_STATEMENT = 'only a single statement is allowed';
49
53
  /**
50
54
  * Assert `sql` is one read-only statement; returns the trimmed statement to run.
51
- * Throws an `Error` whose message names the offending token — it crosses the RPC
52
- * boundary and lands in the console UI, so it is written for the person typing.
55
+ * Throws a `validation_failed` refusal whose message names the offending token — it
56
+ * lands in the console UI, so it is written for the person typing.
53
57
  */
54
58
  export function assertReadOnlyQuery(sql) {
55
59
  const tokens = [];
@@ -86,7 +90,7 @@ export function assertReadOnlyQuery(sql) {
86
90
  i += 1;
87
91
  }
88
92
  if (statementEnded)
89
- throw new Error('read-only console: only a single statement is allowed');
93
+ throw refuse(SINGLE_STATEMENT);
90
94
  continue;
91
95
  }
92
96
  if (c === '[') {
@@ -94,7 +98,7 @@ export function assertReadOnlyQuery(sql) {
94
98
  i += 1;
95
99
  i += 1;
96
100
  if (statementEnded)
97
- throw new Error('read-only console: only a single statement is allowed');
101
+ throw refuse(SINGLE_STATEMENT);
98
102
  continue;
99
103
  }
100
104
  if (c === ';') {
@@ -108,24 +112,24 @@ export function assertReadOnlyQuery(sql) {
108
112
  while (j < n && /[A-Za-z0-9_]/.test(sql[j]))
109
113
  j += 1;
110
114
  if (statementEnded)
111
- throw new Error('read-only console: only a single statement is allowed');
115
+ throw refuse(SINGLE_STATEMENT);
112
116
  tokens.push(sql.slice(i, j).toLowerCase());
113
117
  i = j;
114
118
  continue;
115
119
  }
116
120
  if (statementEnded && /\S/.test(c))
117
- throw new Error('read-only console: only a single statement is allowed');
121
+ throw refuse(SINGLE_STATEMENT);
118
122
  i += 1;
119
123
  }
120
124
  const first = tokens[0];
121
125
  if (!first)
122
- throw new Error('read-only console: empty statement');
126
+ throw refuse('empty statement');
123
127
  if (!FIRST_KEYWORDS.has(first)) {
124
- throw new Error(`read-only console: statement must start with SELECT, WITH, VALUES, or EXPLAIN (got '${first.toUpperCase()}')`);
128
+ throw refuse(`statement must start with SELECT, WITH, VALUES, or EXPLAIN (got '${first.toUpperCase()}')`);
125
129
  }
126
130
  for (const t of tokens) {
127
131
  if (FORBIDDEN.has(t)) {
128
- throw new Error(`read-only console: '${t.toUpperCase()}' is not allowed (quote it if it names a column or table)`);
132
+ throw refuse(`'${t.toUpperCase()}' is not allowed (quote it if it names a column or table)`);
129
133
  }
130
134
  }
131
135
  const trimmed = sql.trim();
@@ -1 +1 @@
1
- {"version":3,"file":"read-only-sql.js","sourceRoot":"","sources":["../src/read-only-sql.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"read-only-sql.js","sourceRoot":"","sources":["../src/read-only-sql.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,24 @@ import type { SwitchSql } from './system-switch.js';
13
13
  * An unknown source (`sourceScopeId` or `destScopeId` absent) counts as a copy: a load that cannot
14
14
  * say where the dump came from cannot show it is a return. Only a control plane that predates the
15
15
  * field sends a restore with no source.
16
+ *
17
+ * Two kinds of state are decided here without a rule in the load, because the load never sees
18
+ * the part that holds the power (#2004):
19
+ *
20
+ * - **Staff impersonation sessions (K-42).** `_substrat_impersonations` is a DIRECTORY table on
21
+ * both adapters, never a scope table, so no dump carries a session and a copy holds none. The
22
+ * session's power is checked at the door, against the (tenant, scope) the session names
23
+ * (`assertSessionUsable`), so a live session opened on the source is refused at every copy of
24
+ * it and still honoured at the source after a return. What a copy does carry is the history:
25
+ * the impersonation stamps on the rows the session wrote, as the source recorded them.
26
+ * - **Cross-vertical import cursors (#1705).** `_substrat_import_cursors` and `_substrat_imports`
27
+ * are carried as they are, on a copy and on a return. A cursor is written in the same
28
+ * transaction as the import handlers' writes, so it describes the data it sits beside, and the
29
+ * journal is what dedupes a re-delivery. Reset either and the data would be handed events whose
30
+ * effects it already holds. The power to consume lives in the delivery, not the cursor: the
31
+ * sweep visits primary scopes only (`isPrimaryScope`), and `deliverToPeer` into a copy answers
32
+ * `paused` with nothing run and the watermark unmoved. The producer keeps no record of a
33
+ * consumer's position, so a copy's cursor acknowledges nothing at the source either.
16
34
  */
17
35
  export declare const isCopyLoad: (destScopeId: string | undefined, sourceScopeId: string | undefined) => boolean;
18
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"scope-copy.d.ts","sourceRoot":"","sources":["../src/scope-copy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,gBAAiB,MAAM,GAAG,SAAS,iBAAiB,MAAM,GAAG,SAAS,KAAG,OACpC,CAAC;AAK7D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,EACtF,MAAM,EAAE,CAAC,EAAE,EACX,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,CAAC,EAAE,CAGL;AAcD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,eAAe,8cAW3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,sBAAiB,MACqD,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,GAAG,EAAE,MAAM,GACV,IAAI,CAiDN;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAInE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,QAEa,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAIpE;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,0FAA0F,CAAC"}
1
+ {"version":3,"file":"scope-copy.d.ts","sourceRoot":"","sources":["../src/scope-copy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,UAAU,gBAAiB,MAAM,GAAG,SAAS,iBAAiB,MAAM,GAAG,SAAS,KAAG,OACpC,CAAC;AAK7D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE,EACtF,MAAM,EAAE,CAAC,EAAE,EACX,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,CAAC,EAAE,CAGL;AAcD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,eAAe,8cAW3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,sBAAiB,MACqD,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,GAAG,EAAE,MAAM,GACV,IAAI,CAiDN;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAInE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,QAEa,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAIpE;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,0FAA0F,CAAC"}
@@ -13,6 +13,24 @@ import { CAPABILITY_TABLE_NAMES } from './capability.js';
13
13
  * An unknown source (`sourceScopeId` or `destScopeId` absent) counts as a copy: a load that cannot
14
14
  * say where the dump came from cannot show it is a return. Only a control plane that predates the
15
15
  * field sends a restore with no source.
16
+ *
17
+ * Two kinds of state are decided here without a rule in the load, because the load never sees
18
+ * the part that holds the power (#2004):
19
+ *
20
+ * - **Staff impersonation sessions (K-42).** `_substrat_impersonations` is a DIRECTORY table on
21
+ * both adapters, never a scope table, so no dump carries a session and a copy holds none. The
22
+ * session's power is checked at the door, against the (tenant, scope) the session names
23
+ * (`assertSessionUsable`), so a live session opened on the source is refused at every copy of
24
+ * it and still honoured at the source after a return. What a copy does carry is the history:
25
+ * the impersonation stamps on the rows the session wrote, as the source recorded them.
26
+ * - **Cross-vertical import cursors (#1705).** `_substrat_import_cursors` and `_substrat_imports`
27
+ * are carried as they are, on a copy and on a return. A cursor is written in the same
28
+ * transaction as the import handlers' writes, so it describes the data it sits beside, and the
29
+ * journal is what dedupes a re-delivery. Reset either and the data would be handed events whose
30
+ * effects it already holds. The power to consume lives in the delivery, not the cursor: the
31
+ * sweep visits primary scopes only (`isPrimaryScope`), and `deliverToPeer` into a copy answers
32
+ * `paused` with nothing run and the watermark unmoved. The producer keeps no record of a
33
+ * consumer's position, so a copy's cursor acknowledges nothing at the source either.
16
34
  */
17
35
  export const isCopyLoad = (destScopeId, sourceScopeId) => destScopeId === undefined || sourceScopeId !== destScopeId;
18
36
  /** The capability tables, as `capabilitiesForLoad` matches a dump's (lowercased) names. */
@@ -1 +1 @@
1
- {"version":3,"file":"scope-copy.js","sourceRoot":"","sources":["../src/scope-copy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGzD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,WAA+B,EAAE,aAAiC,EAAW,EAAE,CACxG,WAAW,KAAK,SAAS,IAAI,aAAa,KAAK,WAAW,CAAC;AAE7D,2FAA2F;AAC3F,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,sBAAsB,CAAC,CAAC;AAE/E;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAW,EACX,WAA+B,EAC/B,aAAiC;IAEjC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,aAAa,CAAC;QAAE,OAAO,MAAM,CAAC;IAC3D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnG,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,GAAG,CAAC,aAAiC,EAAU,EAAE,CAC/D,4BAA4B,aAAa,CAAC,CAAC,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC,CAAC,eAAe,kBAAkB;IACxG,yDAAyD,CAAC;AAE5D,0FAA0F;AAC1F,MAAM,mBAAmB,GAAG,IAAI,CAAC,SAAS,CAAC;IACzC,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE,qBAAqB;IAC3B,UAAU,EAAE,IAAI;CACgB,CAAC,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;CAW9B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAK,GAAG,EAAE,EAAU,EAAE,CAChD,GAAG,KAAK,oFAAoF,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAc,EACd,WAA+B,EAC/B,aAAiC,EACjC,GAAW;IAEX,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,aAAa,CAAC;QAAE,OAAO;IACpD,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,CACL;;+BAE2B,EAC3B,MAAM,EACN,mBAAmB,EACnB,GAAG,CACJ,CAAC;IACF,GAAG,CAAC,GAAG,CACL;wCACoC,EACpC,MAAM,EACN,GAAG,CACJ,CAAC;IACF,GAAG,CAAC,GAAG,CACL;;+BAE2B,EAC3B,MAAM,EACN,GAAG,EACH,GAAG,CACJ,CAAC;IACF,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC7E,8FAA8F;IAC9F,+FAA+F;IAC/F,6FAA6F;IAC7F,yBAAyB;IACzB,EAAE;IACF,6FAA6F;IAC7F,wFAAwF;IACxF,0FAA0F;IAC1F,qBAAqB;IACrB,GAAG,CAAC,GAAG,CACL;;;;;;;;mEAQ+D,EAC/D,aAAa,IAAI,IAAI,EACrB,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAC1C,GAAG,CACJ,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,GAAc,EAAE,GAAW;IACxD,IAAI,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,yHAAyH;IACzH,4CAA4C,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAc;IAC5C,IAAI,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvD,GAAG,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;IACrE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,uFAAuF,CAAC"}
1
+ {"version":3,"file":"scope-copy.js","sourceRoot":"","sources":["../src/scope-copy.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { ModuleLog } from './module-log.js';
2
- 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, 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, CauseChain, EventFacetResult, HistoryEntry, ErrorCode, PlatformRequestFailureOrigin, IssueEntry, IssueStatus, IssueStatusInput, DeclaredMigration, CheckSubject } from '@substrat-run/contracts';
2
+ import type { DeliveryRefusal } from './delivery-refusal.js';
3
+ 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, 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, DeclaredMigration, CheckSubject } from '@substrat-run/contracts';
3
4
  import type { ConnectionUseOutcome } from './connector-calls.js';
4
5
  import type { CapabilityVerbs } from './capability.js';
5
6
  import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
@@ -593,6 +594,18 @@ export interface InvokeOptions {
593
594
  * uncapped count, so a reader can tell a short list from a truncated one.
594
595
  */
595
596
  readonly onEmitted?: (report: EmittedReport) => void;
597
+ /**
598
+ * Called after the operation COMMITS and its executors ran inline (#1184), with what each
599
+ * delivery of this call's events did. The inline path is the common case of K-22 §4.2,
600
+ * so the request holder is the one who can tell a person their accept was refused rather
601
+ * than report success for an effect that never happened.
602
+ *
603
+ * Every delivery the call's post-commit tail attempted, which can include an earlier
604
+ * call's retry that came due: a caller picks out its own by `eventType` and `entity`.
605
+ * Never called for a rolled-back operation, a read-only session or an
606
+ * idempotent replay. Absent from a host that predates it — read that as "not reported".
607
+ */
608
+ readonly onExecutorOutcomes?: (outcomes: readonly ExecutorOutcome[]) => void;
596
609
  }
597
610
  /** How many of an invocation's own events `onEmitted` names (#1746). `total` is uncapped. */
598
611
  export declare const EMITTED_REPORT_CAP = 20;
@@ -723,6 +736,48 @@ export type ConsumerHandler = (ctx: OperationContext, event: DomainEvent) => voi
723
736
  * code, and the version you declared is the only one you will be handed.
724
737
  */
725
738
  export type ImportHandler = (ctx: OperationContext, event: ImportedEvent) => void | Promise<void>;
739
+ /**
740
+ * One membership change for `HostAdmin.applyMembership` (#1184): a TENANT-level role assigned
741
+ * or taken away, and nothing else. No org is joined or left — what an org confers lives partly
742
+ * in each scope's own store, where no directory unit can bound it, so authorizing an org
743
+ * membership is its own capability.
744
+ */
745
+ export type MembershipChange = {
746
+ tenantId: TenantId;
747
+ principal: PrincipalId;
748
+ /** The tenant-level role assigned or taken away. */
749
+ roleKey: string;
750
+ /**
751
+ * Whose authority bounds the write (§5.1) — the inviter of an add, the remover of a removal:
752
+ * the unit applies nothing unless this principal covers every permission `roleKey` carries,
753
+ * read inside the unit (`tenantCoverage`).
754
+ */
755
+ boundedBy: PrincipalId;
756
+ } & ({
757
+ op: 'add';
758
+ /** Apply nothing when `principal`'s removal fence stands at or after this instant. */
759
+ unlessRemovedSince: Instant;
760
+ } | {
761
+ op: 'remove';
762
+ });
763
+ /**
764
+ * What the unit did: applied, or why not — fenced by a removal (an add only), or out of the
765
+ * bound. A removal that applied says whether it took anything (`changed`); one that took
766
+ * nothing still raised the fence.
767
+ */
768
+ export type MembershipChangeResult = {
769
+ applied: true;
770
+ changed?: boolean;
771
+ } | {
772
+ applied: false;
773
+ removedAt: string;
774
+ } | {
775
+ applied: false;
776
+ missing: PermissionKey[];
777
+ } | {
778
+ applied: false;
779
+ unknownRole: string;
780
+ };
726
781
  /**
727
782
  * An **executor**: out-of-band host code that effects, outside a scope, what a module
728
783
  * asked for inside one (K-22 §4.2; D-18's triage rule — effects on the outside world
@@ -742,8 +797,53 @@ export type ImportHandler = (ctx: OperationContext, event: ImportedEvent) => voi
742
797
  * It receives `HostAdmin`, not `ctx`: it acts with platform authority, which is
743
798
  * precisely what module code must never hold. Admin writes it makes are stamped with
744
799
  * the causing event's id (`causedBy`), so the split trail joins.
800
+ *
801
+ * A handler that decides an event must never be effected RETURNS `refuseDelivery(reason)`
802
+ * (#1184). The delivery is journaled terminal with the reason, never retried. A return
803
+ * value rather than a thrown error, so module code cannot produce one: the most an
804
+ * operation can do is throw, and a throw is retried like any other failure.
745
805
  */
746
- export type ExecutorHandler = (admin: HostAdmin, event: DomainEvent) => void | Promise<void>;
806
+ export type ExecutorHandler = (admin: HostAdmin, event: DomainEvent, scope: ExecutorScope) => void | DeliveryRefusal | Promise<void | DeliveryRefusal>;
807
+ /**
808
+ * The scope an executor's event came from, as the executor may read it (#1184).
809
+ *
810
+ * Here rather than through `HostAdmin` or the host, and the pure adapter is why: its
811
+ * handler runs INSIDE the scope's actor task, and every host read of a scope re-enqueues
812
+ * on that actor, so `host.canAssign` from a handler waits on the task holding it and never
813
+ * returns. The adapter builds this to suit where the handler runs, as it builds a
814
+ * connector's `openAttachment`.
815
+ *
816
+ * Reads only, and checks no permission: the caller is host code that already holds
817
+ * platform authority. Both answer about the event's own (tenant, scope), never another.
818
+ */
819
+ export interface ExecutorScope {
820
+ /** One entity's history in this scope — `readHistory`'s answer, oldest first. */
821
+ history(entity: EntityRef, page?: ListPage): Promise<Page<HistoryEntry>>;
822
+ /**
823
+ * May `principal` confer `roleKey` at this scope's node, or at the tenant node
824
+ * (`level: 'tenant'`)? The K-21 set comparison `ctx.canAssign` answers, narrowing-aware,
825
+ * over the tenant's projected role. Throws `unknownRoleError` for a role this tenant does
826
+ * not define.
827
+ */
828
+ covers(principal: PrincipalId, roleKey: string, level: 'scope' | 'tenant'): Promise<Coverage>;
829
+ }
830
+ /**
831
+ * What one executor delivery did inside the call that emitted its event (#1184) — what
832
+ * `InvokeOptions.onExecutorOutcomes` reports. `refused` is terminal by the handler's own
833
+ * decision; `dead-lettered` is terminal by exhaustion; `retrying` will be tried again.
834
+ */
835
+ export interface ExecutorOutcome {
836
+ readonly executorId: string;
837
+ readonly eventId: string;
838
+ readonly eventType: string;
839
+ /** The event's entity, as `<entityType>:<entityId>` — how a caller finds its own event. */
840
+ readonly entity: string;
841
+ readonly outcome: 'delivered' | 'retrying' | 'dead-lettered' | 'refused' | 'inert' | 'routed';
842
+ /** The refusal reason, or the failure's message. Absent on a delivery. */
843
+ readonly error?: string;
844
+ }
845
+ /** One delivery's `ExecutorOutcome` — the one builder both adapters report through. */
846
+ export declare function executorOutcomeOf(executorId: string, event: DomainEvent, outcome: ExecutorOutcome['outcome'], error?: unknown): ExecutorOutcome;
747
847
  /**
748
848
  * How hard the host tries before it gives up on one delivery (#100).
749
849
  *
@@ -1433,6 +1533,10 @@ export interface HostAdmin {
1433
1533
  * role that was never assigned (or already revoked) is a silent no-op. Takes a
1434
1534
  * `PlatformActorId` like every admin mutation — the caller's own authority to do
1435
1535
  * this is decided above the kernel (e.g. the dashboard's manage-members check).
1536
+ *
1537
+ * At the TENANT node it also raises the principal's removal fence (#1184), in the same
1538
+ * unit as the revoke and whether or not anything was held, so a membership-executor add
1539
+ * still on its way for them is refused. The no-op stays unaudited.
1436
1540
  */
1437
1541
  unassignRole(actor: PlatformActorId, assignment: RoleAssignment): Promise<void>;
1438
1542
  grant(actor: PlatformActorId, grant: CapabilityGrant): Promise<void>;
@@ -1563,6 +1667,22 @@ export interface HostAdmin {
1563
1667
  * `moduleIds`. Access-logged (K-24), as the fleet read is.
1564
1668
  */
1565
1669
  tenantHeldSystemModules(actor: PlatformActorId, tenantId: TenantId, moduleIds: readonly ModuleId[]): Promise<ModuleId[]>;
1670
+ /**
1671
+ * The peer half of a carry (#2029): the peers the directory records OFF on this one scope
1672
+ * (`revokeFromPeer` writes the record, as `revokeFromSystem` writes the module's), and of those
1673
+ * the ones the tenant holds a live tenant-level `vertical:` grant for (#2030). Carried into a
1674
+ * deployment's provision, reconcile or restore beside the modules, so its in-unit OFF puts them
1675
+ * back off too. Empty lists when none is. Access-logged (K-24), as the module reads are.
1676
+ */
1677
+ peerSwitchCarry(actor: PlatformActorId, node: {
1678
+ tenantId: TenantId;
1679
+ scopeId: ScopeId;
1680
+ }): Promise<{
1681
+ switchedOffPeers: string[];
1682
+ tenantHeldPeers: string[];
1683
+ /** #2045: each recorded-off peer's fence (the record's operation id), by slug. */
1684
+ fences: Record<string, string>;
1685
+ }>;
1566
1686
  /**
1567
1687
  * Put the directory's OFF positions back into one scope (#1674), for a scope whose storage
1568
1688
  * lost them: a wipe then re-provision, or a restore of a dump taken before the switch was
@@ -1590,6 +1710,10 @@ export interface HostAdmin {
1590
1710
  * deployment applying it. That move is undone (switched back on) and audited as
1591
1711
  * `staleCarry`, BEFORE the record is read again for the OFF pass. Only a module recorded
1592
1712
  * `on` is ever switched on here.
1713
+ *
1714
+ * Every kind of switch (#2029): after the modules, every peer the record holds `off` on the
1715
+ * scope is switched off again the same way (through `revokeFromPeer`'s switch), audited as
1716
+ * `reassertPeerSwitch`, and answered as an entry naming `vertical` rather than `moduleId`.
1593
1717
  */
1594
1718
  reassertSystemSwitches(actor: PlatformActorId, node: {
1595
1719
  tenantId: TenantId;
@@ -1653,6 +1777,21 @@ export interface HostAdmin {
1653
1777
  revokeCapability(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, capabilityId: CapabilityId): Promise<void>;
1654
1778
  grantToOrg(actor: PlatformActorId, orgId: OrgId, permission: PermissionKey, node: Node, entity?: EntityRef): Promise<void>;
1655
1779
  addMember(actor: PlatformActorId, tenantId: TenantId, principal: PrincipalId, orgId: OrgId): Promise<void>;
1780
+ /**
1781
+ * One membership change, applied as ONE directory unit (#1184): a single SQLite transaction or
1782
+ * a single synchronous ControlPlaneDO method, with no await inside it. In that unit it
1783
+ * re-evaluates the bound — `boundedBy` must still cover every permission `roleKey` carries,
1784
+ * against the directory as it stands (`tenantCoverage`) — and only then writes. An ADD also
1785
+ * reads `principal`'s removal fence first, then assigns the TENANT-level role and writes its
1786
+ * audit row. A REMOVE unassigns it and raises the fence, as a tenant-level `unassignRole`
1787
+ * does. A removal, a grant, a role redefinition or a demotion lands wholly before the unit
1788
+ * (and governs it) or wholly after it, never between its check and its write.
1789
+ *
1790
+ * The fence is `_substrat_membership_fences`: every tenant-level `unassignRole` and every
1791
+ * `removeMember` raises it for the person, in the same unit as its revoke — a no-op included,
1792
+ * since a removal of someone whose add is still on its way must still win.
1793
+ */
1794
+ applyMembership(actor: PlatformActorId, change: MembershipChange): Promise<MembershipChangeResult>;
1656
1795
  /**
1657
1796
  * Revoke a membership (K-21). **Tombstones, never deletes**: the tuple keeps its
1658
1797
  * row, gains a `revokedAt`, and the permission walk skips it. Deletion would
@@ -1664,6 +1803,9 @@ export interface HostAdmin {
1664
1803
  * no-op, and a no-op is not audited. Re-adding via `addMember` clears the
1665
1804
  * tombstone (they are a member again); the add/revoke history lives in the admin
1666
1805
  * log, which is append-only.
1806
+ *
1807
+ * Raises the principal's removal fence (#1184) in the same unit as the revoke, a no-op
1808
+ * included, so a membership-executor add still on its way for them is refused.
1667
1809
  */
1668
1810
  removeMember(actor: PlatformActorId, tenantId: TenantId, principal: PrincipalId, orgId: OrgId): Promise<void>;
1669
1811
  /**
@@ -2359,6 +2501,13 @@ export interface HostAdmin {
2359
2501
  * Reads only the lifecycle field of unclassified payloads; logged like every read here.
2360
2502
  */
2361
2503
  lifecycleFlow(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, input: LifecycleFlowInput): Promise<LifecycleFlowResult>;
2504
+ /**
2505
+ * Calls per `(entityType, operation)` per time bucket over the scope's outbox (#1750) —
2506
+ * `readOperationSeries`, hoisted: the business volumes Pulse draws. The caller passes
2507
+ * the pairs (from the running version's declared lifecycles); the scope holds no model.
2508
+ * Reads no payload; logged like every read here.
2509
+ */
2510
+ operationSeries(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, input: OperationSeriesInput): Promise<OperationSeriesResult>;
2362
2511
  /**
2363
2512
  * One read-only SQL statement against the scope's database — the console the two
2364
2513
  * table-shaped reads deliberately weren't (#219). User SQL DOES reach the DB here,
@@ -4140,12 +4289,19 @@ export interface ScopeHost {
4140
4289
  * and this driver's contract in `job-run.ts`. `retry` is the DEFAULT policy for
4141
4290
  * the job's steps; a step may pass its own.
4142
4291
  *
4292
+ * `options.leaseMs` (#2034) is how long one pass holds its run without reaching a
4293
+ * step boundary, which renews it — default `JOB_LEASE_MS`. Past it, the run is due
4294
+ * again and the next drive takes it over, counting the silent pass as a failed
4295
+ * attempt. A job whose pass may go longer than that between two steps says so here.
4296
+ *
4143
4297
  * The fourth driver, and a SIBLING of the three that already exist rather than a
4144
4298
  * widening of any of them. An executor retries one delivery whole; a schedule
4145
4299
  * fires one operation that must finish; the platform sweep does a pass of
4146
4300
  * maintenance. None of them can stop halfway through an hour and carry on.
4147
4301
  */
4148
- registerJob(moduleId: ModuleId, name: string, handler: JobHandler, retry?: ExecutorRetryPolicy): void;
4302
+ registerJob(moduleId: ModuleId, name: string, handler: JobHandler, retry?: ExecutorRetryPolicy, options?: {
4303
+ leaseMs?: number;
4304
+ }): void;
4149
4305
  /**
4150
4306
  * Start a run, or JOIN the one already in flight for the same
4151
4307
  * `(module, job, instance)` — the coalescing half of the driver.
@@ -4187,12 +4343,10 @@ export interface ScopeHost {
4187
4343
  * grant gate here would STALL it silently rather than refuse it. The authority it
4188
4344
  * exercises is checked where it is used, inside the operations its steps invoke.
4189
4345
  *
4190
- * **One driver per scope at a time**, and that is a bound the caller holds, not one
4191
- * this enforces — there is no lease. Coalescing stops duplicate RUNS; two concurrent
4192
- * calls of THIS would advance the same run together. Every topology the driver is
4193
- * built for gives a scope one tick (the platform sweep does one call per scope, a
4194
- * scope DO's alarm fires for its own), so the bound holds by construction; the full
4195
- * argument, and what an overlap would actually cost, is in `job-run.ts`.
4346
+ * **Overlapping calls are safe** (#2034): each run is CLAIMED before its pass, with a
4347
+ * lease the pass renews at every step boundary, so two calls on one scope never run
4348
+ * one run's handler together. A run whose pass died is taken over once its lease
4349
+ * expires. The full argument is in `job-run.ts`.
4196
4350
  */
4197
4351
  runDueJobs(tenantId: TenantId, scopeId: ScopeId, options?: {
4198
4352
  maxPasses?: number;
@@ -4341,6 +4495,11 @@ export interface ScopeHost {
4341
4495
  * changed at 14:02 is information about that row, so "the body was empty" is not a
4342
4496
  * defence: the filter runs whether or not there is a payload to withhold.
4343
4497
  *
4498
+ * A subscription may be narrowed `within` one entity (#1853), which only removes frames;
4499
+ * the one exception to the per-principal check is a `within` the vertical built with
4500
+ * `vouchedWithin`, whose subscriber is sent bare `LiveNudge` frames and nothing that
4501
+ * names an entity.
4502
+ *
4344
4503
  * **Generic over the runtime's request and response, because the kernel names
4345
4504
  * neither.** This package has one dependency and no DOM or workers lib
4346
4505
  * (`docs/architecture/dependency-policy.md`), which is why `FetchLike` above describes
@@ -4375,8 +4534,58 @@ export interface LiveReadSurface<Req extends LiveUpgradeRequest = LiveUpgradeReq
4375
4534
  principal: PrincipalId;
4376
4535
  /** The upgrade request as it arrived, carried whole so the host reads its own headers. */
4377
4536
  request: Req;
4537
+ /**
4538
+ * Narrow the feed to one entity and what hangs beneath it (#1853).
4539
+ *
4540
+ * A frame is then delivered only if its entity IS this one or reaches it upward
4541
+ * through live declared `parent` edges (what `ctx.link` / `ctx.relink` write) — the
4542
+ * walk `ctx.check` makes, rooted here instead of at a grant.
4543
+ *
4544
+ * - **An `EntityRef` narrows and nothing else.** The principal's own per-frame
4545
+ * `liveTargets` check still runs; `within` is ANDed with it, so it can only take
4546
+ * frames away. A screen watching one conversation passes it to stop hearing the
4547
+ * rest of the desk.
4548
+ * - **A `vouchedWithin(…)` value replaces the principal's check.** For a subscriber
4549
+ * confined by something other than a grant — a widget visitor holding a session
4550
+ * token the vertical has just redeemed. The vertical asserts access to the root,
4551
+ * the same trust it already extends in naming `principal`, and the scope's walk is
4552
+ * the whole filter. Such a subscriber receives `LiveNudge` frames only, which name
4553
+ * no event type and no entity.
4554
+ */
4555
+ within?: EntityRef | VouchedWithin;
4378
4556
  }): Promise<Res>;
4379
4557
  }
4558
+ /** The brand only `vouchedWithin` can apply — a literal cannot type-check as one. */
4559
+ declare const vouchedBrand: unique symbol;
4560
+ /**
4561
+ * A `within` root the VERTICAL vouches the subscriber may watch, in place of the
4562
+ * principal's own grants (#1853). Built only by `vouchedWithin`.
4563
+ */
4564
+ export interface VouchedWithin {
4565
+ readonly [vouchedBrand]: true;
4566
+ readonly entity: EntityRef;
4567
+ /** Why the vertical vouches — what it checked. Required, and kept on the subscription. */
4568
+ readonly because: string;
4569
+ }
4570
+ /**
4571
+ * Vouch that the subscriber may watch `entity` and everything beneath it, though the
4572
+ * principal it subscribes as holds no read on it.
4573
+ *
4574
+ * The one way to reach the replacing mode, so every use is greppable and none is an
4575
+ * accident: an object literal is refused by the type checker, and anything not built
4576
+ * here is refused by the host at run time (`isVouchedWithin`). Call it only after the
4577
+ * vertical itself has proven access — for ticket0's widget, by redeeming the session
4578
+ * token for the session it names.
4579
+ *
4580
+ * What it costs the subscriber is detail: a vouched feed carries `LiveNudge` frames, so
4581
+ * the scope never tells it which entity changed or how. Root it at an entity whose
4582
+ * subtree holds only what the subscriber may see — the walk is the whole filter.
4583
+ */
4584
+ export declare function vouchedWithin(entity: EntityRef, opts: {
4585
+ because: string;
4586
+ }): VouchedWithin;
4587
+ /** Was this value built by `vouchedWithin`? A host asks before it drops the principal's check. */
4588
+ export declare function isVouchedWithin(value: unknown): value is VouchedWithin;
4380
4589
  /**
4381
4590
  * Is this request asking to be upgraded to a WebSocket?
4382
4591
  *
@@ -4436,6 +4645,24 @@ export interface LiveChange {
4436
4645
  /** When the event was emitted (ISO 8601), i.e. the emitting operation's instant. */
4437
4646
  readonly at: string;
4438
4647
  }
4648
+ /**
4649
+ * What a vouched subscriber is told (#1853): something beneath its root changed, and
4650
+ * nothing else.
4651
+ *
4652
+ * No event type and no entity, deliberately. A vouched subscriber holds no read on the
4653
+ * entities its frames are about — the vertical vouched for the ROOT, and the scope
4654
+ * cannot know which rows under it the vertical's own read would show. Naming the type
4655
+ * or the id would tell it what it never asked to read. The client re-reads, as it does
4656
+ * on a `LiveChange`.
4657
+ */
4658
+ export interface LiveNudge {
4659
+ readonly kind: 'nudge';
4660
+ /** The event id, as on `LiveChange` — for ordering and de-duplication only. */
4661
+ readonly id: string;
4662
+ readonly at: string;
4663
+ }
4664
+ /** Any frame a live read sends. */
4665
+ export type LiveFrame = LiveChange | LiveNudge;
4439
4666
  /**
4440
4667
  * Refuse a cutoff in the future — at the HostAdmin boundary, not only at the HTTP door.
4441
4668
  *