@substrat-run/kernel 0.139.0 → 0.141.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.
- package/dist/admin-log-ddl.d.ts +21 -0
- package/dist/admin-log-ddl.d.ts.map +1 -0
- package/dist/admin-log-ddl.js +32 -0
- package/dist/admin-log-ddl.js.map +1 -0
- package/dist/attachment-extractor.d.ts +12 -1
- package/dist/attachment-extractor.d.ts.map +1 -1
- package/dist/attachment-extractor.js +10 -5
- package/dist/attachment-extractor.js.map +1 -1
- package/dist/audit-outcome.d.ts +149 -0
- package/dist/audit-outcome.d.ts.map +1 -0
- package/dist/audit-outcome.js +233 -0
- package/dist/audit-outcome.js.map +1 -0
- package/dist/capability.d.ts +5 -2
- package/dist/capability.d.ts.map +1 -1
- package/dist/capability.js +17 -8
- package/dist/capability.js.map +1 -1
- package/dist/connect-links.d.ts +91 -0
- package/dist/connect-links.d.ts.map +1 -0
- package/dist/connect-links.js +189 -0
- package/dist/connect-links.js.map +1 -0
- package/dist/connect-state.d.ts +12 -3
- package/dist/connect-state.d.ts.map +1 -1
- package/dist/connect-state.js +8 -3
- package/dist/connect-state.js.map +1 -1
- package/dist/derived-object.d.ts +14 -0
- package/dist/derived-object.d.ts.map +1 -0
- package/dist/derived-object.js +2 -0
- package/dist/derived-object.js.map +1 -0
- package/dist/derived-objects.d.ts +108 -0
- package/dist/derived-objects.d.ts.map +1 -0
- package/dist/derived-objects.js +199 -0
- package/dist/derived-objects.js.map +1 -0
- package/dist/email-intent.d.ts +56 -0
- package/dist/email-intent.d.ts.map +1 -0
- package/dist/email-intent.js +71 -0
- package/dist/email-intent.js.map +1 -0
- package/dist/entity-edges.d.ts +6 -1
- package/dist/entity-edges.d.ts.map +1 -1
- package/dist/entity-edges.js +13 -0
- package/dist/entity-edges.js.map +1 -1
- package/dist/entity-grant-shape.d.ts +94 -0
- package/dist/entity-grant-shape.d.ts.map +1 -0
- package/dist/entity-grant-shape.js +329 -0
- package/dist/entity-grant-shape.js.map +1 -0
- package/dist/entity-grant.d.ts +45 -0
- package/dist/entity-grant.d.ts.map +1 -0
- package/dist/entity-grant.js +71 -0
- package/dist/entity-grant.js.map +1 -0
- package/dist/entity-state-reads.d.ts +4 -7
- package/dist/entity-state-reads.d.ts.map +1 -1
- package/dist/entity-state-reads.js +13 -57
- package/dist/entity-state-reads.js.map +1 -1
- package/dist/entity-state.d.ts +37 -10
- package/dist/entity-state.d.ts.map +1 -1
- package/dist/entity-state.js +80 -69
- package/dist/entity-state.js.map +1 -1
- package/dist/entity-trash.d.ts +245 -0
- package/dist/entity-trash.d.ts.map +1 -0
- package/dist/entity-trash.js +380 -0
- package/dist/entity-trash.js.map +1 -0
- package/dist/grant-scoped-read.d.ts +41 -0
- package/dist/grant-scoped-read.d.ts.map +1 -0
- package/dist/grant-scoped-read.js +100 -0
- package/dist/grant-scoped-read.js.map +1 -0
- package/dist/index.d.ts +35 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -5
- package/dist/index.js.map +1 -1
- package/dist/kernel-outbox.d.ts +14 -0
- package/dist/kernel-outbox.d.ts.map +1 -0
- package/dist/kernel-outbox.js +37 -0
- package/dist/kernel-outbox.js.map +1 -0
- package/dist/list-index.d.ts +11 -2
- package/dist/list-index.d.ts.map +1 -1
- package/dist/list-index.js +29 -21
- package/dist/list-index.js.map +1 -1
- package/dist/mail-sender.d.ts +78 -0
- package/dist/mail-sender.d.ts.map +1 -0
- package/dist/mail-sender.js +2 -0
- package/dist/mail-sender.js.map +1 -0
- package/dist/membership-executor.d.ts +0 -52
- package/dist/membership-executor.d.ts.map +1 -1
- package/dist/membership-executor.js +19 -59
- package/dist/membership-executor.js.map +1 -1
- package/dist/migration-digest.d.ts +161 -0
- package/dist/migration-digest.d.ts.map +1 -0
- package/dist/migration-digest.js +237 -0
- package/dist/migration-digest.js.map +1 -0
- package/dist/module-erasure.d.ts +156 -0
- package/dist/module-erasure.d.ts.map +1 -0
- package/dist/module-erasure.js +440 -0
- package/dist/module-erasure.js.map +1 -0
- package/dist/peer.d.ts +9 -1
- package/dist/peer.d.ts.map +1 -1
- package/dist/peer.js +29 -0
- package/dist/peer.js.map +1 -1
- package/dist/permission-checker.d.ts +6 -0
- package/dist/permission-checker.d.ts.map +1 -1
- package/dist/permission-checker.js.map +1 -1
- package/dist/permission-eval.d.ts +10 -1
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +51 -2
- package/dist/permission-eval.js.map +1 -1
- package/dist/platform-sweep.d.ts +1 -1
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +5 -0
- package/dist/platform-sweep.js.map +1 -1
- package/dist/scope-copy-ledger.d.ts +79 -0
- package/dist/scope-copy-ledger.d.ts.map +1 -0
- package/dist/scope-copy-ledger.js +82 -0
- package/dist/scope-copy-ledger.js.map +1 -0
- package/dist/scope-host.d.ts +402 -25
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +74 -5
- package/dist/scope-host.js.map +1 -1
- package/dist/search-index.d.ts +8 -0
- package/dist/search-index.d.ts.map +1 -1
- package/dist/search-index.js +51 -21
- package/dist/search-index.js.map +1 -1
- package/dist/spine-guard.d.ts +7 -0
- package/dist/spine-guard.d.ts.map +1 -1
- package/dist/spine-guard.js +54 -6
- package/dist/spine-guard.js.map +1 -1
- package/dist/spine-restore.d.ts.map +1 -1
- package/dist/spine-restore.js +5 -0
- package/dist/spine-restore.js.map +1 -1
- package/dist/sql-statements.d.ts +71 -0
- package/dist/sql-statements.d.ts.map +1 -0
- package/dist/sql-statements.js +191 -0
- package/dist/sql-statements.js.map +1 -0
- package/dist/subject-redaction.d.ts +9 -1
- package/dist/subject-redaction.d.ts.map +1 -1
- package/dist/subject-redaction.js.map +1 -1
- package/dist/system-switch-record.d.ts +3 -0
- package/dist/system-switch-record.d.ts.map +1 -1
- package/dist/system-switch-record.js +2 -0
- package/dist/system-switch-record.js.map +1 -1
- package/dist/table-ownership.d.ts +103 -0
- package/dist/table-ownership.d.ts.map +1 -0
- package/dist/table-ownership.js +285 -0
- package/dist/table-ownership.js.map +1 -0
- package/dist/timeline.d.ts +4 -4
- package/dist/timeline.js +4 -4
- package/dist/visible-continuation.d.ts +38 -0
- package/dist/visible-continuation.d.ts.map +1 -0
- package/dist/visible-continuation.js +162 -0
- package/dist/visible-continuation.js.map +1 -0
- package/package.json +4 -4
package/dist/scope-host.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
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 {
|
|
4
|
+
import type { SubjectRedactionCounts } from './subject-redaction.js';
|
|
5
|
+
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
6
|
import type { ConnectionUseOutcome } from './connector-calls.js';
|
|
6
7
|
import type { CapabilityVerbs } from './capability.js';
|
|
8
|
+
import type { AuditedOperationRef, AuditedOperationRow } from './audit-outcome.js';
|
|
7
9
|
import { type EntityStateName } from '@substrat-run/contracts';
|
|
8
10
|
import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
|
|
9
11
|
import type { FindingPruneReport } from './findings.js';
|
|
10
12
|
import type { SealedSecret } from './secret-box.js';
|
|
13
|
+
import type { OnSubjectErased } from './module-erasure.js';
|
|
11
14
|
import type { SystemSwitchReassert, SystemSwitchReassertOptions, SystemSwitchRecordFilter } from './system-switch-record.js';
|
|
12
15
|
import type { SearchHit, SearchOptions } from './search-index.js';
|
|
16
|
+
import type { GrantedEntitiesPage } from './grant-scoped-read.js';
|
|
13
17
|
import type { EntityVersion } from './entity-version.js';
|
|
14
18
|
import type { UndrainedEvents } from './outbox-event.js';
|
|
15
19
|
import type { JobDriveReport, JobHandler, JobRun, JobRunFilter, StartJobRunInput } from './job-run.js';
|
|
@@ -40,6 +44,11 @@ export interface PageParams {
|
|
|
40
44
|
* archive. Refused for an entity that declares no archive. The bin is `ctx.pageTrashed`.
|
|
41
45
|
*/
|
|
42
46
|
readonly view?: Exclude<EntityStateName, 'trashed'>;
|
|
47
|
+
/**
|
|
48
|
+
* Also return each row's own cursor, as `rowCursors` (#2073) — asked for by `pageVisible`,
|
|
49
|
+
* which may stop partway through a page. Off by default, so an ordinary page is unchanged.
|
|
50
|
+
*/
|
|
51
|
+
readonly rowCursors?: boolean;
|
|
43
52
|
}
|
|
44
53
|
/**
|
|
45
54
|
* The scope-host contract — the adapter seam (§5.1 of the design doc).
|
|
@@ -90,6 +99,13 @@ export interface OperationContext {
|
|
|
90
99
|
readonly tenantId: TenantId;
|
|
91
100
|
readonly scopeId: ScopeId;
|
|
92
101
|
readonly principal: PrincipalId;
|
|
102
|
+
/** Seal positions for one filtered list under this scope, caller and operation. */
|
|
103
|
+
pageContinuation(list: string, query: unknown): {
|
|
104
|
+
/** Read-only and copy walks cannot persist a hidden budget-stop position. */
|
|
105
|
+
writable?: boolean;
|
|
106
|
+
open(cursor: string): Promise<string>;
|
|
107
|
+
seal(position: string, hidden: boolean): Promise<string | null>;
|
|
108
|
+
};
|
|
93
109
|
readonly sql: ScopedSql;
|
|
94
110
|
/**
|
|
95
111
|
* The operation's instant (#812) — the ONLY clock module code may read.
|
|
@@ -209,6 +225,18 @@ export interface OperationContext {
|
|
|
209
225
|
* than returning a denial, and records none (#1642, `assertPermissionKey`).
|
|
210
226
|
*/
|
|
211
227
|
check(permission: PermissionKey, entity?: EntityRef): Promise<Decision>;
|
|
228
|
+
/**
|
|
229
|
+
* Page entity ids reachable through this caller's live grants and declared parent edges.
|
|
230
|
+
* `all` means a node grant makes every entity of this type visible; `incomplete` means
|
|
231
|
+
* this checker cannot enumerate the path, so callers must use a checked full walk.
|
|
232
|
+
* An `ids` page is complete only after its cursor becomes null. Ids may repeat across
|
|
233
|
+
* pages on multiple parent paths: accumulate a Set, and do not treat page lengths as
|
|
234
|
+
* a count or total. The union of all pages is complete. Every id passes `ctx.check`.
|
|
235
|
+
*/
|
|
236
|
+
grantedEntities(permission: PermissionKey, entityType: string, options?: {
|
|
237
|
+
limit?: number;
|
|
238
|
+
cursor?: string;
|
|
239
|
+
}): Promise<GrantedEntitiesPage>;
|
|
212
240
|
/**
|
|
213
241
|
* Find entities of one type by what a person typed (#827) — the read a picker
|
|
214
242
|
* over 40 000 customers needs and `ctx.sql` cannot express without every
|
|
@@ -265,9 +293,9 @@ export interface OperationContext {
|
|
|
265
293
|
*
|
|
266
294
|
* **This does not check permission** — nothing on `ctx` does, and a paged read
|
|
267
295
|
* 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
|
|
269
|
-
*
|
|
270
|
-
*
|
|
296
|
+
* A read that filters per ROW after the fact (a portal walk) cannot hand this
|
|
297
|
+
* page on as it is: a page of 20 filtered down to 3 is not a page, and its cursor
|
|
298
|
+
* may be a refused row's. It walks this read with `pageVisible` instead (#2073).
|
|
271
299
|
*
|
|
272
300
|
* Throws `NotListable` for an entity no operation declared `paged.over` on,
|
|
273
301
|
* `SortNotDeclared` for a `?sort=` outside the vocabulary, and
|
|
@@ -334,6 +362,12 @@ export interface OperationContext {
|
|
|
334
362
|
* revives it and records that as one `entity.linked` spine event, since access resumes.
|
|
335
363
|
*
|
|
336
364
|
* Checks no permission: the operation does, in its own vocabulary.
|
|
365
|
+
*
|
|
366
|
+
* A parent in the TRASH (#119) is refused `not_found`: nothing new hangs off a binned
|
|
367
|
+
* entity, since it is gone from everyone's view until restored. A MISSING parent of a type that
|
|
368
|
+
* declares trash gets the identical refusal, so the two cannot be told apart. An ARCHIVED parent
|
|
369
|
+
* is allowed — archived is filed away and still readable, and whether one may still be added to
|
|
370
|
+
* is the vertical's rule, not the kernel's.
|
|
337
371
|
*/
|
|
338
372
|
link(child: EntityRef, parent: EntityRef): void;
|
|
339
373
|
/**
|
|
@@ -346,6 +380,9 @@ export interface OperationContext {
|
|
|
346
380
|
* move would make the child its own ancestor.
|
|
347
381
|
* - `from` must be a live parent edge of `child`; `conflict` otherwise. Every other
|
|
348
382
|
* parent a multi-parent entity has is left alone.
|
|
383
|
+
* - A `to` in the TRASH is refused `not_found`, as `link` refuses one (#119) — the same refusal a
|
|
384
|
+
* missing `to` of that type gets. `from` is not: moving a child out from under a binned
|
|
385
|
+
* parent is how it is rescued.
|
|
349
386
|
* - `from` equal to `to` is a no-op: nothing written, nothing emitted.
|
|
350
387
|
* - A `to` that is ALREADY a live parent of `child` is the way to detach one parent of a
|
|
351
388
|
* multi-parent child (#2044): the `from` edge is tombstoned, the live `to` edge stays
|
|
@@ -876,9 +913,9 @@ export type MembershipChangeResult = {
|
|
|
876
913
|
* It receives `HostAdmin`, not `ctx`: it acts with platform authority, which is
|
|
877
914
|
* precisely what module code must never hold. Admin writes it makes through the `admin`
|
|
878
915
|
* 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
|
|
880
|
-
* writes
|
|
881
|
-
*
|
|
916
|
+
* joins. That `admin` is bound to this one event, never the host's own (#2055). A handler
|
|
917
|
+
* that writes for a person attributes from it — `admin.attributed(onBehalfOf)` — which keeps
|
|
918
|
+
* the event (#2069); a view of the HOST knows nothing of the event.
|
|
882
919
|
*
|
|
883
920
|
* A handler that decides an event must never be effected RETURNS `refuseDelivery(reason)`
|
|
884
921
|
* (#1184). The delivery is journaled terminal with the reason, never retried. A return
|
|
@@ -1491,6 +1528,11 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
1491
1528
|
* nothing, and it reads as coverage. A bound operation with no entry is
|
|
1492
1529
|
* allowed and means what it always meant — nothing was declared to parse.
|
|
1493
1530
|
*
|
|
1531
|
+
* **It also carries the declared surface (#119).** `operationInputsOf` records, for the frozen
|
|
1532
|
+
* map it returns, every declared operation and the entity each addresses by id. The host DERIVES
|
|
1533
|
+
* its trash refusal from that — nothing else is handed over — and a module with a trashable
|
|
1534
|
+
* entity must pass that map as returned, with every operation it binds declared in it.
|
|
1535
|
+
*
|
|
1494
1536
|
* Typed structurally rather than as `z.ZodType` so the kernel keeps its single
|
|
1495
1537
|
* dependency and no zod version is pinned by the scope-host contract. The
|
|
1496
1538
|
* shape is the whole surface the host uses: throw to refuse, return the value
|
|
@@ -1567,6 +1609,15 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
1567
1609
|
* contribute the same name.
|
|
1568
1610
|
*/
|
|
1569
1611
|
predicates?: Record<string, GuardPredicate>;
|
|
1612
|
+
/**
|
|
1613
|
+
* This module's own step in a subject erasure (#2068), for the rows its declared
|
|
1614
|
+
* `erasure: { subjects }` cannot reach — a link the row does not hold itself. Run by
|
|
1615
|
+
* `shredSubject` inside the erasure's one transaction, after the declared entities and
|
|
1616
|
+
* before the key is destroyed; a throw refuses the whole erasure and rolls it back.
|
|
1617
|
+
* Synchronous, idempotent, and handed a `sql` that reaches the module's own tables only
|
|
1618
|
+
* (`module-erasure.ts`). Requires `manifest.erasure`, whose `tables` is that reach.
|
|
1619
|
+
*/
|
|
1620
|
+
onSubjectErased?: OnSubjectErased;
|
|
1570
1621
|
}
|
|
1571
1622
|
/**
|
|
1572
1623
|
* Admin surface for enforcement input (design doc §4; control-plane.md §4.4).
|
|
@@ -1591,6 +1642,16 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
1591
1642
|
* stay sync — they are code-time bookkeeping, not control-plane state.)
|
|
1592
1643
|
*/
|
|
1593
1644
|
export interface HostAdmin {
|
|
1645
|
+
/**
|
|
1646
|
+
* This same admin, with every row written through it also naming `onBehalfOf` (#977, #2069),
|
|
1647
|
+
* and keeping whatever this one already carries — for the `admin` an executor handler is
|
|
1648
|
+
* handed, the event (`causedBy`). A handler that writes for a person attributes from that
|
|
1649
|
+
* `admin`, so the cause comes along by construction and there is no option to forget.
|
|
1650
|
+
*
|
|
1651
|
+
* Optional so an admin that predates it still satisfies the interface; a caller that finds it
|
|
1652
|
+
* absent writes through the admin it holds, unattributed to a person.
|
|
1653
|
+
*/
|
|
1654
|
+
attributed?(onBehalfOf: OnBehalfOf): HostAdmin;
|
|
1594
1655
|
defineRole(actor: PlatformActorId, tenantId: TenantId, role: RoleDefinition): Promise<void>;
|
|
1595
1656
|
/**
|
|
1596
1657
|
* Every role the directory holds, ordered by (tenantId, key).
|
|
@@ -1622,6 +1683,57 @@ export interface HostAdmin {
|
|
|
1622
1683
|
*/
|
|
1623
1684
|
unassignRole(actor: PlatformActorId, assignment: RoleAssignment): Promise<void>;
|
|
1624
1685
|
grant(actor: PlatformActorId, grant: CapabilityGrant): Promise<void>;
|
|
1686
|
+
/**
|
|
1687
|
+
* Give a person a vertical's declared entity-grant SHAPE on one entity (#2071): every key in
|
|
1688
|
+
* `permissions`, plus the marker that makes them a holder of the shape, so a key the shape
|
|
1689
|
+
* gains later reaches them at the next {@link reconcileEntityGrantShapes}. What a vertical
|
|
1690
|
+
* calls where it used to `grant` its `ENTITY_GRANTS` keys one at a time. It brings back a key
|
|
1691
|
+
* or marker a revoke tombstoned, as `grant` does, and leaves a live row as it is, expiry
|
|
1692
|
+
* included, as `ctx.grant` does. Audited as `grantEntityShape`.
|
|
1693
|
+
*/
|
|
1694
|
+
grantEntityShape(actor: PlatformActorId, grant: {
|
|
1695
|
+
principalId: PrincipalId;
|
|
1696
|
+
node: {
|
|
1697
|
+
tenantId: TenantId;
|
|
1698
|
+
scopeId: ScopeId;
|
|
1699
|
+
};
|
|
1700
|
+
entity: EntityRef;
|
|
1701
|
+
permissions: readonly PermissionKey[];
|
|
1702
|
+
grantedBy: PrincipalId;
|
|
1703
|
+
}): Promise<void>;
|
|
1704
|
+
/**
|
|
1705
|
+
* Top every holder of each declared shape up to the shape as it is now (#2071): a key the
|
|
1706
|
+
* shape gained is granted to each person holding the shape's marker on an entity of that
|
|
1707
|
+
* type. Never a key revoked from that person there (its K-21 tombstone stays). A key merely
|
|
1708
|
+
* dropped from the shape is left in place; a key the shape declares `retired` (#2082) is taken
|
|
1709
|
+
* back from every holder instead, its row tombstoned, once per scope — including a direct grant
|
|
1710
|
+
* of that key to the holder on the same entity, which is the same row. Until it is done on a scope, a
|
|
1711
|
+
* shape that declares a `holder` is also backfilled: each person its holder relationship names
|
|
1712
|
+
* as the owner of an entity (`'self'`, or the vertical's own column) is marked if they hold a
|
|
1713
|
+
* key of the shape there. Never inferred from key sets, so a grant delegated on someone else's
|
|
1714
|
+
* record is never marked. Without a `holder`, there is no backfill.
|
|
1715
|
+
*
|
|
1716
|
+
* Only shapes declared `bootstrap: true` are reconciled — those given with
|
|
1717
|
+
* {@link grantEntityShape} on a person's own record. A SHARING shape, reached through
|
|
1718
|
+
* `ctx.grant` (todo's `list`), is skipped whole: reconciling one would mark every person
|
|
1719
|
+
* something was fully shared with as a holder.
|
|
1720
|
+
*
|
|
1721
|
+
* Bounded: `batch` rows of work (default 500, an integer from 1 to 5000, or `validation_failed`)
|
|
1722
|
+
* per scope transaction, backfill included, repeated until a pass finishes — so it is safe
|
|
1723
|
+
* on a large scope, and finishes on a re-run if interrupted. Each (person, entity) topped up is
|
|
1724
|
+
* an `entity.grants-topped-up` event on the entity, and each one retired keys were taken from an
|
|
1725
|
+
* `entity.grants-retired` event. Audited as `reconcileEntityGrantShapes` when it changed
|
|
1726
|
+
* anything. Returns how many (person, entity) it topped up, and how many it retired keys from.
|
|
1727
|
+
*/
|
|
1728
|
+
reconcileEntityGrantShapes(actor: PlatformActorId, node: {
|
|
1729
|
+
tenantId: TenantId;
|
|
1730
|
+
scopeId: ScopeId;
|
|
1731
|
+
}, shapes: readonly EntityGrantShape[], opts?: {
|
|
1732
|
+
batch?: number;
|
|
1733
|
+
}): Promise<{
|
|
1734
|
+
toppedUp: number;
|
|
1735
|
+
retired: number;
|
|
1736
|
+
}>;
|
|
1625
1737
|
/** Grant to an organization (portal customers); members reach it via membership tuples. */
|
|
1626
1738
|
/**
|
|
1627
1739
|
* Grant a permission to a CONNECTION (#97) — how a connector is allowed to
|
|
@@ -2096,6 +2208,9 @@ export interface HostAdmin {
|
|
|
2096
2208
|
snapshot?: boolean;
|
|
2097
2209
|
acknowledge?: BindAcknowledgement;
|
|
2098
2210
|
expectedVersionId?: string | null;
|
|
2211
|
+
expectedErasureEpoch?: number;
|
|
2212
|
+
/** #1722: the copy move this bind routes the scope onto, confirmed in the same write. */
|
|
2213
|
+
confirmMove?: ScopeCopyMoveConfirmation;
|
|
2099
2214
|
}): Promise<void>;
|
|
2100
2215
|
/**
|
|
2101
2216
|
* Which apps in the scope's tenant binding `versionId` would break (#1756): the answer
|
|
@@ -2168,6 +2283,8 @@ export interface HostAdmin {
|
|
|
2168
2283
|
*/
|
|
2169
2284
|
setScopeServingRef(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, servingRef: string | null, opts?: {
|
|
2170
2285
|
acknowledge?: BindAcknowledgement;
|
|
2286
|
+
expectedErasureEpoch?: number;
|
|
2287
|
+
confirmMove?: ScopeCopyMoveConfirmation;
|
|
2171
2288
|
}): Promise<void>;
|
|
2172
2289
|
/**
|
|
2173
2290
|
* Move a fork's GC deadline (preview-and-snapshots.md §9). The reap sweep deletes any
|
|
@@ -2185,6 +2302,54 @@ export interface HostAdmin {
|
|
|
2185
2302
|
* write revision before and after. The directory changes nothing else. Refuses an unknown scope.
|
|
2186
2303
|
*/
|
|
2187
2304
|
recordKeptCopyResolution(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, resolution: KeptCopyResolution): Promise<void>;
|
|
2305
|
+
/** A script that holds or held this scope's data. The moveId fences delayed cleanup
|
|
2306
|
+
* from a later rollback and carry through the same script. */
|
|
2307
|
+
recordScopeScriptCopy(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, scriptRef: string, moveId: string,
|
|
2308
|
+
/** Which end of the move this script is, and for a destination the load stamp its restore
|
|
2309
|
+
* will leave: what a crash-recovery sweep fences its wipe on. The entry is leased to the
|
|
2310
|
+
* recording move for `leaseMs` (default `SCOPE_COPY_LEASE_MS`). */
|
|
2311
|
+
opts?: {
|
|
2312
|
+
role?: ScopeCopyRole;
|
|
2313
|
+
loadStamp?: string | null;
|
|
2314
|
+
leaseMs?: number;
|
|
2315
|
+
}): Promise<void>;
|
|
2316
|
+
/**
|
|
2317
|
+
* Claim up to `limit` pending entries whose lease ran out before `now` (a move that crashed,
|
|
2318
|
+
* or a sweep that did), leasing each to `owner` until `leaseUntil`. An entry whose lease is
|
|
2319
|
+
* live is never returned, so a move still in flight keeps it; and a move whose lease ran out
|
|
2320
|
+
* can no longer confirm (`confirmMove`), so the claimant cannot lose a race to it.
|
|
2321
|
+
*/
|
|
2322
|
+
claimExpiredScopeScriptCopies(actor: PlatformActorId, input: {
|
|
2323
|
+
now: string;
|
|
2324
|
+
leaseUntil: string;
|
|
2325
|
+
owner: string;
|
|
2326
|
+
limit: number;
|
|
2327
|
+
}): Promise<ScopeScriptCopy[]>;
|
|
2328
|
+
/** Reserve a scope for script cleanup only when no copy move is in flight. The claim is a
|
|
2329
|
+
* column on the directory row, not a ledger entry: it refuses new moves and the conditional
|
|
2330
|
+
* bind until the row itself is deleted, and a crashed reaper resumes it. */
|
|
2331
|
+
beginScopeScriptReap(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId): Promise<void>;
|
|
2332
|
+
/** Only a confirmed move makes a source eligible for a nonterminal wipe. */
|
|
2333
|
+
settleScopeScriptCopy(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, scriptRef: string, moveId: string, state: 'eligible' | 'retained' | 'kept' | 'done', marker?: {
|
|
2334
|
+
loadStamp: string | null;
|
|
2335
|
+
revision: string | null;
|
|
2336
|
+
},
|
|
2337
|
+
/** A sweep's own claim: the entry settles only while it is still pending under that owner. */
|
|
2338
|
+
opts?: {
|
|
2339
|
+
claimedBy?: string;
|
|
2340
|
+
}): Promise<boolean>;
|
|
2341
|
+
/** Move a failed retry to the back of the due queue without changing its state. */
|
|
2342
|
+
touchScopeScriptCopy(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, scriptRef: string, moveId: string): Promise<void>;
|
|
2343
|
+
listScopeScriptCopies(actor: PlatformActorId, filter: {
|
|
2344
|
+
tenantId?: TenantId;
|
|
2345
|
+
scopeId?: ScopeId;
|
|
2346
|
+
state?: 'pending' | 'eligible' | 'retained' | 'kept' | 'done';
|
|
2347
|
+
limit?: number;
|
|
2348
|
+
after?: {
|
|
2349
|
+
scriptRef: string;
|
|
2350
|
+
moveId: string;
|
|
2351
|
+
};
|
|
2352
|
+
}): Promise<ScopeScriptCopy[]>;
|
|
2188
2353
|
/**
|
|
2189
2354
|
* When each of a CO-LOCATED scope's migrations actually ran (#1236), newest
|
|
2190
2355
|
* first. `_substrat_migrations.applied_at` has been written since the table
|
|
@@ -2308,6 +2473,17 @@ export interface HostAdmin {
|
|
|
2308
2473
|
* per-call path, before any staff member is involved.
|
|
2309
2474
|
*/
|
|
2310
2475
|
resolveVerticalInstance(tenantId: TenantId, vertical: string): Promise<VerticalResolution>;
|
|
2476
|
+
/** Resolve for one caller at execution time. An explicit stale binding refuses without fallback. */
|
|
2477
|
+
resolvePeerInstance(tenantId: TenantId, callerScopeId: ScopeId, vertical: string): Promise<PeerInstanceResolution>;
|
|
2478
|
+
/** Read the caller's explicit choice, if any. Tenant-scoped and recorded as an admin read. */
|
|
2479
|
+
peerBinding(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId, vertical: string): Promise<PeerBinding | undefined>;
|
|
2480
|
+
/** Every explicit target choice this calling scope currently holds. */
|
|
2481
|
+
peerBindings(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId): Promise<PeerBinding[]>;
|
|
2482
|
+
/** Bind or clear this caller's target. Both endpoints are checked in the directory at write. */
|
|
2483
|
+
setPeerBinding(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId, vertical: string, targetScopeId: ScopeId | null): Promise<{
|
|
2484
|
+
changed: boolean;
|
|
2485
|
+
previous: ScopeId | null;
|
|
2486
|
+
}>;
|
|
2311
2487
|
/**
|
|
2312
2488
|
* Persist a tenant. Idempotent on the id — re-creating an existing tenant is a
|
|
2313
2489
|
* no-op, not an error (control-plane.md §4.1). `status` starts `active` and
|
|
@@ -2801,6 +2977,17 @@ export interface HostAdmin {
|
|
|
2801
2977
|
* undocumented gets oversold by someone who was not in the room.
|
|
2802
2978
|
*/
|
|
2803
2979
|
shredSubject(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, subjectId: string): Promise<SubjectShredReceipt>;
|
|
2980
|
+
/** Monotone directory fence against a carry from before subject erasure. */
|
|
2981
|
+
scopeErasureEpoch(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId): Promise<number>;
|
|
2982
|
+
/** Finish an orchestrated erasure only after every script holding a copy has redacted it.
|
|
2983
|
+
* The copy count fences a move that was recorded after the coordinator listed scripts;
|
|
2984
|
+
* ledger rows are append-only, so a changed count means that inventory is stale. */
|
|
2985
|
+
finalizeSubjectShred(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, subjectId: string, redactions: readonly SubjectRedactionCounts[], expected: {
|
|
2986
|
+
versionId: string | null;
|
|
2987
|
+
servingRef: string | null;
|
|
2988
|
+
epoch: number;
|
|
2989
|
+
copyCount: number;
|
|
2990
|
+
}): Promise<SubjectShredReceipt>;
|
|
2804
2991
|
/**
|
|
2805
2992
|
* Open a session that acts as `input.principal`, and record it.
|
|
2806
2993
|
*
|
|
@@ -2952,8 +3139,11 @@ export interface HostAdmin {
|
|
|
2952
3139
|
* the audit metadata — the rotate-side analogue of `createdBy` on create
|
|
2953
3140
|
* (§3.5.1). Omitted ⇒ the effecting actor stands alone, the platform-driven
|
|
2954
3141
|
* refresh path.
|
|
3142
|
+
*
|
|
3143
|
+
* `expiresAt`: a date replaces the connection's expiry, `null` clears it (the credential
|
|
3144
|
+
* that had one was replaced by one that has none), and omitted keeps it.
|
|
2955
3145
|
*/
|
|
2956
|
-
updateConnectionSecret(actor: PlatformActorId, id: ConnectionId, secret: ConnectionSecret, expiresAt?: string, opts?: {
|
|
3146
|
+
updateConnectionSecret(actor: PlatformActorId, id: ConnectionId, secret: ConnectionSecret, expiresAt?: string | null, opts?: {
|
|
2957
3147
|
rotatedBy?: string;
|
|
2958
3148
|
}): Promise<void>;
|
|
2959
3149
|
/**
|
|
@@ -2963,6 +3153,37 @@ export interface HostAdmin {
|
|
|
2963
3153
|
* constraint ignores revoked rows.
|
|
2964
3154
|
*/
|
|
2965
3155
|
revokeConnection(actor: PlatformActorId, id: ConnectionId): Promise<void>;
|
|
3156
|
+
/**
|
|
3157
|
+
* A vertical's MAILED connect link (connections.md §3.5.4) — the row that makes a consent
|
|
3158
|
+
* round single-use and revocable when it lives days rather than minutes. Beside the
|
|
3159
|
+
* connections because it is their front door: the round it starts ends in
|
|
3160
|
+
* `createConnection` (or a rotation) for the same tenant and scope.
|
|
3161
|
+
*
|
|
3162
|
+
* Every verb below is keyed by (tenant, scope): a link of another scope or tenant is
|
|
3163
|
+
* `undefined` / `unknown`, never a refusal that admits it exists. Expiry is judged against
|
|
3164
|
+
* the host's own clock. Mint, revoke, consume and restore are each audited — metadata
|
|
3165
|
+
* only, though nothing here is secret: the credential does not exist until a round is
|
|
3166
|
+
* consumed, and it never touches this row.
|
|
3167
|
+
*/
|
|
3168
|
+
mintConnectLink(actor: PlatformActorId, input: MintConnectLinkInput): Promise<ConnectLink>;
|
|
3169
|
+
getConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<ConnectLink | undefined>;
|
|
3170
|
+
/** Newest first. `outstandingOnly`: outstanding AND unexpired — the links that still open. */
|
|
3171
|
+
listConnectLinks(actor: PlatformActorId, filter: ConnectLinkFilter): Promise<ConnectLink[]>;
|
|
3172
|
+
/**
|
|
3173
|
+
* Outstanding → revoked. Idempotent: a revoked or used link is answered as it stands and
|
|
3174
|
+
* nothing is written. `undefined` when the scope holds no such link.
|
|
3175
|
+
*/
|
|
3176
|
+
revokeConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<ConnectLink | undefined>;
|
|
3177
|
+
/**
|
|
3178
|
+
* Outstanding and unexpired → used, recording what the consent attached. Atomic: of two
|
|
3179
|
+
* concurrent consumes exactly one answers `ok`. Otherwise the typed reason.
|
|
3180
|
+
*/
|
|
3181
|
+
consumeConnectLink(actor: PlatformActorId, input: ConsumeConnectLinkInput): Promise<ConnectLinkConsume>;
|
|
3182
|
+
/**
|
|
3183
|
+
* Used → outstanding, the best-effort undo after the credential store failed. Only a
|
|
3184
|
+
* used, unexpired link moves; `false` for anything else, a revoked link included.
|
|
3185
|
+
*/
|
|
3186
|
+
restoreConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<boolean>;
|
|
2966
3187
|
/**
|
|
2967
3188
|
* Open the credential for one (tenant, vertical, provider[, account]) — the
|
|
2968
3189
|
* connector's read, and the only path in the system that yields plaintext.
|
|
@@ -3263,6 +3484,27 @@ export interface HostAdmin {
|
|
|
3263
3484
|
* cannot be written.
|
|
3264
3485
|
*/
|
|
3265
3486
|
recordMemberChange(actor: PlatformActorId, entry: MemberChangeAudit): Promise<void>;
|
|
3487
|
+
/** One intent or outcome of a tenant's peer-instance binding change (#1720). */
|
|
3488
|
+
recordPeerBindingChange(actor: PlatformActorId, entry: PeerBindingAudit): Promise<void>;
|
|
3489
|
+
/**
|
|
3490
|
+
* Close one audited change whose intent has no outcome (#2064): in ONE transaction, read the
|
|
3491
|
+
* intent `intentId` names, and if no outcome row exists for its operation, write an `unknown`
|
|
3492
|
+
* outcome carrying `error` and an ops-failure row (`audit.<action>`, stage `outcome-unknown`)
|
|
3493
|
+
* for the staff digest. Answers `true` when it wrote them, `false` when an outcome was already
|
|
3494
|
+
* there, so concurrent callers settle an operation once. Throws `not_found` for an id that is
|
|
3495
|
+
* not an audited-change intent. See `audit-outcome.ts` for why a later real outcome still wins.
|
|
3496
|
+
*/
|
|
3497
|
+
settleUnrecordedOutcome(actor: PlatformActorId, input: {
|
|
3498
|
+
intentId: string;
|
|
3499
|
+
error: string;
|
|
3500
|
+
}): Promise<boolean>;
|
|
3501
|
+
/**
|
|
3502
|
+
* Every admin-log row of the given audited operations (#2064), read through the operation-id
|
|
3503
|
+
* index in bounded batches — one statement per `AUDITED_OPERATIONS_BATCH` ids, never a scan of
|
|
3504
|
+
* the log. Only rows of the exact (action, operation, tenant, scope) asked about are returned.
|
|
3505
|
+
* Reading the audit trail is itself recorded, once per call.
|
|
3506
|
+
*/
|
|
3507
|
+
auditedOperations(actor: PlatformActorId, refs: readonly AuditedOperationRef[]): Promise<AuditedOperationRow[]>;
|
|
3266
3508
|
/**
|
|
3267
3509
|
* Stamp `drainedAt` on every not-yet-drained access row up to and including
|
|
3268
3510
|
* `upToId`, marking them shipped to Tier 2. Returns how many rows moved.
|
|
@@ -3828,6 +4070,38 @@ export interface OpsFailureInput {
|
|
|
3828
4070
|
/** The upstream provider's trace reference, when the message carried one. */
|
|
3829
4071
|
reference?: string | null;
|
|
3830
4072
|
}
|
|
4073
|
+
/** How long a recorded copy move owns its pending entries before a sweep may settle them (#1722). */
|
|
4074
|
+
export declare const SCOPE_COPY_LEASE_MS: number;
|
|
4075
|
+
/** Which end of a copy move a script is (#1722). Null on an entry recorded without one. */
|
|
4076
|
+
export type ScopeCopyRole = 'source' | 'destination';
|
|
4077
|
+
/**
|
|
4078
|
+
* A copy move's confirmation, written with the bind that routes the scope onto its destination
|
|
4079
|
+
* (#1722). The bind lands only while every entry of the move is still pending, leased to the move
|
|
4080
|
+
* (no sweep has claimed it) and inside its lease; in the same write the destination entries
|
|
4081
|
+
* settle `done` (the route reaches them) and the source entries settle as the `source` field
|
|
4082
|
+
* says, with `sourceMarker` as the marker a later fenced wipe expects.
|
|
4083
|
+
*/
|
|
4084
|
+
export interface ScopeCopyMoveConfirmation {
|
|
4085
|
+
moveId: string;
|
|
4086
|
+
source: 'eligible' | 'retained';
|
|
4087
|
+
sourceMarker?: {
|
|
4088
|
+
loadStamp: string | null;
|
|
4089
|
+
revision: string | null;
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
/** Directory inventory of one script's copy of a scope (#1722). */
|
|
4093
|
+
export interface ScopeScriptCopy {
|
|
4094
|
+
tenantId: TenantId;
|
|
4095
|
+
scopeId: ScopeId;
|
|
4096
|
+
scriptRef: string;
|
|
4097
|
+
moveId: string;
|
|
4098
|
+
state: 'pending' | 'eligible' | 'retained' | 'kept' | 'done';
|
|
4099
|
+
loadStamp: string | null;
|
|
4100
|
+
revision: string | null;
|
|
4101
|
+
role: ScopeCopyRole | null;
|
|
4102
|
+
/** Until when the pending entry belongs to its move (or to the sweep that claimed it). */
|
|
4103
|
+
leaseUntil: string | null;
|
|
4104
|
+
}
|
|
3831
4105
|
/** One staff resolution of a kept copy (#1722), as `recordKeptCopyResolution` logs it. */
|
|
3832
4106
|
export interface KeptCopyResolution {
|
|
3833
4107
|
/** `discard` wiped the kept copy; `restore-forward` restored it over the live store, then wiped it;
|
|
@@ -3908,10 +4182,19 @@ export interface ScopeHost {
|
|
|
3908
4182
|
* Optional so a host that predates it still satisfies the interface; a transport
|
|
3909
4183
|
* that finds it absent writes unattributed rows, which is what every row was before.
|
|
3910
4184
|
*
|
|
3911
|
-
*
|
|
3912
|
-
*
|
|
3913
|
-
|
|
3914
|
-
|
|
4185
|
+
* A view of the host knows nothing of any event: an executor handler attributes from the
|
|
4186
|
+
* `admin` it is handed instead (`HostAdmin.attributed`), which keeps its `causedBy`.
|
|
4187
|
+
*/
|
|
4188
|
+
attributed?(onBehalfOf: OnBehalfOf): ScopeHost;
|
|
4189
|
+
/**
|
|
4190
|
+
* The same view, its rows also naming `options.causedBy` as the event they are the effect
|
|
4191
|
+
* of (K-22, #2055).
|
|
4192
|
+
*
|
|
4193
|
+
* @deprecated since #2069: an executor handler attributes from the `admin` it is handed —
|
|
4194
|
+
* `admin.attributed(onBehalfOf)` — which carries its event by construction. This form needs
|
|
4195
|
+
* the event passed again, and a handler that forgets writes `causedBy` NULL with nothing
|
|
4196
|
+
* failing. It stays because it shipped, and because it is how the membership executor still
|
|
4197
|
+
* attributes on a host whose handed admin predates `HostAdmin.attributed`.
|
|
3915
4198
|
*/
|
|
3916
4199
|
attributed?(onBehalfOf: OnBehalfOf, options?: {
|
|
3917
4200
|
causedBy?: string;
|
|
@@ -4270,9 +4553,10 @@ export interface ScopeHost {
|
|
|
4270
4553
|
* NOW. So a capability reads no attachment its minter could not read at this moment, and
|
|
4271
4554
|
* none outside its subtree. The session is re-resolved on EVERY call, as on the invoke
|
|
4272
4555
|
* door: a revoked or expired capability, or an expired session, refuses `unauthenticated`
|
|
4273
|
-
* before any key is checked
|
|
4274
|
-
*
|
|
4275
|
-
* takes a use — a use is
|
|
4556
|
+
* before any key is checked. An operations-narrowed capability refuses reads by default;
|
|
4557
|
+
* its mint may set `attachments: 'read'` to opt into list, open and search. That field
|
|
4558
|
+
* does not replace the `readPermission` check above. A read never takes a use — a use is
|
|
4559
|
+
* an exchange.
|
|
4276
4560
|
*
|
|
4277
4561
|
* **Writes are refused.** `upload` and `remove` throw `capabilityAttachmentWriteRefused`
|
|
4278
4562
|
* whatever keys the capability carries, recorded in the denial log against
|
|
@@ -4618,6 +4902,11 @@ export interface ScopeHost {
|
|
|
4618
4902
|
lastError?: string | null;
|
|
4619
4903
|
/** WHO refused (#841). Omitted by a caller too old to attribute — stored as NULL. */
|
|
4620
4904
|
failure?: PlatformRequestFailure | null;
|
|
4905
|
+
/**
|
|
4906
|
+
* #2102: an outcome event written into the scope with the settle, in its transaction,
|
|
4907
|
+
* and only when this settle moves the row out of `pending` (`settlePlatformRequestIn`).
|
|
4908
|
+
*/
|
|
4909
|
+
event?: PlatformOutcomeEvent | null;
|
|
4621
4910
|
}): Promise<void>;
|
|
4622
4911
|
/**
|
|
4623
4912
|
* Live reads (#938) — a subscription to this scope's change feed, or absent.
|
|
@@ -4671,10 +4960,12 @@ export interface ScopeHost {
|
|
|
4671
4960
|
* changed at 14:02 is information about that row, so "the body was empty" is not a
|
|
4672
4961
|
* defence: the filter runs whether or not there is a payload to withhold.
|
|
4673
4962
|
*
|
|
4674
|
-
* A subscription may be narrowed `within` one entity (#1853), which only removes frames
|
|
4675
|
-
*
|
|
4676
|
-
*
|
|
4677
|
-
*
|
|
4963
|
+
* A subscription may be narrowed `within` one entity (#1853), which only removes frames.
|
|
4964
|
+
* Two built roots replace the per-row check with one about the ROOT, and their subscribers
|
|
4965
|
+
* are sent bare `LiveNudge` frames that name no entity: `checkedWithin`, whose principal must
|
|
4966
|
+
* pass a stated key on the root at the handshake and again on every pass that has something
|
|
4967
|
+
* to send (#938), and `vouchedWithin`, which the vertical asserts once and nothing re-checks
|
|
4968
|
+
* (#1853). Which to use is on `checkedWithin`.
|
|
4678
4969
|
*
|
|
4679
4970
|
* **Generic over the runtime's request and response, because the kernel names
|
|
4680
4971
|
* neither.** This package has one dependency and no DOM or workers lib
|
|
@@ -4727,10 +5018,51 @@ export interface LiveReadSurface<Req extends LiveUpgradeRequest = LiveUpgradeReq
|
|
|
4727
5018
|
* the same trust it already extends in naming `principal`, and the scope's walk is
|
|
4728
5019
|
* the whole filter. Such a subscriber receives `LiveNudge` frames only, which name
|
|
4729
5020
|
* no event type and no entity.
|
|
5021
|
+
* - **A `checkedWithin(…)` value replaces the per-row check with one on the root**
|
|
5022
|
+
* (#938). The principal must pass the stated key on the root, at the handshake (a
|
|
5023
|
+
* `403` otherwise) and again on every pass that has a row beneath it to announce,
|
|
5024
|
+
* so a grant withdrawn, or a root moved out from under it, closes the socket
|
|
5025
|
+
* instead of nudging it. `LiveNudge` frames only, as for a vouched root.
|
|
5026
|
+
*
|
|
5027
|
+
* **Freshness (Codex #2077 r5).** A decision is reused within a pass only while the
|
|
5028
|
+
* scope has had no write and nothing it holds has reached its `expires_at` since the
|
|
5029
|
+
* decision. It is never reused while the scope reads its permissions from the directory.
|
|
5030
|
+
* Both are checked against the clock immediately before each send. A change that lands
|
|
5031
|
+
* while a check is still being evaluated can let that one frame through. Frames name
|
|
5032
|
+
* nothing the subscriber could not read when checked, and the next frame, pass and poll
|
|
5033
|
+
* see the change: a push is a hint at most one evaluation stale.
|
|
5034
|
+
*/
|
|
5035
|
+
within?: EntityRef | VouchedWithin | CheckedWithin;
|
|
5036
|
+
/**
|
|
5037
|
+
* When the credential that proved `principal` stops being valid (ISO 8601), so the
|
|
5038
|
+
* socket never outlives it (#938). Pass the session's or the bearer's expiry
|
|
5039
|
+
* (`AuthSubject.expiresAt`). A handshake at or past it is refused, and the scope closes
|
|
5040
|
+
* the socket (`1008`) on its first pass at or past it, before sending anything on that
|
|
5041
|
+
* pass. Absent: the socket lives until either end closes it, as before.
|
|
4730
5042
|
*/
|
|
4731
|
-
|
|
5043
|
+
expiresAt?: string;
|
|
4732
5044
|
}): Promise<Res>;
|
|
4733
5045
|
}
|
|
5046
|
+
/**
|
|
5047
|
+
* The close codes a live socket ends with, so a client can tell "try again" from "poll".
|
|
5048
|
+
*
|
|
5049
|
+
* - `1008` (policy): the subscriber may no longer watch what it subscribed to — a
|
|
5050
|
+
* `checkedWithin` gate refused, or the session that opened it ended. A reconnect meets
|
|
5051
|
+
* the handshake's own refusal.
|
|
5052
|
+
* - `4429`: this principal already holds `LIVE_SOCKETS_PER_PRINCIPAL` sockets on the scope.
|
|
5053
|
+
* Not a reason to retry: the client should poll, which it does anyway, and stop asking.
|
|
5054
|
+
*/
|
|
5055
|
+
export declare const LIVE_CLOSE: {
|
|
5056
|
+
readonly revoked: 1008;
|
|
5057
|
+
readonly tooMany: 4429;
|
|
5058
|
+
};
|
|
5059
|
+
/**
|
|
5060
|
+
* How many live sockets one principal may hold on one scope (#938). A tab holds one per
|
|
5061
|
+
* feed (the desk's, and one per open conversation in ticket0's portal), and each socket is
|
|
5062
|
+
* work on every post-commit pass, so a principal opening more is a cost on everybody else
|
|
5063
|
+
* writing to the scope. Eight covers several tabs; past it a socket is closed `4429`.
|
|
5064
|
+
*/
|
|
5065
|
+
export declare const LIVE_SOCKETS_PER_PRINCIPAL = 8;
|
|
4734
5066
|
/** The brand only `vouchedWithin` can apply — a literal cannot type-check as one. */
|
|
4735
5067
|
declare const vouchedBrand: unique symbol;
|
|
4736
5068
|
/**
|
|
@@ -4762,6 +5094,50 @@ export declare function vouchedWithin(entity: EntityRef, opts: {
|
|
|
4762
5094
|
}): VouchedWithin;
|
|
4763
5095
|
/** Was this value built by `vouchedWithin`? A host asks before it drops the principal's check. */
|
|
4764
5096
|
export declare function isVouchedWithin(value: unknown): value is VouchedWithin;
|
|
5097
|
+
/** The brand only `checkedWithin` can apply. */
|
|
5098
|
+
declare const checkedBrand: unique symbol;
|
|
5099
|
+
/**
|
|
5100
|
+
* A `within` root the PRINCIPAL must be allowed `permission` on, re-checked for as long
|
|
5101
|
+
* as the socket is open (#938). Built only by `checkedWithin`.
|
|
5102
|
+
*/
|
|
5103
|
+
export interface CheckedWithin {
|
|
5104
|
+
readonly [checkedBrand]: true;
|
|
5105
|
+
readonly entity: EntityRef;
|
|
5106
|
+
readonly permission: PermissionKey;
|
|
5107
|
+
}
|
|
5108
|
+
/**
|
|
5109
|
+
* Watch `entity` and everything beneath it, for as long as the subscriber may
|
|
5110
|
+
* `permission` on `entity` itself.
|
|
5111
|
+
*
|
|
5112
|
+
* For a subscriber whose grant reaches the ROOT but not each row under it the way a
|
|
5113
|
+
* `liveTargets` key would: ticket0's portal customer holds `conversation:read-own` on
|
|
5114
|
+
* their contact, which reaches the conversation's public thread, while the per-row key
|
|
5115
|
+
* on a message is the staff read. Rooted at an entity whose subtree is exactly what that
|
|
5116
|
+
* key's read returns, the walk is the row filter and the key is the gate.
|
|
5117
|
+
*
|
|
5118
|
+
* The gate is asked at the handshake (`403` if it refuses) and again, once per socket,
|
|
5119
|
+
* on every post-commit pass that has a row beneath the root to announce. A refusal or a
|
|
5120
|
+
* check that throws closes the socket (`1008`) and sends nothing on that pass, so a
|
|
5121
|
+
* revoked grant, a principal who is gone, or a root relinked out of the grant's reach
|
|
5122
|
+
* ends the subscription instead of nudging it, and the client's reconnect meets the
|
|
5123
|
+
* handshake's `403`. A pass with nothing beneath the root asks nothing, so an idle
|
|
5124
|
+
* subscription costs no checks.
|
|
5125
|
+
*
|
|
5126
|
+
* **`checkedWithin` or `vouchedWithin`?** Use `checkedWithin` whenever the subscriber is
|
|
5127
|
+
* a principal with a grant that reaches the root: authority leaves with the grant. Use
|
|
5128
|
+
* `vouchedWithin` only when there is no such grant to ask, as for ticket0's widget
|
|
5129
|
+
* visitor, who holds a session token rather than a principal of their own. A vouched
|
|
5130
|
+
* socket is asserted once, so a token revoked mid-socket keeps receiving content-free
|
|
5131
|
+
* nudges until the socket closes. That is known and accepted for the widget: the nudge
|
|
5132
|
+
* names nothing, and every re-read it causes is checked again.
|
|
5133
|
+
*
|
|
5134
|
+
* Both send `LiveNudge` frames only: the subscriber may not read each row, so it is
|
|
5135
|
+
* never told which one changed. Only rows of a type some module declared in
|
|
5136
|
+
* `liveTargets` are announced, as for every other subscriber.
|
|
5137
|
+
*/
|
|
5138
|
+
export declare function checkedWithin(entity: EntityRef, permission: string): CheckedWithin;
|
|
5139
|
+
/** Was this value built by `checkedWithin`? */
|
|
5140
|
+
export declare function isCheckedWithin(value: unknown): value is CheckedWithin;
|
|
4765
5141
|
/**
|
|
4766
5142
|
* Is this request asking to be upgraded to a WebSocket?
|
|
4767
5143
|
*
|
|
@@ -4822,12 +5198,13 @@ export interface LiveChange {
|
|
|
4822
5198
|
readonly at: string;
|
|
4823
5199
|
}
|
|
4824
5200
|
/**
|
|
4825
|
-
* What a vouched subscriber is told (#1853): something beneath its root
|
|
4826
|
-
* nothing else.
|
|
5201
|
+
* What a vouched or checked subscriber is told (#1853, #938): something beneath its root
|
|
5202
|
+
* changed, and nothing else.
|
|
4827
5203
|
*
|
|
4828
|
-
* No event type and no entity, deliberately.
|
|
4829
|
-
* entities its frames are about — the vertical vouched for the ROOT,
|
|
4830
|
-
* cannot know which rows under it the vertical's
|
|
5204
|
+
* No event type and no entity, deliberately. Such a subscriber holds no read on the
|
|
5205
|
+
* entities its frames are about — the vertical vouched for the ROOT, or the principal was
|
|
5206
|
+
* checked on the root alone, and the scope cannot know which rows under it the vertical's
|
|
5207
|
+
* own read would show. Naming the type
|
|
4831
5208
|
* or the id would tell it what it never asked to read. The client re-reads, as it does
|
|
4832
5209
|
* on a `LiveChange`.
|
|
4833
5210
|
*/
|