@substrat-run/kernel 0.137.0 → 0.139.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/attachment-extractor.d.ts +9 -1
- package/dist/attachment-extractor.d.ts.map +1 -1
- package/dist/attachment-extractor.js +21 -1
- 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/delivery-refusal.d.ts +21 -0
- package/dist/delivery-refusal.d.ts.map +1 -0
- package/dist/delivery-refusal.js +21 -0
- package/dist/delivery-refusal.js.map +1 -0
- package/dist/entity-state-reads.d.ts +43 -0
- package/dist/entity-state-reads.d.ts.map +1 -0
- package/dist/entity-state-reads.js +135 -0
- package/dist/entity-state-reads.js.map +1 -0
- package/dist/entity-state.d.ts +177 -0
- package/dist/entity-state.d.ts.map +1 -0
- package/dist/entity-state.js +345 -0
- package/dist/entity-state.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/index.d.ts +30 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -14
- package/dist/index.js.map +1 -1
- package/dist/job-run.d.ts +309 -48
- package/dist/job-run.d.ts.map +1 -1
- package/dist/job-run.js +388 -80
- package/dist/job-run.js.map +1 -1
- package/dist/list-index.d.ts +25 -3
- package/dist/list-index.d.ts.map +1 -1
- package/dist/list-index.js +70 -26
- package/dist/list-index.js.map +1 -1
- package/dist/membership-executor.d.ts +109 -0
- package/dist/membership-executor.d.ts.map +1 -0
- package/dist/membership-executor.js +261 -0
- package/dist/membership-executor.js.map +1 -0
- package/dist/membership-fence.d.ts +57 -0
- package/dist/membership-fence.d.ts.map +1 -0
- package/dist/membership-fence.js +90 -0
- package/dist/membership-fence.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/operation-series.d.ts +58 -0
- package/dist/operation-series.d.ts.map +1 -0
- package/dist/operation-series.js +63 -0
- package/dist/operation-series.js.map +1 -0
- package/dist/peer.d.ts +13 -8
- package/dist/peer.d.ts.map +1 -1
- package/dist/peer.js +13 -14
- package/dist/peer.js.map +1 -1
- package/dist/permission-eval.d.ts +80 -1
- package/dist/permission-eval.d.ts.map +1 -1
- package/dist/permission-eval.js +164 -18
- package/dist/permission-eval.js.map +1 -1
- package/dist/platform-sweep.d.ts +7 -1
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +12 -0
- package/dist/platform-sweep.js.map +1 -1
- package/dist/read-only-sql.d.ts +2 -26
- package/dist/read-only-sql.d.ts.map +1 -1
- package/dist/read-only-sql.js +13 -9
- package/dist/read-only-sql.js.map +1 -1
- package/dist/scope-copy.d.ts +18 -0
- package/dist/scope-copy.d.ts.map +1 -1
- package/dist/scope-copy.js +18 -0
- package/dist/scope-copy.js.map +1 -1
- package/dist/scope-host.d.ts +416 -13
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +51 -2
- 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 +13 -1
- package/dist/search-index.d.ts.map +1 -1
- package/dist/search-index.js +9 -16
- package/dist/search-index.js.map +1 -1
- package/dist/spine-guard.d.ts +64 -2
- package/dist/spine-guard.d.ts.map +1 -1
- package/dist/spine-guard.js +345 -10
- package/dist/spine-guard.js.map +1 -1
- package/dist/spine-restore.d.ts +7 -2
- package/dist/spine-restore.d.ts.map +1 -1
- package/dist/spine-restore.js +13 -5
- 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/system-switch-record.d.ts +277 -77
- package/dist/system-switch-record.d.ts.map +1 -1
- package/dist/system-switch-record.js +322 -105
- package/dist/system-switch-record.js.map +1 -1
- package/dist/system-switch.d.ts +110 -14
- package/dist/system-switch.d.ts.map +1 -1
- package/dist/system-switch.js +92 -17
- package/dist/system-switch.js.map +1 -1
- package/dist/timeline.d.ts +23 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +30 -0
- package/dist/timeline.js.map +1 -1
- package/dist/ulid.d.ts +5 -0
- package/dist/ulid.d.ts.map +1 -1
- package/dist/ulid.js +9 -0
- package/dist/ulid.js.map +1 -1
- package/dist/vertical-events.d.ts +7 -1
- package/dist/vertical-events.d.ts.map +1 -1
- package/dist/vertical-events.js +14 -0
- package/dist/vertical-events.js.map +1 -1
- package/package.json +4 -4
package/dist/scope-host.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ModuleLog } from './module-log.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DeliveryRefusal } from './delivery-refusal.js';
|
|
3
|
+
import type { ScopeRoleHolder } from './scope-role-admin.js';
|
|
4
|
+
import type { OnBehalfOf, ExportReadInput, ExportedBatch, ImportBatch, ImportedEvent, ImportResult, ImportState, ImportCursorMove, ImportCursorMoved, AdminAction, BecomeCapabilityInput, CapabilityExchange, CapabilityFilter, CapabilityId, CapabilityPage, MintedCapability, ListPage, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, DelegatedReadRecord, OwnerTransferAudit, MemberChangeAudit, CopyMarkAudit, BindHostnameInput, AdminLogEntry, OpsFailureEntry, PlatformRequestDrainTotalsEntry, SweepRunEntry, SweepRunKind, SweepRunOutcome, ModelUsageEntry, ModelUsageSummary, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequest, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, PeerGrantsStatusEntry, SystemGrantsStatusEntry, SystemSwitchRecord, SystemSwitch, SystemSwitchResult, PeerCoverage, PeerSwitch, PeerSwitchResult, VerticalCaller, VerticalResolution, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ExportBreak, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, BindAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, IdentityMembership, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, RefusalFilter, RefusalRecord, PermissionDenial, Coverage, BeginImpersonationInput, ImpersonationFilter, ImpersonationSession, ImpersonationSessionId, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage, FreshnessSpec, EntityHistoryInput, EventFacetInput, EventCauseInput, EventEffectsInput, EffectsTree, InvocationEventsInput, InvocationEvents, DeadLettersInput, DeadLetter, LifecycleFlowInput, LifecycleFlowResult, OperationSeriesInput, OperationSeriesResult, CauseChain, EventFacetResult, HistoryEntry, ErrorCode, PlatformRequestFailureOrigin, IssueEntry, IssueStatus, IssueStatusInput, FindingEntry, FindingFilter, FindingRuleEntry, FindingRuleInput, FindingStatusInput, DeclaredMigration, CheckSubject } from '@substrat-run/contracts';
|
|
3
5
|
import type { ConnectionUseOutcome } from './connector-calls.js';
|
|
4
6
|
import type { CapabilityVerbs } from './capability.js';
|
|
7
|
+
import { type EntityStateName } from '@substrat-run/contracts';
|
|
5
8
|
import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
|
|
9
|
+
import type { FindingPruneReport } from './findings.js';
|
|
6
10
|
import type { SealedSecret } from './secret-box.js';
|
|
7
11
|
import type { SystemSwitchReassert, SystemSwitchReassertOptions, SystemSwitchRecordFilter } from './system-switch-record.js';
|
|
8
12
|
import type { SearchHit, SearchOptions } from './search-index.js';
|
|
@@ -31,6 +35,11 @@ export interface PageParams {
|
|
|
31
35
|
readonly filters?: Readonly<Record<string, unknown>>;
|
|
32
36
|
/** Also count the filtered set — the declaration's `total`, passed through. */
|
|
33
37
|
readonly total?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Which rows of an archivable entity (#119): `active` when unset, `archived` to read the
|
|
40
|
+
* archive. Refused for an entity that declares no archive. The bin is `ctx.pageTrashed`.
|
|
41
|
+
*/
|
|
42
|
+
readonly view?: Exclude<EntityStateName, 'trashed'>;
|
|
34
43
|
}
|
|
35
44
|
/**
|
|
36
45
|
* The scope-host contract — the adapter seam (§5.1 of the design doc).
|
|
@@ -338,6 +347,13 @@ export interface OperationContext {
|
|
|
338
347
|
* - `from` must be a live parent edge of `child`; `conflict` otherwise. Every other
|
|
339
348
|
* parent a multi-parent entity has is left alone.
|
|
340
349
|
* - `from` equal to `to` is a no-op: nothing written, nothing emitted.
|
|
350
|
+
* - A `to` that is ALREADY a live parent of `child` is the way to detach one parent of a
|
|
351
|
+
* multi-parent child (#2044): the `from` edge is tombstoned, the live `to` edge stays
|
|
352
|
+
* live — no second row, no revive, no `entity.linked` — and `entity.relinked` is still
|
|
353
|
+
* emitted. Like every edge `link` or `relink` leaves live, `to` is left PERMANENT: a
|
|
354
|
+
* future expiry on it (which only a restored dump can hold) is cleared, silently. The
|
|
355
|
+
* child keeps every other parent untouched, expiry included. The contract suite holds
|
|
356
|
+
* both adapters to it.
|
|
341
357
|
* - The old edge is tombstoned (K-21), not deleted, and one `entity.relinked` spine event
|
|
342
358
|
* records the move on the child's timeline, stamped like any event the operation emits.
|
|
343
359
|
* - Transactional with the operation: a relink whose operation throws never happened.
|
|
@@ -353,6 +369,56 @@ export interface OperationContext {
|
|
|
353
369
|
* ```
|
|
354
370
|
*/
|
|
355
371
|
relink(child: EntityRef, from: EntityRef, to: EntityRef): void;
|
|
372
|
+
/**
|
|
373
|
+
* Archive an entity (#119): hide it from active views and keep it. Only an entity whose
|
|
374
|
+
* model declares `archive: { permission }` can be archived.
|
|
375
|
+
*
|
|
376
|
+
* - **Checks the declared archive key on the entity itself** — unlike `link`, because the
|
|
377
|
+
* key is declared beside the entity and an operation archiving under another key is the
|
|
378
|
+
* one mistake this exists to make impossible. A denial throws `PermissionDenied`.
|
|
379
|
+
* - Only an `active` entity: anything else is `conflict` (`reason: 'invalid_transition'`);
|
|
380
|
+
* a missing one is `not_found`.
|
|
381
|
+
* - Stamps `_substrat_archived_at` with the operation's instant and emits one kernel-authored
|
|
382
|
+
* `entity.archived`, with the actor and the authorization chain on its envelope.
|
|
383
|
+
* - Transactional with the operation, like every other write.
|
|
384
|
+
*
|
|
385
|
+
* Resolves to the state the entity is now in.
|
|
386
|
+
*/
|
|
387
|
+
archive(entity: EntityRef): Promise<EntityStateName>;
|
|
388
|
+
/** Bring an archived entity back to active. Same key, refusals and recording as `archive`. */
|
|
389
|
+
unarchive(entity: EntityRef): Promise<EntityStateName>;
|
|
390
|
+
/**
|
|
391
|
+
* Move an entity to the trash (#119) — a reversible delete. Needs `trash: { permission }`
|
|
392
|
+
* on the entity, and checks that key on it. An `active` or an `archived` entity may be
|
|
393
|
+
* trashed; the archive survives the trip, so a `restore` returns it to the archive.
|
|
394
|
+
* Emits `entity.trashed`.
|
|
395
|
+
*/
|
|
396
|
+
trash(entity: EntityRef): Promise<EntityStateName>;
|
|
397
|
+
/**
|
|
398
|
+
* Take an entity out of the trash, back to the state it was trashed from — `archived` if it
|
|
399
|
+
* was archived, `active` otherwise. Checks the trash key. Emits `entity.restored`, whose
|
|
400
|
+
* payload's `to` says which — and so does the value it resolves to.
|
|
401
|
+
*/
|
|
402
|
+
restore(entity: EntityRef): Promise<EntityStateName>;
|
|
403
|
+
/**
|
|
404
|
+
* The state of one archivable/trashable entity — `active`, `archived` or `trashed` — or
|
|
405
|
+
* `null` when the row does not exist. What a get-by-id reads before deciding what to answer:
|
|
406
|
+
* the kernel filters the reads it composes, never a handler's own `SELECT`.
|
|
407
|
+
*
|
|
408
|
+
* Checks no permission, like every read on `ctx`. Throws `validation_failed` for an entity
|
|
409
|
+
* type that declares neither.
|
|
410
|
+
*/
|
|
411
|
+
entityState(entity: EntityRef): EntityStateName | null;
|
|
412
|
+
/**
|
|
413
|
+
* One page of the TRASH of a declared entity (#119) — `ctx.page` over the trashed rows,
|
|
414
|
+
* with the declared trash key checked on EACH row inside the kernel, so a handler cannot
|
|
415
|
+
* forget it. A row the caller may not see in the bin is left out, which can make a page
|
|
416
|
+
* short; the walk still ends only at a null `nextCursor`. No `total`: a count over rows the
|
|
417
|
+
* caller cannot see would disclose them.
|
|
418
|
+
*/
|
|
419
|
+
pageTrashed<T>(entityType: string, params: Omit<PageParams, 'view' | 'total'>): Promise<Page<T>>;
|
|
420
|
+
/** `ctx.search` over the trash, the declared trash key checked per hit (#119). */
|
|
421
|
+
searchTrashed(entityType: string, term: string, options?: Omit<SearchOptions, 'view'>): Promise<SearchHit[]>;
|
|
356
422
|
/**
|
|
357
423
|
* Narrow a permission the CALLER ALREADY HOLDS onto one entity — how an app
|
|
358
424
|
* expresses user-initiated sharing.
|
|
@@ -593,6 +659,18 @@ export interface InvokeOptions {
|
|
|
593
659
|
* uncapped count, so a reader can tell a short list from a truncated one.
|
|
594
660
|
*/
|
|
595
661
|
readonly onEmitted?: (report: EmittedReport) => void;
|
|
662
|
+
/**
|
|
663
|
+
* Called after the operation COMMITS and its executors ran inline (#1184), with what each
|
|
664
|
+
* delivery of this call's events did. The inline path is the common case of K-22 §4.2,
|
|
665
|
+
* so the request holder is the one who can tell a person their accept was refused rather
|
|
666
|
+
* than report success for an effect that never happened.
|
|
667
|
+
*
|
|
668
|
+
* Every delivery the call's post-commit tail attempted, which can include an earlier
|
|
669
|
+
* call's retry that came due: a caller picks out its own by `eventType` and `entity`.
|
|
670
|
+
* Never called for a rolled-back operation, a read-only session or an
|
|
671
|
+
* idempotent replay. Absent from a host that predates it — read that as "not reported".
|
|
672
|
+
*/
|
|
673
|
+
readonly onExecutorOutcomes?: (outcomes: readonly ExecutorOutcome[]) => void;
|
|
596
674
|
}
|
|
597
675
|
/** How many of an invocation's own events `onEmitted` names (#1746). `total` is uncapped. */
|
|
598
676
|
export declare const EMITTED_REPORT_CAP = 20;
|
|
@@ -723,6 +801,62 @@ export type ConsumerHandler = (ctx: OperationContext, event: DomainEvent) => voi
|
|
|
723
801
|
* code, and the version you declared is the only one you will be handed.
|
|
724
802
|
*/
|
|
725
803
|
export type ImportHandler = (ctx: OperationContext, event: ImportedEvent) => void | Promise<void>;
|
|
804
|
+
/**
|
|
805
|
+
* One membership change for `HostAdmin.applyMembership` (#1184): a TENANT-level role assigned
|
|
806
|
+
* or taken away and, when `orgId` is given (#2047), the org joined or left in the same unit.
|
|
807
|
+
*/
|
|
808
|
+
export type MembershipChange = {
|
|
809
|
+
tenantId: TenantId;
|
|
810
|
+
principal: PrincipalId;
|
|
811
|
+
/** The tenant-level role assigned or taken away. */
|
|
812
|
+
roleKey: string;
|
|
813
|
+
/**
|
|
814
|
+
* Whose authority bounds the write (§5.1) — the inviter of an add, the remover of a removal:
|
|
815
|
+
* the unit applies nothing unless this principal covers every permission `roleKey` carries,
|
|
816
|
+
* read inside the unit (`tenantCoverage`).
|
|
817
|
+
*/
|
|
818
|
+
boundedBy: PrincipalId;
|
|
819
|
+
/**
|
|
820
|
+
* The org joined (an add) or left (a removal) beside the role (#2047). Bounded by membership:
|
|
821
|
+
* the unit applies nothing unless `boundedBy` is a live member of it, read inside the unit
|
|
822
|
+
* (`liveOrgMembership`). A member holds everything the org confers — its grants in each
|
|
823
|
+
* scope's own store included — so that is the whole bound, and no scope is read. A join's
|
|
824
|
+
* membership expires no later than `boundedBy`'s own (`joinedMembershipExpiry`). Absent, the
|
|
825
|
+
* change is the role alone.
|
|
826
|
+
*/
|
|
827
|
+
orgId?: OrgId;
|
|
828
|
+
} & ({
|
|
829
|
+
op: 'add';
|
|
830
|
+
/** Apply nothing when `principal`'s removal fence stands at or after this instant. */
|
|
831
|
+
unlessRemovedSince: Instant;
|
|
832
|
+
} | {
|
|
833
|
+
op: 'remove';
|
|
834
|
+
});
|
|
835
|
+
/**
|
|
836
|
+
* What the unit did: applied, or why not — fenced by a removal (an add only), out of the role's
|
|
837
|
+
* bound, or out of the org's (#2047: `boundedBy` is no live member of it). A removal that
|
|
838
|
+
* applied says whether it took anything (`changed`); one that took nothing still raised the
|
|
839
|
+
* fence.
|
|
840
|
+
*/
|
|
841
|
+
export type MembershipChangeResult = {
|
|
842
|
+
applied: true;
|
|
843
|
+
changed?: boolean;
|
|
844
|
+
} | {
|
|
845
|
+
applied: false;
|
|
846
|
+
removedAt: string;
|
|
847
|
+
} | {
|
|
848
|
+
applied: false;
|
|
849
|
+
missing: PermissionKey[];
|
|
850
|
+
} | {
|
|
851
|
+
applied: false;
|
|
852
|
+
unknownRole: string;
|
|
853
|
+
} | {
|
|
854
|
+
applied: false;
|
|
855
|
+
unknownOrg: OrgId;
|
|
856
|
+
} | {
|
|
857
|
+
applied: false;
|
|
858
|
+
notMember: OrgId;
|
|
859
|
+
};
|
|
726
860
|
/**
|
|
727
861
|
* An **executor**: out-of-band host code that effects, outside a scope, what a module
|
|
728
862
|
* asked for inside one (K-22 §4.2; D-18's triage rule — effects on the outside world
|
|
@@ -740,10 +874,58 @@ export type ImportHandler = (ctx: OperationContext, event: ImportedEvent) => voi
|
|
|
740
874
|
* consumers must.
|
|
741
875
|
*
|
|
742
876
|
* It receives `HostAdmin`, not `ctx`: it acts with platform authority, which is
|
|
743
|
-
* precisely what module code must never hold. Admin writes it makes
|
|
744
|
-
* the causing event's id (`causedBy`), so the split trail
|
|
877
|
+
* precisely what module code must never hold. Admin writes it makes through the `admin`
|
|
878
|
+
* it is handed are stamped with the causing event's id (`causedBy`), so the split trail
|
|
879
|
+
* joins. That `admin` is bound to this one event, never the host's own: a handler that
|
|
880
|
+
* writes through the host instead — `host.attributed(…)` — passes `{ causedBy: event.id }`
|
|
881
|
+
* itself (#2055).
|
|
882
|
+
*
|
|
883
|
+
* A handler that decides an event must never be effected RETURNS `refuseDelivery(reason)`
|
|
884
|
+
* (#1184). The delivery is journaled terminal with the reason, never retried. A return
|
|
885
|
+
* value rather than a thrown error, so module code cannot produce one: the most an
|
|
886
|
+
* operation can do is throw, and a throw is retried like any other failure.
|
|
887
|
+
*/
|
|
888
|
+
export type ExecutorHandler = (admin: HostAdmin, event: DomainEvent, scope: ExecutorScope) => void | DeliveryRefusal | Promise<void | DeliveryRefusal>;
|
|
889
|
+
/**
|
|
890
|
+
* The scope an executor's event came from, as the executor may read it (#1184).
|
|
891
|
+
*
|
|
892
|
+
* Here rather than through `HostAdmin` or the host, and the pure adapter is why: its
|
|
893
|
+
* handler runs INSIDE the scope's actor task, and every host read of a scope re-enqueues
|
|
894
|
+
* on that actor, so `host.canAssign` from a handler waits on the task holding it and never
|
|
895
|
+
* returns. The adapter builds this to suit where the handler runs, as it builds a
|
|
896
|
+
* connector's `openAttachment`.
|
|
897
|
+
*
|
|
898
|
+
* Reads only, and checks no permission: the caller is host code that already holds
|
|
899
|
+
* platform authority. Both answer about the event's own (tenant, scope), never another.
|
|
900
|
+
*/
|
|
901
|
+
export interface ExecutorScope {
|
|
902
|
+
/** One entity's history in this scope — `readHistory`'s answer, oldest first. */
|
|
903
|
+
history(entity: EntityRef, page?: ListPage): Promise<Page<HistoryEntry>>;
|
|
904
|
+
/**
|
|
905
|
+
* May `principal` confer `roleKey` at this scope's node, or at the tenant node
|
|
906
|
+
* (`level: 'tenant'`)? The K-21 set comparison `ctx.canAssign` answers, narrowing-aware,
|
|
907
|
+
* over the tenant's projected role. Throws `unknownRoleError` for a role this tenant does
|
|
908
|
+
* not define.
|
|
909
|
+
*/
|
|
910
|
+
covers(principal: PrincipalId, roleKey: string, level: 'scope' | 'tenant'): Promise<Coverage>;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* What one executor delivery did inside the call that emitted its event (#1184) — what
|
|
914
|
+
* `InvokeOptions.onExecutorOutcomes` reports. `refused` is terminal by the handler's own
|
|
915
|
+
* decision; `dead-lettered` is terminal by exhaustion; `retrying` will be tried again.
|
|
745
916
|
*/
|
|
746
|
-
export
|
|
917
|
+
export interface ExecutorOutcome {
|
|
918
|
+
readonly executorId: string;
|
|
919
|
+
readonly eventId: string;
|
|
920
|
+
readonly eventType: string;
|
|
921
|
+
/** The event's entity, as `<entityType>:<entityId>` — how a caller finds its own event. */
|
|
922
|
+
readonly entity: string;
|
|
923
|
+
readonly outcome: 'delivered' | 'retrying' | 'dead-lettered' | 'refused' | 'inert' | 'routed';
|
|
924
|
+
/** The refusal reason, or the failure's message. Absent on a delivery. */
|
|
925
|
+
readonly error?: string;
|
|
926
|
+
}
|
|
927
|
+
/** One delivery's `ExecutorOutcome` — the one builder both adapters report through. */
|
|
928
|
+
export declare function executorOutcomeOf(executorId: string, event: DomainEvent, outcome: ExecutorOutcome['outcome'], error?: unknown): ExecutorOutcome;
|
|
747
929
|
/**
|
|
748
930
|
* How hard the host tries before it gives up on one delivery (#100).
|
|
749
931
|
*
|
|
@@ -1433,6 +1615,10 @@ export interface HostAdmin {
|
|
|
1433
1615
|
* role that was never assigned (or already revoked) is a silent no-op. Takes a
|
|
1434
1616
|
* `PlatformActorId` like every admin mutation — the caller's own authority to do
|
|
1435
1617
|
* this is decided above the kernel (e.g. the dashboard's manage-members check).
|
|
1618
|
+
*
|
|
1619
|
+
* At the TENANT node it also raises the principal's removal fence (#1184), in the same
|
|
1620
|
+
* unit as the revoke and whether or not anything was held, so a membership-executor add
|
|
1621
|
+
* still on its way for them is refused. The no-op stays unaudited.
|
|
1436
1622
|
*/
|
|
1437
1623
|
unassignRole(actor: PlatformActorId, assignment: RoleAssignment): Promise<void>;
|
|
1438
1624
|
grant(actor: PlatformActorId, grant: CapabilityGrant): Promise<void>;
|
|
@@ -1563,6 +1749,22 @@ export interface HostAdmin {
|
|
|
1563
1749
|
* `moduleIds`. Access-logged (K-24), as the fleet read is.
|
|
1564
1750
|
*/
|
|
1565
1751
|
tenantHeldSystemModules(actor: PlatformActorId, tenantId: TenantId, moduleIds: readonly ModuleId[]): Promise<ModuleId[]>;
|
|
1752
|
+
/**
|
|
1753
|
+
* The peer half of a carry (#2029): the peers the directory records OFF on this one scope
|
|
1754
|
+
* (`revokeFromPeer` writes the record, as `revokeFromSystem` writes the module's), and of those
|
|
1755
|
+
* the ones the tenant holds a live tenant-level `vertical:` grant for (#2030). Carried into a
|
|
1756
|
+
* deployment's provision, reconcile or restore beside the modules, so its in-unit OFF puts them
|
|
1757
|
+
* back off too. Empty lists when none is. Access-logged (K-24), as the module reads are.
|
|
1758
|
+
*/
|
|
1759
|
+
peerSwitchCarry(actor: PlatformActorId, node: {
|
|
1760
|
+
tenantId: TenantId;
|
|
1761
|
+
scopeId: ScopeId;
|
|
1762
|
+
}): Promise<{
|
|
1763
|
+
switchedOffPeers: string[];
|
|
1764
|
+
tenantHeldPeers: string[];
|
|
1765
|
+
/** #2045: each recorded-off peer's fence (the record's operation id), by slug. */
|
|
1766
|
+
fences: Record<string, string>;
|
|
1767
|
+
}>;
|
|
1566
1768
|
/**
|
|
1567
1769
|
* Put the directory's OFF positions back into one scope (#1674), for a scope whose storage
|
|
1568
1770
|
* lost them: a wipe then re-provision, or a restore of a dump taken before the switch was
|
|
@@ -1590,6 +1792,10 @@ export interface HostAdmin {
|
|
|
1590
1792
|
* deployment applying it. That move is undone (switched back on) and audited as
|
|
1591
1793
|
* `staleCarry`, BEFORE the record is read again for the OFF pass. Only a module recorded
|
|
1592
1794
|
* `on` is ever switched on here.
|
|
1795
|
+
*
|
|
1796
|
+
* Every kind of switch (#2029): after the modules, every peer the record holds `off` on the
|
|
1797
|
+
* scope is switched off again the same way (through `revokeFromPeer`'s switch), audited as
|
|
1798
|
+
* `reassertPeerSwitch`, and answered as an entry naming `vertical` rather than `moduleId`.
|
|
1593
1799
|
*/
|
|
1594
1800
|
reassertSystemSwitches(actor: PlatformActorId, node: {
|
|
1595
1801
|
tenantId: TenantId;
|
|
@@ -1652,7 +1858,34 @@ export interface HostAdmin {
|
|
|
1652
1858
|
*/
|
|
1653
1859
|
revokeCapability(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, capabilityId: CapabilityId): Promise<void>;
|
|
1654
1860
|
grantToOrg(actor: PlatformActorId, orgId: OrgId, permission: PermissionKey, node: Node, entity?: EntityRef): Promise<void>;
|
|
1655
|
-
|
|
1861
|
+
/**
|
|
1862
|
+
* Make `principal` a member of the org. `opts.expiresAt` (#2047) makes the membership lapse
|
|
1863
|
+
* then, as any expiring tuple does; absent, it never lapses. Re-adding replaces the row,
|
|
1864
|
+
* clearing a tombstone and setting the expiry anew.
|
|
1865
|
+
*/
|
|
1866
|
+
addMember(actor: PlatformActorId, tenantId: TenantId, principal: PrincipalId, orgId: OrgId, opts?: {
|
|
1867
|
+
expiresAt?: Instant;
|
|
1868
|
+
}): Promise<void>;
|
|
1869
|
+
/**
|
|
1870
|
+
* One membership change, applied as ONE directory unit (#1184): a single SQLite transaction or
|
|
1871
|
+
* a single synchronous ControlPlaneDO method, with no await inside it. In that unit it
|
|
1872
|
+
* re-evaluates the bound — `boundedBy` must still cover every permission `roleKey` carries,
|
|
1873
|
+
* against the directory as it stands (`tenantCoverage`) — and only then writes. An ADD also
|
|
1874
|
+
* reads `principal`'s removal fence first, then assigns the TENANT-level role and writes its
|
|
1875
|
+
* audit row. A REMOVE unassigns it and raises the fence, as a tenant-level `unassignRole`
|
|
1876
|
+
* does. A removal, a grant, a role redefinition or a demotion lands wholly before the unit
|
|
1877
|
+
* (and governs it) or wholly after it, never between its check and its write.
|
|
1878
|
+
*
|
|
1879
|
+
* With `orgId` (#2047) the org is joined or left in the same unit, and bounded there too:
|
|
1880
|
+
* `boundedBy` must be a live member of it. An add writes the membership — expiring no later
|
|
1881
|
+
* than `boundedBy`'s own — with an `addMember` audit row; a removal tombstones it, with a
|
|
1882
|
+
* `removeMember` row if it took anything. The role and the org apply together or not at all.
|
|
1883
|
+
*
|
|
1884
|
+
* The fence is `_substrat_membership_fences`: every tenant-level `unassignRole` and every
|
|
1885
|
+
* `removeMember` raises it for the person, in the same unit as its revoke — a no-op included,
|
|
1886
|
+
* since a removal of someone whose add is still on its way must still win.
|
|
1887
|
+
*/
|
|
1888
|
+
applyMembership(actor: PlatformActorId, change: MembershipChange): Promise<MembershipChangeResult>;
|
|
1656
1889
|
/**
|
|
1657
1890
|
* Revoke a membership (K-21). **Tombstones, never deletes**: the tuple keeps its
|
|
1658
1891
|
* row, gains a `revokedAt`, and the permission walk skips it. Deletion would
|
|
@@ -1664,6 +1897,9 @@ export interface HostAdmin {
|
|
|
1664
1897
|
* no-op, and a no-op is not audited. Re-adding via `addMember` clears the
|
|
1665
1898
|
* tombstone (they are a member again); the add/revoke history lives in the admin
|
|
1666
1899
|
* log, which is append-only.
|
|
1900
|
+
*
|
|
1901
|
+
* Raises the principal's removal fence (#1184) in the same unit as the revoke, a no-op
|
|
1902
|
+
* included, so a membership-executor add still on its way for them is refused.
|
|
1667
1903
|
*/
|
|
1668
1904
|
removeMember(actor: PlatformActorId, tenantId: TenantId, principal: PrincipalId, orgId: OrgId): Promise<void>;
|
|
1669
1905
|
/**
|
|
@@ -2359,6 +2595,13 @@ export interface HostAdmin {
|
|
|
2359
2595
|
* Reads only the lifecycle field of unclassified payloads; logged like every read here.
|
|
2360
2596
|
*/
|
|
2361
2597
|
lifecycleFlow(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, input: LifecycleFlowInput): Promise<LifecycleFlowResult>;
|
|
2598
|
+
/**
|
|
2599
|
+
* Calls per `(entityType, operation)` per time bucket over the scope's outbox (#1750) —
|
|
2600
|
+
* `readOperationSeries`, hoisted: the business volumes Pulse draws. The caller passes
|
|
2601
|
+
* the pairs (from the running version's declared lifecycles); the scope holds no model.
|
|
2602
|
+
* Reads no payload; logged like every read here.
|
|
2603
|
+
*/
|
|
2604
|
+
operationSeries(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, input: OperationSeriesInput): Promise<OperationSeriesResult>;
|
|
2362
2605
|
/**
|
|
2363
2606
|
* One read-only SQL statement against the scope's database — the console the two
|
|
2364
2607
|
* table-shaped reads deliberately weren't (#219). User SQL DOES reach the DB here,
|
|
@@ -2909,6 +3152,42 @@ export interface HostAdmin {
|
|
|
2909
3152
|
* for an unknown fingerprint. Audited with the before/after status diff (K-33).
|
|
2910
3153
|
*/
|
|
2911
3154
|
setIssueStatus(actor: PlatformActorId, fingerprint: string, status: IssueStatusInput): Promise<IssueEntry | undefined>;
|
|
3155
|
+
/**
|
|
3156
|
+
* Findings (#1748), most recently seen first: the tenant-scoped inbox. `filter.tenantId`
|
|
3157
|
+
* absent is the staff fleet read; every tenant-facing caller passes it, and the HTTP surface
|
|
3158
|
+
* forces it from the principal. Access-logged (K-24). No cursor, for `listIssues`'s reason.
|
|
3159
|
+
*/
|
|
3160
|
+
listFindings(actor: PlatformActorId, filter?: FindingFilter): Promise<FindingEntry[]>;
|
|
3161
|
+
/**
|
|
3162
|
+
* A verdict on one of `tenantId`'s findings: acknowledge, resolve, or reopen. Keyed on the
|
|
3163
|
+
* tenant as well as the id, so another tenant's finding reads as unknown (undefined).
|
|
3164
|
+
* Audited with the before/after diff (K-33).
|
|
3165
|
+
*/
|
|
3166
|
+
setFindingStatus(actor: PlatformActorId, tenantId: TenantId, id: string, status: FindingStatusInput): Promise<FindingEntry | undefined>;
|
|
3167
|
+
/**
|
|
3168
|
+
* Suppress with a rule: a scope (kind / operation / code / subject) and an expiry of at most
|
|
3169
|
+
* `FINDING_RULE_MAX_DAYS`. The findings it covers now are suppressed at once; later
|
|
3170
|
+
* occurrences it covers are counted but keep their finding suppressed. Audited, naming how
|
|
3171
|
+
* many findings it suppressed.
|
|
3172
|
+
*/
|
|
3173
|
+
createFindingRule(actor: PlatformActorId, tenantId: TenantId, input: FindingRuleInput): Promise<{
|
|
3174
|
+
rule: FindingRuleEntry;
|
|
3175
|
+
suppressed: number;
|
|
3176
|
+
}>;
|
|
3177
|
+
/** End one of `tenantId`'s rules now. Undefined for an unknown rule. Audited. */
|
|
3178
|
+
revokeFindingRule(actor: PlatformActorId, tenantId: TenantId, ruleId: string): Promise<FindingRuleEntry | undefined>;
|
|
3179
|
+
/**
|
|
3180
|
+
* The findings retention pass (#1748) — `pruneFindings`: a quiet open or acked finding is
|
|
3181
|
+
* resolved as `stale` with a `resolveStaleFinding` audit row written in the same unit, never
|
|
3182
|
+
* deleted; closed findings and expired rules past the window are deleted, at most `limit` of
|
|
3183
|
+
* each per call. Optional so a host that predates findings degrades the sweep's phase to `null`.
|
|
3184
|
+
*/
|
|
3185
|
+
pruneFindings?(actor: PlatformActorId, limit: number): Promise<FindingPruneReport>;
|
|
3186
|
+
/** `tenantId`'s suppress rules, newest first; `active` keeps the unexpired ones. Access-logged. */
|
|
3187
|
+
listFindingRules(actor: PlatformActorId, tenantId: TenantId, filter?: {
|
|
3188
|
+
active?: boolean;
|
|
3189
|
+
limit?: number;
|
|
3190
|
+
}): Promise<FindingRuleEntry[]>;
|
|
2912
3191
|
/**
|
|
2913
3192
|
* Meter 3's ledger (#1054): one line per model call a vertical made through the
|
|
2914
3193
|
* platform's model host, drained here as a `model-usage` intent. Idempotent on the
|
|
@@ -2976,6 +3255,14 @@ export interface HostAdmin {
|
|
|
2976
3255
|
* cannot be written.
|
|
2977
3256
|
*/
|
|
2978
3257
|
recordCopyMark(actor: PlatformActorId, entry: CopyMarkAudit): Promise<void>;
|
|
3258
|
+
/**
|
|
3259
|
+
* Record one phase of a dashboard member change (#1150) — an invite, a role move or a removal
|
|
3260
|
+
* on an installed vertical's scope — as `manageScopeMember`. The change runs in the vertical's
|
|
3261
|
+
* deployment, so the control plane writes these around its call: `intent` first (and must not
|
|
3262
|
+
* call if this throws), then the outcome. `recordOwnerTransfer`'s shape. Throws when the row
|
|
3263
|
+
* cannot be written.
|
|
3264
|
+
*/
|
|
3265
|
+
recordMemberChange(actor: PlatformActorId, entry: MemberChangeAudit): Promise<void>;
|
|
2979
3266
|
/**
|
|
2980
3267
|
* Stamp `drainedAt` on every not-yet-drained access row up to and including
|
|
2981
3268
|
* `upToId`, marking them shipped to Tier 2. Returns how many rows moved.
|
|
@@ -3434,6 +3721,12 @@ export declare function telemetryRetentionStatements(nowMs: number, limit: numbe
|
|
|
3434
3721
|
sql: string;
|
|
3435
3722
|
params: [string, number];
|
|
3436
3723
|
}[];
|
|
3724
|
+
/**
|
|
3725
|
+
* One bounded retention DELETE: at most `LIMIT ?` rows whose `column` is before `?`, oldest
|
|
3726
|
+
* first through the column's own index, `RETURNING 1` so the count is what was deleted. Takes
|
|
3727
|
+
* `(horizon, limit)`. The statement `telemetryRetentionStatements` and `pruneFindings` share.
|
|
3728
|
+
*/
|
|
3729
|
+
export declare function boundedRetentionDelete(table: string, column: string): string;
|
|
3437
3730
|
/** Filter for `listIssues` (#1233). Bounded by `limit` only — see the verb's doc. */
|
|
3438
3731
|
export interface IssueFilter {
|
|
3439
3732
|
status?: IssueStatus;
|
|
@@ -3614,8 +3907,15 @@ export interface ScopeHost {
|
|
|
3614
3907
|
*
|
|
3615
3908
|
* Optional so a host that predates it still satisfies the interface; a transport
|
|
3616
3909
|
* that finds it absent writes unattributed rows, which is what every row was before.
|
|
3910
|
+
*
|
|
3911
|
+
* `options.causedBy` (#2055) is the event whose effect the view's writes are (K-22): an
|
|
3912
|
+
* executor handler passes its own event's id, because the host stamps nothing ambiently —
|
|
3913
|
+
* a field set around the handler's `await` would also stamp every other admin call the
|
|
3914
|
+
* host served meanwhile. The `admin` a handler is handed already carries it.
|
|
3617
3915
|
*/
|
|
3618
|
-
attributed?(onBehalfOf: OnBehalfOf
|
|
3916
|
+
attributed?(onBehalfOf: OnBehalfOf, options?: {
|
|
3917
|
+
causedBy?: string;
|
|
3918
|
+
}): ScopeHost;
|
|
3619
3919
|
/**
|
|
3620
3920
|
* Mint a capability stub for a principal. Validates the (tenantId, scopeId)
|
|
3621
3921
|
* pair against the directory — a mismatched pair fails closed (K-3), it never
|
|
@@ -4041,6 +4341,31 @@ export interface ScopeHost {
|
|
|
4041
4341
|
* `canAssign`; an unknown role throws `not_found`.
|
|
4042
4342
|
*/
|
|
4043
4343
|
assignScopeRoleBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, assignee: PrincipalId, roleKey: string): Promise<Coverage>;
|
|
4344
|
+
/**
|
|
4345
|
+
* The scope's role roster (#1150): every live scope-level role assignment, one row per
|
|
4346
|
+
* (principal, role) — or one principal's, with `principal`. One scope, addressed by the caller
|
|
4347
|
+
* after its own (tenant, scope) check — K-3 is asserted here too — so it is a read of this
|
|
4348
|
+
* scope, never a walk across scopes.
|
|
4349
|
+
*/
|
|
4350
|
+
listScopeRoleHolders(tenantId: TenantId, scopeId: ScopeId, principal?: PrincipalId): Promise<ScopeRoleHolder[]>;
|
|
4351
|
+
/**
|
|
4352
|
+
* Move `principal` from scope role `from` to `to` in ONE scope task (#1150): the caller's
|
|
4353
|
+
* bound is asked over both roles (taking `from` away is bounded like granting it, §5.1
|
|
4354
|
+
* consequence 1), then `from` is tombstoned and `to` granted together. Nothing is written
|
|
4355
|
+
* on a refusal, so nobody is left holding both roles or neither. Throws `not_found` for a
|
|
4356
|
+
* `to` this tenant does not define, and `conflict` when `principal` does not hold `from`.
|
|
4357
|
+
*/
|
|
4358
|
+
changeScopeRoleBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, principal: PrincipalId, from: string, to: string): Promise<Coverage>;
|
|
4359
|
+
/**
|
|
4360
|
+
* Take every scope role `principal` holds, in ONE scope task (#1150), bounded by the
|
|
4361
|
+
* caller's authority over each (§5.1 consequence 1: you cannot strip what you could not
|
|
4362
|
+
* have granted). A role the tenant no longer defines confers nothing and is taken without a
|
|
4363
|
+
* bound. A refusal writes nothing; `revoked` names what was taken.
|
|
4364
|
+
*/
|
|
4365
|
+
revokeScopeRolesBounded(tenantId: TenantId, scopeId: ScopeId, caller: PrincipalId, principal: PrincipalId): Promise<{
|
|
4366
|
+
coverage: Coverage;
|
|
4367
|
+
revoked: string[];
|
|
4368
|
+
}>;
|
|
4044
4369
|
/**
|
|
4045
4370
|
* The recurring-work declarations of every module registered on this host (#383)
|
|
4046
4371
|
* — each module's id, the vertical it belongs to, and its `schedules`. Sync like
|
|
@@ -4140,12 +4465,19 @@ export interface ScopeHost {
|
|
|
4140
4465
|
* and this driver's contract in `job-run.ts`. `retry` is the DEFAULT policy for
|
|
4141
4466
|
* the job's steps; a step may pass its own.
|
|
4142
4467
|
*
|
|
4468
|
+
* `options.leaseMs` (#2034) is how long one pass holds its run without reaching a
|
|
4469
|
+
* step boundary, which renews it — default `JOB_LEASE_MS`. Past it, the run is due
|
|
4470
|
+
* again and the next drive takes it over, counting the silent pass as a failed
|
|
4471
|
+
* attempt. A job whose pass may go longer than that between two steps says so here.
|
|
4472
|
+
*
|
|
4143
4473
|
* The fourth driver, and a SIBLING of the three that already exist rather than a
|
|
4144
4474
|
* widening of any of them. An executor retries one delivery whole; a schedule
|
|
4145
4475
|
* fires one operation that must finish; the platform sweep does a pass of
|
|
4146
4476
|
* maintenance. None of them can stop halfway through an hour and carry on.
|
|
4147
4477
|
*/
|
|
4148
|
-
registerJob(moduleId: ModuleId, name: string, handler: JobHandler, retry?: ExecutorRetryPolicy
|
|
4478
|
+
registerJob(moduleId: ModuleId, name: string, handler: JobHandler, retry?: ExecutorRetryPolicy, options?: {
|
|
4479
|
+
leaseMs?: number;
|
|
4480
|
+
}): void;
|
|
4149
4481
|
/**
|
|
4150
4482
|
* Start a run, or JOIN the one already in flight for the same
|
|
4151
4483
|
* `(module, job, instance)` — the coalescing half of the driver.
|
|
@@ -4187,12 +4519,10 @@ export interface ScopeHost {
|
|
|
4187
4519
|
* grant gate here would STALL it silently rather than refuse it. The authority it
|
|
4188
4520
|
* exercises is checked where it is used, inside the operations its steps invoke.
|
|
4189
4521
|
*
|
|
4190
|
-
* **
|
|
4191
|
-
*
|
|
4192
|
-
*
|
|
4193
|
-
*
|
|
4194
|
-
* scope DO's alarm fires for its own), so the bound holds by construction; the full
|
|
4195
|
-
* argument, and what an overlap would actually cost, is in `job-run.ts`.
|
|
4522
|
+
* **Overlapping calls are safe** (#2034): each run is CLAIMED before its pass, with a
|
|
4523
|
+
* lease the pass renews at every step boundary, so two calls on one scope never run
|
|
4524
|
+
* one run's handler together. A run whose pass died is taken over once its lease
|
|
4525
|
+
* expires. The full argument is in `job-run.ts`.
|
|
4196
4526
|
*/
|
|
4197
4527
|
runDueJobs(tenantId: TenantId, scopeId: ScopeId, options?: {
|
|
4198
4528
|
maxPasses?: number;
|
|
@@ -4341,6 +4671,11 @@ export interface ScopeHost {
|
|
|
4341
4671
|
* changed at 14:02 is information about that row, so "the body was empty" is not a
|
|
4342
4672
|
* defence: the filter runs whether or not there is a payload to withhold.
|
|
4343
4673
|
*
|
|
4674
|
+
* A subscription may be narrowed `within` one entity (#1853), which only removes frames;
|
|
4675
|
+
* the one exception to the per-principal check is a `within` the vertical built with
|
|
4676
|
+
* `vouchedWithin`, whose subscriber is sent bare `LiveNudge` frames and nothing that
|
|
4677
|
+
* names an entity.
|
|
4678
|
+
*
|
|
4344
4679
|
* **Generic over the runtime's request and response, because the kernel names
|
|
4345
4680
|
* neither.** This package has one dependency and no DOM or workers lib
|
|
4346
4681
|
* (`docs/architecture/dependency-policy.md`), which is why `FetchLike` above describes
|
|
@@ -4375,8 +4710,58 @@ export interface LiveReadSurface<Req extends LiveUpgradeRequest = LiveUpgradeReq
|
|
|
4375
4710
|
principal: PrincipalId;
|
|
4376
4711
|
/** The upgrade request as it arrived, carried whole so the host reads its own headers. */
|
|
4377
4712
|
request: Req;
|
|
4713
|
+
/**
|
|
4714
|
+
* Narrow the feed to one entity and what hangs beneath it (#1853).
|
|
4715
|
+
*
|
|
4716
|
+
* A frame is then delivered only if its entity IS this one or reaches it upward
|
|
4717
|
+
* through live declared `parent` edges (what `ctx.link` / `ctx.relink` write) — the
|
|
4718
|
+
* walk `ctx.check` makes, rooted here instead of at a grant.
|
|
4719
|
+
*
|
|
4720
|
+
* - **An `EntityRef` narrows and nothing else.** The principal's own per-frame
|
|
4721
|
+
* `liveTargets` check still runs; `within` is ANDed with it, so it can only take
|
|
4722
|
+
* frames away. A screen watching one conversation passes it to stop hearing the
|
|
4723
|
+
* rest of the desk.
|
|
4724
|
+
* - **A `vouchedWithin(…)` value replaces the principal's check.** For a subscriber
|
|
4725
|
+
* confined by something other than a grant — a widget visitor holding a session
|
|
4726
|
+
* token the vertical has just redeemed. The vertical asserts access to the root,
|
|
4727
|
+
* the same trust it already extends in naming `principal`, and the scope's walk is
|
|
4728
|
+
* the whole filter. Such a subscriber receives `LiveNudge` frames only, which name
|
|
4729
|
+
* no event type and no entity.
|
|
4730
|
+
*/
|
|
4731
|
+
within?: EntityRef | VouchedWithin;
|
|
4378
4732
|
}): Promise<Res>;
|
|
4379
4733
|
}
|
|
4734
|
+
/** The brand only `vouchedWithin` can apply — a literal cannot type-check as one. */
|
|
4735
|
+
declare const vouchedBrand: unique symbol;
|
|
4736
|
+
/**
|
|
4737
|
+
* A `within` root the VERTICAL vouches the subscriber may watch, in place of the
|
|
4738
|
+
* principal's own grants (#1853). Built only by `vouchedWithin`.
|
|
4739
|
+
*/
|
|
4740
|
+
export interface VouchedWithin {
|
|
4741
|
+
readonly [vouchedBrand]: true;
|
|
4742
|
+
readonly entity: EntityRef;
|
|
4743
|
+
/** Why the vertical vouches — what it checked. Required, and kept on the subscription. */
|
|
4744
|
+
readonly because: string;
|
|
4745
|
+
}
|
|
4746
|
+
/**
|
|
4747
|
+
* Vouch that the subscriber may watch `entity` and everything beneath it, though the
|
|
4748
|
+
* principal it subscribes as holds no read on it.
|
|
4749
|
+
*
|
|
4750
|
+
* The one way to reach the replacing mode, so every use is greppable and none is an
|
|
4751
|
+
* accident: an object literal is refused by the type checker, and anything not built
|
|
4752
|
+
* here is refused by the host at run time (`isVouchedWithin`). Call it only after the
|
|
4753
|
+
* vertical itself has proven access — for ticket0's widget, by redeeming the session
|
|
4754
|
+
* token for the session it names.
|
|
4755
|
+
*
|
|
4756
|
+
* What it costs the subscriber is detail: a vouched feed carries `LiveNudge` frames, so
|
|
4757
|
+
* the scope never tells it which entity changed or how. Root it at an entity whose
|
|
4758
|
+
* subtree holds only what the subscriber may see — the walk is the whole filter.
|
|
4759
|
+
*/
|
|
4760
|
+
export declare function vouchedWithin(entity: EntityRef, opts: {
|
|
4761
|
+
because: string;
|
|
4762
|
+
}): VouchedWithin;
|
|
4763
|
+
/** Was this value built by `vouchedWithin`? A host asks before it drops the principal's check. */
|
|
4764
|
+
export declare function isVouchedWithin(value: unknown): value is VouchedWithin;
|
|
4380
4765
|
/**
|
|
4381
4766
|
* Is this request asking to be upgraded to a WebSocket?
|
|
4382
4767
|
*
|
|
@@ -4436,6 +4821,24 @@ export interface LiveChange {
|
|
|
4436
4821
|
/** When the event was emitted (ISO 8601), i.e. the emitting operation's instant. */
|
|
4437
4822
|
readonly at: string;
|
|
4438
4823
|
}
|
|
4824
|
+
/**
|
|
4825
|
+
* What a vouched subscriber is told (#1853): something beneath its root changed, and
|
|
4826
|
+
* nothing else.
|
|
4827
|
+
*
|
|
4828
|
+
* No event type and no entity, deliberately. A vouched subscriber holds no read on the
|
|
4829
|
+
* entities its frames are about — the vertical vouched for the ROOT, and the scope
|
|
4830
|
+
* cannot know which rows under it the vertical's own read would show. Naming the type
|
|
4831
|
+
* or the id would tell it what it never asked to read. The client re-reads, as it does
|
|
4832
|
+
* on a `LiveChange`.
|
|
4833
|
+
*/
|
|
4834
|
+
export interface LiveNudge {
|
|
4835
|
+
readonly kind: 'nudge';
|
|
4836
|
+
/** The event id, as on `LiveChange` — for ordering and de-duplication only. */
|
|
4837
|
+
readonly id: string;
|
|
4838
|
+
readonly at: string;
|
|
4839
|
+
}
|
|
4840
|
+
/** Any frame a live read sends. */
|
|
4841
|
+
export type LiveFrame = LiveChange | LiveNudge;
|
|
4439
4842
|
/**
|
|
4440
4843
|
* Refuse a cutoff in the future — at the HostAdmin boundary, not only at the HTTP door.
|
|
4441
4844
|
*
|