@substrat-run/kernel 0.138.0 → 0.140.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +21 -2
- package/dist/attachment-extractor.d.ts.map +1 -1
- package/dist/attachment-extractor.js +31 -6
- package/dist/attachment-extractor.js.map +1 -1
- package/dist/attachment-text.d.ts +64 -0
- package/dist/attachment-text.d.ts.map +1 -1
- package/dist/attachment-text.js +87 -16
- package/dist/attachment-text.js.map +1 -1
- package/dist/attribution.d.ts +36 -3
- package/dist/attribution.d.ts.map +1 -1
- package/dist/attribution.js +21 -8
- package/dist/attribution.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/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 +40 -0
- package/dist/entity-state-reads.d.ts.map +1 -0
- package/dist/entity-state-reads.js +91 -0
- package/dist/entity-state-reads.js.map +1 -0
- package/dist/entity-state.d.ts +204 -0
- package/dist/entity-state.d.ts.map +1 -0
- package/dist/entity-state.js +356 -0
- package/dist/entity-state.js.map +1 -0
- 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/findings.d.ts +143 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +463 -0
- package/dist/findings.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 +40 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -6
- 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 +34 -3
- package/dist/list-index.d.ts.map +1 -1
- package/dist/list-index.js +83 -31
- 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 +7 -47
- package/dist/membership-executor.d.ts.map +1 -1
- package/dist/membership-executor.js +28 -53
- 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/module-migrations.d.ts +6 -3
- package/dist/module-migrations.d.ts.map +1 -1
- package/dist/module-migrations.js +12 -5
- package/dist/module-migrations.js.map +1 -1
- 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 +51 -1
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +104 -2
- package/dist/permission-eval.js.map +1 -1
- package/dist/platform-sweep.d.ts +8 -2
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +17 -0
- package/dist/platform-sweep.js.map +1 -1
- package/dist/scope-host.d.ts +479 -24
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +82 -7
- package/dist/scope-host.js.map +1 -1
- package/dist/scope-role-admin.d.ts +68 -0
- package/dist/scope-role-admin.d.ts.map +1 -0
- package/dist/scope-role-admin.js +80 -0
- package/dist/scope-role-admin.js.map +1 -0
- package/dist/search-index.d.ts +21 -1
- package/dist/search-index.d.ts.map +1 -1
- package/dist/search-index.js +60 -37
- package/dist/search-index.js.map +1 -1
- package/dist/spine-guard.d.ts +71 -2
- package/dist/spine-guard.d.ts.map +1 -1
- package/dist/spine-guard.js +393 -10
- 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-identifier.d.ts +12 -0
- package/dist/sql-identifier.d.ts.map +1 -0
- package/dist/sql-identifier.js +17 -0
- package/dist/sql-identifier.js.map +1 -0
- 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 +2 -2
package/dist/scope-host.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { ModuleLog } from './module-log.js';
|
|
2
2
|
import type { DeliveryRefusal } from './delivery-refusal.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ScopeRoleHolder } from './scope-role-admin.js';
|
|
4
|
+
import type { OnBehalfOf, ExportReadInput, ExportedBatch, ImportBatch, ImportedEvent, ImportResult, ImportState, ImportCursorMove, ImportCursorMoved, AdminAction, BecomeCapabilityInput, CapabilityExchange, CapabilityFilter, CapabilityId, CapabilityPage, MintedCapability, ListPage, 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';
|
|
4
5
|
import type { ConnectionUseOutcome } from './connector-calls.js';
|
|
5
6
|
import type { CapabilityVerbs } from './capability.js';
|
|
7
|
+
import type { AuditedOperationRef, AuditedOperationRow } from './audit-outcome.js';
|
|
8
|
+
import { type EntityStateName } from '@substrat-run/contracts';
|
|
6
9
|
import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
|
|
10
|
+
import type { FindingPruneReport } from './findings.js';
|
|
7
11
|
import type { SealedSecret } from './secret-box.js';
|
|
12
|
+
import type { OnSubjectErased } from './module-erasure.js';
|
|
8
13
|
import type { SystemSwitchReassert, SystemSwitchReassertOptions, SystemSwitchRecordFilter } from './system-switch-record.js';
|
|
9
14
|
import type { SearchHit, SearchOptions } from './search-index.js';
|
|
15
|
+
import type { GrantedEntitiesPage } from './grant-scoped-read.js';
|
|
10
16
|
import type { EntityVersion } from './entity-version.js';
|
|
11
17
|
import type { UndrainedEvents } from './outbox-event.js';
|
|
12
18
|
import type { JobDriveReport, JobHandler, JobRun, JobRunFilter, StartJobRunInput } from './job-run.js';
|
|
@@ -32,6 +38,16 @@ export interface PageParams {
|
|
|
32
38
|
readonly filters?: Readonly<Record<string, unknown>>;
|
|
33
39
|
/** Also count the filtered set — the declaration's `total`, passed through. */
|
|
34
40
|
readonly total?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Which rows of an archivable entity (#119): `active` when unset, `archived` to read the
|
|
43
|
+
* archive. Refused for an entity that declares no archive. The bin is `ctx.pageTrashed`.
|
|
44
|
+
*/
|
|
45
|
+
readonly view?: Exclude<EntityStateName, 'trashed'>;
|
|
46
|
+
/**
|
|
47
|
+
* Also return each row's own cursor, as `rowCursors` (#2073) — asked for by `pageVisible`,
|
|
48
|
+
* which may stop partway through a page. Off by default, so an ordinary page is unchanged.
|
|
49
|
+
*/
|
|
50
|
+
readonly rowCursors?: boolean;
|
|
35
51
|
}
|
|
36
52
|
/**
|
|
37
53
|
* The scope-host contract — the adapter seam (§5.1 of the design doc).
|
|
@@ -82,6 +98,13 @@ export interface OperationContext {
|
|
|
82
98
|
readonly tenantId: TenantId;
|
|
83
99
|
readonly scopeId: ScopeId;
|
|
84
100
|
readonly principal: PrincipalId;
|
|
101
|
+
/** Seal positions for one filtered list under this scope, caller and operation. */
|
|
102
|
+
pageContinuation(list: string, query: unknown): {
|
|
103
|
+
/** Read-only and copy walks cannot persist a hidden budget-stop position. */
|
|
104
|
+
writable?: boolean;
|
|
105
|
+
open(cursor: string): Promise<string>;
|
|
106
|
+
seal(position: string, hidden: boolean): Promise<string | null>;
|
|
107
|
+
};
|
|
85
108
|
readonly sql: ScopedSql;
|
|
86
109
|
/**
|
|
87
110
|
* The operation's instant (#812) — the ONLY clock module code may read.
|
|
@@ -201,6 +224,18 @@ export interface OperationContext {
|
|
|
201
224
|
* than returning a denial, and records none (#1642, `assertPermissionKey`).
|
|
202
225
|
*/
|
|
203
226
|
check(permission: PermissionKey, entity?: EntityRef): Promise<Decision>;
|
|
227
|
+
/**
|
|
228
|
+
* Page entity ids reachable through this caller's live grants and declared parent edges.
|
|
229
|
+
* `all` means a node grant makes every entity of this type visible; `incomplete` means
|
|
230
|
+
* this checker cannot enumerate the path, so callers must use a checked full walk.
|
|
231
|
+
* An `ids` page is complete only after its cursor becomes null. Ids may repeat across
|
|
232
|
+
* pages on multiple parent paths: accumulate a Set, and do not treat page lengths as
|
|
233
|
+
* a count or total. The union of all pages is complete. Every id passes `ctx.check`.
|
|
234
|
+
*/
|
|
235
|
+
grantedEntities(permission: PermissionKey, entityType: string, options?: {
|
|
236
|
+
limit?: number;
|
|
237
|
+
cursor?: string;
|
|
238
|
+
}): Promise<GrantedEntitiesPage>;
|
|
204
239
|
/**
|
|
205
240
|
* Find entities of one type by what a person typed (#827) — the read a picker
|
|
206
241
|
* over 40 000 customers needs and `ctx.sql` cannot express without every
|
|
@@ -257,9 +292,9 @@ export interface OperationContext {
|
|
|
257
292
|
*
|
|
258
293
|
* **This does not check permission** — nothing on `ctx` does, and a paged read
|
|
259
294
|
* is not an exception. The operation's own `assertAllowed` still comes first.
|
|
260
|
-
* A read that filters per ROW after the fact (a portal walk) cannot
|
|
261
|
-
*
|
|
262
|
-
*
|
|
295
|
+
* A read that filters per ROW after the fact (a portal walk) cannot hand this
|
|
296
|
+
* page on as it is: a page of 20 filtered down to 3 is not a page, and its cursor
|
|
297
|
+
* may be a refused row's. It walks this read with `pageVisible` instead (#2073).
|
|
263
298
|
*
|
|
264
299
|
* Throws `NotListable` for an entity no operation declared `paged.over` on,
|
|
265
300
|
* `SortNotDeclared` for a `?sort=` outside the vocabulary, and
|
|
@@ -326,6 +361,12 @@ export interface OperationContext {
|
|
|
326
361
|
* revives it and records that as one `entity.linked` spine event, since access resumes.
|
|
327
362
|
*
|
|
328
363
|
* Checks no permission: the operation does, in its own vocabulary.
|
|
364
|
+
*
|
|
365
|
+
* A parent in the TRASH (#119) is refused `not_found`: nothing new hangs off a binned
|
|
366
|
+
* entity, since it is gone from everyone's view until restored. A MISSING parent of a type that
|
|
367
|
+
* declares trash gets the identical refusal, so the two cannot be told apart. An ARCHIVED parent
|
|
368
|
+
* is allowed — archived is filed away and still readable, and whether one may still be added to
|
|
369
|
+
* is the vertical's rule, not the kernel's.
|
|
329
370
|
*/
|
|
330
371
|
link(child: EntityRef, parent: EntityRef): void;
|
|
331
372
|
/**
|
|
@@ -338,7 +379,17 @@ export interface OperationContext {
|
|
|
338
379
|
* move would make the child its own ancestor.
|
|
339
380
|
* - `from` must be a live parent edge of `child`; `conflict` otherwise. Every other
|
|
340
381
|
* parent a multi-parent entity has is left alone.
|
|
382
|
+
* - A `to` in the TRASH is refused `not_found`, as `link` refuses one (#119) — the same refusal a
|
|
383
|
+
* missing `to` of that type gets. `from` is not: moving a child out from under a binned
|
|
384
|
+
* parent is how it is rescued.
|
|
341
385
|
* - `from` equal to `to` is a no-op: nothing written, nothing emitted.
|
|
386
|
+
* - A `to` that is ALREADY a live parent of `child` is the way to detach one parent of a
|
|
387
|
+
* multi-parent child (#2044): the `from` edge is tombstoned, the live `to` edge stays
|
|
388
|
+
* live — no second row, no revive, no `entity.linked` — and `entity.relinked` is still
|
|
389
|
+
* emitted. Like every edge `link` or `relink` leaves live, `to` is left PERMANENT: a
|
|
390
|
+
* future expiry on it (which only a restored dump can hold) is cleared, silently. The
|
|
391
|
+
* child keeps every other parent untouched, expiry included. The contract suite holds
|
|
392
|
+
* both adapters to it.
|
|
342
393
|
* - The old edge is tombstoned (K-21), not deleted, and one `entity.relinked` spine event
|
|
343
394
|
* records the move on the child's timeline, stamped like any event the operation emits.
|
|
344
395
|
* - Transactional with the operation: a relink whose operation throws never happened.
|
|
@@ -354,6 +405,56 @@ export interface OperationContext {
|
|
|
354
405
|
* ```
|
|
355
406
|
*/
|
|
356
407
|
relink(child: EntityRef, from: EntityRef, to: EntityRef): void;
|
|
408
|
+
/**
|
|
409
|
+
* Archive an entity (#119): hide it from active views and keep it. Only an entity whose
|
|
410
|
+
* model declares `archive: { permission }` can be archived.
|
|
411
|
+
*
|
|
412
|
+
* - **Checks the declared archive key on the entity itself** — unlike `link`, because the
|
|
413
|
+
* key is declared beside the entity and an operation archiving under another key is the
|
|
414
|
+
* one mistake this exists to make impossible. A denial throws `PermissionDenied`.
|
|
415
|
+
* - Only an `active` entity: anything else is `conflict` (`reason: 'invalid_transition'`);
|
|
416
|
+
* a missing one is `not_found`.
|
|
417
|
+
* - Stamps `_substrat_archived_at` with the operation's instant and emits one kernel-authored
|
|
418
|
+
* `entity.archived`, with the actor and the authorization chain on its envelope.
|
|
419
|
+
* - Transactional with the operation, like every other write.
|
|
420
|
+
*
|
|
421
|
+
* Resolves to the state the entity is now in.
|
|
422
|
+
*/
|
|
423
|
+
archive(entity: EntityRef): Promise<EntityStateName>;
|
|
424
|
+
/** Bring an archived entity back to active. Same key, refusals and recording as `archive`. */
|
|
425
|
+
unarchive(entity: EntityRef): Promise<EntityStateName>;
|
|
426
|
+
/**
|
|
427
|
+
* Move an entity to the trash (#119) — a reversible delete. Needs `trash: { permission }`
|
|
428
|
+
* on the entity, and checks that key on it. An `active` or an `archived` entity may be
|
|
429
|
+
* trashed; the archive survives the trip, so a `restore` returns it to the archive.
|
|
430
|
+
* Emits `entity.trashed`.
|
|
431
|
+
*/
|
|
432
|
+
trash(entity: EntityRef): Promise<EntityStateName>;
|
|
433
|
+
/**
|
|
434
|
+
* Take an entity out of the trash, back to the state it was trashed from — `archived` if it
|
|
435
|
+
* was archived, `active` otherwise. Checks the trash key. Emits `entity.restored`, whose
|
|
436
|
+
* payload's `to` says which — and so does the value it resolves to.
|
|
437
|
+
*/
|
|
438
|
+
restore(entity: EntityRef): Promise<EntityStateName>;
|
|
439
|
+
/**
|
|
440
|
+
* The state of one archivable/trashable entity — `active`, `archived` or `trashed` — or
|
|
441
|
+
* `null` when the row does not exist. What a get-by-id reads before deciding what to answer:
|
|
442
|
+
* the kernel filters the reads it composes, never a handler's own `SELECT`.
|
|
443
|
+
*
|
|
444
|
+
* Checks no permission, like every read on `ctx`. Throws `validation_failed` for an entity
|
|
445
|
+
* type that declares neither.
|
|
446
|
+
*/
|
|
447
|
+
entityState(entity: EntityRef): EntityStateName | null;
|
|
448
|
+
/**
|
|
449
|
+
* One page of the TRASH of a declared entity (#119) — `ctx.page` over the trashed rows,
|
|
450
|
+
* with the declared trash key checked on EACH row inside the kernel, so a handler cannot
|
|
451
|
+
* forget it. A row the caller may not see in the bin is left out, which can make a page
|
|
452
|
+
* short; the walk still ends only at a null `nextCursor`. No `total`: a count over rows the
|
|
453
|
+
* caller cannot see would disclose them.
|
|
454
|
+
*/
|
|
455
|
+
pageTrashed<T>(entityType: string, params: Omit<PageParams, 'view' | 'total'>): Promise<Page<T>>;
|
|
456
|
+
/** `ctx.search` over the trash, the declared trash key checked per hit (#119). */
|
|
457
|
+
searchTrashed(entityType: string, term: string, options?: Omit<SearchOptions, 'view'>): Promise<SearchHit[]>;
|
|
357
458
|
/**
|
|
358
459
|
* Narrow a permission the CALLER ALREADY HOLDS onto one entity — how an app
|
|
359
460
|
* expresses user-initiated sharing.
|
|
@@ -738,9 +839,7 @@ export type ConsumerHandler = (ctx: OperationContext, event: DomainEvent) => voi
|
|
|
738
839
|
export type ImportHandler = (ctx: OperationContext, event: ImportedEvent) => void | Promise<void>;
|
|
739
840
|
/**
|
|
740
841
|
* One membership change for `HostAdmin.applyMembership` (#1184): a TENANT-level role assigned
|
|
741
|
-
* or taken away
|
|
742
|
-
* in each scope's own store, where no directory unit can bound it, so authorizing an org
|
|
743
|
-
* membership is its own capability.
|
|
842
|
+
* or taken away and, when `orgId` is given (#2047), the org joined or left in the same unit.
|
|
744
843
|
*/
|
|
745
844
|
export type MembershipChange = {
|
|
746
845
|
tenantId: TenantId;
|
|
@@ -753,6 +852,15 @@ export type MembershipChange = {
|
|
|
753
852
|
* read inside the unit (`tenantCoverage`).
|
|
754
853
|
*/
|
|
755
854
|
boundedBy: PrincipalId;
|
|
855
|
+
/**
|
|
856
|
+
* The org joined (an add) or left (a removal) beside the role (#2047). Bounded by membership:
|
|
857
|
+
* the unit applies nothing unless `boundedBy` is a live member of it, read inside the unit
|
|
858
|
+
* (`liveOrgMembership`). A member holds everything the org confers — its grants in each
|
|
859
|
+
* scope's own store included — so that is the whole bound, and no scope is read. A join's
|
|
860
|
+
* membership expires no later than `boundedBy`'s own (`joinedMembershipExpiry`). Absent, the
|
|
861
|
+
* change is the role alone.
|
|
862
|
+
*/
|
|
863
|
+
orgId?: OrgId;
|
|
756
864
|
} & ({
|
|
757
865
|
op: 'add';
|
|
758
866
|
/** Apply nothing when `principal`'s removal fence stands at or after this instant. */
|
|
@@ -761,9 +869,10 @@ export type MembershipChange = {
|
|
|
761
869
|
op: 'remove';
|
|
762
870
|
});
|
|
763
871
|
/**
|
|
764
|
-
* What the unit did: applied, or why not — fenced by a removal (an add only),
|
|
765
|
-
* bound
|
|
766
|
-
* nothing still raised the
|
|
872
|
+
* What the unit did: applied, or why not — fenced by a removal (an add only), out of the role's
|
|
873
|
+
* bound, or out of the org's (#2047: `boundedBy` is no live member of it). A removal that
|
|
874
|
+
* applied says whether it took anything (`changed`); one that took nothing still raised the
|
|
875
|
+
* fence.
|
|
767
876
|
*/
|
|
768
877
|
export type MembershipChangeResult = {
|
|
769
878
|
applied: true;
|
|
@@ -777,6 +886,12 @@ export type MembershipChangeResult = {
|
|
|
777
886
|
} | {
|
|
778
887
|
applied: false;
|
|
779
888
|
unknownRole: string;
|
|
889
|
+
} | {
|
|
890
|
+
applied: false;
|
|
891
|
+
unknownOrg: OrgId;
|
|
892
|
+
} | {
|
|
893
|
+
applied: false;
|
|
894
|
+
notMember: OrgId;
|
|
780
895
|
};
|
|
781
896
|
/**
|
|
782
897
|
* An **executor**: out-of-band host code that effects, outside a scope, what a module
|
|
@@ -795,8 +910,11 @@ export type MembershipChangeResult = {
|
|
|
795
910
|
* consumers must.
|
|
796
911
|
*
|
|
797
912
|
* It receives `HostAdmin`, not `ctx`: it acts with platform authority, which is
|
|
798
|
-
* precisely what module code must never hold. Admin writes it makes
|
|
799
|
-
* the causing event's id (`causedBy`), so the split trail
|
|
913
|
+
* precisely what module code must never hold. Admin writes it makes through the `admin`
|
|
914
|
+
* it is handed are stamped with the causing event's id (`causedBy`), so the split trail
|
|
915
|
+
* joins. That `admin` is bound to this one event, never the host's own (#2055). A handler
|
|
916
|
+
* that writes for a person attributes from it — `admin.attributed(onBehalfOf)` — which keeps
|
|
917
|
+
* the event (#2069); a view of the HOST knows nothing of the event.
|
|
800
918
|
*
|
|
801
919
|
* A handler that decides an event must never be effected RETURNS `refuseDelivery(reason)`
|
|
802
920
|
* (#1184). The delivery is journaled terminal with the reason, never retried. A return
|
|
@@ -1409,6 +1527,11 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
1409
1527
|
* nothing, and it reads as coverage. A bound operation with no entry is
|
|
1410
1528
|
* allowed and means what it always meant — nothing was declared to parse.
|
|
1411
1529
|
*
|
|
1530
|
+
* **It also carries the declared surface (#119).** `operationInputsOf` records, for the frozen
|
|
1531
|
+
* map it returns, every declared operation and the entity each addresses by id. The host DERIVES
|
|
1532
|
+
* its trash refusal from that — nothing else is handed over — and a module with a trashable
|
|
1533
|
+
* entity must pass that map as returned, with every operation it binds declared in it.
|
|
1534
|
+
*
|
|
1412
1535
|
* Typed structurally rather than as `z.ZodType` so the kernel keeps its single
|
|
1413
1536
|
* dependency and no zod version is pinned by the scope-host contract. The
|
|
1414
1537
|
* shape is the whole surface the host uses: throw to refuse, return the value
|
|
@@ -1485,6 +1608,15 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
1485
1608
|
* contribute the same name.
|
|
1486
1609
|
*/
|
|
1487
1610
|
predicates?: Record<string, GuardPredicate>;
|
|
1611
|
+
/**
|
|
1612
|
+
* This module's own step in a subject erasure (#2068), for the rows its declared
|
|
1613
|
+
* `erasure: { subjects }` cannot reach — a link the row does not hold itself. Run by
|
|
1614
|
+
* `shredSubject` inside the erasure's one transaction, after the declared entities and
|
|
1615
|
+
* before the key is destroyed; a throw refuses the whole erasure and rolls it back.
|
|
1616
|
+
* Synchronous, idempotent, and handed a `sql` that reaches the module's own tables only
|
|
1617
|
+
* (`module-erasure.ts`). Requires `manifest.erasure`, whose `tables` is that reach.
|
|
1618
|
+
*/
|
|
1619
|
+
onSubjectErased?: OnSubjectErased;
|
|
1488
1620
|
}
|
|
1489
1621
|
/**
|
|
1490
1622
|
* Admin surface for enforcement input (design doc §4; control-plane.md §4.4).
|
|
@@ -1509,6 +1641,16 @@ export interface ModuleRegistration<C extends readonly EventContract[] = []> {
|
|
|
1509
1641
|
* stay sync — they are code-time bookkeeping, not control-plane state.)
|
|
1510
1642
|
*/
|
|
1511
1643
|
export interface HostAdmin {
|
|
1644
|
+
/**
|
|
1645
|
+
* This same admin, with every row written through it also naming `onBehalfOf` (#977, #2069),
|
|
1646
|
+
* and keeping whatever this one already carries — for the `admin` an executor handler is
|
|
1647
|
+
* handed, the event (`causedBy`). A handler that writes for a person attributes from that
|
|
1648
|
+
* `admin`, so the cause comes along by construction and there is no option to forget.
|
|
1649
|
+
*
|
|
1650
|
+
* Optional so an admin that predates it still satisfies the interface; a caller that finds it
|
|
1651
|
+
* absent writes through the admin it holds, unattributed to a person.
|
|
1652
|
+
*/
|
|
1653
|
+
attributed?(onBehalfOf: OnBehalfOf): HostAdmin;
|
|
1512
1654
|
defineRole(actor: PlatformActorId, tenantId: TenantId, role: RoleDefinition): Promise<void>;
|
|
1513
1655
|
/**
|
|
1514
1656
|
* Every role the directory holds, ordered by (tenantId, key).
|
|
@@ -1540,6 +1682,57 @@ export interface HostAdmin {
|
|
|
1540
1682
|
*/
|
|
1541
1683
|
unassignRole(actor: PlatformActorId, assignment: RoleAssignment): Promise<void>;
|
|
1542
1684
|
grant(actor: PlatformActorId, grant: CapabilityGrant): Promise<void>;
|
|
1685
|
+
/**
|
|
1686
|
+
* Give a person a vertical's declared entity-grant SHAPE on one entity (#2071): every key in
|
|
1687
|
+
* `permissions`, plus the marker that makes them a holder of the shape, so a key the shape
|
|
1688
|
+
* gains later reaches them at the next {@link reconcileEntityGrantShapes}. What a vertical
|
|
1689
|
+
* calls where it used to `grant` its `ENTITY_GRANTS` keys one at a time. It brings back a key
|
|
1690
|
+
* or marker a revoke tombstoned, as `grant` does, and leaves a live row as it is, expiry
|
|
1691
|
+
* included, as `ctx.grant` does. Audited as `grantEntityShape`.
|
|
1692
|
+
*/
|
|
1693
|
+
grantEntityShape(actor: PlatformActorId, grant: {
|
|
1694
|
+
principalId: PrincipalId;
|
|
1695
|
+
node: {
|
|
1696
|
+
tenantId: TenantId;
|
|
1697
|
+
scopeId: ScopeId;
|
|
1698
|
+
};
|
|
1699
|
+
entity: EntityRef;
|
|
1700
|
+
permissions: readonly PermissionKey[];
|
|
1701
|
+
grantedBy: PrincipalId;
|
|
1702
|
+
}): Promise<void>;
|
|
1703
|
+
/**
|
|
1704
|
+
* Top every holder of each declared shape up to the shape as it is now (#2071): a key the
|
|
1705
|
+
* shape gained is granted to each person holding the shape's marker on an entity of that
|
|
1706
|
+
* type. Never a key revoked from that person there (its K-21 tombstone stays). A key merely
|
|
1707
|
+
* dropped from the shape is left in place; a key the shape declares `retired` (#2082) is taken
|
|
1708
|
+
* back from every holder instead, its row tombstoned, once per scope — including a direct grant
|
|
1709
|
+
* of that key to the holder on the same entity, which is the same row. Until it is done on a scope, a
|
|
1710
|
+
* shape that declares a `holder` is also backfilled: each person its holder relationship names
|
|
1711
|
+
* as the owner of an entity (`'self'`, or the vertical's own column) is marked if they hold a
|
|
1712
|
+
* key of the shape there. Never inferred from key sets, so a grant delegated on someone else's
|
|
1713
|
+
* record is never marked. Without a `holder`, there is no backfill.
|
|
1714
|
+
*
|
|
1715
|
+
* Only shapes declared `bootstrap: true` are reconciled — those given with
|
|
1716
|
+
* {@link grantEntityShape} on a person's own record. A SHARING shape, reached through
|
|
1717
|
+
* `ctx.grant` (todo's `list`), is skipped whole: reconciling one would mark every person
|
|
1718
|
+
* something was fully shared with as a holder.
|
|
1719
|
+
*
|
|
1720
|
+
* Bounded: `batch` rows of work (default 500, an integer from 1 to 5000, or `validation_failed`)
|
|
1721
|
+
* per scope transaction, backfill included, repeated until a pass finishes — so it is safe
|
|
1722
|
+
* on a large scope, and finishes on a re-run if interrupted. Each (person, entity) topped up is
|
|
1723
|
+
* an `entity.grants-topped-up` event on the entity, and each one retired keys were taken from an
|
|
1724
|
+
* `entity.grants-retired` event. Audited as `reconcileEntityGrantShapes` when it changed
|
|
1725
|
+
* anything. Returns how many (person, entity) it topped up, and how many it retired keys from.
|
|
1726
|
+
*/
|
|
1727
|
+
reconcileEntityGrantShapes(actor: PlatformActorId, node: {
|
|
1728
|
+
tenantId: TenantId;
|
|
1729
|
+
scopeId: ScopeId;
|
|
1730
|
+
}, shapes: readonly EntityGrantShape[], opts?: {
|
|
1731
|
+
batch?: number;
|
|
1732
|
+
}): Promise<{
|
|
1733
|
+
toppedUp: number;
|
|
1734
|
+
retired: number;
|
|
1735
|
+
}>;
|
|
1543
1736
|
/** Grant to an organization (portal customers); members reach it via membership tuples. */
|
|
1544
1737
|
/**
|
|
1545
1738
|
* Grant a permission to a CONNECTION (#97) — how a connector is allowed to
|
|
@@ -1776,7 +1969,14 @@ export interface HostAdmin {
|
|
|
1776
1969
|
*/
|
|
1777
1970
|
revokeCapability(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, capabilityId: CapabilityId): Promise<void>;
|
|
1778
1971
|
grantToOrg(actor: PlatformActorId, orgId: OrgId, permission: PermissionKey, node: Node, entity?: EntityRef): Promise<void>;
|
|
1779
|
-
|
|
1972
|
+
/**
|
|
1973
|
+
* Make `principal` a member of the org. `opts.expiresAt` (#2047) makes the membership lapse
|
|
1974
|
+
* then, as any expiring tuple does; absent, it never lapses. Re-adding replaces the row,
|
|
1975
|
+
* clearing a tombstone and setting the expiry anew.
|
|
1976
|
+
*/
|
|
1977
|
+
addMember(actor: PlatformActorId, tenantId: TenantId, principal: PrincipalId, orgId: OrgId, opts?: {
|
|
1978
|
+
expiresAt?: Instant;
|
|
1979
|
+
}): Promise<void>;
|
|
1780
1980
|
/**
|
|
1781
1981
|
* One membership change, applied as ONE directory unit (#1184): a single SQLite transaction or
|
|
1782
1982
|
* a single synchronous ControlPlaneDO method, with no await inside it. In that unit it
|
|
@@ -1787,6 +1987,11 @@ export interface HostAdmin {
|
|
|
1787
1987
|
* does. A removal, a grant, a role redefinition or a demotion lands wholly before the unit
|
|
1788
1988
|
* (and governs it) or wholly after it, never between its check and its write.
|
|
1789
1989
|
*
|
|
1990
|
+
* With `orgId` (#2047) the org is joined or left in the same unit, and bounded there too:
|
|
1991
|
+
* `boundedBy` must be a live member of it. An add writes the membership — expiring no later
|
|
1992
|
+
* than `boundedBy`'s own — with an `addMember` audit row; a removal tombstones it, with a
|
|
1993
|
+
* `removeMember` row if it took anything. The role and the org apply together or not at all.
|
|
1994
|
+
*
|
|
1790
1995
|
* The fence is `_substrat_membership_fences`: every tenant-level `unassignRole` and every
|
|
1791
1996
|
* `removeMember` raises it for the person, in the same unit as its revoke — a no-op included,
|
|
1792
1997
|
* since a removal of someone whose add is still on its way must still win.
|
|
@@ -2214,6 +2419,17 @@ export interface HostAdmin {
|
|
|
2214
2419
|
* per-call path, before any staff member is involved.
|
|
2215
2420
|
*/
|
|
2216
2421
|
resolveVerticalInstance(tenantId: TenantId, vertical: string): Promise<VerticalResolution>;
|
|
2422
|
+
/** Resolve for one caller at execution time. An explicit stale binding refuses without fallback. */
|
|
2423
|
+
resolvePeerInstance(tenantId: TenantId, callerScopeId: ScopeId, vertical: string): Promise<PeerInstanceResolution>;
|
|
2424
|
+
/** Read the caller's explicit choice, if any. Tenant-scoped and recorded as an admin read. */
|
|
2425
|
+
peerBinding(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId, vertical: string): Promise<PeerBinding | undefined>;
|
|
2426
|
+
/** Every explicit target choice this calling scope currently holds. */
|
|
2427
|
+
peerBindings(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId): Promise<PeerBinding[]>;
|
|
2428
|
+
/** Bind or clear this caller's target. Both endpoints are checked in the directory at write. */
|
|
2429
|
+
setPeerBinding(actor: PlatformActorId, tenantId: TenantId, callerScopeId: ScopeId, vertical: string, targetScopeId: ScopeId | null): Promise<{
|
|
2430
|
+
changed: boolean;
|
|
2431
|
+
previous: ScopeId | null;
|
|
2432
|
+
}>;
|
|
2217
2433
|
/**
|
|
2218
2434
|
* Persist a tenant. Idempotent on the id — re-creating an existing tenant is a
|
|
2219
2435
|
* no-op, not an error (control-plane.md §4.1). `status` starts `active` and
|
|
@@ -2858,8 +3074,11 @@ export interface HostAdmin {
|
|
|
2858
3074
|
* the audit metadata — the rotate-side analogue of `createdBy` on create
|
|
2859
3075
|
* (§3.5.1). Omitted ⇒ the effecting actor stands alone, the platform-driven
|
|
2860
3076
|
* refresh path.
|
|
3077
|
+
*
|
|
3078
|
+
* `expiresAt`: a date replaces the connection's expiry, `null` clears it (the credential
|
|
3079
|
+
* that had one was replaced by one that has none), and omitted keeps it.
|
|
2861
3080
|
*/
|
|
2862
|
-
updateConnectionSecret(actor: PlatformActorId, id: ConnectionId, secret: ConnectionSecret, expiresAt?: string, opts?: {
|
|
3081
|
+
updateConnectionSecret(actor: PlatformActorId, id: ConnectionId, secret: ConnectionSecret, expiresAt?: string | null, opts?: {
|
|
2863
3082
|
rotatedBy?: string;
|
|
2864
3083
|
}): Promise<void>;
|
|
2865
3084
|
/**
|
|
@@ -2869,6 +3088,37 @@ export interface HostAdmin {
|
|
|
2869
3088
|
* constraint ignores revoked rows.
|
|
2870
3089
|
*/
|
|
2871
3090
|
revokeConnection(actor: PlatformActorId, id: ConnectionId): Promise<void>;
|
|
3091
|
+
/**
|
|
3092
|
+
* A vertical's MAILED connect link (connections.md §3.5.4) — the row that makes a consent
|
|
3093
|
+
* round single-use and revocable when it lives days rather than minutes. Beside the
|
|
3094
|
+
* connections because it is their front door: the round it starts ends in
|
|
3095
|
+
* `createConnection` (or a rotation) for the same tenant and scope.
|
|
3096
|
+
*
|
|
3097
|
+
* Every verb below is keyed by (tenant, scope): a link of another scope or tenant is
|
|
3098
|
+
* `undefined` / `unknown`, never a refusal that admits it exists. Expiry is judged against
|
|
3099
|
+
* the host's own clock. Mint, revoke, consume and restore are each audited — metadata
|
|
3100
|
+
* only, though nothing here is secret: the credential does not exist until a round is
|
|
3101
|
+
* consumed, and it never touches this row.
|
|
3102
|
+
*/
|
|
3103
|
+
mintConnectLink(actor: PlatformActorId, input: MintConnectLinkInput): Promise<ConnectLink>;
|
|
3104
|
+
getConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<ConnectLink | undefined>;
|
|
3105
|
+
/** Newest first. `outstandingOnly`: outstanding AND unexpired — the links that still open. */
|
|
3106
|
+
listConnectLinks(actor: PlatformActorId, filter: ConnectLinkFilter): Promise<ConnectLink[]>;
|
|
3107
|
+
/**
|
|
3108
|
+
* Outstanding → revoked. Idempotent: a revoked or used link is answered as it stands and
|
|
3109
|
+
* nothing is written. `undefined` when the scope holds no such link.
|
|
3110
|
+
*/
|
|
3111
|
+
revokeConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<ConnectLink | undefined>;
|
|
3112
|
+
/**
|
|
3113
|
+
* Outstanding and unexpired → used, recording what the consent attached. Atomic: of two
|
|
3114
|
+
* concurrent consumes exactly one answers `ok`. Otherwise the typed reason.
|
|
3115
|
+
*/
|
|
3116
|
+
consumeConnectLink(actor: PlatformActorId, input: ConsumeConnectLinkInput): Promise<ConnectLinkConsume>;
|
|
3117
|
+
/**
|
|
3118
|
+
* Used → outstanding, the best-effort undo after the credential store failed. Only a
|
|
3119
|
+
* used, unexpired link moves; `false` for anything else, a revoked link included.
|
|
3120
|
+
*/
|
|
3121
|
+
restoreConnectLink(actor: PlatformActorId, key: ConnectLinkKey): Promise<boolean>;
|
|
2872
3122
|
/**
|
|
2873
3123
|
* Open the credential for one (tenant, vertical, provider[, account]) — the
|
|
2874
3124
|
* connector's read, and the only path in the system that yields plaintext.
|
|
@@ -3058,6 +3308,42 @@ export interface HostAdmin {
|
|
|
3058
3308
|
* for an unknown fingerprint. Audited with the before/after status diff (K-33).
|
|
3059
3309
|
*/
|
|
3060
3310
|
setIssueStatus(actor: PlatformActorId, fingerprint: string, status: IssueStatusInput): Promise<IssueEntry | undefined>;
|
|
3311
|
+
/**
|
|
3312
|
+
* Findings (#1748), most recently seen first: the tenant-scoped inbox. `filter.tenantId`
|
|
3313
|
+
* absent is the staff fleet read; every tenant-facing caller passes it, and the HTTP surface
|
|
3314
|
+
* forces it from the principal. Access-logged (K-24). No cursor, for `listIssues`'s reason.
|
|
3315
|
+
*/
|
|
3316
|
+
listFindings(actor: PlatformActorId, filter?: FindingFilter): Promise<FindingEntry[]>;
|
|
3317
|
+
/**
|
|
3318
|
+
* A verdict on one of `tenantId`'s findings: acknowledge, resolve, or reopen. Keyed on the
|
|
3319
|
+
* tenant as well as the id, so another tenant's finding reads as unknown (undefined).
|
|
3320
|
+
* Audited with the before/after diff (K-33).
|
|
3321
|
+
*/
|
|
3322
|
+
setFindingStatus(actor: PlatformActorId, tenantId: TenantId, id: string, status: FindingStatusInput): Promise<FindingEntry | undefined>;
|
|
3323
|
+
/**
|
|
3324
|
+
* Suppress with a rule: a scope (kind / operation / code / subject) and an expiry of at most
|
|
3325
|
+
* `FINDING_RULE_MAX_DAYS`. The findings it covers now are suppressed at once; later
|
|
3326
|
+
* occurrences it covers are counted but keep their finding suppressed. Audited, naming how
|
|
3327
|
+
* many findings it suppressed.
|
|
3328
|
+
*/
|
|
3329
|
+
createFindingRule(actor: PlatformActorId, tenantId: TenantId, input: FindingRuleInput): Promise<{
|
|
3330
|
+
rule: FindingRuleEntry;
|
|
3331
|
+
suppressed: number;
|
|
3332
|
+
}>;
|
|
3333
|
+
/** End one of `tenantId`'s rules now. Undefined for an unknown rule. Audited. */
|
|
3334
|
+
revokeFindingRule(actor: PlatformActorId, tenantId: TenantId, ruleId: string): Promise<FindingRuleEntry | undefined>;
|
|
3335
|
+
/**
|
|
3336
|
+
* The findings retention pass (#1748) — `pruneFindings`: a quiet open or acked finding is
|
|
3337
|
+
* resolved as `stale` with a `resolveStaleFinding` audit row written in the same unit, never
|
|
3338
|
+
* deleted; closed findings and expired rules past the window are deleted, at most `limit` of
|
|
3339
|
+
* each per call. Optional so a host that predates findings degrades the sweep's phase to `null`.
|
|
3340
|
+
*/
|
|
3341
|
+
pruneFindings?(actor: PlatformActorId, limit: number): Promise<FindingPruneReport>;
|
|
3342
|
+
/** `tenantId`'s suppress rules, newest first; `active` keeps the unexpired ones. Access-logged. */
|
|
3343
|
+
listFindingRules(actor: PlatformActorId, tenantId: TenantId, filter?: {
|
|
3344
|
+
active?: boolean;
|
|
3345
|
+
limit?: number;
|
|
3346
|
+
}): Promise<FindingRuleEntry[]>;
|
|
3061
3347
|
/**
|
|
3062
3348
|
* Meter 3's ledger (#1054): one line per model call a vertical made through the
|
|
3063
3349
|
* platform's model host, drained here as a `model-usage` intent. Idempotent on the
|
|
@@ -3125,6 +3411,35 @@ export interface HostAdmin {
|
|
|
3125
3411
|
* cannot be written.
|
|
3126
3412
|
*/
|
|
3127
3413
|
recordCopyMark(actor: PlatformActorId, entry: CopyMarkAudit): Promise<void>;
|
|
3414
|
+
/**
|
|
3415
|
+
* Record one phase of a dashboard member change (#1150) — an invite, a role move or a removal
|
|
3416
|
+
* on an installed vertical's scope — as `manageScopeMember`. The change runs in the vertical's
|
|
3417
|
+
* deployment, so the control plane writes these around its call: `intent` first (and must not
|
|
3418
|
+
* call if this throws), then the outcome. `recordOwnerTransfer`'s shape. Throws when the row
|
|
3419
|
+
* cannot be written.
|
|
3420
|
+
*/
|
|
3421
|
+
recordMemberChange(actor: PlatformActorId, entry: MemberChangeAudit): Promise<void>;
|
|
3422
|
+
/** One intent or outcome of a tenant's peer-instance binding change (#1720). */
|
|
3423
|
+
recordPeerBindingChange(actor: PlatformActorId, entry: PeerBindingAudit): Promise<void>;
|
|
3424
|
+
/**
|
|
3425
|
+
* Close one audited change whose intent has no outcome (#2064): in ONE transaction, read the
|
|
3426
|
+
* intent `intentId` names, and if no outcome row exists for its operation, write an `unknown`
|
|
3427
|
+
* outcome carrying `error` and an ops-failure row (`audit.<action>`, stage `outcome-unknown`)
|
|
3428
|
+
* for the staff digest. Answers `true` when it wrote them, `false` when an outcome was already
|
|
3429
|
+
* there, so concurrent callers settle an operation once. Throws `not_found` for an id that is
|
|
3430
|
+
* not an audited-change intent. See `audit-outcome.ts` for why a later real outcome still wins.
|
|
3431
|
+
*/
|
|
3432
|
+
settleUnrecordedOutcome(actor: PlatformActorId, input: {
|
|
3433
|
+
intentId: string;
|
|
3434
|
+
error: string;
|
|
3435
|
+
}): Promise<boolean>;
|
|
3436
|
+
/**
|
|
3437
|
+
* Every admin-log row of the given audited operations (#2064), read through the operation-id
|
|
3438
|
+
* index in bounded batches — one statement per `AUDITED_OPERATIONS_BATCH` ids, never a scan of
|
|
3439
|
+
* the log. Only rows of the exact (action, operation, tenant, scope) asked about are returned.
|
|
3440
|
+
* Reading the audit trail is itself recorded, once per call.
|
|
3441
|
+
*/
|
|
3442
|
+
auditedOperations(actor: PlatformActorId, refs: readonly AuditedOperationRef[]): Promise<AuditedOperationRow[]>;
|
|
3128
3443
|
/**
|
|
3129
3444
|
* Stamp `drainedAt` on every not-yet-drained access row up to and including
|
|
3130
3445
|
* `upToId`, marking them shipped to Tier 2. Returns how many rows moved.
|
|
@@ -3583,6 +3898,12 @@ export declare function telemetryRetentionStatements(nowMs: number, limit: numbe
|
|
|
3583
3898
|
sql: string;
|
|
3584
3899
|
params: [string, number];
|
|
3585
3900
|
}[];
|
|
3901
|
+
/**
|
|
3902
|
+
* One bounded retention DELETE: at most `LIMIT ?` rows whose `column` is before `?`, oldest
|
|
3903
|
+
* first through the column's own index, `RETURNING 1` so the count is what was deleted. Takes
|
|
3904
|
+
* `(horizon, limit)`. The statement `telemetryRetentionStatements` and `pruneFindings` share.
|
|
3905
|
+
*/
|
|
3906
|
+
export declare function boundedRetentionDelete(table: string, column: string): string;
|
|
3586
3907
|
/** Filter for `listIssues` (#1233). Bounded by `limit` only — see the verb's doc. */
|
|
3587
3908
|
export interface IssueFilter {
|
|
3588
3909
|
status?: IssueStatus;
|
|
@@ -3763,8 +4084,24 @@ export interface ScopeHost {
|
|
|
3763
4084
|
*
|
|
3764
4085
|
* Optional so a host that predates it still satisfies the interface; a transport
|
|
3765
4086
|
* that finds it absent writes unattributed rows, which is what every row was before.
|
|
4087
|
+
*
|
|
4088
|
+
* A view of the host knows nothing of any event: an executor handler attributes from the
|
|
4089
|
+
* `admin` it is handed instead (`HostAdmin.attributed`), which keeps its `causedBy`.
|
|
3766
4090
|
*/
|
|
3767
4091
|
attributed?(onBehalfOf: OnBehalfOf): ScopeHost;
|
|
4092
|
+
/**
|
|
4093
|
+
* The same view, its rows also naming `options.causedBy` as the event they are the effect
|
|
4094
|
+
* of (K-22, #2055).
|
|
4095
|
+
*
|
|
4096
|
+
* @deprecated since #2069: an executor handler attributes from the `admin` it is handed —
|
|
4097
|
+
* `admin.attributed(onBehalfOf)` — which carries its event by construction. This form needs
|
|
4098
|
+
* the event passed again, and a handler that forgets writes `causedBy` NULL with nothing
|
|
4099
|
+
* failing. It stays because it shipped, and because it is how the membership executor still
|
|
4100
|
+
* attributes on a host whose handed admin predates `HostAdmin.attributed`.
|
|
4101
|
+
*/
|
|
4102
|
+
attributed?(onBehalfOf: OnBehalfOf, options?: {
|
|
4103
|
+
causedBy?: string;
|
|
4104
|
+
}): ScopeHost;
|
|
3768
4105
|
/**
|
|
3769
4106
|
* Mint a capability stub for a principal. Validates the (tenantId, scopeId)
|
|
3770
4107
|
* pair against the directory — a mismatched pair fails closed (K-3), it never
|
|
@@ -4190,6 +4527,31 @@ export interface ScopeHost {
|
|
|
4190
4527
|
* `canAssign`; an unknown role throws `not_found`.
|
|
4191
4528
|
*/
|
|
4192
4529
|
assignScopeRoleBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, assignee: PrincipalId, roleKey: string): Promise<Coverage>;
|
|
4530
|
+
/**
|
|
4531
|
+
* The scope's role roster (#1150): every live scope-level role assignment, one row per
|
|
4532
|
+
* (principal, role) — or one principal's, with `principal`. One scope, addressed by the caller
|
|
4533
|
+
* after its own (tenant, scope) check — K-3 is asserted here too — so it is a read of this
|
|
4534
|
+
* scope, never a walk across scopes.
|
|
4535
|
+
*/
|
|
4536
|
+
listScopeRoleHolders(tenantId: TenantId, scopeId: ScopeId, principal?: PrincipalId): Promise<ScopeRoleHolder[]>;
|
|
4537
|
+
/**
|
|
4538
|
+
* Move `principal` from scope role `from` to `to` in ONE scope task (#1150): the caller's
|
|
4539
|
+
* bound is asked over both roles (taking `from` away is bounded like granting it, §5.1
|
|
4540
|
+
* consequence 1), then `from` is tombstoned and `to` granted together. Nothing is written
|
|
4541
|
+
* on a refusal, so nobody is left holding both roles or neither. Throws `not_found` for a
|
|
4542
|
+
* `to` this tenant does not define, and `conflict` when `principal` does not hold `from`.
|
|
4543
|
+
*/
|
|
4544
|
+
changeScopeRoleBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, principal: PrincipalId, from: string, to: string): Promise<Coverage>;
|
|
4545
|
+
/**
|
|
4546
|
+
* Take every scope role `principal` holds, in ONE scope task (#1150), bounded by the
|
|
4547
|
+
* caller's authority over each (§5.1 consequence 1: you cannot strip what you could not
|
|
4548
|
+
* have granted). A role the tenant no longer defines confers nothing and is taken without a
|
|
4549
|
+
* bound. A refusal writes nothing; `revoked` names what was taken.
|
|
4550
|
+
*/
|
|
4551
|
+
revokeScopeRolesBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, principal: PrincipalId): Promise<{
|
|
4552
|
+
coverage: Coverage;
|
|
4553
|
+
revoked: string[];
|
|
4554
|
+
}>;
|
|
4193
4555
|
/**
|
|
4194
4556
|
* The recurring-work declarations of every module registered on this host (#383)
|
|
4195
4557
|
* — each module's id, the vertical it belongs to, and its `schedules`. Sync like
|
|
@@ -4442,6 +4804,11 @@ export interface ScopeHost {
|
|
|
4442
4804
|
lastError?: string | null;
|
|
4443
4805
|
/** WHO refused (#841). Omitted by a caller too old to attribute — stored as NULL. */
|
|
4444
4806
|
failure?: PlatformRequestFailure | null;
|
|
4807
|
+
/**
|
|
4808
|
+
* #2102: an outcome event written into the scope with the settle, in its transaction,
|
|
4809
|
+
* and only when this settle moves the row out of `pending` (`settlePlatformRequestIn`).
|
|
4810
|
+
*/
|
|
4811
|
+
event?: PlatformOutcomeEvent | null;
|
|
4445
4812
|
}): Promise<void>;
|
|
4446
4813
|
/**
|
|
4447
4814
|
* Live reads (#938) — a subscription to this scope's change feed, or absent.
|
|
@@ -4495,10 +4862,12 @@ export interface ScopeHost {
|
|
|
4495
4862
|
* changed at 14:02 is information about that row, so "the body was empty" is not a
|
|
4496
4863
|
* defence: the filter runs whether or not there is a payload to withhold.
|
|
4497
4864
|
*
|
|
4498
|
-
* A subscription may be narrowed `within` one entity (#1853), which only removes frames
|
|
4499
|
-
*
|
|
4500
|
-
*
|
|
4501
|
-
*
|
|
4865
|
+
* A subscription may be narrowed `within` one entity (#1853), which only removes frames.
|
|
4866
|
+
* Two built roots replace the per-row check with one about the ROOT, and their subscribers
|
|
4867
|
+
* are sent bare `LiveNudge` frames that name no entity: `checkedWithin`, whose principal must
|
|
4868
|
+
* pass a stated key on the root at the handshake and again on every pass that has something
|
|
4869
|
+
* to send (#938), and `vouchedWithin`, which the vertical asserts once and nothing re-checks
|
|
4870
|
+
* (#1853). Which to use is on `checkedWithin`.
|
|
4502
4871
|
*
|
|
4503
4872
|
* **Generic over the runtime's request and response, because the kernel names
|
|
4504
4873
|
* neither.** This package has one dependency and no DOM or workers lib
|
|
@@ -4551,10 +4920,51 @@ export interface LiveReadSurface<Req extends LiveUpgradeRequest = LiveUpgradeReq
|
|
|
4551
4920
|
* the same trust it already extends in naming `principal`, and the scope's walk is
|
|
4552
4921
|
* the whole filter. Such a subscriber receives `LiveNudge` frames only, which name
|
|
4553
4922
|
* no event type and no entity.
|
|
4923
|
+
* - **A `checkedWithin(…)` value replaces the per-row check with one on the root**
|
|
4924
|
+
* (#938). The principal must pass the stated key on the root, at the handshake (a
|
|
4925
|
+
* `403` otherwise) and again on every pass that has a row beneath it to announce,
|
|
4926
|
+
* so a grant withdrawn, or a root moved out from under it, closes the socket
|
|
4927
|
+
* instead of nudging it. `LiveNudge` frames only, as for a vouched root.
|
|
4928
|
+
*
|
|
4929
|
+
* **Freshness (Codex #2077 r5).** A decision is reused within a pass only while the
|
|
4930
|
+
* scope has had no write and nothing it holds has reached its `expires_at` since the
|
|
4931
|
+
* decision. It is never reused while the scope reads its permissions from the directory.
|
|
4932
|
+
* Both are checked against the clock immediately before each send. A change that lands
|
|
4933
|
+
* while a check is still being evaluated can let that one frame through. Frames name
|
|
4934
|
+
* nothing the subscriber could not read when checked, and the next frame, pass and poll
|
|
4935
|
+
* see the change: a push is a hint at most one evaluation stale.
|
|
4554
4936
|
*/
|
|
4555
|
-
within?: EntityRef | VouchedWithin;
|
|
4937
|
+
within?: EntityRef | VouchedWithin | CheckedWithin;
|
|
4938
|
+
/**
|
|
4939
|
+
* When the credential that proved `principal` stops being valid (ISO 8601), so the
|
|
4940
|
+
* socket never outlives it (#938). Pass the session's or the bearer's expiry
|
|
4941
|
+
* (`AuthSubject.expiresAt`). A handshake at or past it is refused, and the scope closes
|
|
4942
|
+
* the socket (`1008`) on its first pass at or past it, before sending anything on that
|
|
4943
|
+
* pass. Absent: the socket lives until either end closes it, as before.
|
|
4944
|
+
*/
|
|
4945
|
+
expiresAt?: string;
|
|
4556
4946
|
}): Promise<Res>;
|
|
4557
4947
|
}
|
|
4948
|
+
/**
|
|
4949
|
+
* The close codes a live socket ends with, so a client can tell "try again" from "poll".
|
|
4950
|
+
*
|
|
4951
|
+
* - `1008` (policy): the subscriber may no longer watch what it subscribed to — a
|
|
4952
|
+
* `checkedWithin` gate refused, or the session that opened it ended. A reconnect meets
|
|
4953
|
+
* the handshake's own refusal.
|
|
4954
|
+
* - `4429`: this principal already holds `LIVE_SOCKETS_PER_PRINCIPAL` sockets on the scope.
|
|
4955
|
+
* Not a reason to retry: the client should poll, which it does anyway, and stop asking.
|
|
4956
|
+
*/
|
|
4957
|
+
export declare const LIVE_CLOSE: {
|
|
4958
|
+
readonly revoked: 1008;
|
|
4959
|
+
readonly tooMany: 4429;
|
|
4960
|
+
};
|
|
4961
|
+
/**
|
|
4962
|
+
* How many live sockets one principal may hold on one scope (#938). A tab holds one per
|
|
4963
|
+
* feed (the desk's, and one per open conversation in ticket0's portal), and each socket is
|
|
4964
|
+
* work on every post-commit pass, so a principal opening more is a cost on everybody else
|
|
4965
|
+
* writing to the scope. Eight covers several tabs; past it a socket is closed `4429`.
|
|
4966
|
+
*/
|
|
4967
|
+
export declare const LIVE_SOCKETS_PER_PRINCIPAL = 8;
|
|
4558
4968
|
/** The brand only `vouchedWithin` can apply — a literal cannot type-check as one. */
|
|
4559
4969
|
declare const vouchedBrand: unique symbol;
|
|
4560
4970
|
/**
|
|
@@ -4586,6 +4996,50 @@ export declare function vouchedWithin(entity: EntityRef, opts: {
|
|
|
4586
4996
|
}): VouchedWithin;
|
|
4587
4997
|
/** Was this value built by `vouchedWithin`? A host asks before it drops the principal's check. */
|
|
4588
4998
|
export declare function isVouchedWithin(value: unknown): value is VouchedWithin;
|
|
4999
|
+
/** The brand only `checkedWithin` can apply. */
|
|
5000
|
+
declare const checkedBrand: unique symbol;
|
|
5001
|
+
/**
|
|
5002
|
+
* A `within` root the PRINCIPAL must be allowed `permission` on, re-checked for as long
|
|
5003
|
+
* as the socket is open (#938). Built only by `checkedWithin`.
|
|
5004
|
+
*/
|
|
5005
|
+
export interface CheckedWithin {
|
|
5006
|
+
readonly [checkedBrand]: true;
|
|
5007
|
+
readonly entity: EntityRef;
|
|
5008
|
+
readonly permission: PermissionKey;
|
|
5009
|
+
}
|
|
5010
|
+
/**
|
|
5011
|
+
* Watch `entity` and everything beneath it, for as long as the subscriber may
|
|
5012
|
+
* `permission` on `entity` itself.
|
|
5013
|
+
*
|
|
5014
|
+
* For a subscriber whose grant reaches the ROOT but not each row under it the way a
|
|
5015
|
+
* `liveTargets` key would: ticket0's portal customer holds `conversation:read-own` on
|
|
5016
|
+
* their contact, which reaches the conversation's public thread, while the per-row key
|
|
5017
|
+
* on a message is the staff read. Rooted at an entity whose subtree is exactly what that
|
|
5018
|
+
* key's read returns, the walk is the row filter and the key is the gate.
|
|
5019
|
+
*
|
|
5020
|
+
* The gate is asked at the handshake (`403` if it refuses) and again, once per socket,
|
|
5021
|
+
* on every post-commit pass that has a row beneath the root to announce. A refusal or a
|
|
5022
|
+
* check that throws closes the socket (`1008`) and sends nothing on that pass, so a
|
|
5023
|
+
* revoked grant, a principal who is gone, or a root relinked out of the grant's reach
|
|
5024
|
+
* ends the subscription instead of nudging it, and the client's reconnect meets the
|
|
5025
|
+
* handshake's `403`. A pass with nothing beneath the root asks nothing, so an idle
|
|
5026
|
+
* subscription costs no checks.
|
|
5027
|
+
*
|
|
5028
|
+
* **`checkedWithin` or `vouchedWithin`?** Use `checkedWithin` whenever the subscriber is
|
|
5029
|
+
* a principal with a grant that reaches the root: authority leaves with the grant. Use
|
|
5030
|
+
* `vouchedWithin` only when there is no such grant to ask, as for ticket0's widget
|
|
5031
|
+
* visitor, who holds a session token rather than a principal of their own. A vouched
|
|
5032
|
+
* socket is asserted once, so a token revoked mid-socket keeps receiving content-free
|
|
5033
|
+
* nudges until the socket closes. That is known and accepted for the widget: the nudge
|
|
5034
|
+
* names nothing, and every re-read it causes is checked again.
|
|
5035
|
+
*
|
|
5036
|
+
* Both send `LiveNudge` frames only: the subscriber may not read each row, so it is
|
|
5037
|
+
* never told which one changed. Only rows of a type some module declared in
|
|
5038
|
+
* `liveTargets` are announced, as for every other subscriber.
|
|
5039
|
+
*/
|
|
5040
|
+
export declare function checkedWithin(entity: EntityRef, permission: string): CheckedWithin;
|
|
5041
|
+
/** Was this value built by `checkedWithin`? */
|
|
5042
|
+
export declare function isCheckedWithin(value: unknown): value is CheckedWithin;
|
|
4589
5043
|
/**
|
|
4590
5044
|
* Is this request asking to be upgraded to a WebSocket?
|
|
4591
5045
|
*
|
|
@@ -4646,12 +5100,13 @@ export interface LiveChange {
|
|
|
4646
5100
|
readonly at: string;
|
|
4647
5101
|
}
|
|
4648
5102
|
/**
|
|
4649
|
-
* What a vouched subscriber is told (#1853): something beneath its root
|
|
4650
|
-
* nothing else.
|
|
5103
|
+
* What a vouched or checked subscriber is told (#1853, #938): something beneath its root
|
|
5104
|
+
* changed, and nothing else.
|
|
4651
5105
|
*
|
|
4652
|
-
* No event type and no entity, deliberately.
|
|
4653
|
-
* entities its frames are about — the vertical vouched for the ROOT,
|
|
4654
|
-
* cannot know which rows under it the vertical's
|
|
5106
|
+
* No event type and no entity, deliberately. Such a subscriber holds no read on the
|
|
5107
|
+
* entities its frames are about — the vertical vouched for the ROOT, or the principal was
|
|
5108
|
+
* checked on the root alone, and the scope cannot know which rows under it the vertical's
|
|
5109
|
+
* own read would show. Naming the type
|
|
4655
5110
|
* or the id would tell it what it never asked to read. The client re-reads, as it does
|
|
4656
5111
|
* on a `LiveChange`.
|
|
4657
5112
|
*/
|