@substrat-run/kernel 0.162.0 → 0.164.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-bytes.d.ts +121 -0
- package/dist/attachment-bytes.d.ts.map +1 -0
- package/dist/attachment-bytes.js +94 -0
- package/dist/attachment-bytes.js.map +1 -0
- package/dist/attachment-sweep.d.ts +2 -1
- package/dist/attachment-sweep.d.ts.map +1 -1
- package/dist/attachment-sweep.js +1 -1
- package/dist/attachment-sweep.js.map +1 -1
- package/dist/attachment-text.d.ts +1 -1
- package/dist/attachment-text.d.ts.map +1 -1
- package/dist/attachment-text.js +1 -1
- package/dist/attachment-text.js.map +1 -1
- package/dist/billing-retention.js +1 -1
- package/dist/billing-retention.js.map +1 -1
- package/dist/connection-context.d.ts +69 -0
- package/dist/connection-context.d.ts.map +1 -0
- package/dist/connection-context.js +19 -0
- package/dist/connection-context.js.map +1 -0
- package/dist/connection-landings.d.ts +8 -0
- package/dist/connection-landings.d.ts.map +1 -1
- package/dist/connection-landings.js +5 -1
- package/dist/connection-landings.js.map +1 -1
- package/dist/connector-transport.d.ts +313 -0
- package/dist/connector-transport.d.ts.map +1 -0
- package/dist/connector-transport.js +66 -0
- package/dist/connector-transport.js.map +1 -0
- package/dist/deadline-fetch.d.ts +1 -1
- package/dist/deadline-fetch.d.ts.map +1 -1
- package/dist/findings.js +1 -1
- package/dist/findings.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/job-run.d.ts +2 -1
- package/dist/job-run.d.ts.map +1 -1
- package/dist/job-run.js +2 -1
- package/dist/job-run.js.map +1 -1
- package/dist/live-read.d.ts +258 -0
- package/dist/live-read.d.ts.map +1 -0
- package/dist/live-read.js +101 -0
- package/dist/live-read.js.map +1 -0
- package/dist/membership-executor.d.ts +2 -1
- package/dist/membership-executor.d.ts.map +1 -1
- package/dist/membership-executor.js.map +1 -1
- package/dist/meter-days.js +1 -1
- package/dist/meter-days.js.map +1 -1
- package/dist/migration-digest.d.ts +1 -1
- package/dist/migration-digest.d.ts.map +1 -1
- package/dist/migration-digest.js +1 -1
- package/dist/migration-digest.js.map +1 -1
- package/dist/platform-sweep.d.ts +22 -4
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +69 -26
- package/dist/platform-sweep.js.map +1 -1
- package/dist/promote-review.d.ts +42 -4
- package/dist/promote-review.d.ts.map +1 -1
- package/dist/promote-review.js +43 -15
- package/dist/promote-review.js.map +1 -1
- package/dist/request-rollup.js +1 -1
- package/dist/request-rollup.js.map +1 -1
- package/dist/reserved-roles.d.ts +12 -0
- package/dist/reserved-roles.d.ts.map +1 -1
- package/dist/reserved-roles.js +22 -3
- package/dist/reserved-roles.js.map +1 -1
- package/dist/retention.d.ts +106 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +131 -0
- package/dist/retention.js.map +1 -0
- package/dist/scope-host.d.ts +23 -791
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js +1 -385
- package/dist/scope-host.js.map +1 -1
- package/dist/storage-gauge.js +1 -1
- package/dist/storage-gauge.js.map +1 -1
- package/dist/upload-link.d.ts +1 -1
- package/dist/upload-link.d.ts.map +1 -1
- package/dist/upload-link.js +1 -1
- package/dist/upload-link.js.map +1 -1
- package/dist/wire-nonce.d.ts +80 -0
- package/dist/wire-nonce.d.ts.map +1 -0
- package/dist/wire-nonce.js +126 -0
- package/dist/wire-nonce.js.map +1 -0
- package/package.json +2 -2
package/dist/scope-host.d.ts
CHANGED
|
@@ -11,11 +11,15 @@ import type { StoreDriverHoldMode, StoreDriverRebuildFailure, StoreDriverRebuild
|
|
|
11
11
|
import type { SubjectRedactionCounts } from './subject-redaction.js';
|
|
12
12
|
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, ConnectionBindingAudit, CopyMarkAudit, OrphanSweepAudit, BindHostnameInput, AdminLogEntry, OpsFailureEntry, PlatformRequestDrainTotalsEntry, SweepRunEntry, SweepRunKind, SweepRunOutcome, ModelUsageEntry, ModelUsageSummary, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequestEntry, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, PlatformOutcomeEvent, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, MeterDay, MeterDayTake, VerticalOwnerPeriod, ScopeStorageSample, RequestDay, RequestRollupDay, 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, DeploymentWireAuth, RouteTarget, DirectoryDump, IdentityMembership, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, ScopeDumpInput, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, RefusalFilter, RefusalRecord, PermissionDenial, Coverage, BeginImpersonationInput, ImpersonationFilter, ImpersonationSession, ImpersonationSessionId, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, UploadLinkView, 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';
|
|
13
13
|
import type { ConnectionUseOutcome } from './connector-calls.js';
|
|
14
|
-
import {
|
|
14
|
+
import type { ByteBody } from './byte-stream.js';
|
|
15
|
+
import type { BlobStoreProvisionInput, BlobStoreRecord, OpenedAttachment, OpenedAttachmentStream } from './attachment-bytes.js';
|
|
16
|
+
import type { ConnectorOptions, ExecutorDeadLetter, ExecutorRetryPolicy, ScopedConnectorConnection } from './connector-transport.js';
|
|
17
|
+
import type { TelemetryPruneReport } from './retention.js';
|
|
18
|
+
import type { LiveReadSurface } from './live-read.js';
|
|
15
19
|
import type { CapabilityVerbs } from './capability.js';
|
|
16
20
|
import type { AttachmentVerbs } from './upload-link.js';
|
|
17
21
|
import type { AuditedOperationRef, AuditedOperationRow } from './audit-outcome.js';
|
|
18
|
-
import { type
|
|
22
|
+
import { type EntityStateName } from '@substrat-run/contracts';
|
|
19
23
|
import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
|
|
20
24
|
import type { FindingPruneReport } from './findings.js';
|
|
21
25
|
import type { ScopeStorageAttempt, ScopeStorageFilter, ScopeStorageReadingInput } from './storage-gauge.js';
|
|
@@ -1056,21 +1060,6 @@ export interface ExecutorOutcome {
|
|
|
1056
1060
|
}
|
|
1057
1061
|
/** One delivery's `ExecutorOutcome` — the one builder both adapters report through. */
|
|
1058
1062
|
export declare function executorOutcomeOf(executorId: string, event: DomainEvent, outcome: ExecutorOutcome['outcome'], error?: unknown): ExecutorOutcome;
|
|
1059
|
-
/**
|
|
1060
|
-
* How hard the host tries before it gives up on one delivery (#100).
|
|
1061
|
-
*
|
|
1062
|
-
* Defaults suit a directory write. A connector making an outbound HTTP call
|
|
1063
|
-
* wants a longer tail — that is the whole reason this is per-executor rather
|
|
1064
|
-
* than a host-wide constant.
|
|
1065
|
-
*/
|
|
1066
|
-
export interface ExecutorRetryPolicy {
|
|
1067
|
-
/** Total attempts including the first. Reaching it dead-letters. Default 5. */
|
|
1068
|
-
maxAttempts?: number;
|
|
1069
|
-
/** First backoff step; doubles per attempt. Default 1000ms. `0` retries at once. */
|
|
1070
|
-
baseDelayMs?: number;
|
|
1071
|
-
/** Ceiling on the doubling. Default 300_000ms (5 min). */
|
|
1072
|
-
maxDelayMs?: number;
|
|
1073
|
-
}
|
|
1074
1063
|
/**
|
|
1075
1064
|
* What one drain pass did. `retrying` and `deadLettered` are the numbers a
|
|
1076
1065
|
* health surface reports; a caller that ignores them learns nothing, which is
|
|
@@ -1225,51 +1214,6 @@ export interface ScheduleTick {
|
|
|
1225
1214
|
/** Platform requests the purge runs of the tick's earlier calls on this scope enqueued (their `purgeRequests`). */
|
|
1226
1215
|
earlierPurgeRequests?: number;
|
|
1227
1216
|
}
|
|
1228
|
-
/**
|
|
1229
|
-
* The web-standard fetch surface, structurally typed.
|
|
1230
|
-
*
|
|
1231
|
-
* Declared rather than imported: the kernel depends on no platform typings, and
|
|
1232
|
-
* `RequestInit`/`Response` come from DOM lib in Node and from workers-types in
|
|
1233
|
-
* Workers. Structural typing means both satisfy this without either being
|
|
1234
|
-
* required — the same reason `crypto` and `TextEncoder` are declared locally.
|
|
1235
|
-
*/
|
|
1236
|
-
export interface FetchLike {
|
|
1237
|
-
(input: string, init?: ConnectorRequestInit): Promise<ConnectorResponse>;
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* The runtime's own `fetch`, as a `FetchLike` — the ONE place the structural cast
|
|
1241
|
-
* lives. A host that takes a `fetch` defaults to this; a caller that hands one to a
|
|
1242
|
-
* connector passes this. Nobody else spells `globalThis.fetch` (`lint:bound-fetch`).
|
|
1243
|
-
*
|
|
1244
|
-
* The cast is needed at all because a real `fetch` is NOT assignable to `FetchLike`
|
|
1245
|
-
* under strict TypeScript: `ConnectorRequestInit.body` is a `Uint8Array` the DOM lib's
|
|
1246
|
-
* `BodyInit` no longer accepts unparameterized, and `signal` is `unknown`. Structurally
|
|
1247
|
-
* the runtime satisfies it — every field is one `fetch` reads — and this is where that
|
|
1248
|
-
* claim is made, once. Code typed against the DOM `fetch` itself (a `Response` in hand)
|
|
1249
|
-
* does not want this; `globalThis.fetch.bind(globalThis)` is that type with no cast.
|
|
1250
|
-
*
|
|
1251
|
-
* An arrow, deliberately, rather than the global handed on as a value: workerd
|
|
1252
|
-
* refuses the bare global invoked with any other receiver — `TypeError: Illegal
|
|
1253
|
-
* invocation` — and a callee is free to call what it was given as `input.fetch(…)`.
|
|
1254
|
-
* Node's fetch accepts any `this`, and so does the wrapper the workers vitest pool
|
|
1255
|
-
* installs, so no suite in this repo sees the difference; the Fortnox consent
|
|
1256
|
-
* callback shipped green and failed every hosted round that way (#1291). Closing
|
|
1257
|
-
* over the global here means the receiver is never in play, anywhere downstream.
|
|
1258
|
-
*
|
|
1259
|
-
* A `SizedByteStream` body (#2498) is converted by `fetchLikeOf`, which this is built from.
|
|
1260
|
-
*/
|
|
1261
|
-
export declare const globalFetch: FetchLike;
|
|
1262
|
-
/**
|
|
1263
|
-
* A runtime `fetch` (the global, a service binding's, a test's stand-in) as a `FetchLike`
|
|
1264
|
-
* (#2498): the one place a `SizedByteStream` body becomes a request the runtime can send, as
|
|
1265
|
-
* `knownLengthInit` builds it. The runtime takes no such value: handed one as it is, a `fetch`
|
|
1266
|
-
* sends its string form, `"[object Object]"`, and answers as if nothing were wrong. So
|
|
1267
|
-
* `globalFetch` is built from this, and both hosts pass the `fetch` they are given through it,
|
|
1268
|
-
* as `runPlatformSweep` does, so a sized body reaches no runtime `fetch` unconverted whichever
|
|
1269
|
-
* one was injected. Wrapping a `FetchLike` that is already one is harmless: a converted body is
|
|
1270
|
-
* no longer a `SizedByteStream`.
|
|
1271
|
-
*/
|
|
1272
|
-
export declare function fetchLikeOf(runtimeFetch: (input: string, init?: never) => Promise<ConnectorResponse>): FetchLike;
|
|
1273
1217
|
/**
|
|
1274
1218
|
* The host's clock (#812) — what `ctx.now()` reads.
|
|
1275
1219
|
*
|
|
@@ -1300,210 +1244,6 @@ export declare function fetchLikeOf(runtimeFetch: (input: string, init?: never)
|
|
|
1300
1244
|
export interface Clock {
|
|
1301
1245
|
(): Instant;
|
|
1302
1246
|
}
|
|
1303
|
-
export interface ConnectorRequestInit {
|
|
1304
|
-
method?: string;
|
|
1305
|
-
headers?: Record<string, string>;
|
|
1306
|
-
/**
|
|
1307
|
-
* `Uint8Array` as well as `string` because a real provider upload is binary:
|
|
1308
|
-
* Scrive's `setfile` is `multipart/form-data`, whose body is a byte sequence a
|
|
1309
|
-
* string cannot carry without corrupting the file. Web `fetch` accepts both, so
|
|
1310
|
-
* this only widens the declared surface — the adapter passes it straight
|
|
1311
|
-
* through.
|
|
1312
|
-
*
|
|
1313
|
-
* A {@link SizedByteStream} as well (#2498), for a body too large to hold in memory. The
|
|
1314
|
-
* length is part of the value because a provider wants a `Content-Length`, and a stream
|
|
1315
|
-
* carries none. The runtime cannot take one as it is, so it is converted by `fetchLikeOf`:
|
|
1316
|
-
* `globalFetch` is built from it, and both hosts and `runPlatformSweep` pass the `fetch` they
|
|
1317
|
-
* are given through it. A runtime `fetch` handed straight to a connector is made a
|
|
1318
|
-
* `FetchLike` with `fetchLikeOf` too, never by a cast.
|
|
1319
|
-
*/
|
|
1320
|
-
body?: string | Uint8Array | SizedByteStream;
|
|
1321
|
-
signal?: unknown;
|
|
1322
|
-
}
|
|
1323
|
-
export interface ConnectorResponse {
|
|
1324
|
-
readonly ok: boolean;
|
|
1325
|
-
readonly status: number;
|
|
1326
|
-
text(): Promise<string>;
|
|
1327
|
-
json(): Promise<unknown>;
|
|
1328
|
-
/**
|
|
1329
|
-
* The raw bytes — for provider responses that are a file, not JSON. Scrive's
|
|
1330
|
-
* sealed signed PDF (`documents/{id}/files/main`) comes back as `application/pdf`,
|
|
1331
|
-
* which `text()` would corrupt. Web `Response` already has this, so declaring it
|
|
1332
|
-
* only widens the structural surface the adapter passes straight through.
|
|
1333
|
-
*/
|
|
1334
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
1335
|
-
/**
|
|
1336
|
-
* The body as a stream (#2498), for a provider file too large to hold: read it chunk by chunk,
|
|
1337
|
-
* or hand it on to `upload` as a `SizedByteStream`. `null` for a response with no body, as on
|
|
1338
|
-
* a web `Response`, which every adapter's `fetch` returns.
|
|
1339
|
-
*
|
|
1340
|
-
* The stream carries the body as `fetch` delivers it: DECODED. A response sent with a
|
|
1341
|
-
* `Content-Encoding` (gzip, br) is decompressed on the way, while its `Content-Length` still
|
|
1342
|
-
* counts the compressed bytes, so that header is the stream's length only when the response
|
|
1343
|
-
* has no `Content-Encoding`. Otherwise take the length from the provider's own record of the
|
|
1344
|
-
* file (its item metadata); a stream sized by the wrong number is refused, not landed.
|
|
1345
|
-
*
|
|
1346
|
-
* The stream is bounded by IDLENESS, not by total transfer time (#2518). A connection's `fetch`
|
|
1347
|
-
* holds the connector's `timeoutMs` (default 30 s) over the call up to the headers and over a
|
|
1348
|
-
* buffered read, and still holds it while `body` is only looked at or never read. From the first
|
|
1349
|
-
* read of the stream the deadline hands over: it errors (`TimeoutError`) when the provider sends
|
|
1350
|
-
* no byte for `STREAM_IDLE_TIMEOUT_MS` (an empty chunk is not a byte), or when
|
|
1351
|
-
* `STREAM_MAX_DURATION_MS` has passed in all. A transfer that keeps flowing completes within
|
|
1352
|
-
* that. A stalled or endless one errors, the connection is released, and an upload reading it
|
|
1353
|
-
* lands no object and no row.
|
|
1354
|
-
*
|
|
1355
|
-
* OPTIONAL for the reason `headers` is: a hand-built stand-in is an object literal, and most
|
|
1356
|
-
* connectors never read a stream. A caller that needs one falls back to `arrayBuffer()` when it
|
|
1357
|
-
* is absent. Read a body once, by one of these.
|
|
1358
|
-
*/
|
|
1359
|
-
readonly body?: ByteStream | null;
|
|
1360
|
-
/**
|
|
1361
|
-
* The response headers, when the runtime behind this seam has them.
|
|
1362
|
-
*
|
|
1363
|
-
* OPTIONAL, and the optionality is the whole design. Every adapter's `fetch`
|
|
1364
|
-
* returns a real web `Response`, which always carries headers — so a connector
|
|
1365
|
-
* reading one gets a value in production, always. What is not guaranteed is a
|
|
1366
|
-
* hand-built stand-in: a mock or an in-memory provider is a plain object
|
|
1367
|
-
* literal, and requiring `headers` would break every one of them for a field
|
|
1368
|
-
* most connectors never read.
|
|
1369
|
-
*
|
|
1370
|
-
* So a caller must handle absence, and the callers that need it do. It exists
|
|
1371
|
-
* because some provider instructions live ONLY in a header and a connector that
|
|
1372
|
-
* cannot read them has to invent a substitute: Planima answers a rate limit with
|
|
1373
|
-
* `Retry-After`, and a client that cannot see it backs off on a schedule of its
|
|
1374
|
-
* own guessing — either too soon (spending another request to be told the same
|
|
1375
|
-
* thing) or too late. Declaring it here is what keeps that read inside the
|
|
1376
|
-
* sanctioned seam instead of a structural cast in a connector.
|
|
1377
|
-
*/
|
|
1378
|
-
readonly headers?: {
|
|
1379
|
-
get(name: string): string | null;
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
/**
|
|
1383
|
-
* A connection with its credential opened, plus egress bound to it.
|
|
1384
|
-
*
|
|
1385
|
-
* `fetch` is bound to the connection rather than sitting on the context because
|
|
1386
|
-
* health has to land on the right row by construction. An ambient
|
|
1387
|
-
* `ctx.fetch` would need the runtime to guess which connection a call belonged
|
|
1388
|
-
* to, and it would guess wrong the first time a connector talked to two.
|
|
1389
|
-
*/
|
|
1390
|
-
export interface ConnectorConnection extends OpenConnection {
|
|
1391
|
-
/**
|
|
1392
|
-
* Sanctioned egress: a timeout, and success/failure recorded against THIS
|
|
1393
|
-
* connection.
|
|
1394
|
-
*
|
|
1395
|
-
* The connector is handed its `fetch` rather than importing one — the same
|
|
1396
|
-
* move `ctx.sql` makes for module code, and for the same reason. Egress
|
|
1397
|
-
* policy, timeouts and health become properties of the seam instead of
|
|
1398
|
-
* conventions a connector author has to remember. Module code still cannot
|
|
1399
|
-
* reach any of this: boundary-lint R3 bans `fetch` outright, and a connector
|
|
1400
|
-
* is host code.
|
|
1401
|
-
*/
|
|
1402
|
-
fetch(input: string, init?: ConnectorRequestInit): Promise<ConnectorResponse>;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* A connection opened INSIDE a dispatch (#711) — `ctx.connection(provider)`.
|
|
1406
|
-
*
|
|
1407
|
-
* Strictly more than a `ConnectorConnection`, and the difference is a scope. A
|
|
1408
|
-
* dispatch happens *for* one scope, which is ambient in the context, so a
|
|
1409
|
-
* connection opened there can reach that scope's attachments. A connection
|
|
1410
|
-
* reopened top-level — a probe of a candidate credential, a poll driver
|
|
1411
|
-
* rebuilding egress from the directory — has no such scope, and the type says so
|
|
1412
|
-
* rather than handing out a method that would have to throw.
|
|
1413
|
-
*/
|
|
1414
|
-
export interface ScopedConnectorConnection extends ConnectorConnection {
|
|
1415
|
-
/**
|
|
1416
|
-
* Read ONE attachment's bytes from the scope this delivery is for (#711) — the
|
|
1417
|
-
* outbound mirror of `getConnectorAttachments`, which is the return path's door.
|
|
1418
|
-
*
|
|
1419
|
-
* A signing connector sends a document. Until this existed it could only send a
|
|
1420
|
-
* document it RENDERED ITSELF, because `create` had no way to be handed the
|
|
1421
|
-
* vertical's own file: the bytes were in the attachment store, and the store was
|
|
1422
|
-
* unreachable from inside a dispatch. So a Swedish counterparty was asked to sign
|
|
1423
|
-
* a page of identifiers with BankID rather than the contract.
|
|
1424
|
-
*
|
|
1425
|
-
* **On the connection, for the same reason `fetch` is.** The read is authorized as
|
|
1426
|
-
* a connection, and the only connection it can correctly be authorized as is the
|
|
1427
|
-
* one the handler opened — this object. An ambient "the provider this connector is
|
|
1428
|
-
* registered under" would be a second name for the same thing, and two names for
|
|
1429
|
-
* one fact is how they come to disagree: `registerScriveConnector({ id: 'scrive-eu' })`
|
|
1430
|
-
* opens its credential as `'scrive'` and would have read as `'scrive-eu'`, so the
|
|
1431
|
-
* egress half kept working while the document half failed on every contract.
|
|
1432
|
-
* Handing the door to whoever holds the credential makes that unrepresentable.
|
|
1433
|
-
*
|
|
1434
|
-
* **Why not `getConnectorAttachments` from in here.** On the pure adapter a
|
|
1435
|
-
* connector runs INSIDE the scope's actor task, and every verb of that surface
|
|
1436
|
-
* re-enqueues on the same actor — the nested task waits on the task holding it,
|
|
1437
|
-
* and the invoke never returns. (Pinned in `connector-reads.test.ts`.) The adapter
|
|
1438
|
-
* builds this read to suit where it is running: reentrant when the caller already
|
|
1439
|
-
* holds the actor, ordinary and serialized when it does not.
|
|
1440
|
-
*
|
|
1441
|
-
* **Reads only, and only by id.** No `list`, deliberately. A connector that
|
|
1442
|
-
* SEARCHES for the document to send has to have a rule for picking among several
|
|
1443
|
-
* — and the return path lands the sealed signed copy on the same entity, so a
|
|
1444
|
-
* wrong rule mails the counterparty their own signed contract to sign again.
|
|
1445
|
-
* Naming the id makes that unrepresentable: the caller says which bytes, the
|
|
1446
|
-
* event carries it, and nothing has to be disambiguated. Writes stay top-level
|
|
1447
|
-
* (`getConnectorAttachments().upload`), where a spine event and its consumers
|
|
1448
|
-
* have a transaction to live in.
|
|
1449
|
-
*
|
|
1450
|
-
* Gated by the target's `readPermission` checked against this connection's
|
|
1451
|
-
* `connection:<id>` grants. `null` for an id the scope does not know — a caller
|
|
1452
|
-
* falls back rather than failing a dispatch over a missing file.
|
|
1453
|
-
*/
|
|
1454
|
-
openAttachment(attachmentId: string): Promise<OpenedAttachment | null>;
|
|
1455
|
-
/**
|
|
1456
|
-
* `openAttachment`, with the bytes as a stream that is never held whole (#2514), for a connector
|
|
1457
|
-
* that sends a file on to a provider. Gated the same way, before any byte is read. The sha256 is
|
|
1458
|
-
* checked as the bytes flow, and a mismatch errors the stream before its last chunk is handed on
|
|
1459
|
-
* (see {@link OpenedAttachmentStream}). The bytes delivered before a stream error are a prefix of
|
|
1460
|
-
* an unverified file, never a shorter file: a connector that sends what it read discards them and
|
|
1461
|
-
* undoes anything it wrote from them (an upload session cancelled, a request aborted before it
|
|
1462
|
-
* completes), as the hosted read's refusal says (`STREAMED_ATTACHMENT_UNVERIFIED` in
|
|
1463
|
-
* `@substrat-run/control-plane-api`). A stream it decides not to read is
|
|
1464
|
-
* cancelled (`body.stream.getReader().cancel()`), which releases the stored object at once.
|
|
1465
|
-
*/
|
|
1466
|
-
openAttachmentStream(attachmentId: string): Promise<OpenedAttachmentStream | null>;
|
|
1467
|
-
/**
|
|
1468
|
-
* What this connection may do in the scope this delivery is for (#726 gap 1) —
|
|
1469
|
-
* `connectionGrantsInScope`, narrowed to this connection.
|
|
1470
|
-
*
|
|
1471
|
-
* Here so a connector can check its own preconditions at the top of a dispatch and
|
|
1472
|
-
* fail saying which grant is missing, instead of discovering it three calls later as
|
|
1473
|
-
* a refusal the drain then captions as the provider's (#841). A connector that needs
|
|
1474
|
-
* a standing grant for its RETURN path — where there is no delivered event to carry
|
|
1475
|
-
* authority — can say so on the way out, which is the only moment it is cheap to fix.
|
|
1476
|
-
*
|
|
1477
|
-
* Deliberately the permission keys and nothing else: a connector asks whether it may
|
|
1478
|
-
* act, never who granted it or when. Both are the operator's question, and both are
|
|
1479
|
-
* on the drawer that already renders them.
|
|
1480
|
-
*/
|
|
1481
|
-
grants(): Promise<PermissionKey[]>;
|
|
1482
|
-
/**
|
|
1483
|
-
* Open a cell the scope sealed TO this connection (#687) — the egress half of
|
|
1484
|
-
* `ctx.sealToConnection`.
|
|
1485
|
-
*
|
|
1486
|
-
* **Here rather than on `ConnectorConnection`, and the probe path is why.** A
|
|
1487
|
-
* connection rebuilt top-level may have no directory row at all — the credential
|
|
1488
|
-
* probe builds one from a candidate secret and a zero id — so it holds no
|
|
1489
|
-
* keypair and could only ever throw. A sealed cell arrives on a delivered event,
|
|
1490
|
-
* which is to say inside a dispatch, which is exactly the shape that has one.
|
|
1491
|
-
*
|
|
1492
|
-
* **On the connection, for the same reason `fetch` and `openAttachment` are.**
|
|
1493
|
-
* The private half belongs to one connection, and the only connection it can
|
|
1494
|
-
* correctly be is the one this handler opened. An ambient `ctx.unseal` would
|
|
1495
|
-
* have to guess, and it would guess wrong the first time a tenant held two
|
|
1496
|
-
* credentials for one provider.
|
|
1497
|
-
*
|
|
1498
|
-
* Key material never crosses this seam: the adapter holds the keyId-indexed map
|
|
1499
|
-
* and picks by the cell's own `keyId`, so a connector cannot mislay a private
|
|
1500
|
-
* key it never had. Throws `SealedKeyUnavailableError` when the named key is not
|
|
1501
|
-
* held — which after a rotation-as-erasure (D-5) is the correct and permanent
|
|
1502
|
-
* answer, not a mystery. A scope restored from backup can resurrect a pending
|
|
1503
|
-
* request whose key is gone, and that delivery should dead-letter saying so.
|
|
1504
|
-
*/
|
|
1505
|
-
unseal(sealed: SealedSecret): Promise<string>;
|
|
1506
|
-
}
|
|
1507
1247
|
/**
|
|
1508
1248
|
* What a connector sees. Strictly more than an executor: an executor effects
|
|
1509
1249
|
* something in the DIRECTORY, a connector effects something in the OUTSIDE
|
|
@@ -1527,50 +1267,6 @@ export interface ConnectorContext {
|
|
|
1527
1267
|
connection(provider: string): Promise<ScopedConnectorConnection>;
|
|
1528
1268
|
}
|
|
1529
1269
|
export type ConnectorHandler = (ctx: ConnectorContext, event: DomainEvent) => void | Promise<void>;
|
|
1530
|
-
/** Tuning for one connector's egress. */
|
|
1531
|
-
export interface ConnectorOptions extends ExecutorRetryPolicy {
|
|
1532
|
-
/**
|
|
1533
|
-
* Per-request timeout. Default 30s. It bounds the call up to the response headers and a
|
|
1534
|
-
* buffered read of the body. A streamed body (`ConnectorResponse.body`) is bounded by an idle
|
|
1535
|
-
* window instead (#2518, `STREAM_IDLE_TIMEOUT_MS`) and an absolute cap (`STREAM_MAX_DURATION_MS`):
|
|
1536
|
-
* it may take longer than this, as long as bytes keep arriving.
|
|
1537
|
-
*/
|
|
1538
|
-
timeoutMs?: number;
|
|
1539
|
-
/**
|
|
1540
|
-
* The provider slug this connector operates (#574 phase 3) — the routing key a
|
|
1541
|
-
* CP-less host uses when it cannot run the handler itself: the delivery becomes a
|
|
1542
|
-
* `connector:<provider>` platform intent, and the platform's drain dispatches it to
|
|
1543
|
-
* the handler registered for that same slug. Defaults to the registration id, which
|
|
1544
|
-
* for the shipped connectors is already the provider name ('scrive'). Irrelevant on
|
|
1545
|
-
* a host that reaches the connection directory (self-host, the control plane): there
|
|
1546
|
-
* the handler runs in-process and this key is never consulted.
|
|
1547
|
-
*/
|
|
1548
|
-
provider?: string;
|
|
1549
|
-
}
|
|
1550
|
-
/** A delivery that exhausted its attempts. The evidence, not a silent drop. */
|
|
1551
|
-
export interface ExecutorDeadLetter {
|
|
1552
|
-
eventId: string;
|
|
1553
|
-
executorId: string;
|
|
1554
|
-
eventType: string;
|
|
1555
|
-
attempts: number;
|
|
1556
|
-
error: string;
|
|
1557
|
-
lastAttemptAt: string;
|
|
1558
|
-
}
|
|
1559
|
-
/**
|
|
1560
|
-
* Retry defaults (#100). Tuned for the directory write the first executor does;
|
|
1561
|
-
* a connector making an outbound call should raise `maxAttempts` explicitly,
|
|
1562
|
-
* which is why the policy is per-executor rather than a host constant.
|
|
1563
|
-
*/
|
|
1564
|
-
export declare function resolveRetryPolicy(retry?: ExecutorRetryPolicy): Required<ExecutorRetryPolicy>;
|
|
1565
|
-
/**
|
|
1566
|
-
* When attempt `attempts` should next be tried: exponential, capped, jittered.
|
|
1567
|
-
*
|
|
1568
|
-
* Jitter is ±20% and is skipped entirely at zero delay, so a test setting
|
|
1569
|
-
* `baseDelayMs: 0` gets deterministic immediate retries rather than a race. It
|
|
1570
|
-
* matters at real delays because every scope in a fleet retries a downed
|
|
1571
|
-
* provider on the same schedule otherwise.
|
|
1572
|
-
*/
|
|
1573
|
-
export declare function backoffAt(attempts: number, retry: Required<ExecutorRetryPolicy>, from: Date): string;
|
|
1574
1270
|
/**
|
|
1575
1271
|
* A named, manifest-wired pre-condition on an operation (K-17; engine-protocol
|
|
1576
1272
|
* §6, open question 11). One module CONTRIBUTES a predicate under a name; a
|
|
@@ -2352,7 +2048,10 @@ export interface HostAdmin {
|
|
|
2352
2048
|
* "someone reviewed it" becomes evidence rather than a claim.
|
|
2353
2049
|
*
|
|
2354
2050
|
* Only admitted versions may be promoted, for the same reason they are the only
|
|
2355
|
-
* ones bindable.
|
|
2051
|
+
* ones bindable. **#2565: prod of a listed vertical takes no auto-admitted version**
|
|
2052
|
+
* (`AUTO_ADMISSION_NOTE`), the rule `setVerticalListed` holds prod to, and `unlistedOnly` refuses
|
|
2053
|
+
* (`forbidden`) a vertical that is listed. Both are read in the unit that moves the channel, so a
|
|
2054
|
+
* listing that lands while the promote is judged is seen.
|
|
2356
2055
|
*
|
|
2357
2056
|
* **#1705 PR 3: refuses a promotion that breaks an installed consumer** unless it is
|
|
2358
2057
|
* acknowledged (`exportBreak`). A break is an exported (type, schemaVersion) the outgoing
|
|
@@ -2360,7 +2059,9 @@ export interface HostAdmin {
|
|
|
2360
2059
|
* (`exportBreaksOf`). The refusal counts what breaks and names no tenant, since this layer does
|
|
2361
2060
|
* not know who is asking. `promotionImpact` is the listing, for a caller that may see it.
|
|
2362
2061
|
*/
|
|
2363
|
-
promoteVersion(actor: PlatformActorId, verticalSlug: string, channel: ChannelName, versionId: string, acknowledge?: PromotionAcknowledgement
|
|
2062
|
+
promoteVersion(actor: PlatformActorId, verticalSlug: string, channel: ChannelName, versionId: string, acknowledge?: PromotionAcknowledgement, options?: {
|
|
2063
|
+
unlistedOnly?: boolean;
|
|
2064
|
+
}): Promise<void>;
|
|
2364
2065
|
/**
|
|
2365
2066
|
* Which installed consumers promoting `versionId` to `channel` would break (#1705 PR 3): the
|
|
2366
2067
|
* `exportBreaksOf` answer the promote gate refuses on, as a read. Empty for a first promote,
|
|
@@ -2475,8 +2176,17 @@ export interface HostAdmin {
|
|
|
2475
2176
|
* and the DO-class/migration-tag base the NEXT upload diffs against. Written only
|
|
2476
2177
|
* AFTER the upload succeeded — a failed serve leaves `servingVersionId` trailing
|
|
2477
2178
|
* the prod channel, which is the visible, retryable state. Audited.
|
|
2179
|
+
*
|
|
2180
|
+
* A compare-and-set (#2528): the write lands only if the row still holds the serving state this
|
|
2181
|
+
* method read, its ref, version, class set and migration tag, judged in the unit that writes.
|
|
2182
|
+
* `expected` (#2577) is the state the caller computed its upload from (`verticalServing`'s answer
|
|
2183
|
+
* before the upload, null for nothing served); a row whose class set or tag differs from it is
|
|
2184
|
+
* refused before the gate, so the record never names a migration base diffed from one that is
|
|
2185
|
+
* gone. Its ref and version are not compared: a serve recorded meanwhile that moved only those
|
|
2186
|
+
* left the upload's migration right, and the record lands over it as it did before. Either
|
|
2187
|
+
* refusal is `precondition_failed` (`servingRaceRefusal`) and writes nothing.
|
|
2478
2188
|
*/
|
|
2479
|
-
setVerticalServing(actor: PlatformActorId, verticalSlug: string, state: VerticalServingState): Promise<void>;
|
|
2189
|
+
setVerticalServing(actor: PlatformActorId, verticalSlug: string, state: VerticalServingState, expected?: VerticalServingState | null): Promise<void>;
|
|
2480
2190
|
/**
|
|
2481
2191
|
* The pushed DeployManifest (JSON) of one version — what a serve rebuilds upload
|
|
2482
2192
|
* metadata from. Null for a version pushed before manifests were retained; such a
|
|
@@ -4209,106 +3919,6 @@ export interface TenantStoreRecord {
|
|
|
4209
3919
|
ref: string;
|
|
4210
3920
|
createdAt: string;
|
|
4211
3921
|
}
|
|
4212
|
-
/** What `provisionBlobStore` needs to mint (or idempotently re-resolve) a per-tenant
|
|
4213
|
-
* blob store (#473). Keyed by (tenant, vertical, binding), exactly like tenant stores. */
|
|
4214
|
-
export interface BlobStoreProvisionInput {
|
|
4215
|
-
tenantId: TenantId;
|
|
4216
|
-
/** The vertical the store belongs to — its `blobStoreNeed` binding is scoped to it. */
|
|
4217
|
-
vertical: string;
|
|
4218
|
-
/** The declared `blobStoreNeed.binding` this store satisfies (SCREAMING_SNAKE). */
|
|
4219
|
-
binding: string;
|
|
4220
|
-
}
|
|
4221
|
-
/**
|
|
4222
|
-
* A live per-tenant blob store (#473) — the byte side of the attachment surface. The
|
|
4223
|
-
* contract is async and byte-shaped (Uint8Array + web-standard types only) because the
|
|
4224
|
-
* store is only reachable asynchronously on Cloudflare (an `R2Bucket` binding); the pure
|
|
4225
|
-
* adapter backs it with a per-tenant directory and resolves immediately.
|
|
4226
|
-
*
|
|
4227
|
-
* Keys are PLATFORM-DERIVED (`attachmentBlobKey`), never caller-supplied strings — the
|
|
4228
|
-
* per-scope prefix inside a per-tenant store is constructed in kernel/adapter code, which
|
|
4229
|
-
* is what turns "scope/<id>/ is a convention" into "cross-scope keys are unwritable".
|
|
4230
|
-
*/
|
|
4231
|
-
export interface TenantBlobStore {
|
|
4232
|
-
/**
|
|
4233
|
-
* Write `body` under `key`. A `SizedByteStream` (#2498) is written as it is read, never
|
|
4234
|
-
* collected first, and refused if it carries other than its declared length. The put
|
|
4235
|
-
* resolves only once the whole stream has been read.
|
|
4236
|
-
*/
|
|
4237
|
-
put(key: string, body: ByteBody, opts?: {
|
|
4238
|
-
contentType?: string;
|
|
4239
|
-
}): Promise<void>;
|
|
4240
|
-
get(key: string): Promise<{
|
|
4241
|
-
body: Uint8Array;
|
|
4242
|
-
contentType?: string;
|
|
4243
|
-
} | null>;
|
|
4244
|
-
/**
|
|
4245
|
-
* The object under `key` as a stream (#2514), with the size the store holds for it, read as the
|
|
4246
|
-
* consumer reads it and never collected first. Optional, so a store written before it still
|
|
4247
|
-
* satisfies this interface: an open over such a store reads the object with `get` and hands it on
|
|
4248
|
-
* as a stream, which works but holds the object whole.
|
|
4249
|
-
*/
|
|
4250
|
-
getStream?(key: string): Promise<{
|
|
4251
|
-
stream: ByteStream;
|
|
4252
|
-
size: number;
|
|
4253
|
-
contentType?: string;
|
|
4254
|
-
} | null>;
|
|
4255
|
-
delete(key: string): Promise<void>;
|
|
4256
|
-
/** Keys under `prefix` — the GC/ops walk. */
|
|
4257
|
-
list(prefix: string): Promise<string[]>;
|
|
4258
|
-
}
|
|
4259
|
-
/**
|
|
4260
|
-
* One row of the blob-store ledger (#473) — same idempotency/deploy/reap roles as
|
|
4261
|
-
* {@link TenantStoreRecord}: a retried provision re-resolves the same `ref`, the deploy
|
|
4262
|
-
* path derives which `r2_bucket` bindings must ride every serving-script upload, and a
|
|
4263
|
-
* tenant reap knows what to tear down.
|
|
4264
|
-
*/
|
|
4265
|
-
export interface BlobStoreRecord {
|
|
4266
|
-
tenantId: TenantId;
|
|
4267
|
-
vertical: string;
|
|
4268
|
-
binding: string;
|
|
4269
|
-
kind: 'blob';
|
|
4270
|
-
ref: string;
|
|
4271
|
-
createdAt: string;
|
|
4272
|
-
}
|
|
4273
|
-
/**
|
|
4274
|
-
* The blob key an attachment's bytes live under inside the per-tenant store (#473).
|
|
4275
|
-
* Scope-prefixed by construction: every key this platform ever writes for a scope sits
|
|
4276
|
-
* under `scope/<scopeId>/`, and the attachment id (a fresh ULID per upload) makes keys
|
|
4277
|
-
* write-once — the two properties the attachment integrity story rests on. Exported so
|
|
4278
|
-
* both adapters (and an ops GC walk) derive the same key; module and route code never do.
|
|
4279
|
-
*/
|
|
4280
|
-
export declare function attachmentBlobKey(scopeId: string, attachmentId: string): string;
|
|
4281
|
-
/**
|
|
4282
|
-
* An attachment's integrity witness: the lowercase hex SHA-256 of its bytes (Web Crypto).
|
|
4283
|
-
* One definition for the hash an upload records and every read that holds bytes to it —
|
|
4284
|
-
* both adapters' `open`, and the extraction job (#1575).
|
|
4285
|
-
*/
|
|
4286
|
-
export declare function attachmentSha256(body: Uint8Array): Promise<string>;
|
|
4287
|
-
/**
|
|
4288
|
-
* Write an upload's bytes to the blob store and answer the record they make (#2498) — the "bytes
|
|
4289
|
-
* first" half of every attachment write, one path for both adapters. The record's fields are
|
|
4290
|
-
* judged before a byte moves, so a record the surface would refuse lands no object. A buffer is
|
|
4291
|
-
* then hashed and written, as before. A stream is hashed and counted while the store reads it, so
|
|
4292
|
-
* it is never held whole; `sha256` is the adapter's running digest, since the kernel has none of
|
|
4293
|
-
* its own. The caller writes the row.
|
|
4294
|
-
*
|
|
4295
|
-
* A streamed write that fails, or whose store answers without reading the stream to its end, has
|
|
4296
|
-
* its object deleted and is refused: no record may witness bytes nobody saw.
|
|
4297
|
-
*/
|
|
4298
|
-
export declare function putAttachmentBytes(store: TenantBlobStore, key: string, fields: AttachmentRecordFields, body: ByteBody, sha256: () => IncrementalSha256): Promise<AttachmentRecord>;
|
|
4299
|
-
/**
|
|
4300
|
-
* The bytes of an attachment the scope has already AUTHORIZED, as a stream held to its record (#2514):
|
|
4301
|
-
* the "bytes" half of every streamed open, one path for both adapters, as {@link putAttachmentBytes}
|
|
4302
|
-
* is for the write. The caller checks the read permission first and calls this only once it passed,
|
|
4303
|
-
* so a refused read never reaches the store.
|
|
4304
|
-
*
|
|
4305
|
-
* The length is the record's `size`, which the upload took from the bytes themselves. The store's own
|
|
4306
|
-
* size for the object is held to it before the first byte, so an object that is not the recorded one
|
|
4307
|
-
* is refused up front. The sha256 is checked as the bytes flow ({@link verifiedStream}): a mismatch
|
|
4308
|
-
* errors the stream before its last chunk is handed on. A store without `getStream` is read whole with
|
|
4309
|
-
* `get` and handed on the same way.
|
|
4310
|
-
*/
|
|
4311
|
-
export declare function openAttachmentByteStream(store: TenantBlobStore, key: string, record: AttachmentRecord, sha256: () => IncrementalSha256): Promise<OpenedAttachmentStream>;
|
|
4312
3922
|
/**
|
|
4313
3923
|
* Who binds an operation, as the §4.3 entitlement gate needs to know it: the owning
|
|
4314
3924
|
* module, the SKU flag its manifest declares, and the operations that module declares
|
|
@@ -4440,24 +4050,6 @@ export type RedeemedUploadLink = AttachmentRecord & {
|
|
|
4440
4050
|
/** Present only on a replay (#2315): the file was stored by the request that spent the link, and nothing was written now. */
|
|
4441
4051
|
replayed?: true;
|
|
4442
4052
|
};
|
|
4443
|
-
/** An opened attachment: the metadata fact plus the bytes it witnesses. */
|
|
4444
|
-
export interface OpenedAttachment {
|
|
4445
|
-
record: AttachmentRecord;
|
|
4446
|
-
body: Uint8Array;
|
|
4447
|
-
contentType: string;
|
|
4448
|
-
}
|
|
4449
|
-
/**
|
|
4450
|
-
* An opened attachment whose bytes are a stream (#2514): the metadata fact, and its bytes at the
|
|
4451
|
-
* record's `size`, read as the consumer reads them. The sha256 is checked as they pass, so a
|
|
4452
|
-
* mismatch errors the stream before its last chunk instead of refusing up front: a consumer that
|
|
4453
|
-
* has received all `size` bytes has received bytes that matched the record. One-shot: read it once,
|
|
4454
|
-
* or cancel it, which releases the stored object; a stream that is neither holds it until collected.
|
|
4455
|
-
*/
|
|
4456
|
-
export interface OpenedAttachmentStream {
|
|
4457
|
-
record: AttachmentRecord;
|
|
4458
|
-
body: SizedByteStream;
|
|
4459
|
-
contentType: string;
|
|
4460
|
-
}
|
|
4461
4053
|
/**
|
|
4462
4054
|
* The attachment surface a host mints per (principal, scope) — `attachmentTargets`
|
|
4463
4055
|
* finally consumed (#473). Every method is gated INSIDE the platform by the declared
|
|
@@ -4564,111 +4156,6 @@ export interface AccessLogFilter extends ListPage {
|
|
|
4564
4156
|
*/
|
|
4565
4157
|
drained?: boolean;
|
|
4566
4158
|
}
|
|
4567
|
-
/**
|
|
4568
|
-
* How long a recorded operational failure is kept (#559). Telemetry, not evidence:
|
|
4569
|
-
* unlike the never-swept admin log, the ops-failures table self-prunes on write in
|
|
4570
|
-
* every adapter, so it can never grow without bound and needs no cron wiring or
|
|
4571
|
-
* operator decision. 90 days comfortably outlives any incident follow-up (a
|
|
4572
|
-
* Cloudflare ticket round-trip) while keeping directory storage flat.
|
|
4573
|
-
*/
|
|
4574
|
-
export declare const OPS_FAILURE_RETENTION_DAYS = 90;
|
|
4575
|
-
/**
|
|
4576
|
-
* How long a sweep-run row is kept (#1232). Much shorter than ops failures: this
|
|
4577
|
-
* is high-frequency telemetry (a pass every few minutes times every connection
|
|
4578
|
-
* and schedule), and its job is the recent-runs strip plus an incident window —
|
|
4579
|
-
* 14 days covers both. Pruned on write in every adapter, like ops failures and
|
|
4580
|
-
* for the same reason: the table stays bounded even where no scheduled pass runs.
|
|
4581
|
-
*/
|
|
4582
|
-
export declare const SWEEP_RUN_RETENTION_DAYS = 14;
|
|
4583
|
-
/**
|
|
4584
|
-
* The drained batch's dedupe key on the directory's `_substrat_sweep_runs` (#1232), as
|
|
4585
|
-
* both adapters build it: `recordSweepRun` is an `INSERT OR IGNORE` against this index,
|
|
4586
|
-
* which is what makes a replayed drain write nothing twice.
|
|
4587
|
-
*
|
|
4588
|
-
* `kind` is in it since #1572, and that is the fix rather than a tidy-up. Every entry of
|
|
4589
|
-
* one drained batch shares its `request_id`, and `unit` is built from two namespaces that
|
|
4590
|
-
* nothing keeps apart — `<scopeId>:<operation>` for a schedule, `<scopeId>:<eventType>`
|
|
4591
|
-
* for a freshness verdict — while `scheduleSpec.operation` is `z.string().min(1)`. A
|
|
4592
|
-
* schedule named exactly like an event type its scope expects freshness on collided on
|
|
4593
|
-
* `(request_id, unit)`, and the ignore-on-conflict write discarded the second row with no
|
|
4594
|
-
* error anywhere. Both rows are signals by their absence (a stopped evaluator, a missed
|
|
4595
|
-
* run), so losing one read as the very thing it was meant to rule out. It is #1288's
|
|
4596
|
-
* collision, in the record table beside the state table.
|
|
4597
|
-
*
|
|
4598
|
-
* Shared rather than spelled in each adapter because a directory that predates it
|
|
4599
|
-
* re-creates it (`ensureSweepRunsIntentKind`, in both), and `lint:spine-ddl` sees only
|
|
4600
|
-
* the created copy. One definition keeps the re-created shape and the created shape the
|
|
4601
|
-
* same shape; the gate then holds each adapter's DDL to including it.
|
|
4602
|
-
*/
|
|
4603
|
-
export declare const SWEEP_RUNS_INTENT_INDEX = "CREATE UNIQUE INDEX IF NOT EXISTS _substrat_sweep_runs_intent ON _substrat_sweep_runs (request_id, kind, unit);";
|
|
4604
|
-
/**
|
|
4605
|
-
* Whether a directory's `_substrat_sweep_runs_intent` already has `kind` among its
|
|
4606
|
-
* columns, read off the index's `sql` in `sqlite_master` — which DO SQLite serves and
|
|
4607
|
-
* `PRAGMA` does not, so both adapters detect by one strategy. `false` means the index
|
|
4608
|
-
* must be DROPPED and created again: `CREATE … IF NOT EXISTS` matches on the name alone,
|
|
4609
|
-
* so on every existing directory the old `(request_id, unit)` index would otherwise
|
|
4610
|
-
* stand for good, while a fresh one — and any test that starts from one — gets the new.
|
|
4611
|
-
*/
|
|
4612
|
-
export declare function sweepRunsIntentHasKind(indexSql: string): boolean;
|
|
4613
|
-
/**
|
|
4614
|
-
* How long an issue row outlives its last occurrence (#1233). Deliberately longer
|
|
4615
|
-
* than the 90-day evidence beneath it: an issue is the compressed memory of a
|
|
4616
|
-
* failure class, and "we saw this five months ago" is exactly what a regression
|
|
4617
|
-
* needs to be recognizable. Pruned on write like everything else here, and on the
|
|
4618
|
-
* scheduled pass (`pruneTelemetry`, #1632), so a quiet directory sheds it on time too.
|
|
4619
|
-
*/
|
|
4620
|
-
export declare const ISSUE_RETENTION_DAYS = 180;
|
|
4621
|
-
/** What one `pruneTelemetry` call deleted, per table. */
|
|
4622
|
-
export interface TelemetryPruneReport {
|
|
4623
|
-
opsFailures: number;
|
|
4624
|
-
issues: number;
|
|
4625
|
-
sweepRuns: number;
|
|
4626
|
-
}
|
|
4627
|
-
/**
|
|
4628
|
-
* A caller-supplied row bound, checked before it reaches a SQL `LIMIT` (#1632). SQLite reads a
|
|
4629
|
-
* negative `LIMIT` as no limit at all, so `-1` turns a bounded batch into an unbounded one; a
|
|
4630
|
-
* fractional or non-finite value is refused by SQLite itself, as a failure every later call
|
|
4631
|
-
* repeats. Neither is a value anyone means, so it is a configuration error, refused here with the
|
|
4632
|
-
* option's name, and never silently normalized into something the caller did not ask for.
|
|
4633
|
-
* Returns the value, so a call site can bind it in place.
|
|
4634
|
-
*/
|
|
4635
|
-
export declare function assertRowLimit(name: string, value: number): number;
|
|
4636
|
-
/**
|
|
4637
|
-
* A caller-supplied row offset, checked before it reaches a SQL `OFFSET` — `assertRowLimit`'s
|
|
4638
|
-
* sibling. Zero is a valid offset; a negative, fractional or non-finite one is refused rather
|
|
4639
|
-
* than passed to SQLite, which fails the statement on it.
|
|
4640
|
-
*/
|
|
4641
|
-
export declare function assertRowOffset(name: string, value: number): number;
|
|
4642
|
-
/** How many rows of EACH telemetry table one `pruneTelemetry` call deletes, by default. */
|
|
4643
|
-
export declare const TELEMETRY_PRUNE_BATCH = 500;
|
|
4644
|
-
/**
|
|
4645
|
-
* The three telemetry retentions as statements, for `pruneTelemetry` on both adapters (#1632).
|
|
4646
|
-
*
|
|
4647
|
-
* Each table already prunes on write, and that bound holds only while something is written:
|
|
4648
|
-
* a quiet directory keeps a row past its retention for as long as nothing new arrives in
|
|
4649
|
-
* that table. An issue is the case that matters. Erasure skips an issue whose exemplar owner
|
|
4650
|
-
* is unknown, on the promise that it ages out within `ISSUE_RETENTION_DAYS`, and on a
|
|
4651
|
-
* directory with no new failures nothing ever deleted it. The scheduled pass runs these
|
|
4652
|
-
* statements, which makes the stated retention a bound in time rather than in traffic.
|
|
4653
|
-
*
|
|
4654
|
-
* **Bounded per call, oldest first.** After a long pause or a restore the backlog can be a
|
|
4655
|
-
* year of sweep runs, and one unbounded DELETE over it is a pass that fails on the Durable
|
|
4656
|
-
* Object every tick. Each statement deletes at most `limit` rows, chosen through the table's
|
|
4657
|
-
* own retention index (`… WHERE rowid IN (SELECT rowid … ORDER BY <ts> LIMIT ?)`), so the
|
|
4658
|
-
* backlog drains over passes. `RETURNING` makes the count what was actually deleted on both
|
|
4659
|
-
* adapters, rather than a rows-written figure that would also count index writes.
|
|
4660
|
-
*/
|
|
4661
|
-
export declare function telemetryRetentionStatements(nowMs: number, limit: number): {
|
|
4662
|
-
table: keyof TelemetryPruneReport;
|
|
4663
|
-
sql: string;
|
|
4664
|
-
params: [string, number];
|
|
4665
|
-
}[];
|
|
4666
|
-
/**
|
|
4667
|
-
* One bounded retention DELETE: at most `LIMIT ?` rows whose `column` is before `?`, oldest
|
|
4668
|
-
* first through the column's own index, `RETURNING 1` so the count is what was deleted. Takes
|
|
4669
|
-
* `(horizon, limit)`. The statement `telemetryRetentionStatements` and `pruneFindings` share.
|
|
4670
|
-
*/
|
|
4671
|
-
export declare function boundedRetentionDelete(table: string, column: string): string;
|
|
4672
4159
|
/** Filter for `listIssues` (#1233). Bounded by `limit` only — see the verb's doc. */
|
|
4673
4160
|
export interface IssueFilter {
|
|
4674
4161
|
status?: IssueStatus;
|
|
@@ -5737,261 +5224,6 @@ export interface ScopeHost {
|
|
|
5737
5224
|
defineOperation<I, O>(name: string, handler: OperationHandler<I, O>): void;
|
|
5738
5225
|
close(): Promise<void>;
|
|
5739
5226
|
}
|
|
5740
|
-
/**
|
|
5741
|
-
* The live-read surface (#938): one `Upgrade` in, one long-lived subscription out.
|
|
5742
|
-
*
|
|
5743
|
-
* **What crosses it is an invalidation, never a payload.** A frame names the event
|
|
5744
|
-
* type and the `(entityType, entityId)` it was about, and the client re-reads that
|
|
5745
|
-
* entity through the ordinary operation it already calls. That is what keeps the
|
|
5746
|
-
* change feed from quietly becoming a second, ungoverned read API: every field a
|
|
5747
|
-
* subscriber ever sees came back through the declared read surface, with that read's
|
|
5748
|
-
* own permission check, its own field-level omissions and its own PII handling.
|
|
5749
|
-
*
|
|
5750
|
-
* **Every frame is filtered per subscriber, after the commit and after the check.**
|
|
5751
|
-
* The host offers each committed event to each subscriber and delivers it only if
|
|
5752
|
-
* that subscriber passes the entity type's declared `liveTargets.readPermission` ON
|
|
5753
|
-
* THAT ENTITY — the same walk `ctx.check(key, entityRef)` makes, through the same
|
|
5754
|
-
* evaluator, so an entity-narrowed grant decides a push exactly as it decides a read.
|
|
5755
|
-
* An entity type no module declared reaches nobody. Knowing that a row exists and
|
|
5756
|
-
* changed at 14:02 is information about that row, so "the body was empty" is not a
|
|
5757
|
-
* defence: the filter runs whether or not there is a payload to withhold.
|
|
5758
|
-
*
|
|
5759
|
-
* A subscription may be narrowed `within` one entity (#1853), which only removes frames.
|
|
5760
|
-
* Two built roots replace the per-row check with one about the ROOT, and their subscribers
|
|
5761
|
-
* are sent bare `LiveNudge` frames that name no entity: `checkedWithin`, whose principal must
|
|
5762
|
-
* pass a stated key on the root at the handshake and again on every pass that has something
|
|
5763
|
-
* to send (#938), and `vouchedWithin`, which the vertical asserts once and nothing re-checks
|
|
5764
|
-
* (#1853). Which to use is on `checkedWithin`.
|
|
5765
|
-
*
|
|
5766
|
-
* **Generic over the runtime's request and response, because the kernel names
|
|
5767
|
-
* neither.** This package has one dependency and no DOM or workers lib
|
|
5768
|
-
* (`docs/architecture/dependency-policy.md`), which is why `FetchLike` above describes
|
|
5769
|
-
* a `fetch` structurally rather than importing one. The same rule applies here, and it
|
|
5770
|
-
* is load-bearing in the other direction too: what a caller gets back must be the
|
|
5771
|
-
* runtime's OWN response object, because a Worker's handler has to return one. A
|
|
5772
|
-
* structural stand-in would describe it correctly and still not be returnable. So the
|
|
5773
|
-
* type travels through instead of being restated — `CloudflareScopeHost` binds it to
|
|
5774
|
-
* the real `Request`/`Response`, and code holding the bare contract gets `unknown` back
|
|
5775
|
-
* and narrows at its own mount point, where it knows which host it is on.
|
|
5776
|
-
*/
|
|
5777
|
-
export interface LiveReadSurface<Req extends LiveUpgradeRequest = LiveUpgradeRequest, Res = unknown> {
|
|
5778
|
-
/**
|
|
5779
|
-
* Accept a WebSocket `Upgrade` and subscribe the principal to the scope's changes.
|
|
5780
|
-
*
|
|
5781
|
-
* The caller is the vertical, and it has already done the one thing this surface
|
|
5782
|
-
* cannot do for itself: resolved WHO is asking. The principal is an authenticated
|
|
5783
|
-
* fact the vertical's session carries, exactly as it is for an `invoke` — this
|
|
5784
|
-
* surface trusts it the same way and no further, which is why every frame is still
|
|
5785
|
-
* checked against it individually rather than a subscription being authorized once.
|
|
5786
|
-
*
|
|
5787
|
-
* Returns the 101 to hand back to the client, or a refusal to return as-is: a
|
|
5788
|
-
* request that is not an upgrade gets 426, and one arriving over a hop that cannot
|
|
5789
|
-
* carry a socket gets a refusal naming that, so the client can fall back to polling
|
|
5790
|
-
* KNOWINGLY. Neither is thrown, because both are answers to a client rather than
|
|
5791
|
-
* faults of the caller.
|
|
5792
|
-
*/
|
|
5793
|
-
subscribe(input: {
|
|
5794
|
-
tenantId: TenantId;
|
|
5795
|
-
scopeId: ScopeId;
|
|
5796
|
-
/** WHO is subscribing — resolved by the vertical from its own session, never by the client. */
|
|
5797
|
-
principal: PrincipalId;
|
|
5798
|
-
/** The upgrade request as it arrived, carried whole so the host reads its own headers. */
|
|
5799
|
-
request: Req;
|
|
5800
|
-
/**
|
|
5801
|
-
* Narrow the feed to one entity and what hangs beneath it (#1853).
|
|
5802
|
-
*
|
|
5803
|
-
* A frame is then delivered only if its entity IS this one or reaches it upward
|
|
5804
|
-
* through live declared `parent` edges (what `ctx.link` / `ctx.relink` write) — the
|
|
5805
|
-
* walk `ctx.check` makes, rooted here instead of at a grant.
|
|
5806
|
-
*
|
|
5807
|
-
* - **An `EntityRef` narrows and nothing else.** The principal's own per-frame
|
|
5808
|
-
* `liveTargets` check still runs; `within` is ANDed with it, so it can only take
|
|
5809
|
-
* frames away. A screen watching one conversation passes it to stop hearing the
|
|
5810
|
-
* rest of the desk.
|
|
5811
|
-
* - **A `vouchedWithin(…)` value replaces the principal's check.** For a subscriber
|
|
5812
|
-
* confined by something other than a grant — a widget visitor holding a session
|
|
5813
|
-
* token the vertical has just redeemed. The vertical asserts access to the root,
|
|
5814
|
-
* the same trust it already extends in naming `principal`, and the scope's walk is
|
|
5815
|
-
* the whole filter. Such a subscriber receives `LiveNudge` frames only, which name
|
|
5816
|
-
* no event type and no entity.
|
|
5817
|
-
* - **A `checkedWithin(…)` value replaces the per-row check with one on the root**
|
|
5818
|
-
* (#938). The principal must pass the stated key on the root, at the handshake (a
|
|
5819
|
-
* `403` otherwise) and again on every pass that has a row beneath it to announce,
|
|
5820
|
-
* so a grant withdrawn, or a root moved out from under it, closes the socket
|
|
5821
|
-
* instead of nudging it. `LiveNudge` frames only, as for a vouched root.
|
|
5822
|
-
*
|
|
5823
|
-
* **Freshness (Codex #2077 r5).** A decision is reused within a pass only while the
|
|
5824
|
-
* scope has had no write and nothing it holds has reached its `expires_at` since the
|
|
5825
|
-
* decision. It is never reused while the scope reads its permissions from the directory.
|
|
5826
|
-
* Both are checked against the clock immediately before each send. A change that lands
|
|
5827
|
-
* while a check is still being evaluated can let that one frame through. Frames name
|
|
5828
|
-
* nothing the subscriber could not read when checked, and the next frame, pass and poll
|
|
5829
|
-
* see the change: a push is a hint at most one evaluation stale.
|
|
5830
|
-
*/
|
|
5831
|
-
within?: EntityRef | VouchedWithin | CheckedWithin;
|
|
5832
|
-
/**
|
|
5833
|
-
* When the credential that proved `principal` stops being valid (ISO 8601), so the
|
|
5834
|
-
* socket never outlives it (#938). Pass the session's or the bearer's expiry
|
|
5835
|
-
* (`AuthSubject.expiresAt`). A handshake at or past it is refused, and the scope closes
|
|
5836
|
-
* the socket (`1008`) on its first pass at or past it, before sending anything on that
|
|
5837
|
-
* pass. Absent: the socket lives until either end closes it, as before.
|
|
5838
|
-
*/
|
|
5839
|
-
expiresAt?: string;
|
|
5840
|
-
}): Promise<Res>;
|
|
5841
|
-
}
|
|
5842
|
-
/** The brand only `vouchedWithin` can apply — a literal cannot type-check as one. */
|
|
5843
|
-
declare const vouchedBrand: unique symbol;
|
|
5844
|
-
/**
|
|
5845
|
-
* A `within` root the VERTICAL vouches the subscriber may watch, in place of the
|
|
5846
|
-
* principal's own grants (#1853). Built only by `vouchedWithin`.
|
|
5847
|
-
*/
|
|
5848
|
-
export interface VouchedWithin {
|
|
5849
|
-
readonly [vouchedBrand]: true;
|
|
5850
|
-
readonly entity: EntityRef;
|
|
5851
|
-
/** Why the vertical vouches — what it checked. Required, and kept on the subscription. */
|
|
5852
|
-
readonly because: string;
|
|
5853
|
-
}
|
|
5854
|
-
/**
|
|
5855
|
-
* Vouch that the subscriber may watch `entity` and everything beneath it, though the
|
|
5856
|
-
* principal it subscribes as holds no read on it.
|
|
5857
|
-
*
|
|
5858
|
-
* The one way to reach the replacing mode, so every use is greppable and none is an
|
|
5859
|
-
* accident: an object literal is refused by the type checker, and anything not built
|
|
5860
|
-
* here is refused by the host at run time (`isVouchedWithin`). Call it only after the
|
|
5861
|
-
* vertical itself has proven access — for ticket0's widget, by redeeming the session
|
|
5862
|
-
* token for the session it names.
|
|
5863
|
-
*
|
|
5864
|
-
* What it costs the subscriber is detail: a vouched feed carries `LiveNudge` frames, so
|
|
5865
|
-
* the scope never tells it which entity changed or how. Root it at an entity whose
|
|
5866
|
-
* subtree holds only what the subscriber may see — the walk is the whole filter.
|
|
5867
|
-
*/
|
|
5868
|
-
export declare function vouchedWithin(entity: EntityRef, opts: {
|
|
5869
|
-
because: string;
|
|
5870
|
-
}): VouchedWithin;
|
|
5871
|
-
/** Was this value built by `vouchedWithin`? A host asks before it drops the principal's check. */
|
|
5872
|
-
export declare function isVouchedWithin(value: unknown): value is VouchedWithin;
|
|
5873
|
-
/** The brand only `checkedWithin` can apply. */
|
|
5874
|
-
declare const checkedBrand: unique symbol;
|
|
5875
|
-
/**
|
|
5876
|
-
* A `within` root the PRINCIPAL must be allowed `permission` on, re-checked for as long
|
|
5877
|
-
* as the socket is open (#938). Built only by `checkedWithin`.
|
|
5878
|
-
*/
|
|
5879
|
-
export interface CheckedWithin {
|
|
5880
|
-
readonly [checkedBrand]: true;
|
|
5881
|
-
readonly entity: EntityRef;
|
|
5882
|
-
readonly permission: PermissionKey;
|
|
5883
|
-
}
|
|
5884
|
-
/**
|
|
5885
|
-
* Watch `entity` and everything beneath it, for as long as the subscriber may
|
|
5886
|
-
* `permission` on `entity` itself.
|
|
5887
|
-
*
|
|
5888
|
-
* For a subscriber whose grant reaches the ROOT but not each row under it the way a
|
|
5889
|
-
* `liveTargets` key would: ticket0's portal customer holds `conversation:read-own` on
|
|
5890
|
-
* their contact, which reaches the conversation's public thread, while the per-row key
|
|
5891
|
-
* on a message is the staff read. Rooted at an entity whose subtree is exactly what that
|
|
5892
|
-
* key's read returns, the walk is the row filter and the key is the gate.
|
|
5893
|
-
*
|
|
5894
|
-
* The gate is asked at the handshake (`403` if it refuses) and again, once per socket,
|
|
5895
|
-
* on every post-commit pass that has a row beneath the root to announce. A refusal or a
|
|
5896
|
-
* check that throws closes the socket (`1008`) and sends nothing on that pass, so a
|
|
5897
|
-
* revoked grant, a principal who is gone, or a root relinked out of the grant's reach
|
|
5898
|
-
* ends the subscription instead of nudging it, and the client's reconnect meets the
|
|
5899
|
-
* handshake's `403`. A pass with nothing beneath the root asks nothing, so an idle
|
|
5900
|
-
* subscription costs no checks.
|
|
5901
|
-
*
|
|
5902
|
-
* **`checkedWithin` or `vouchedWithin`?** Use `checkedWithin` whenever the subscriber is
|
|
5903
|
-
* a principal with a grant that reaches the root: authority leaves with the grant. Use
|
|
5904
|
-
* `vouchedWithin` only when there is no such grant to ask, as for ticket0's widget
|
|
5905
|
-
* visitor, who holds a session token rather than a principal of their own. A vouched
|
|
5906
|
-
* socket is asserted once, so a token revoked mid-socket keeps receiving content-free
|
|
5907
|
-
* nudges until the socket closes. That is known and accepted for the widget: the nudge
|
|
5908
|
-
* names nothing, and every re-read it causes is checked again.
|
|
5909
|
-
*
|
|
5910
|
-
* Both send `LiveNudge` frames only: the subscriber may not read each row, so it is
|
|
5911
|
-
* never told which one changed. Only rows of a type some module declared in
|
|
5912
|
-
* `liveTargets` are announced, as for every other subscriber.
|
|
5913
|
-
*/
|
|
5914
|
-
export declare function checkedWithin(entity: EntityRef, permission: string): CheckedWithin;
|
|
5915
|
-
/** Was this value built by `checkedWithin`? */
|
|
5916
|
-
export declare function isCheckedWithin(value: unknown): value is CheckedWithin;
|
|
5917
|
-
/**
|
|
5918
|
-
* Is this request asking to be upgraded to a WebSocket?
|
|
5919
|
-
*
|
|
5920
|
-
* Paired with `LIVE_MODE_HEADER` (`@substrat-run/contracts`) because a vertical's live
|
|
5921
|
-
* route asks it first, before anything else it decides: only a WebSocket handshake is a
|
|
5922
|
-
* live read, and a browser always sends `Origin` on one, so everything past this check
|
|
5923
|
-
* can trust that a missing `Origin` did not come from a browser page. A plain GET with a
|
|
5924
|
-
* cookie on it cannot.
|
|
5925
|
-
*/
|
|
5926
|
-
export declare function isUpgradeRequest(request: LiveUpgradeRequest): boolean;
|
|
5927
|
-
/**
|
|
5928
|
-
* The only thing this surface needs of an incoming request: its headers.
|
|
5929
|
-
*
|
|
5930
|
-
* Narrow on purpose. A live-read door reads `Upgrade` to know what is being asked for
|
|
5931
|
-
* and `cf-connecting-o2o` to know whether this hop can carry it, and nothing else — it
|
|
5932
|
-
* does not route on the path, read the body, or care about the method. Describing
|
|
5933
|
-
* exactly that much is what lets a test drive the door with a two-line object instead
|
|
5934
|
-
* of constructing a runtime request.
|
|
5935
|
-
*/
|
|
5936
|
-
export interface LiveUpgradeRequest {
|
|
5937
|
-
readonly headers: {
|
|
5938
|
-
get(name: string): string | null;
|
|
5939
|
-
};
|
|
5940
|
-
}
|
|
5941
|
-
/**
|
|
5942
|
-
* One frame on a live read (#938) — the wire shape, so both ends name it once.
|
|
5943
|
-
*
|
|
5944
|
-
* Deliberately not the outbox envelope. An envelope carries the payload, the
|
|
5945
|
-
* authorization chain, the impersonation stamp and the PII class, and none of those
|
|
5946
|
-
* belong on a channel whose contract is "re-read it yourself". What a subscriber
|
|
5947
|
-
* needs in order to act is what is here: which entity changed, and what happened to it.
|
|
5948
|
-
*/
|
|
5949
|
-
export interface LiveChange {
|
|
5950
|
-
/** Always `'change'` today; a field rather than an assumption, so a second kind can be added. */
|
|
5951
|
-
readonly kind: 'change';
|
|
5952
|
-
/**
|
|
5953
|
-
* The event id — a ULID, so frames sort and a repeat is recognisable.
|
|
5954
|
-
*
|
|
5955
|
-
* **Not a gap detector, and a client must not use it as one.** ULIDs are ordered but
|
|
5956
|
-
* not contiguous, so "the next id is not the one after this" says nothing on its own.
|
|
5957
|
-
* More to the point, gaps here are the NORMAL case and the deliberate one: the
|
|
5958
|
-
* permission filter withholds every event this subscriber may not read, so the ids it
|
|
5959
|
-
* receives are a sparse subset of what the scope emitted, by design. A client
|
|
5960
|
-
* inferring missed updates from the spacing would be reading someone else's
|
|
5961
|
-
* entitlements as its own packet loss.
|
|
5962
|
-
*
|
|
5963
|
-
* What it is good for: ordering frames that arrive out of order, and discarding one
|
|
5964
|
-
* it has already acted on. Detecting a genuinely missed update would need an
|
|
5965
|
-
* authorized contiguous cursor — a per-subscriber sequence, counted after the filter —
|
|
5966
|
-
* which this protocol does not have and should not grow by accident.
|
|
5967
|
-
*/
|
|
5968
|
-
readonly id: string;
|
|
5969
|
-
/** The emitted event type, e.g. `'ticket0/message-posted'`. */
|
|
5970
|
-
readonly type: string;
|
|
5971
|
-
readonly entityType: string;
|
|
5972
|
-
readonly entityId: string;
|
|
5973
|
-
/** When the event was emitted (ISO 8601), i.e. the emitting operation's instant. */
|
|
5974
|
-
readonly at: string;
|
|
5975
|
-
}
|
|
5976
|
-
/**
|
|
5977
|
-
* What a vouched or checked subscriber is told (#1853, #938): something beneath its root
|
|
5978
|
-
* changed, and nothing else.
|
|
5979
|
-
*
|
|
5980
|
-
* No event type and no entity, deliberately. Such a subscriber holds no read on the
|
|
5981
|
-
* entities its frames are about — the vertical vouched for the ROOT, or the principal was
|
|
5982
|
-
* checked on the root alone, and the scope cannot know which rows under it the vertical's
|
|
5983
|
-
* own read would show. Naming the type
|
|
5984
|
-
* or the id would tell it what it never asked to read. The client re-reads, as it does
|
|
5985
|
-
* on a `LiveChange`.
|
|
5986
|
-
*/
|
|
5987
|
-
export interface LiveNudge {
|
|
5988
|
-
readonly kind: 'nudge';
|
|
5989
|
-
/** The event id, as on `LiveChange` — for ordering and de-duplication only. */
|
|
5990
|
-
readonly id: string;
|
|
5991
|
-
readonly at: string;
|
|
5992
|
-
}
|
|
5993
|
-
/** Any frame a live read sends. */
|
|
5994
|
-
export type LiveFrame = LiveChange | LiveNudge;
|
|
5995
5227
|
/**
|
|
5996
5228
|
* Refuse a cutoff in the future — at the HostAdmin boundary, not only at the HTTP door.
|
|
5997
5229
|
*
|