@voltro/runtime 0.32.0 → 0.34.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/CHANGELOG.md +2006 -0
- package/THIRD-PARTY-NOTICES.md +1 -29
- package/dist/index.d.ts +1358 -89
- package/dist/index.js +2716 -1630
- package/package.json +16 -10
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { AnyApprovalPolicy } from '@voltro/protocol';
|
|
1
2
|
import { AnyCheckSpec } from '@voltro/protocol';
|
|
3
|
+
import { AnyGuardSpec } from '@voltro/protocol';
|
|
4
|
+
import { AuthMiddleware } from '@voltro/protocol';
|
|
2
5
|
import { AuthStrategy } from '@voltro/protocol';
|
|
3
6
|
import { CaughtUpVerdict } from '@voltro/database';
|
|
4
7
|
import { ChangeEvent } from '@voltro/database';
|
|
5
8
|
import { clearRetentions } from '@voltro/database';
|
|
9
|
+
import { ConnectionCredential } from '@voltro/protocol';
|
|
6
10
|
import { ConnectionInfo } from '@voltro/protocol';
|
|
11
|
+
import { ConnectionInfoMiddleware } from '@voltro/protocol';
|
|
7
12
|
import { ConnectionKind } from '@voltro/protocol';
|
|
8
13
|
import { ConnectionState } from '@voltro/protocol';
|
|
9
14
|
import { Context } from 'effect';
|
|
@@ -38,10 +43,12 @@ import { inspectWorkflow } from '@voltro/workflow';
|
|
|
38
43
|
import { Kv } from '@voltro/kv';
|
|
39
44
|
import { KvStoreShape } from '@voltro/kv';
|
|
40
45
|
import { Layer } from 'effect';
|
|
46
|
+
import { listRetentionConflicts } from '@voltro/database';
|
|
41
47
|
import { listRetentions } from '@voltro/database';
|
|
42
48
|
import { Metric } from 'effect';
|
|
43
49
|
import { MetricBoundaries } from 'effect';
|
|
44
50
|
import { MetricReader } from '@opentelemetry/sdk-metrics';
|
|
51
|
+
import { PendingApproval } from '@voltro/protocol';
|
|
45
52
|
import { PluginHttpRoute } from '@voltro/protocol';
|
|
46
53
|
import { PluginRefOrphanPolicy } from '@voltro/database';
|
|
47
54
|
import { Predicate } from '@voltro/database';
|
|
@@ -50,6 +57,8 @@ import { QueryDescriptor } from '@voltro/database';
|
|
|
50
57
|
import { Redis } from 'ioredis';
|
|
51
58
|
import { registerRetention } from '@voltro/database';
|
|
52
59
|
import { RestRouteDescriptor } from '@voltro/protocol/rest';
|
|
60
|
+
import { RetentionConflict } from '@voltro/database';
|
|
61
|
+
import { RetentionSource } from '@voltro/database';
|
|
53
62
|
import { RetentionSpec } from '@voltro/database';
|
|
54
63
|
import { retentionTtlMsFromEnv } from '@voltro/database';
|
|
55
64
|
import { Row } from '@voltro/database';
|
|
@@ -66,6 +75,7 @@ import { spawn } from 'node:child_process';
|
|
|
66
75
|
import { SqlClient } from '@effect/sql';
|
|
67
76
|
import { Stream } from 'effect';
|
|
68
77
|
import { Subject } from '@voltro/protocol';
|
|
78
|
+
import { SubjectResolution } from '@voltro/protocol';
|
|
69
79
|
import { SubjectService } from '@voltro/protocol';
|
|
70
80
|
import { SubscriptionEvent } from '@voltro/protocol';
|
|
71
81
|
import { sweepRetention } from '@voltro/database';
|
|
@@ -527,26 +537,24 @@ export declare type AnalyticsMetric = 'count' | 'unique' | {
|
|
|
527
537
|
readonly avg: string;
|
|
528
538
|
};
|
|
529
539
|
|
|
530
|
-
/**
|
|
531
|
-
* A single reactive-table change to mirror into the warehouse. Mirrors
|
|
532
|
-
* the framework's internal `ChangeEvent` but flattened to the shape a
|
|
533
|
-
* sink needs: the table name, the operation, and the row. For `insert`
|
|
534
|
-
* / `update` the post-image is in `row`; for `delete` the pre-image is
|
|
535
|
-
* in `row` (so the sink knows which primary key to remove).
|
|
536
|
-
*/
|
|
537
|
-
export declare interface AnalyticsMirrorChange {
|
|
538
|
-
readonly table: string;
|
|
539
|
-
readonly op: 'insert' | 'update' | 'delete';
|
|
540
|
-
/** Post-image for insert/update; pre-image for delete. */
|
|
541
|
-
readonly row: Readonly<Record<string, unknown>>;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
540
|
export declare interface AnalyticsMirrorHandle {
|
|
545
541
|
/** Tables actually being mirrored (the sink's `mirror.tables`). Empty
|
|
546
542
|
* when the sink declares no mirror. */
|
|
547
543
|
readonly tables: ReadonlyArray<string>;
|
|
548
|
-
/** Detach the `onChange` subscription. */
|
|
544
|
+
/** Detach the `onChange` subscription and stop the repair loop. */
|
|
549
545
|
readonly detach: () => void;
|
|
546
|
+
/** Settle every queued + in-flight write. Resolves when the mirror has
|
|
547
|
+
* nothing left to apply (or has queued it for repair). */
|
|
548
|
+
readonly flush: () => Promise<void>;
|
|
549
|
+
/**
|
|
550
|
+
* Re-drive every key awaiting repair: re-read its CURRENT row from the
|
|
551
|
+
* store and re-apply it (upsert when present, remove when gone) under
|
|
552
|
+
* a fresh version. Runs on the `repairIntervalMs` timer; exposed so an
|
|
553
|
+
* operator or a shutdown hook can force a pass. Resolves with the
|
|
554
|
+
* number of keys that landed.
|
|
555
|
+
*/
|
|
556
|
+
readonly repair: () => Promise<number>;
|
|
557
|
+
readonly stats: () => AnalyticsMirrorStats;
|
|
550
558
|
}
|
|
551
559
|
|
|
552
560
|
/**
|
|
@@ -561,6 +569,13 @@ export declare interface AnalyticsMirrorHandle {
|
|
|
561
569
|
* MUST be idempotent — the same change may be re-delivered after a
|
|
562
570
|
* reconnect, and the upsert is keyed on `primaryKey` so re-applying a
|
|
563
571
|
* row is a no-op-equivalent overwrite.
|
|
572
|
+
*
|
|
573
|
+
* Idempotent is not enough on its own: re-delivery can also arrive OUT
|
|
574
|
+
* OF ORDER, so both methods additionally receive a {@link MirrorVersion}
|
|
575
|
+
* and MUST ignore a write whose version is not greater than the version
|
|
576
|
+
* already stored for that key (ClickHouse gets this from
|
|
577
|
+
* `ReplacingMergeTree(version)`; DuckDB from an `ON CONFLICT … WHERE
|
|
578
|
+
* excluded.version > version` guard).
|
|
564
579
|
*/
|
|
565
580
|
export declare interface AnalyticsMirrorImpl {
|
|
566
581
|
/** Reactive tables this sink mirrors. The framework only forwards
|
|
@@ -569,10 +584,81 @@ export declare interface AnalyticsMirrorImpl {
|
|
|
569
584
|
/** Primary-key column the sink upserts/deletes by. Defaults to `'id'`
|
|
570
585
|
* when omitted. */
|
|
571
586
|
readonly primaryKey?: string;
|
|
572
|
-
/** Apply an insert/update. Idempotent upsert keyed on `primaryKey
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
587
|
+
/** Apply an insert/update. Idempotent upsert keyed on `primaryKey`,
|
|
588
|
+
* version-guarded by `write.version`. */
|
|
589
|
+
readonly upsert: (write: AnalyticsMirrorUpsert) => Effect.Effect<void, AnalyticsFailure>;
|
|
590
|
+
/** Apply a delete. Idempotent — removing an absent key is a no-op —
|
|
591
|
+
* and version-guarded by `write.version`. */
|
|
592
|
+
readonly remove: (write: AnalyticsMirrorRemove) => Effect.Effect<void, AnalyticsFailure>;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** One delete to apply to the mirror. */
|
|
596
|
+
export declare interface AnalyticsMirrorRemove {
|
|
597
|
+
readonly table: string;
|
|
598
|
+
/** Primary-key value of the removed row. */
|
|
599
|
+
readonly primaryKeyValue: unknown;
|
|
600
|
+
/** See {@link MirrorVersion}. A delete is ordered against upserts of
|
|
601
|
+
* the same key by exactly this number. */
|
|
602
|
+
readonly version: MirrorVersion;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Counters a caller (boot log, inspect endpoint, test) can read. */
|
|
606
|
+
export declare interface AnalyticsMirrorStats {
|
|
607
|
+
/** Writes the sink accepted. */
|
|
608
|
+
readonly forwarded: number;
|
|
609
|
+
/** Keys with a queued-but-not-yet-applied write. */
|
|
610
|
+
readonly pending: number;
|
|
611
|
+
/** Keys awaiting repair (retries exhausted). */
|
|
612
|
+
readonly awaitingRepair: number;
|
|
613
|
+
/** Changes provably lost — repair-queue overflow. */
|
|
614
|
+
readonly dropped: number;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Every number the mirror picks on the app's behalf, with its default.
|
|
619
|
+
* Each is also readable from the environment so an operator can tune a
|
|
620
|
+
* running deployment without a code change.
|
|
621
|
+
*/
|
|
622
|
+
export declare interface AnalyticsMirrorTunables {
|
|
623
|
+
/**
|
|
624
|
+
* Total attempts for one mirror write (1 = no retry).
|
|
625
|
+
* Default `5`. Env: `VOLTRO_ANALYTICS_MIRROR_RETRY_ATTEMPTS`.
|
|
626
|
+
*/
|
|
627
|
+
readonly retryAttempts: number;
|
|
628
|
+
/**
|
|
629
|
+
* First backoff delay; doubles per attempt.
|
|
630
|
+
* Default `100`ms. Env: `VOLTRO_ANALYTICS_MIRROR_RETRY_BASE_MS`.
|
|
631
|
+
*/
|
|
632
|
+
readonly retryBaseDelayMs: number;
|
|
633
|
+
/**
|
|
634
|
+
* Ceiling for the doubling backoff.
|
|
635
|
+
* Default `30_000`ms. Env: `VOLTRO_ANALYTICS_MIRROR_RETRY_MAX_MS`.
|
|
636
|
+
*/
|
|
637
|
+
readonly retryMaxDelayMs: number;
|
|
638
|
+
/**
|
|
639
|
+
* How often the repair loop re-drives changes whose retries ran out.
|
|
640
|
+
* `0` disables the loop (the queue then only drains via an explicit
|
|
641
|
+
* `handle.repair()`).
|
|
642
|
+
* Default `60_000`ms. Env: `VOLTRO_ANALYTICS_MIRROR_REPAIR_INTERVAL_MS`.
|
|
643
|
+
*/
|
|
644
|
+
readonly repairIntervalMs: number;
|
|
645
|
+
/**
|
|
646
|
+
* Maximum keys held for repair. Past this, the oldest queued key is
|
|
647
|
+
* dropped, counted and logged at error level — a bounded queue that
|
|
648
|
+
* says so beats an unbounded one that ends the process.
|
|
649
|
+
* Default `10_000`. Env: `VOLTRO_ANALYTICS_MIRROR_REPAIR_QUEUE_LIMIT`.
|
|
650
|
+
*/
|
|
651
|
+
readonly repairQueueLimit: number;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** One insert/update to apply to the mirror. */
|
|
655
|
+
export declare interface AnalyticsMirrorUpsert {
|
|
656
|
+
readonly table: string;
|
|
657
|
+
/** Post-image of the changed row. */
|
|
658
|
+
readonly row: Readonly<Record<string, unknown>>;
|
|
659
|
+
/** See {@link MirrorVersion} — persist it, compare against it, never
|
|
660
|
+
* replace it with a local clock reading. */
|
|
661
|
+
readonly version: MirrorVersion;
|
|
576
662
|
}
|
|
577
663
|
|
|
578
664
|
/**
|
|
@@ -680,6 +766,9 @@ declare type AnyRow = Record<string, unknown>;
|
|
|
680
766
|
|
|
681
767
|
declare type AnyRow_2 = Record<string, unknown>;
|
|
682
768
|
|
|
769
|
+
/** Locked-down policy for an API response. Nothing loads, nothing frames it. */
|
|
770
|
+
export declare const API_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
|
|
771
|
+
|
|
683
772
|
export declare const API_KEYS_TABLE = "_voltro_api_keys";
|
|
684
773
|
|
|
685
774
|
/** Admin-gated management routes (issue / list / revoke) for the built-in keys.
|
|
@@ -924,6 +1013,21 @@ export declare interface AppContext {
|
|
|
924
1013
|
readonly loadMany: DataLoader['loadMany'];
|
|
925
1014
|
}
|
|
926
1015
|
|
|
1016
|
+
/**
|
|
1017
|
+
* The connection's headers as the auth chain should see them: the transport's
|
|
1018
|
+
* own headers with the bound credential patched over them.
|
|
1019
|
+
*
|
|
1020
|
+
* Returns the input UNCHANGED when nothing is bound — the overwhelmingly common
|
|
1021
|
+
* case is a connection that never re-authenticated, and it must not pay for a
|
|
1022
|
+
* clone.
|
|
1023
|
+
*
|
|
1024
|
+
* Header names are lowercased on the patched keys because the rest of the chain
|
|
1025
|
+
* reads them lowercase (`headers['cookie']`, `headers['authorization']`), and a
|
|
1026
|
+
* patch written as `Authorization` that lands beside an existing `authorization`
|
|
1027
|
+
* is a credential that is present and invisible.
|
|
1028
|
+
*/
|
|
1029
|
+
export declare const applyConnectionCredential: (clientId: number, headers: Record<string, string | undefined>) => Record<string, string | undefined>;
|
|
1030
|
+
|
|
927
1031
|
/**
|
|
928
1032
|
* Pure helper backing `ctx.store.applyDefined`. Picks the listed keys from
|
|
929
1033
|
* `input` whose value is not `undefined`, so a PATCH sets exactly the fields
|
|
@@ -987,6 +1091,72 @@ export declare const applyUndoInvocation: (input: {
|
|
|
987
1091
|
readonly ok: boolean;
|
|
988
1092
|
}>;
|
|
989
1093
|
|
|
1094
|
+
/** What the dispatch spine hands the gate. */
|
|
1095
|
+
export declare interface ApprovalAdmissionInput {
|
|
1096
|
+
readonly procedure: string;
|
|
1097
|
+
readonly kind: 'mutation' | 'action';
|
|
1098
|
+
readonly policy: AnyApprovalPolicy;
|
|
1099
|
+
readonly subject: Subject | null;
|
|
1100
|
+
readonly input: unknown;
|
|
1101
|
+
readonly traceId?: string | undefined;
|
|
1102
|
+
/** Distinguishes a deliberate repeat of an identical request. */
|
|
1103
|
+
readonly nonce?: string | undefined;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/** The slice of AppContext the approval executors need. */
|
|
1107
|
+
export declare interface ApprovalExecutorCtx {
|
|
1108
|
+
readonly store: DataStore;
|
|
1109
|
+
readonly request?: {
|
|
1110
|
+
readonly subject?: Subject | null;
|
|
1111
|
+
} | undefined;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
export declare interface ApprovalGateApi {
|
|
1115
|
+
/**
|
|
1116
|
+
* ADMIT or REFUSE one approval-requiring call.
|
|
1117
|
+
*
|
|
1118
|
+
* Resolves when the call may proceed (an approval was found and CONSUMED, so
|
|
1119
|
+
* it cannot be spent twice). REJECTS with one of the typed approval errors
|
|
1120
|
+
* otherwise — the caller re-throws it and the rpc layer matches it against the
|
|
1121
|
+
* descriptor's `error:` union.
|
|
1122
|
+
*/
|
|
1123
|
+
readonly admit: (input: ApprovalAdmissionInput) => Promise<void>;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* The content-addressed identity of an intent.
|
|
1128
|
+
*
|
|
1129
|
+
* LENGTH-PREFIXED, not separator-joined, for the reason `promptVersionDigest`
|
|
1130
|
+
* records: no printable separator is injective over arbitrary prose, and the
|
|
1131
|
+
* repo's rule forbids the NUL that would be. A procedure named `a` with
|
|
1132
|
+
* requester `b:c` must not digest the same as `a:b` with requester `c`.
|
|
1133
|
+
*/
|
|
1134
|
+
export declare const approvalIntentKey: (input: {
|
|
1135
|
+
readonly procedure: string;
|
|
1136
|
+
readonly requestedBy: string | null;
|
|
1137
|
+
readonly tenantId: string | null;
|
|
1138
|
+
readonly payload: string;
|
|
1139
|
+
/** Distinguishes two DELIBERATELY identical requests. The caller states it;
|
|
1140
|
+
* the framework never invents one (that would defeat the whole idea). */
|
|
1141
|
+
readonly nonce?: string | undefined;
|
|
1142
|
+
}) => string;
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Mint a nonce for a DELIBERATE repeat of an identical request.
|
|
1146
|
+
*
|
|
1147
|
+
* Exported for callers that genuinely need two identical intents in flight (the
|
|
1148
|
+
* same refund, twice, on purpose). Never called by the framework: inventing one
|
|
1149
|
+
* per attempt is exactly the too-fine identity the header rejects.
|
|
1150
|
+
*/
|
|
1151
|
+
export declare const approvalNonce: () => string;
|
|
1152
|
+
|
|
1153
|
+
export declare const APPROVALS_TABLE = "_voltro_approvals";
|
|
1154
|
+
|
|
1155
|
+
/** `__voltro.approvals.pending` — the calling subject's approval work. */
|
|
1156
|
+
export declare const approvalsPendingExecutor: (input: {
|
|
1157
|
+
readonly limit?: number;
|
|
1158
|
+
}, ctx: ApprovalExecutorCtx) => Promise<ReadonlyArray<PendingApproval>>;
|
|
1159
|
+
|
|
990
1160
|
/**
|
|
991
1161
|
* Controls the lifecycle of the app process the orchestrator fronts.
|
|
992
1162
|
* Both `start` and `stop` are idempotent so the orchestrator can call
|
|
@@ -1021,6 +1191,14 @@ export declare const assertCan: (subject: RebacSubject, action: string, resource
|
|
|
1021
1191
|
*/
|
|
1022
1192
|
export declare const assertConnectionCipherConfigured: (definitions: ReadonlyArray<ConnectionDefinition>) => void;
|
|
1023
1193
|
|
|
1194
|
+
/**
|
|
1195
|
+
* Boot gate: every mounted incoming-webhook route must carry a verification
|
|
1196
|
+
* declaration. Throws `UnverifiedWebhookRoute` for the first that does not.
|
|
1197
|
+
*/
|
|
1198
|
+
export declare const assertWebhookRoutesDeclareVerification: (routes: ReadonlyMap<string, {
|
|
1199
|
+
readonly handle: unknown;
|
|
1200
|
+
}> | undefined) => void;
|
|
1201
|
+
|
|
1024
1202
|
/**
|
|
1025
1203
|
* Assign a subject to a variant — STABLE (same subject ⇒ same variant, always)
|
|
1026
1204
|
* and BALANCED (proportions converge to the weights over many subjects). The
|
|
@@ -1092,12 +1270,10 @@ export declare interface AsyncKv {
|
|
|
1092
1270
|
* Wire the sink's CDC-mirror to the store's change stream. No-op (and
|
|
1093
1271
|
* returns an empty handle) when the sink declares no `mirror` — so the
|
|
1094
1272
|
* caller can always call this unconditionally.
|
|
1095
|
-
*
|
|
1096
|
-
* Returns the set of mirrored tables (for boot logging) + a detach.
|
|
1097
1273
|
*/
|
|
1098
1274
|
export declare const attachAnalyticsMirror: (options: AttachAnalyticsMirrorOptions) => AnalyticsMirrorHandle;
|
|
1099
1275
|
|
|
1100
|
-
export declare interface AttachAnalyticsMirrorOptions {
|
|
1276
|
+
export declare interface AttachAnalyticsMirrorOptions extends Partial<AnalyticsMirrorTunables> {
|
|
1101
1277
|
readonly store: DataStore;
|
|
1102
1278
|
readonly sink: AnalyticsSinkImpl;
|
|
1103
1279
|
readonly log: SyncLogger;
|
|
@@ -1193,6 +1369,10 @@ export declare interface AuditableQuery {
|
|
|
1193
1369
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
1194
1370
|
}
|
|
1195
1371
|
|
|
1372
|
+
export declare interface AuthMiddlewareLayerOptions {
|
|
1373
|
+
readonly resolveSubject: ResolveSubjectFn;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1196
1376
|
/**
|
|
1197
1377
|
* An Effect that is ALSO awaitable.
|
|
1198
1378
|
*
|
|
@@ -1241,7 +1421,18 @@ export declare interface BeginOAuthResult {
|
|
|
1241
1421
|
readonly state: string;
|
|
1242
1422
|
}
|
|
1243
1423
|
|
|
1244
|
-
|
|
1424
|
+
/**
|
|
1425
|
+
* Present a new credential on an ALREADY-OPEN connection.
|
|
1426
|
+
*
|
|
1427
|
+
* Called from a handler that has just authenticated the caller — `auth.signin`,
|
|
1428
|
+
* a tenant switch — with the credential it minted (typically the session cookie
|
|
1429
|
+
* it is about to `Set-Cookie`). Every subsequent call on this connection is
|
|
1430
|
+
* resolved by the FULL auth chain against these headers, so the caller gets
|
|
1431
|
+
* exactly what a reconnect would give them and nothing more.
|
|
1432
|
+
*
|
|
1433
|
+
* Idempotent; a second bind replaces the first.
|
|
1434
|
+
*/
|
|
1435
|
+
export declare const bindConnectionCredential: (clientId: number, credential: ConnectionCredential) => void;
|
|
1245
1436
|
|
|
1246
1437
|
/**
|
|
1247
1438
|
* Bind one client's subscription to a declared event.
|
|
@@ -1522,6 +1713,14 @@ export declare interface CandidateShape {
|
|
|
1522
1713
|
}
|
|
1523
1714
|
|
|
1524
1715
|
/* Excluded from this release type: canonicalize */
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Canonical JSON — object keys sorted recursively, so two structurally equal
|
|
1719
|
+
* inputs that differ only in key order digest identically. A retry from a
|
|
1720
|
+
* different client build must not mint a second approval.
|
|
1721
|
+
*/
|
|
1722
|
+
export declare const canonicalJson: (value: unknown) => string;
|
|
1723
|
+
|
|
1525
1724
|
export { CaughtUpVerdict }
|
|
1526
1725
|
|
|
1527
1726
|
/** A CDC change event, structurally — what the reactive engine already emits
|
|
@@ -1602,6 +1801,47 @@ declare const CircuitOpen_base: Schema.TaggedErrorClass<CircuitOpen, "CircuitOpe
|
|
|
1602
1801
|
|
|
1603
1802
|
export declare type CircuitState = 'closed' | 'open' | 'half-open';
|
|
1604
1803
|
|
|
1804
|
+
/** How many bucket widths of predecessors a won claim leaves behind. */
|
|
1805
|
+
export declare const CLAIM_RETENTION_BUCKETS = 64;
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* How long a claim row outlives its own bucket before the next winner deletes
|
|
1809
|
+
* it — and the reasoning is the whole safety argument, so read it before
|
|
1810
|
+
* shrinking the number.
|
|
1811
|
+
*
|
|
1812
|
+
* A claim answers ONE question ("has this bucket been taken"), and it is only
|
|
1813
|
+
* ever asked while some replica still has a timer pending for that bucket.
|
|
1814
|
+
* Delete it too early and a straggler re-claims a bucket that already fired,
|
|
1815
|
+
* which is a DOUBLE FIRE — the failure this table exists to prevent. So the
|
|
1816
|
+
* grace has to cover the longest a replica can plausibly be late.
|
|
1817
|
+
*
|
|
1818
|
+
* The two callers are late in different ways, which is why the width is a
|
|
1819
|
+
* parameter rather than a constant:
|
|
1820
|
+
*
|
|
1821
|
+
* - `scheduleCoordinated` computes its bucket from `Date.now()` AT TICK TIME.
|
|
1822
|
+
* A tick stalled by ten minutes therefore claims the CURRENT bucket, never
|
|
1823
|
+
* the one it was armed for — a stale bucket is unreachable by construction,
|
|
1824
|
+
* and 64 widths is generous past the point of paranoia.
|
|
1825
|
+
* - a CRON firing carries its own scheduled instant, so a stalled firing DOES
|
|
1826
|
+
* re-present an old bucket. It passes no width, so it gets 64 × 60 s ≈ 68
|
|
1827
|
+
* minutes of grace, and a cron fires at most once a minute, so that costs
|
|
1828
|
+
* ~68 rows.
|
|
1829
|
+
*
|
|
1830
|
+
* The floor keeps a sub-second task from computing a grace measured in seconds.
|
|
1831
|
+
*/
|
|
1832
|
+
export declare const claimGraceMs: (bucketWidthMs: number | undefined) => number;
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* Decide whether a state-changing request may proceed.
|
|
1836
|
+
*
|
|
1837
|
+
* `headers` keys must already be lowercased (the rpc server lowercases once per
|
|
1838
|
+
* request and passes the same bag to every guard).
|
|
1839
|
+
*/
|
|
1840
|
+
export declare const classifyRequestOrigin: (input: {
|
|
1841
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
1842
|
+
readonly config: OriginGuardConfig;
|
|
1843
|
+
}) => OriginDecision;
|
|
1844
|
+
|
|
1605
1845
|
/**
|
|
1606
1846
|
* Decide whether a candidate matches a maintainable shape. Conservative by
|
|
1607
1847
|
* design — anything not provably maintainable is rejected (→ full recompute),
|
|
@@ -1609,6 +1849,9 @@ export declare type CircuitState = 'closed' | 'open' | 'half-open';
|
|
|
1609
1849
|
*/
|
|
1610
1850
|
export declare const classifyShape: (c: CandidateShape) => ShapeClassification;
|
|
1611
1851
|
|
|
1852
|
+
/** Test seam. */
|
|
1853
|
+
export declare const clearApprovalGate: () => void;
|
|
1854
|
+
|
|
1612
1855
|
/** Test seam — the in-flight map is module state; a test that asserts
|
|
1613
1856
|
* single-flight must be able to start from empty. */
|
|
1614
1857
|
export declare const clearConnectionRefreshFlights: () => void;
|
|
@@ -1621,6 +1864,14 @@ export { clearRetentions }
|
|
|
1621
1864
|
/** Clear the process-wide handle (test teardown). */
|
|
1622
1865
|
export declare const clearSystemStoreHandle: () => void;
|
|
1623
1866
|
|
|
1867
|
+
export declare interface ClientAddressInput {
|
|
1868
|
+
/** `socket.remoteAddress` — the only unforgeable value in the request. */
|
|
1869
|
+
readonly socketAddress: string | undefined;
|
|
1870
|
+
/** Raw `x-forwarded-for` header, if present. */
|
|
1871
|
+
readonly xForwardedFor: string | undefined;
|
|
1872
|
+
readonly config: TrustedProxyConfig;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1624
1875
|
/**
|
|
1625
1876
|
* Collect the declared rules from the discovered tables.
|
|
1626
1877
|
*
|
|
@@ -1837,13 +2088,6 @@ export declare interface ConnectionsFacadeDeps {
|
|
|
1837
2088
|
/** Test-only — number of streams currently registered for a client. */
|
|
1838
2089
|
export declare const _connectionStreamCount: (clientId: number) => number;
|
|
1839
2090
|
|
|
1840
|
-
/** Snapshot for diagnostics / dashboard. */
|
|
1841
|
-
export declare const connectionSubjectsSnapshot: () => ReadonlyArray<{
|
|
1842
|
-
clientId: number;
|
|
1843
|
-
subjectType: string;
|
|
1844
|
-
tenantId: string | null;
|
|
1845
|
-
}>;
|
|
1846
|
-
|
|
1847
2091
|
/** A resolved token set. `expiresAt` is an absolute epoch-ms deadline. */
|
|
1848
2092
|
export declare interface ConnectionTokens {
|
|
1849
2093
|
readonly accessToken: string;
|
|
@@ -1852,6 +2096,8 @@ export declare interface ConnectionTokens {
|
|
|
1852
2096
|
readonly scopes: ReadonlyArray<string>;
|
|
1853
2097
|
}
|
|
1854
2098
|
|
|
2099
|
+
export declare type CoordinatedEffect = () => void | CoordinatedTickOutcome | Promise<void | CoordinatedTickOutcome>;
|
|
2100
|
+
|
|
1855
2101
|
export declare interface CoordinatedScheduleDeps {
|
|
1856
2102
|
/** The exactly-once gate. `singleCoordinator` for one-process
|
|
1857
2103
|
* deployments; `makeAdvisoryLockCoordinator(store, replicaId)` for
|
|
@@ -1861,6 +2107,31 @@ export declare interface CoordinatedScheduleDeps {
|
|
|
1861
2107
|
readonly log?: SchedulerLogger;
|
|
1862
2108
|
/** Stable id of this replica — recorded on the claim row it wins. */
|
|
1863
2109
|
readonly replicaId: string;
|
|
2110
|
+
/**
|
|
2111
|
+
* Ceiling for the idle backoff. Defaults to `VOLTRO_POLL_CEILING_MS` (see
|
|
2112
|
+
* {@link readPollCeilingMs}), else {@link DEFAULT_MAX_IDLE_INTERVAL_MS}.
|
|
2113
|
+
* Set it EQUAL to the base interval to
|
|
2114
|
+
* opt a task out of backing off entirely — which is the right call only for a
|
|
2115
|
+
* task whose work cannot announce itself.
|
|
2116
|
+
*/
|
|
2117
|
+
readonly maxIdleIntervalMs?: number;
|
|
2118
|
+
/**
|
|
2119
|
+
* **Stop ticking entirely** on an idle tick with no known deadline, and come
|
|
2120
|
+
* back only on `wake()`. Default `false`.
|
|
2121
|
+
*
|
|
2122
|
+
* This is the difference between "a poller that got cheaper" and "no poller".
|
|
2123
|
+
* A deployment that never uses the queue this task drains pays ONE tick at
|
|
2124
|
+
* boot — which is not optional, it is what finds work a previous process left
|
|
2125
|
+
* behind — and then nothing at all.
|
|
2126
|
+
*
|
|
2127
|
+
* **Only pass `true` when an arrival is GUARANTEED to produce a `wake()`.**
|
|
2128
|
+
* That is a claim about the deployment, not about the task: with Postgres
|
|
2129
|
+
* LISTEN/NOTIFY or a broadcast transport every replica sees every enqueue, so
|
|
2130
|
+
* it holds. Without either, a REMOTE replica's enqueue produces no local
|
|
2131
|
+
* event, and a disarmed task would sleep through it forever. The backoff
|
|
2132
|
+
* ceiling exists for exactly that case and is the correct choice there.
|
|
2133
|
+
*/
|
|
2134
|
+
readonly disarmWhenIdle?: boolean;
|
|
1864
2135
|
}
|
|
1865
2136
|
|
|
1866
2137
|
export declare interface CoordinatedScheduleHandle {
|
|
@@ -1868,6 +2139,52 @@ export declare interface CoordinatedScheduleHandle {
|
|
|
1868
2139
|
readonly stop: () => void;
|
|
1869
2140
|
/** The task name (for logging / dedup diagnostics). */
|
|
1870
2141
|
readonly name: string;
|
|
2142
|
+
/**
|
|
2143
|
+
* Run a tick now, because something arrived.
|
|
2144
|
+
*
|
|
2145
|
+
* Coalesced to at most one extra tick per BASE interval: a queue drain writes
|
|
2146
|
+
* to the very table whose change events trigger this, so an uncoalesced wake
|
|
2147
|
+
* is a loop that feeds itself. Safe to call from a change handler, from any
|
|
2148
|
+
* replica, at any rate.
|
|
2149
|
+
*/
|
|
2150
|
+
readonly wake: () => void;
|
|
2151
|
+
/** The delay the next tick is currently armed for. Exposed for tests and the
|
|
2152
|
+
* inspect surface — a task sitting at the idle ceiling and one hammering the
|
|
2153
|
+
* base interval look identical from outside otherwise. */
|
|
2154
|
+
readonly currentIntervalMs: () => number;
|
|
2155
|
+
/** `false` once the task has stopped ticking and is waiting for `wake()`
|
|
2156
|
+
* (see `disarmWhenIdle`). A disarmed task and a stopped one are the same
|
|
2157
|
+
* thing from outside otherwise, and only one of them comes back. */
|
|
2158
|
+
readonly isArmed: () => boolean;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
/** Per-task overrides a caller may pass alongside the effect. */
|
|
2162
|
+
export declare interface CoordinatedTaskOptions {
|
|
2163
|
+
/** See {@link CoordinatedScheduleDeps.disarmWhenIdle}. Per TASK rather than
|
|
2164
|
+
* per process, because whether an arrival wakes you is a property of the
|
|
2165
|
+
* queue you drain — one plugin may have a change channel on its table and
|
|
2166
|
+
* another none. */
|
|
2167
|
+
readonly disarmWhenIdle?: boolean;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/** What a tick learned. Returning nothing means "assume there was work" —
|
|
2171
|
+
* the conservative reading, so a task that does not report cannot be backed
|
|
2172
|
+
* off into missing something. */
|
|
2173
|
+
export declare interface CoordinatedTickOutcome {
|
|
2174
|
+
/** `true` when the tick found nothing to do. Only an idle tick backs off. */
|
|
2175
|
+
readonly idle: boolean;
|
|
2176
|
+
/**
|
|
2177
|
+
* Milliseconds until the earliest thing this task already knows is coming —
|
|
2178
|
+
* a debounce window closing, a lease expiring. Caps the backoff, so a task
|
|
2179
|
+
* that is idle RIGHT NOW but has a deadline in 400 ms is armed for 400 ms
|
|
2180
|
+
* rather than for 30 s.
|
|
2181
|
+
*
|
|
2182
|
+
* This is the part a fixed interval cannot express and the part that makes
|
|
2183
|
+
* the backoff safe: without it, backing off is a bet that nothing time-based
|
|
2184
|
+
* is pending, and deferring controls are exactly the case where that bet is
|
|
2185
|
+
* wrong.
|
|
2186
|
+
*/
|
|
2187
|
+
readonly nextDueInMs?: number;
|
|
1871
2188
|
}
|
|
1872
2189
|
|
|
1873
2190
|
export declare type CoordinationOutcome = 'single' | 'wonLock' | 'lostLock' | 'external' | 'cluster';
|
|
@@ -1880,7 +2197,15 @@ export declare type CoordinationOutcome = 'single' | 'wonLock' | 'lostLock' | 'e
|
|
|
1880
2197
|
*/
|
|
1881
2198
|
export declare interface Coordinator {
|
|
1882
2199
|
readonly kind: 'single' | 'advisoryLock' | 'cluster';
|
|
1883
|
-
|
|
2200
|
+
/**
|
|
2201
|
+
* @param bucketWidthMs How far apart two consecutive buckets of THIS caller
|
|
2202
|
+
* are. Optional, and it is not used to decide the claim — it sizes how long
|
|
2203
|
+
* a won claim keeps its own predecessors around (see `claimGraceMs`). A cron
|
|
2204
|
+
* omits it and gets the conservative default; `scheduleCoordinated` passes
|
|
2205
|
+
* its interval, which is how a 250 ms task stops leaving a day of rows
|
|
2206
|
+
* behind.
|
|
2207
|
+
*/
|
|
2208
|
+
tryClaim(scheduleName: string, scheduledAt: Date, bucketWidthMs?: number): Promise<boolean>;
|
|
1884
2209
|
}
|
|
1885
2210
|
|
|
1886
2211
|
/**
|
|
@@ -1938,6 +2263,8 @@ export declare interface CostBudgetDefinition {
|
|
|
1938
2263
|
/** Resolved tumbling-window length in ms, or `null` for a cumulative budget. */
|
|
1939
2264
|
readonly windowMs: number | null;
|
|
1940
2265
|
readonly severity: CostBudgetSeverity;
|
|
2266
|
+
/** Resolved breach behaviour — `'observe'` unless the app opted in. */
|
|
2267
|
+
readonly onExceeded: CostBudgetOnExceeded;
|
|
1941
2268
|
readonly description?: string;
|
|
1942
2269
|
}
|
|
1943
2270
|
|
|
@@ -1962,14 +2289,50 @@ export declare interface CostBudgetDefinitionInput {
|
|
|
1962
2289
|
readonly window?: string;
|
|
1963
2290
|
/** Alerting priority carried on the breach signal. Default `'warn'`. */
|
|
1964
2291
|
readonly severity?: CostBudgetSeverity;
|
|
2292
|
+
/**
|
|
2293
|
+
* What a breach DOES. Default `'observe'` — signal only, the behaviour this
|
|
2294
|
+
* module shipped with.
|
|
2295
|
+
*
|
|
2296
|
+
* `'suspend'` opts the budget into stopping the next spend: a durable run
|
|
2297
|
+
* reaching a pre-spend gate for this budget parks on a
|
|
2298
|
+
* `_voltro_budget_holds` row and resumes when the budget recovers. Opt-in,
|
|
2299
|
+
* because parking a run is a change in what the app DOES and a default that
|
|
2300
|
+
* silently stalls work is worse than one that silently allows it.
|
|
2301
|
+
*/
|
|
2302
|
+
readonly onExceeded?: CostBudgetOnExceeded;
|
|
1965
2303
|
/** Human-facing note surfaced in devtools / the breach message. */
|
|
1966
2304
|
readonly description?: string;
|
|
1967
2305
|
}
|
|
1968
2306
|
|
|
2307
|
+
/**
|
|
2308
|
+
* What a breach DOES, as opposed to how loudly it is reported.
|
|
2309
|
+
*
|
|
2310
|
+
* This file used to state flatly that "the framework never BLOCKS compute on a
|
|
2311
|
+
* budget … an observability-grade signal over work that already happened", and
|
|
2312
|
+
* that was accurate: a cost event arrives after its compute, so a budget here
|
|
2313
|
+
* genuinely cannot refuse the work that produced it.
|
|
2314
|
+
*
|
|
2315
|
+
* What it can do — and now does — is stop the NEXT one. `'suspend'` marks the
|
|
2316
|
+
* budget as one that parks durable runs: a run reaching a pre-spend gate for
|
|
2317
|
+
* this budget SUSPENDS on a `_voltro_budget_holds` row (`@voltro/workflow`'s
|
|
2318
|
+
* `suspendForBudget`) instead of proceeding. The suspension is therefore still
|
|
2319
|
+
* not retroactive, and saying so is the honest version: it bounds future spend,
|
|
2320
|
+
* and the work already metered is already paid for.
|
|
2321
|
+
*
|
|
2322
|
+
* This flag is a DECLARATION, not a wiring. Nothing here subscribes the
|
|
2323
|
+
* accountant's `recovered` signal to `releaseBudgetHolds` — whether a recovered
|
|
2324
|
+
* compute budget should wake a run held on a DIFFERENT (e.g. AI-USD) budget is
|
|
2325
|
+
* an app decision, and a hold re-checks on its own durable clock regardless, so
|
|
2326
|
+
* no run is stranded by the absence. An app that wants the immediate release
|
|
2327
|
+
* subscribes: `registry.subscribe((s) => s.kind === 'recovered' && …)`.
|
|
2328
|
+
*
|
|
2329
|
+
* `'observe'` (default) keeps the previous behaviour exactly — signal only.
|
|
2330
|
+
*/
|
|
2331
|
+
export declare type CostBudgetOnExceeded = 'observe' | 'suspend';
|
|
2332
|
+
|
|
1969
2333
|
/** How loud a budget breach is. A classification carried on the signal for
|
|
1970
|
-
* alerting priority —
|
|
1971
|
-
*
|
|
1972
|
-
* budget is an observability-grade signal over work that already happened. */
|
|
2334
|
+
* alerting priority — orthogonal to {@link CostBudgetOnExceeded}, which decides
|
|
2335
|
+
* whether anything STOPS. */
|
|
1973
2336
|
export declare type CostBudgetSeverity = 'info' | 'warn' | 'critical';
|
|
1974
2337
|
|
|
1975
2338
|
/** Emitted when a budget crosses a threshold for a tenant. `warn` / `exceeded`
|
|
@@ -1996,6 +2359,9 @@ export declare interface CostBudgetState {
|
|
|
1996
2359
|
readonly warnThreshold: number;
|
|
1997
2360
|
/** The unit this budget meters, or `null` for a total-across-units budget. */
|
|
1998
2361
|
readonly unit: string | null;
|
|
2362
|
+
/** What a breach of this budget DOES — carried on the state so an operator
|
|
2363
|
+
* view can tell an observed breach from one that is holding runs. */
|
|
2364
|
+
readonly onExceeded: CostBudgetOnExceeded;
|
|
1999
2365
|
/** When the current window opened (windowed budgets), else `null`. */
|
|
2000
2366
|
readonly windowStartedAt: number | null;
|
|
2001
2367
|
/** When this budget last entered its current `status`. */
|
|
@@ -2111,10 +2477,14 @@ export declare const crud: {
|
|
|
2111
2477
|
readonly id: string;
|
|
2112
2478
|
}, ctx: AppContext) => Promise<Row | null>;
|
|
2113
2479
|
/** Insert the input as a new row (id / tenant / audit auto-stamped). The echoed
|
|
2114
|
-
* row is redacted
|
|
2480
|
+
* row is redacted, and an input that sets a `.serverOnly()` column is REFUSED
|
|
2481
|
+
* with `ServerOnlyColumnWrite`. Guard the DESCRIPTOR — this does not gate. */
|
|
2115
2482
|
create: (table: string, options?: CrudWriteOptions) => (input: Row, ctx: AppContext) => Promise<Row>;
|
|
2116
2483
|
/** Patch a row by id (`{ id, ...patch }`); returns the updated row or `null`.
|
|
2117
|
-
* Redacted
|
|
2484
|
+
* Redacted, and a patch that sets a `.serverOnly()` column is REFUSED with
|
|
2485
|
+
* `ServerOnlyColumnWrite`. On a `tenant()` table the keyed write resolves the
|
|
2486
|
+
* row inside the caller's tenant (`TenantRowNotFound` otherwise — see
|
|
2487
|
+
* `storeMiddleware`). Guard the DESCRIPTOR. */
|
|
2118
2488
|
update: (table: string, options?: CrudWriteOptions) => (input: {
|
|
2119
2489
|
readonly id: string;
|
|
2120
2490
|
} & Record<string, unknown>, ctx: AppContext) => Promise<Row | null>;
|
|
@@ -2251,6 +2621,9 @@ export declare interface CrudWriteOptions {
|
|
|
2251
2621
|
readonly redact?: ReadonlyArray<string>;
|
|
2252
2622
|
}
|
|
2253
2623
|
|
|
2624
|
+
/** Every raw read recorded in the current scope; empty outside one. */
|
|
2625
|
+
export declare const currentRawReads: () => ReadonlyArray<RawReadObservation>;
|
|
2626
|
+
|
|
2254
2627
|
/**
|
|
2255
2628
|
* The current request's loader, or `undefined` outside a request.
|
|
2256
2629
|
*
|
|
@@ -2293,6 +2666,37 @@ export declare const dataStoreIdempotencyStore: (store: DataStore) => Idempotenc
|
|
|
2293
2666
|
/** Build a durable `KvStoreShape` over a raw `DataStore`. */
|
|
2294
2667
|
export declare const dataStoreKvStore: (store: DataStore) => KvStoreShape;
|
|
2295
2668
|
|
|
2669
|
+
/**
|
|
2670
|
+
* Approve or reject ONE intent, applying every refusal in a fixed order.
|
|
2671
|
+
*
|
|
2672
|
+
* The order is asserted in the tests and is not arbitrary: identity checks come
|
|
2673
|
+
* before authority checks so a requester with the approver scope is told they
|
|
2674
|
+
* cannot approve their OWN request (the accurate reason) rather than being
|
|
2675
|
+
* silently allowed through on a scope they do hold.
|
|
2676
|
+
*/
|
|
2677
|
+
export declare const decideApproval: (store: DataStore, input: DecideApprovalInput) => Promise<{
|
|
2678
|
+
readonly approvalId: string;
|
|
2679
|
+
readonly status: "approved" | "rejected";
|
|
2680
|
+
}>;
|
|
2681
|
+
|
|
2682
|
+
export declare interface DecideApprovalInput {
|
|
2683
|
+
readonly approvalId: string;
|
|
2684
|
+
readonly decision: 'approve' | 'reject';
|
|
2685
|
+
readonly note?: string | undefined;
|
|
2686
|
+
readonly approver: Subject | null;
|
|
2687
|
+
readonly now?: () => number;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
/** `__voltro.approvals.decide` — approve or reject one pending intent. */
|
|
2691
|
+
export declare const decideApprovalInvocation: (input: {
|
|
2692
|
+
readonly approvalId: string;
|
|
2693
|
+
readonly decision: "approve" | "reject";
|
|
2694
|
+
readonly note?: string;
|
|
2695
|
+
}, ctx: ApprovalExecutorCtx) => Promise<{
|
|
2696
|
+
readonly approvalId: string;
|
|
2697
|
+
readonly status: "approved" | "rejected";
|
|
2698
|
+
}>;
|
|
2699
|
+
|
|
2296
2700
|
/** Decrypt a value produced by `encryptField` / an `.encrypted()` column. A
|
|
2297
2701
|
* value that is NOT ciphertext (`enc:v1:…`) is returned unchanged — so a
|
|
2298
2702
|
* raw-SQL read path can be switched to encryption while pre-existing plaintext
|
|
@@ -2300,10 +2704,53 @@ export declare const dataStoreKvStore: (store: DataStore) => KvStoreShape;
|
|
|
2300
2704
|
* cipher is registered (a genuine ciphertext with a wrong key throws GCM). */
|
|
2301
2705
|
export declare const decryptField: (value: string) => string;
|
|
2302
2706
|
|
|
2707
|
+
export declare const DEFAULT_ANALYTICS_MIRROR_TUNABLES: AnalyticsMirrorTunables;
|
|
2708
|
+
|
|
2709
|
+
/** Default life of a pending intent when neither the descriptor nor the app
|
|
2710
|
+
* says otherwise. 24 h: long enough to survive a weekend handoff being missed
|
|
2711
|
+
* by a few hours, short enough that a forgotten queue empties itself. */
|
|
2712
|
+
export declare const DEFAULT_APPROVAL_EXPIRY_MS: number;
|
|
2713
|
+
|
|
2714
|
+
/** Assumed distance between buckets when a caller passes none — the cron
|
|
2715
|
+
* engine's finest useful cadence. */
|
|
2716
|
+
export declare const DEFAULT_CLAIM_BUCKET_MS = 60000;
|
|
2717
|
+
|
|
2718
|
+
/**
|
|
2719
|
+
* How many subscribers one change event is delivered to CONCURRENTLY.
|
|
2720
|
+
*
|
|
2721
|
+
* 8 rather than 1 (fully serial, what this replaced) or unbounded. Serial makes
|
|
2722
|
+
* one slow guard's round-trip the prefix of every later subscriber's latency —
|
|
2723
|
+
* measured at 50 subscribers × a 5 ms guard, the last delivery landed 699 ms
|
|
2724
|
+
* after the write. Unbounded would open one DB round-trip per subscriber at the
|
|
2725
|
+
* same instant, which on a 10-connection pool is slower than serial AND starves
|
|
2726
|
+
* the request path that shares it. 8 sits under a default pool and still
|
|
2727
|
+
* collapses the serial chain by roughly its own factor.
|
|
2728
|
+
*/
|
|
2729
|
+
export declare const DEFAULT_DELIVERY_CONCURRENCY = 8;
|
|
2730
|
+
|
|
2731
|
+
/** 180 days + subdomains. Deliberately NOT `preload`: preload is effectively
|
|
2732
|
+
* irreversible for a domain, so it must be a deployment decision, never a
|
|
2733
|
+
* framework default. */
|
|
2734
|
+
export declare const DEFAULT_HSTS = "max-age=15552000; includeSubDomains";
|
|
2735
|
+
|
|
2736
|
+
/** Ceiling the idle backoff climbs to. Deliberately short enough to be a
|
|
2737
|
+
* FLOOR under a missed wake rather than a substitute for one. */
|
|
2738
|
+
export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
2739
|
+
|
|
2303
2740
|
/** Default `POST /rpc` body cap: 8 MiB. Generous for any JSON rpc envelope,
|
|
2304
2741
|
* small enough to stop a pathological body being buffered into memory. */
|
|
2305
2742
|
export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
2306
2743
|
|
|
2744
|
+
/**
|
|
2745
|
+
* How many raw-SQL reads one request records for the undeclared-`dependsOn`
|
|
2746
|
+
* diagnostic. A bound rather than none: the recording lives for as long as the
|
|
2747
|
+
* async context that opened it, and a long-lived non-request context (a
|
|
2748
|
+
* subscriber runner, a boot seed) would otherwise accumulate one entry per raw
|
|
2749
|
+
* read forever. Reads beyond the bound are dropped, not remembered — this is a
|
|
2750
|
+
* diagnostic, and a diagnostic that leaks memory is worse than a missing one.
|
|
2751
|
+
*/
|
|
2752
|
+
export declare const DEFAULT_RAW_READ_TRACKING_LIMIT = 32;
|
|
2753
|
+
|
|
2307
2754
|
export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
|
|
2308
2755
|
|
|
2309
2756
|
/**
|
|
@@ -2574,7 +3021,33 @@ export declare class Dispatcher {
|
|
|
2574
3021
|
*/
|
|
2575
3022
|
private readonly computedSubs;
|
|
2576
3023
|
private readonly computedByTable;
|
|
3024
|
+
/** Resolved delivery tunables. Read once here so `voltro dev` and
|
|
3025
|
+
* `voltro serve` cannot disagree about them — see `reactiveConfig.ts`. */
|
|
3026
|
+
private readonly reactive;
|
|
3027
|
+
/** Query labels already warned about for an undeclared raw read, so a hot
|
|
3028
|
+
* subscription logs once rather than once per subscriber. */
|
|
3029
|
+
private readonly rawReadWarned;
|
|
2577
3030
|
constructor(deps: DispatcherDependencies);
|
|
3031
|
+
/**
|
|
3032
|
+
* REL-14 — a raw read that nothing can invalidate, said out loud.
|
|
3033
|
+
*
|
|
3034
|
+
* `store.raw(...)` is opaque to every mechanism that makes a query live: the
|
|
3035
|
+
* matcher reads a predicate, the dependency graph reads an eager spec, and a
|
|
3036
|
+
* raw fragment is a string neither can parse. `dependsOn` is how an author
|
|
3037
|
+
* tells us; omitting it produces a subscription that opens, delivers once and
|
|
3038
|
+
* then never updates. Nothing throws, nothing logs, and the bug looks like
|
|
3039
|
+
* ours.
|
|
3040
|
+
*
|
|
3041
|
+
* Wired HERE, in the dispatcher, and called from BOTH `subscribe` and
|
|
3042
|
+
* `subscribeComputed` — the one place every reactive subscription passes
|
|
3043
|
+
* through, on both boot paths. `voltro dev` and `voltro serve` build their own
|
|
3044
|
+
* rpc bridges and have drifted before; a check that lives in one of them is
|
|
3045
|
+
* not a check. There is no second place to put this, which is the point.
|
|
3046
|
+
*
|
|
3047
|
+
* Once per query label. A per-subscriber warning on a hot query is its own
|
|
3048
|
+
* kind of useless.
|
|
3049
|
+
*/
|
|
3050
|
+
private warnUndeclaredRawReads;
|
|
2578
3051
|
/**
|
|
2579
3052
|
* Register a logical subscription. The caller's `emit` receives:
|
|
2580
3053
|
* - exactly one `snapshot` event with the initial query result,
|
|
@@ -2698,6 +3171,13 @@ export declare interface DispatcherDependencies {
|
|
|
2698
3171
|
* present → subscriptions whose descriptor opted in (a `cacheBinding`
|
|
2699
3172
|
* is passed to `subscribe`) share + cache their initial snapshot. */
|
|
2700
3173
|
readonly cache?: SnapshotCache;
|
|
3174
|
+
/**
|
|
3175
|
+
* Tunables of the delivery loop — see `reactiveConfig.ts`. Absent → the
|
|
3176
|
+
* documented defaults. Resolved ONCE, in the constructor, so the two boot
|
|
3177
|
+
* paths cannot each derive their own answer; an env override wins over
|
|
3178
|
+
* whatever is passed here.
|
|
3179
|
+
*/
|
|
3180
|
+
readonly reactive?: ReactiveConfigInput;
|
|
2701
3181
|
}
|
|
2702
3182
|
|
|
2703
3183
|
/** Bookkeeping tenant key for the `_voltro_wakeups` rows that dormant schedules
|
|
@@ -2763,6 +3243,25 @@ export declare interface DrainResult {
|
|
|
2763
3243
|
readonly failed: number;
|
|
2764
3244
|
readonly dead: number;
|
|
2765
3245
|
readonly skipped: number;
|
|
3246
|
+
/**
|
|
3247
|
+
* How many rows this pass had to look at. `0` means the queue is EMPTY, which
|
|
3248
|
+
* a caller cannot otherwise tell from "everything delivered" — both leave
|
|
3249
|
+
* `delivered` at whatever it was, and only one of them means the poll has
|
|
3250
|
+
* nothing left to do.
|
|
3251
|
+
*/
|
|
3252
|
+
readonly scanned: number;
|
|
3253
|
+
/**
|
|
3254
|
+
* When the earliest NOT-yet-due pending row comes due, as an epoch ms.
|
|
3255
|
+
*
|
|
3256
|
+
* `undefined` means nothing in the read window is waiting. A LOWER bound (the
|
|
3257
|
+
* window is capped by `batchSize`), which is the safe direction: understating
|
|
3258
|
+
* it costs one early pass, overstating it delays a delivery.
|
|
3259
|
+
*
|
|
3260
|
+
* This is what lets the runner arm for a backoff instead of polling until it
|
|
3261
|
+
* expires — the one thing in this loop that genuinely needs a clock, since a
|
|
3262
|
+
* retry becoming due is not a write anybody can be notified about.
|
|
3263
|
+
*/
|
|
3264
|
+
readonly nextAttemptAt?: number;
|
|
2766
3265
|
}
|
|
2767
3266
|
|
|
2768
3267
|
/**
|
|
@@ -2807,6 +3306,23 @@ export declare const enableGraphObservation: () => void;
|
|
|
2807
3306
|
* columns use). Throws if no cipher is registered. */
|
|
2808
3307
|
export declare const encryptField: (plaintext: string) => string;
|
|
2809
3308
|
|
|
3309
|
+
/**
|
|
3310
|
+
* The dispatch-spine entry point. A no-op for a descriptor with no policy; a
|
|
3311
|
+
* REFUSAL when a policy exists and no gate was installed.
|
|
3312
|
+
*
|
|
3313
|
+
* Fail-closed is the whole point: "the approvals store is not wired, so run the
|
|
3314
|
+
* refund" is precisely the hole the declaration exists to close, and it is the
|
|
3315
|
+
* shape a boot-path parity miss would produce.
|
|
3316
|
+
*/
|
|
3317
|
+
export declare const enforceApproval: (input: {
|
|
3318
|
+
readonly procedure: string;
|
|
3319
|
+
readonly kind: "mutation" | "action";
|
|
3320
|
+
readonly policy: AnyApprovalPolicy | undefined;
|
|
3321
|
+
readonly subject: unknown;
|
|
3322
|
+
readonly input: unknown;
|
|
3323
|
+
readonly traceId?: string | undefined;
|
|
3324
|
+
}) => Promise<void>;
|
|
3325
|
+
|
|
2810
3326
|
export declare interface EnqueueOptions {
|
|
2811
3327
|
/** Drop this enqueue if an undelivered row already carries the same key. */
|
|
2812
3328
|
readonly idempotencyKey?: string;
|
|
@@ -3458,8 +3974,12 @@ export declare interface ExperimentDefinition {
|
|
|
3458
3974
|
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3459
3975
|
};
|
|
3460
3976
|
/** Resolved to a function: reads the subject and normalises it to a non-empty
|
|
3461
|
-
* string, or `null` when the row carries no subject.
|
|
3462
|
-
|
|
3977
|
+
* string, or `null` when the row carries no subject. `null` for a
|
|
3978
|
+
* `variantFrom` experiment, which assigns nothing. */
|
|
3979
|
+
readonly subject: ((row: Readonly<Record<string, unknown>>) => string | null) | null;
|
|
3980
|
+
/** Resolved arm reader for a `variantFrom` experiment; `null` when the
|
|
3981
|
+
* experiment assigns its own arms. Exactly one of these two is non-null. */
|
|
3982
|
+
readonly variantFrom: ((row: Readonly<Record<string, unknown>>) => string | null) | null;
|
|
3463
3983
|
readonly variants: ReadonlyArray<ExperimentVariant>;
|
|
3464
3984
|
/** Resolved holdout fraction in [0, 1); `0` when none. */
|
|
3465
3985
|
readonly holdout: number;
|
|
@@ -3480,8 +4000,33 @@ export declare interface ExperimentDefinitionInput {
|
|
|
3480
4000
|
readonly table: string;
|
|
3481
4001
|
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3482
4002
|
};
|
|
3483
|
-
/**
|
|
3484
|
-
|
|
4003
|
+
/**
|
|
4004
|
+
* The stable assignment key extractor — the experiment ASSIGNS.
|
|
4005
|
+
*
|
|
4006
|
+
* Exactly one of `subject` / `variantFrom` is required. They are the two ways
|
|
4007
|
+
* an experiment can know which arm a row belongs to, and they are mutually
|
|
4008
|
+
* exclusive by construction: declaring both would mean two answers to one
|
|
4009
|
+
* question, which is the defect this exclusion exists to prevent.
|
|
4010
|
+
*/
|
|
4011
|
+
readonly subject?: ExperimentSubject;
|
|
4012
|
+
/**
|
|
4013
|
+
* READ the arm off the row instead of assigning it — for measuring an
|
|
4014
|
+
* assignment something ELSE already made and persisted.
|
|
4015
|
+
*
|
|
4016
|
+
* This is what makes `plugin-flags` + `defineExperiment` one product rather
|
|
4017
|
+
* than two that happen to sit in the same app. A flag serves a variant by its
|
|
4018
|
+
* own deterministic bucketing (FNV-1a over the flag key); an experiment in
|
|
4019
|
+
* `subject` mode buckets independently (FNV-1a over the EXPERIMENT name). Both
|
|
4020
|
+
* are stable, both are uniform, and they DISAGREE — so measuring a flag's
|
|
4021
|
+
* rollout with a `subject`-mode experiment reports the uplift of an arm
|
|
4022
|
+
* assignment nobody was ever served. Reading the arm the flag actually served
|
|
4023
|
+
* (write `flagVariant(ctx, f)` onto the row, point `variantFrom` at that
|
|
4024
|
+
* column) makes the served arm and the measured arm the same fact.
|
|
4025
|
+
*
|
|
4026
|
+
* Persisting the arm is also the only version that survives a weight change:
|
|
4027
|
+
* a re-hash at read time silently re-labels every historical row.
|
|
4028
|
+
*/
|
|
4029
|
+
readonly variantFrom?: ExperimentVariantSource;
|
|
3485
4030
|
/** The arms. At least two (a control + one treatment). */
|
|
3486
4031
|
readonly variants: ReadonlyArray<ExperimentVariantSpec>;
|
|
3487
4032
|
/** Fraction (0..1) of subjects held out ENTIRELY — assigned to the reserved
|
|
@@ -3511,9 +4056,23 @@ export declare class ExperimentEvaluator {
|
|
|
3511
4056
|
private readonly now;
|
|
3512
4057
|
private seeded;
|
|
3513
4058
|
private lastUpdatedAt;
|
|
4059
|
+
/** The declared arm names — the admissible set in `variantFrom` mode. */
|
|
4060
|
+
private readonly declaredVariants;
|
|
3514
4061
|
constructor(def: ExperimentDefinition, deps: ExperimentEvaluatorDeps);
|
|
3515
|
-
/**
|
|
3516
|
-
*
|
|
4062
|
+
/**
|
|
4063
|
+
* The variant an in-scope row belongs to, or `null` when the row is out of the
|
|
4064
|
+
* `where` population, carries no subject, or (in `variantFrom` mode) carries
|
|
4065
|
+
* an arm this experiment does not declare.
|
|
4066
|
+
*
|
|
4067
|
+
* The two modes meet here and only here — the maintainers behind this are
|
|
4068
|
+
* identical either way, which is the point: an experiment that MEASURES an
|
|
4069
|
+
* arm somebody else assigned rides the same per-write IVM aggregate as one
|
|
4070
|
+
* that assigns its own.
|
|
4071
|
+
*
|
|
4072
|
+
* An unrecognised arm name is EXCLUDED, never folded into a neighbour. A typo
|
|
4073
|
+
* that silently landed in the baseline would corrupt the one number every
|
|
4074
|
+
* other row's lift is divided by.
|
|
4075
|
+
*/
|
|
3517
4076
|
private variantOf;
|
|
3518
4077
|
private inScope;
|
|
3519
4078
|
/** Seed both maintainers from the current base rows. Never emits (there is no
|
|
@@ -3659,6 +4218,17 @@ export declare interface ExperimentVariantResult {
|
|
|
3659
4218
|
readonly diff: number | null;
|
|
3660
4219
|
}
|
|
3661
4220
|
|
|
4221
|
+
/**
|
|
4222
|
+
* Where the ARM comes from when the experiment did not assign it.
|
|
4223
|
+
*
|
|
4224
|
+
* A column NAME whose value IS the variant, or a function returning it.
|
|
4225
|
+
* `null` / `undefined` / a name that is not one of the declared variants ⇒ the
|
|
4226
|
+
* row is UNASSIGNED and excluded from every arm — an unrecognised arm name is
|
|
4227
|
+
* never folded into a neighbour, because a typo that silently lands in
|
|
4228
|
+
* `control` corrupts the baseline the whole result is measured against.
|
|
4229
|
+
*/
|
|
4230
|
+
declare type ExperimentVariantSource = string | ((row: Readonly<Record<string, unknown>>) => string | null | undefined);
|
|
4231
|
+
|
|
3662
4232
|
/** A named arm of the experiment. `weight` skews the split (default 1 = equal);
|
|
3663
4233
|
* a subject is assigned in proportion to its weight over the total. */
|
|
3664
4234
|
export declare interface ExperimentVariantSpec {
|
|
@@ -3861,12 +4431,15 @@ export declare const gauge: (name: string, description?: string) => Metric.Metri
|
|
|
3861
4431
|
/** Generate a fresh token `<prefix><40-char-base64url>`. */
|
|
3862
4432
|
export declare const generateApiKeyToken: (prefix?: string) => string;
|
|
3863
4433
|
|
|
4434
|
+
export declare const getApprovalGate: () => ApprovalGateApi | undefined;
|
|
4435
|
+
|
|
4436
|
+
/** The credential bound to a connection, if any. Exposed for diagnostics and
|
|
4437
|
+
* for `applyConnectionCredential`; the auth chain goes through the latter. */
|
|
4438
|
+
export declare const getConnectionCredential: (clientId: number) => ConnectionCredential | undefined;
|
|
4439
|
+
|
|
3864
4440
|
/** The registered resolver, if the app declared any connections. */
|
|
3865
4441
|
export declare const getConnectionResolver: () => ConnectionResolver | undefined;
|
|
3866
4442
|
|
|
3867
|
-
/** Look up the override for a connection. Returns `undefined` if no override is set. */
|
|
3868
|
-
export declare const getConnectionSubject: (clientId: number) => Subject | undefined;
|
|
3869
|
-
|
|
3870
4443
|
export declare const getCredentialExpiry: (clientId: number) => number | undefined;
|
|
3871
4444
|
|
|
3872
4445
|
/** The registered field cipher, if any. The store middleware reads this. */
|
|
@@ -3939,6 +4512,10 @@ export declare const historyProvenance: (history: ReadonlyArray<RowHistoryEntry>
|
|
|
3939
4512
|
* this name when `holdout > 0`. */
|
|
3940
4513
|
export declare const HOLDOUT_VARIANT = "holdout";
|
|
3941
4514
|
|
|
4515
|
+
/** Safe-for-any-page policy: closes clickjacking + base-tag injection + plugin
|
|
4516
|
+
* embedding without constraining what the page itself loads. */
|
|
4517
|
+
export declare const HTML_CSP = "frame-ancestors 'none'; base-uri 'none'; object-src 'none'";
|
|
4518
|
+
|
|
3942
4519
|
/** A secrets backend over a plain JSON HTTP API — no SDK. Wraps fetch + cache.
|
|
3943
4520
|
* Vault (`/v1/secret/data/...`), Doppler, and the cloud control-plane all fit. */
|
|
3944
4521
|
export declare const httpSecretsBackend: (opts: HttpSecretsOptions) => SecretsBackend;
|
|
@@ -4136,6 +4713,8 @@ export declare interface InspectStream {
|
|
|
4136
4713
|
|
|
4137
4714
|
export { inspectWorkflow }
|
|
4138
4715
|
|
|
4716
|
+
export declare const installApprovalGate: (gate: ApprovalGateApi) => void;
|
|
4717
|
+
|
|
4139
4718
|
/**
|
|
4140
4719
|
* Install the resolver that answers `guards: [{ action, resourceType }]`.
|
|
4141
4720
|
*
|
|
@@ -4222,6 +4801,9 @@ export declare type InverseOp = {
|
|
|
4222
4801
|
* inverse needs (e.g. a hard-delete with no captured `prev`). */
|
|
4223
4802
|
export declare const invertChange: (c: ForwardChange) => InverseOp | null;
|
|
4224
4803
|
|
|
4804
|
+
/** Parse an already-normalized address into its bytes (4 for v4, 16 for v6). */
|
|
4805
|
+
export declare const ipToBytes: (ip: string) => Uint8Array | undefined;
|
|
4806
|
+
|
|
4225
4807
|
export declare const isAggregateDefinition: (value: unknown) => value is AggregateDefinition;
|
|
4226
4808
|
|
|
4227
4809
|
export declare const isCostBudgetDefinition: (value: unknown) => value is CostBudgetDefinition;
|
|
@@ -4248,6 +4830,34 @@ export declare const isInfraError: (e: unknown) => boolean;
|
|
|
4248
4830
|
|
|
4249
4831
|
export declare const isObservingGraph: () => boolean;
|
|
4250
4832
|
|
|
4833
|
+
/**
|
|
4834
|
+
* Shadow an INHERITED scope with a fresh one; do nothing when there is none.
|
|
4835
|
+
*
|
|
4836
|
+
* Two decisions here, and both were measured rather than reasoned.
|
|
4837
|
+
*
|
|
4838
|
+
* WHY IT ISOLATES INSTEAD OF REUSING. Nothing stops a non-request caller from
|
|
4839
|
+
* building a handler-facing store on a long-lived context — a boot seed,
|
|
4840
|
+
* `runAsSystem`, a subscriber runner. A scope opened there is found by every
|
|
4841
|
+
* request that runs underneath it (async-local lookup walks outward), so reusing
|
|
4842
|
+
* it would collect every request's raw reads into one bag and let a warning name
|
|
4843
|
+
* a query that never issued the read. A diagnostic that names the WRONG query is
|
|
4844
|
+
* worse than a missing one.
|
|
4845
|
+
*
|
|
4846
|
+
* WHY IT DOES NOTHING WHEN THERE IS NO SCOPE, rather than opening one eagerly.
|
|
4847
|
+
* `enterWith` is not free after it returns: each live frame taxes every
|
|
4848
|
+
* subsequent `await` in that context. Measured — a bare `await Promise.resolve()`
|
|
4849
|
+
* costs 0.165 µs with no frame, 0.256 µs with one, and 0.524 µs with six; they
|
|
4850
|
+
* STACK. Opening a scope for every request would put that on the request path in
|
|
4851
|
+
* exchange for nothing on the overwhelming majority of requests, which never
|
|
4852
|
+
* touch raw SQL at all. `recordRawRead` opens one on demand instead, so the cost
|
|
4853
|
+
* lands only on a request that already paid for a database round-trip.
|
|
4854
|
+
*
|
|
4855
|
+
* What this costs is a raw read inside `store.transactional(...)`: the
|
|
4856
|
+
* transaction's re-wrap isolates, so the read lands in the transaction's scope
|
|
4857
|
+
* and the subscribe never sees it. A missed warning, not a wrong one.
|
|
4858
|
+
*/
|
|
4859
|
+
export declare const isolateRawReadScope: () => void;
|
|
4860
|
+
|
|
4251
4861
|
/**
|
|
4252
4862
|
* A subject that AUTHENTICATED but resolved with NO active org — a `user` (not
|
|
4253
4863
|
* `anonymous`/`system`) carrying the empty-string tenant sentinel. Every
|
|
@@ -4296,6 +4906,10 @@ export declare const isScheduleDefinition: (v: unknown) => v is BrandedScheduleD
|
|
|
4296
4906
|
*/
|
|
4297
4907
|
export declare const isSoftDelete: (change: ChangeEvent) => boolean;
|
|
4298
4908
|
|
|
4909
|
+
/** The methods that can change state. A GET cannot be a CSRF write, and the WS
|
|
4910
|
+
* upgrade is a GET — so the upgrade is guarded by PATH, not by method. */
|
|
4911
|
+
export declare const isStateChangingMethod: (method: string) => boolean;
|
|
4912
|
+
|
|
4299
4913
|
/** A freshly issued key — `token` is shown ONCE and never stored in clear. */
|
|
4300
4914
|
export declare interface IssuedApiKey {
|
|
4301
4915
|
readonly id: string;
|
|
@@ -4323,6 +4937,9 @@ export declare interface IssueInput {
|
|
|
4323
4937
|
readonly metadata?: Readonly<Record<string, unknown>> | null;
|
|
4324
4938
|
}
|
|
4325
4939
|
|
|
4940
|
+
/** Is `address` one of the peers we are willing to take a forwarded chain from? */
|
|
4941
|
+
export declare const isTrustedProxy: (address: string | undefined, config: TrustedProxyConfig) => boolean;
|
|
4942
|
+
|
|
4326
4943
|
export declare const isWorkflowEventTriggerDefinition: (value: unknown) => value is WorkflowEventTriggerDefinition;
|
|
4327
4944
|
|
|
4328
4945
|
/** A maintainable aggregate op. (countDistinct / window funcs are NOT here —
|
|
@@ -4432,6 +5049,8 @@ export declare const listConnectionStates: (store: VaultStore, registry: Connect
|
|
|
4432
5049
|
|
|
4433
5050
|
export declare const listResourcePolicies: () => ReadonlyArray<ResourcePolicy>;
|
|
4434
5051
|
|
|
5052
|
+
export { listRetentionConflicts }
|
|
5053
|
+
|
|
4435
5054
|
export { listRetentions }
|
|
4436
5055
|
|
|
4437
5056
|
export declare interface LoaderDeps {
|
|
@@ -4516,9 +5135,21 @@ export declare const makeActionRunner: (deps: ActionRunnerDeps) => (action: Muta
|
|
|
4516
5135
|
* session-level `pg_advisory_lock` (which is tied to a connection
|
|
4517
5136
|
* that a pool may hand to another query before we unlock).
|
|
4518
5137
|
*
|
|
4519
|
-
* The
|
|
4520
|
-
* winner doesn't block the next firing (= next
|
|
4521
|
-
*
|
|
5138
|
+
* The bucket keying makes claims self-expiring as a DECISION: a crashed
|
|
5139
|
+
* winner doesn't block the next firing (= next bucket = new key). It did
|
|
5140
|
+
* not make them self-expiring as ROWS, and that distinction cost a consumer
|
|
5141
|
+
* their whole deployment — 86 214 rows / 33 MB over two days, read in full
|
|
5142
|
+
* on every claim check, ten of a fifteen-slot pooler pinned on the scan, an
|
|
5143
|
+
* SSR render behind them at 300 490 ms, and a `rollout restart` that could
|
|
5144
|
+
* not complete because the surge pod could not get a connection.
|
|
5145
|
+
*
|
|
5146
|
+
* **A won claim now deletes its own predecessors** (`claimGraceMs` above),
|
|
5147
|
+
* which is what bounds the table rather than merely slowing its growth. The
|
|
5148
|
+
* retention sweep both boot paths register (`wireRetentionSweep`,
|
|
5149
|
+
* `VOLTRO_SCHEDULE_CLAIMS_TTL_HOURS`) STAYS as the backstop, and it is not
|
|
5150
|
+
* redundant: this prune is per SCHEDULE NAME and only runs when that name wins
|
|
5151
|
+
* again, so the rows of a schedule that was renamed or deleted have nothing
|
|
5152
|
+
* left to clean them up.
|
|
4522
5153
|
*
|
|
4523
5154
|
* `scheduledAt` is the DETERMINISTIC cron instant (not `Date.now()`),
|
|
4524
5155
|
* so every replica computes the SAME bucket regardless of clock skew
|
|
@@ -4573,10 +5204,44 @@ export declare const makeApiKeyUsageFlusher: (store: ApiKeyStore) => (id: string
|
|
|
4573
5204
|
* the test-context factory so `ctx.access` can't drift between them. */
|
|
4574
5205
|
export declare const makeAppAccess: (subject: Subject) => AppAccess;
|
|
4575
5206
|
|
|
5207
|
+
/**
|
|
5208
|
+
* Build the gate over a live store. ONE construction, called by the shared cli
|
|
5209
|
+
* builder both boot paths use — the dev/serve parity rule applied to a control
|
|
5210
|
+
* whose absence is a silent open door rather than a crash.
|
|
5211
|
+
*/
|
|
5212
|
+
export declare const makeApprovalGate: (options: MakeApprovalGateOptions) => ApprovalGateApi;
|
|
5213
|
+
|
|
5214
|
+
export declare interface MakeApprovalGateOptions {
|
|
5215
|
+
readonly store: DataStore;
|
|
5216
|
+
/** App-level default expiry (the `approvals.expiresIn` tunable), already
|
|
5217
|
+
* resolved to ms by the caller. Falls back to 24 h. */
|
|
5218
|
+
readonly defaultExpiryMs?: number;
|
|
5219
|
+
readonly now?: () => number;
|
|
5220
|
+
}
|
|
5221
|
+
|
|
4576
5222
|
/** Build the `ctx.kv` facade. `KV_BACKEND` picks the backend; unknown values
|
|
4577
5223
|
* fall back to `database`. */
|
|
4578
5224
|
export declare const makeAsyncKv: ({ store, env }: KvFacadeOptions) => Promise<KvFacade>;
|
|
4579
5225
|
|
|
5226
|
+
/**
|
|
5227
|
+
* The `AuthMiddleware` + `ConnectionInfoMiddleware` pair every rpc surface
|
|
5228
|
+
* needs, wired to one app's strategy chain.
|
|
5229
|
+
*
|
|
5230
|
+
* Per call, in this order:
|
|
5231
|
+
*
|
|
5232
|
+
* 1. patch the connection's soft-reauth credential over the transport headers
|
|
5233
|
+
* (a no-op for a connection that never re-authenticated);
|
|
5234
|
+
* 2. run the FULL chain on the result — strategies, session revocation,
|
|
5235
|
+
* `resolveScopes`, the scope cache;
|
|
5236
|
+
* 3. record what the strategy verified about the credential's lifetime, so
|
|
5237
|
+
* `ConnectionInfo.credentialExpiresAt` reports the token's own `exp` rather
|
|
5238
|
+
* than a cookie derivation that only ever saw one auth shape.
|
|
5239
|
+
*
|
|
5240
|
+
* Step 1 is the only thing a re-authenticated connection gets. It changes WHICH
|
|
5241
|
+
* credential is presented and nothing about how it is judged.
|
|
5242
|
+
*/
|
|
5243
|
+
export declare const makeAuthMiddlewareLayers: (options: AuthMiddlewareLayerOptions) => Layer.Layer<AuthMiddleware | ConnectionInfoMiddleware>;
|
|
5244
|
+
|
|
4580
5245
|
/**
|
|
4581
5246
|
* Detect a serial jump per origin, and report it as a proven count.
|
|
4582
5247
|
*
|
|
@@ -4616,7 +5281,7 @@ export declare const makeConnectionsFacade: (deps: ConnectionsFacadeDeps) => Con
|
|
|
4616
5281
|
* handle is tracked by the caller (the CLI) so `onDeactivate` can stop
|
|
4617
5282
|
* every task a plugin armed.
|
|
4618
5283
|
*/
|
|
4619
|
-
export declare const makeCoordinatedScheduler: (deps: CoordinatedScheduleDeps) =>
|
|
5284
|
+
export declare const makeCoordinatedScheduler: (deps: CoordinatedScheduleDeps) => PluginScheduleCoordinated;
|
|
4620
5285
|
|
|
4621
5286
|
/**
|
|
4622
5287
|
* Build a request-scoped loader. One instance per AppContext — see the module
|
|
@@ -4707,6 +5372,35 @@ export declare const makeMutationRunner: (deps: MutationRunnerDeps) => (mutation
|
|
|
4707
5372
|
*/
|
|
4708
5373
|
export declare const makeOneShotQueryRunner: <D>(deps: OneShotQueryRunnerDeps<D>) => (query: MutationLike, input: unknown, requestContext: ServeRequestContext) => Promise<unknown>;
|
|
4709
5374
|
|
|
5375
|
+
/**
|
|
5376
|
+
* Build the per-boot "is this path outside the origin check" predicate.
|
|
5377
|
+
*
|
|
5378
|
+
* Everything not named here is guarded. The three inputs, and why each is a
|
|
5379
|
+
* different KIND of exemption:
|
|
5380
|
+
*
|
|
5381
|
+
* - `INSPECT_PREFIX` — structural, above.
|
|
5382
|
+
* - `webhookPaths` — the `*.webhook.tsx` mounts. Their caller is a third
|
|
5383
|
+
* party and `assertWebhookRoutesDeclareVerification` refuses to boot one
|
|
5384
|
+
* that has not declared how it authenticates that caller, so the signature
|
|
5385
|
+
* — not the cookie — is the authority. (Most send no `Origin` and would
|
|
5386
|
+
* pass anyway; naming them means a provider that DOES send one is not a
|
|
5387
|
+
* mystery 403 in production.)
|
|
5388
|
+
* - `exemptRoutePaths` — DECLARED, per route, via
|
|
5389
|
+
* `PluginHttpRoute.originGuard: 'exempt'`. The claim and the test to apply
|
|
5390
|
+
* are documented on that field.
|
|
5391
|
+
*
|
|
5392
|
+
* A path shared by SEVERAL routes is exempt only when EVERY route on it
|
|
5393
|
+
* declares the exemption — the pre-routing check cannot know which route in the
|
|
5394
|
+
* group will end up owning the method, so the strictest member decides.
|
|
5395
|
+
*/
|
|
5396
|
+
export declare const makeOriginGuardExemption: (input: {
|
|
5397
|
+
readonly webhookPaths?: Iterable<string> | undefined;
|
|
5398
|
+
readonly httpRoutes?: Iterable<{
|
|
5399
|
+
readonly path: string;
|
|
5400
|
+
readonly originGuard?: "exempt" | undefined;
|
|
5401
|
+
}> | undefined;
|
|
5402
|
+
}) => ((path: string) => boolean);
|
|
5403
|
+
|
|
4710
5404
|
export declare const makeOutboxFacade: (deps: OutboxFacadeDeps) => OutboxFacade;
|
|
4711
5405
|
|
|
4712
5406
|
export declare const makePostCommitWorkflowFacade: (base: WorkflowsAppContext, afterCommit: (work: () => Promise<unknown>) => void) => WorkflowsAppContext;
|
|
@@ -5006,6 +5700,17 @@ declare interface MemoryRywOptions {
|
|
|
5006
5700
|
*/
|
|
5007
5701
|
export declare const memoryRywPositionStore: (options?: MemoryRywOptions) => RywPositionStore;
|
|
5008
5702
|
|
|
5703
|
+
/**
|
|
5704
|
+
* Merge a name→value map into a `Cookie` header string, replacing same-named
|
|
5705
|
+
* pairs and preserving every other cookie the connection carries.
|
|
5706
|
+
*
|
|
5707
|
+
* Replacing the whole header would be simpler and wrong: a rebinder knows its
|
|
5708
|
+
* own cookie and nothing about the CSRF token, the locale, or the second app on
|
|
5709
|
+
* the same host. Values are percent-encoded on the way in because `readCookie`
|
|
5710
|
+
* percent-decodes on the way out, and the round trip has to be lossless.
|
|
5711
|
+
*/
|
|
5712
|
+
export declare const mergeCookieHeader: (existing: string | undefined, patch: Readonly<Record<string, string>>) => string;
|
|
5713
|
+
|
|
5009
5714
|
export { MetricBoundaries }
|
|
5010
5715
|
|
|
5011
5716
|
export declare interface MetricBucketPoint {
|
|
@@ -5043,6 +5748,23 @@ export declare type MetricSampleType = 'counter' | 'gauge' | 'histogram' | 'summ
|
|
|
5043
5748
|
* into the OTel MeterProvider when a `metricReader` is configured. */
|
|
5044
5749
|
export declare type MetricsMode = 'off' | 'console' | 'otlp' | 'memory';
|
|
5045
5750
|
|
|
5751
|
+
/**
|
|
5752
|
+
* Ordering token for one mirrored write.
|
|
5753
|
+
*
|
|
5754
|
+
* **The sink MUST persist this and MUST NOT invent its own.** It is
|
|
5755
|
+
* assigned by the framework at the moment the change leaves the store's
|
|
5756
|
+
* change channel — i.e. in COMMIT order — so a write with a lower
|
|
5757
|
+
* version is, by construction, an older image of that row. Stamping a
|
|
5758
|
+
* version at sink-call time instead (a `Date.now()` inside `upsert`)
|
|
5759
|
+
* makes a late-arriving STALE image outrank the fresh one and win
|
|
5760
|
+
* permanently; that is the bug this type exists to make unrepresentable.
|
|
5761
|
+
*
|
|
5762
|
+
* Monotonically increasing within a process, and wall-clock-derived
|
|
5763
|
+
* (microseconds since the epoch, bumped by one on collision) so it also
|
|
5764
|
+
* increases across a restart.
|
|
5765
|
+
*/
|
|
5766
|
+
export declare type MirrorVersion = number;
|
|
5767
|
+
|
|
5046
5768
|
export declare interface MutationLifecycle {
|
|
5047
5769
|
readonly afterCommit: (work: () => Promise<unknown>) => void;
|
|
5048
5770
|
}
|
|
@@ -5052,6 +5774,9 @@ export declare interface MutationLike {
|
|
|
5052
5774
|
readonly name: string;
|
|
5053
5775
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
5054
5776
|
readonly guards?: ReadonlyArray<AnyCheckSpec> | undefined;
|
|
5777
|
+
/** The second-human control (mutation/action only). Enforced by
|
|
5778
|
+
* `enforceApprovalGate`, immediately after `enforceGuards`. */
|
|
5779
|
+
readonly requiresApproval?: AnyApprovalPolicy | undefined;
|
|
5055
5780
|
readonly target?: {
|
|
5056
5781
|
readonly table: string;
|
|
5057
5782
|
} | ReadonlyArray<{
|
|
@@ -5121,6 +5846,18 @@ export declare interface MutationStore extends DataStore {
|
|
|
5121
5846
|
hardDelete(table: string, primaryKey: string): Promise<boolean>;
|
|
5122
5847
|
}
|
|
5123
5848
|
|
|
5849
|
+
/**
|
|
5850
|
+
* The backoff curve, extracted so it can be asserted directly — a schedule
|
|
5851
|
+
* that backs off wrongly is otherwise only visible as a latency an integration
|
|
5852
|
+
* test does not measure.
|
|
5853
|
+
*
|
|
5854
|
+
* Doubling rather than jumping to the ceiling: a queue that just went quiet is
|
|
5855
|
+
* the likeliest one to receive something next, and doubling keeps the first few
|
|
5856
|
+
* idle ticks cheap in latency while still reaching the ceiling in five steps
|
|
5857
|
+
* from 1 s.
|
|
5858
|
+
*/
|
|
5859
|
+
export declare const nextDelay: (outcome: void | CoordinatedTickOutcome, baseMs: number, currentMs: number, maxIdleMs: number, disarmWhenIdle?: boolean) => number | "disarm";
|
|
5860
|
+
|
|
5124
5861
|
/** Next firing strictly after `after` (default: now). */
|
|
5125
5862
|
export declare const nextFiring: (def: ScheduleDefinition, after?: Date) => Date;
|
|
5126
5863
|
|
|
@@ -5135,6 +5872,9 @@ export declare const nextWakeup: (store: DataStore) => Promise<Wakeup | null>;
|
|
|
5135
5872
|
* registered, or for a system subject. */
|
|
5136
5873
|
export declare const NO_ROW_FILTER: RowFilterScope;
|
|
5137
5874
|
|
|
5875
|
+
/** Nothing trusted: the socket address is the client address. The default. */
|
|
5876
|
+
export declare const NO_TRUSTED_PROXIES: TrustedProxyConfig;
|
|
5877
|
+
|
|
5138
5878
|
/**
|
|
5139
5879
|
* The real single-node supervisor: `spawn`s the app command and considers
|
|
5140
5880
|
* it healthy once `healthUrl` returns 2xx. Stopping sends `stopSignal`
|
|
@@ -5203,6 +5943,15 @@ export declare const noopAnalyticsSpec: AnalyticsSinkSpec;
|
|
|
5203
5943
|
* misses, writes are dropped. Mirrors the cache's `noopCache`. */
|
|
5204
5944
|
export declare const noopKv: AsyncKv;
|
|
5205
5945
|
|
|
5946
|
+
/** Strip the decorations a real-world address string arrives with:
|
|
5947
|
+
* `[::1]:443` → `::1`, `::ffff:10.0.0.1` → `10.0.0.1`, `fe80::1%eth0` → `fe80::1`. */
|
|
5948
|
+
export declare const normalizeIp: (raw: string) => string | undefined;
|
|
5949
|
+
|
|
5950
|
+
/** `HTTPS://Example.com:443/x` → `https://example.com`. Returns the lowercased
|
|
5951
|
+
* input when it is not a parseable absolute URL, so an unparseable Origin can
|
|
5952
|
+
* never accidentally compare equal to a real one. */
|
|
5953
|
+
export declare const normalizeOrigin: (raw: string) => string;
|
|
5954
|
+
|
|
5206
5955
|
/**
|
|
5207
5956
|
* `.one()` matched a number of rows other than exactly one.
|
|
5208
5957
|
*
|
|
@@ -5299,14 +6048,6 @@ export declare interface ObservedProcedure {
|
|
|
5299
6048
|
*/
|
|
5300
6049
|
export declare const observeStore: <S extends object>(store: S) => S;
|
|
5301
6050
|
|
|
5302
|
-
/**
|
|
5303
|
-
* Listen for re-bind events. Returns an unsubscribe function. Used by
|
|
5304
|
-
* the dispatcher to re-scope active subscriptions when a connection's
|
|
5305
|
-
* subject changes (e.g. anonymous → authenticated user on a different
|
|
5306
|
-
* tenant — subscriptions filtered by tenantId need re-evaluation).
|
|
5307
|
-
*/
|
|
5308
|
-
export declare const onBindConnectionSubject: (listener: (clientId: number, subject: Subject) => void) => () => void;
|
|
5309
|
-
|
|
5310
6051
|
export declare interface OneShotQueryRunnerDeps<D> extends QueryProducerDeps<D> {
|
|
5311
6052
|
/**
|
|
5312
6053
|
* Execute a FINALIZED (tenant-scoped) descriptor → rows. On the WS path the
|
|
@@ -5356,6 +6097,38 @@ export declare interface OrchestratorTickDeps {
|
|
|
5356
6097
|
readonly log?: WakeOrchestratorLogger;
|
|
5357
6098
|
}
|
|
5358
6099
|
|
|
6100
|
+
export declare type OriginDecision = {
|
|
6101
|
+
readonly allowed: true;
|
|
6102
|
+
readonly reason: 'guard-off' | 'same-origin' | 'loopback' | 'allowlisted' | 'no-browser-origin';
|
|
6103
|
+
} | {
|
|
6104
|
+
readonly allowed: false;
|
|
6105
|
+
readonly reason: 'cross-origin' | 'opaque-origin' | 'cross-site-metadata';
|
|
6106
|
+
/** What the request claimed, for the log line. Never echoed to the client. */
|
|
6107
|
+
readonly origin: string;
|
|
6108
|
+
};
|
|
6109
|
+
|
|
6110
|
+
export declare interface OriginGuardConfig {
|
|
6111
|
+
readonly mode: OriginGuardMode;
|
|
6112
|
+
/** Extra origins to accept, normalised to `scheme://host[:port]`. Needed for
|
|
6113
|
+
* a split deployment where the web app and the api sit on different hosts. */
|
|
6114
|
+
readonly allowedOrigins: ReadonlyArray<string>;
|
|
6115
|
+
}
|
|
6116
|
+
|
|
6117
|
+
/** `off` disables the check entirely; `same-origin` (default) enforces it. */
|
|
6118
|
+
export declare type OriginGuardMode = 'off' | 'same-origin';
|
|
6119
|
+
|
|
6120
|
+
/**
|
|
6121
|
+
* Map an `import('@effect/opentelemetry')` rejection to what the reader needs.
|
|
6122
|
+
*
|
|
6123
|
+
* Exported so the branch is testable without uninstalling the package. Only a
|
|
6124
|
+
* module-NOT-FOUND becomes the install instruction: anything else is a real
|
|
6125
|
+
* load failure inside a package that IS present, and renaming that to "not
|
|
6126
|
+
* installed" sends the reader to reinstall something already there. A catch-all
|
|
6127
|
+
* that relabels every failure is how a diagnosis gets buried — the same reason
|
|
6128
|
+
* the serve bundle marks its deliberate refusals instead of swallowing throws.
|
|
6129
|
+
*/
|
|
6130
|
+
export declare const otelImportFailure: (cause: unknown) => Error;
|
|
6131
|
+
|
|
5359
6132
|
/**
|
|
5360
6133
|
* Retention, part 1 of 2 — the PER-ENTRY cap.
|
|
5361
6134
|
*
|
|
@@ -5493,6 +6266,13 @@ declare interface P2COptions {
|
|
|
5493
6266
|
readonly random?: () => number;
|
|
5494
6267
|
}
|
|
5495
6268
|
|
|
6269
|
+
/**
|
|
6270
|
+
* Parse an interval string (`'30m'`, `'4h'`, `'7d'`) to ms, or `null`.
|
|
6271
|
+
* A local copy for the same reason `finops.ts` keeps one — this module must not
|
|
6272
|
+
* grow a dependency for five lines of regex.
|
|
6273
|
+
*/
|
|
6274
|
+
export declare const parseApprovalInterval: (spec: string) => number | null;
|
|
6275
|
+
|
|
5496
6276
|
/** Parse a `traceparent` header. Returns null when malformed or when
|
|
5497
6277
|
* the all-zero invalid ids are present (per the spec those are
|
|
5498
6278
|
* treated as "no parent"). */
|
|
@@ -5528,6 +6308,19 @@ export declare const payloadPropertyNames: (schema: Schema.Schema.Any) => {
|
|
|
5528
6308
|
readonly optional: ReadonlyArray<string>;
|
|
5529
6309
|
};
|
|
5530
6310
|
|
|
6311
|
+
/**
|
|
6312
|
+
* The calling subject's approval work: intents they may DECIDE, plus intents
|
|
6313
|
+
* they REQUESTED that are still live.
|
|
6314
|
+
*
|
|
6315
|
+
* Subject-scoped in here rather than by a descriptor guard — see the descriptor
|
|
6316
|
+
* comment for why. An anonymous caller gets an empty list, which is the correct
|
|
6317
|
+
* answer and not a refusal: they have no work.
|
|
6318
|
+
*/
|
|
6319
|
+
export declare const pendingApprovalsFor: (store: DataStore, subject: Subject | null, options?: {
|
|
6320
|
+
readonly limit?: number | undefined;
|
|
6321
|
+
readonly now?: () => number;
|
|
6322
|
+
}) => Promise<ReadonlyArray<PendingApproval>>;
|
|
6323
|
+
|
|
5531
6324
|
/** One resolved rule: "when <targetTable> loses a row, do <policy> to <table.column>". */
|
|
5532
6325
|
export declare interface PluginRefRule {
|
|
5533
6326
|
readonly table: string;
|
|
@@ -5544,8 +6337,8 @@ export declare interface PluginRefStore {
|
|
|
5544
6337
|
delete: (table: string, id: string) => Promise<unknown>;
|
|
5545
6338
|
}
|
|
5546
6339
|
|
|
5547
|
-
/** The
|
|
5548
|
-
export declare type PluginScheduleCoordinated = (name: string, intervalMs: number, effect:
|
|
6340
|
+
/** The signature a plugin sees on its bind-ctx. */
|
|
6341
|
+
export declare type PluginScheduleCoordinated = (name: string, intervalMs: number, effect: CoordinatedEffect, options?: CoordinatedTaskOptions) => CoordinatedScheduleHandle;
|
|
5549
6342
|
|
|
5550
6343
|
export declare const powerOfTwoSelector: (options?: P2COptions) => ReplicaSelector;
|
|
5551
6344
|
|
|
@@ -5738,6 +6531,27 @@ export declare interface QuerySubscriberDeps<D> extends QueryProducerDeps<D> {
|
|
|
5738
6531
|
readonly subscribeComputed: (computed: ComputedQuery, emit: (event: unknown) => void, context: ServeRequestContext) => Promise<() => void>;
|
|
5739
6532
|
}
|
|
5740
6533
|
|
|
6534
|
+
/** One `store.raw(...)` call, as the reactive engine needs to see it. */
|
|
6535
|
+
export declare interface RawReadObservation {
|
|
6536
|
+
/** The fragment's STATIC text, joined with `?` where a value was bound.
|
|
6537
|
+
* Never the values — they are user data and this string reaches a log. */
|
|
6538
|
+
readonly sql: string;
|
|
6539
|
+
/** The tables the caller declared, from `opts.dependsOn` or the fragment.
|
|
6540
|
+
* Empty is the whole point of this module. */
|
|
6541
|
+
readonly dependsOn: ReadonlyArray<string>;
|
|
6542
|
+
}
|
|
6543
|
+
|
|
6544
|
+
/**
|
|
6545
|
+
* The static text of a captured fragment, with every bound value rendered as
|
|
6546
|
+
* `?`.
|
|
6547
|
+
*
|
|
6548
|
+
* The values are deliberately NOT included. This string is written to a log and
|
|
6549
|
+
* a raw fragment's values are the caller's data — a customer email, a token
|
|
6550
|
+
* being looked up. The static text is what identifies the query to its author,
|
|
6551
|
+
* which is all the warning needs.
|
|
6552
|
+
*/
|
|
6553
|
+
export declare const rawSqlPreview: (strings: ReadonlyArray<string>) => string;
|
|
6554
|
+
|
|
5741
6555
|
/** What a reaction does when it fires — run an agent or start a workflow. Both
|
|
5742
6556
|
* identified by name; the serve layer resolves + runs them as `agentActor`. */
|
|
5743
6557
|
export declare type ReactionAct = {
|
|
@@ -5871,6 +6685,32 @@ export declare interface ReactionRunDeps {
|
|
|
5871
6685
|
readonly now?: () => number;
|
|
5872
6686
|
}
|
|
5873
6687
|
|
|
6688
|
+
/** The reactive tunables a caller may declare. Every field optional; every
|
|
6689
|
+
* default above. */
|
|
6690
|
+
export declare interface ReactiveConfigInput {
|
|
6691
|
+
/**
|
|
6692
|
+
* How many subscribers a single change event is delivered to at once.
|
|
6693
|
+
*
|
|
6694
|
+
* Raise it when deliveries are dominated by I/O the framework performs on the
|
|
6695
|
+
* subscriber's behalf — a `guards:` resource resolver or a row-filter loader
|
|
6696
|
+
* that hits the database once per subscriber. Lower it (to `1`, fully serial)
|
|
6697
|
+
* only if you have measured your connection pool being starved by reactive
|
|
6698
|
+
* traffic; the delivery loop shares the pool with the request path.
|
|
6699
|
+
*/
|
|
6700
|
+
readonly deliveryConcurrency?: number;
|
|
6701
|
+
/**
|
|
6702
|
+
* How many raw-SQL reads one request records for the `dependsOn` diagnostic.
|
|
6703
|
+
* Only worth raising if a handler issues many raw reads and you want the
|
|
6704
|
+
* warning to name a later one.
|
|
6705
|
+
*/
|
|
6706
|
+
readonly rawReadTrackingLimit?: number;
|
|
6707
|
+
}
|
|
6708
|
+
|
|
6709
|
+
export declare interface ReactiveEnv {
|
|
6710
|
+
readonly VOLTRO_REACTIVE_DELIVERY_CONCURRENCY?: string;
|
|
6711
|
+
readonly VOLTRO_RAW_READ_TRACKING_LIMIT?: string;
|
|
6712
|
+
}
|
|
6713
|
+
|
|
5874
6714
|
/** A reactive query returns a builder descriptor instead of a value; the store
|
|
5875
6715
|
* runs it and streams the rows. Only queries may do this. */
|
|
5876
6716
|
export declare type ReactiveReturn<D extends ExecutorDescriptor> = D extends {
|
|
@@ -5879,6 +6719,29 @@ export declare type ReactiveReturn<D extends ExecutorDescriptor> = D extends {
|
|
|
5879
6719
|
readonly descriptor: QueryDescriptor;
|
|
5880
6720
|
} : never;
|
|
5881
6721
|
|
|
6722
|
+
/**
|
|
6723
|
+
* The ceiling, tunable per deployment via `VOLTRO_POLL_CEILING_MS`.
|
|
6724
|
+
*
|
|
6725
|
+
* This is the ONE number worth exposing, and the reason is what the ceiling
|
|
6726
|
+
* means: it is how long an arrival can wait when nothing woke the task. With
|
|
6727
|
+
* reactivity it is never reached. WITHOUT it — a dialect with no CDC and no
|
|
6728
|
+
* broadcast transport, where a remote replica's enqueue produces no local
|
|
6729
|
+
* event — it is the whole latency budget, and only the operator knows how much
|
|
6730
|
+
* of one they have.
|
|
6731
|
+
*
|
|
6732
|
+
* An env var rather than an `app.config.ts` field on purpose: it is an
|
|
6733
|
+
* operational number, it must be identical under `voltro dev` and `voltro
|
|
6734
|
+
* serve`, and a second source for one value is how the two boot paths come to
|
|
6735
|
+
* disagree. Read here, once, so neither path can supply its own.
|
|
6736
|
+
*
|
|
6737
|
+
* An unparseable or non-positive value is ignored rather than honoured — a
|
|
6738
|
+
* ceiling of 0 would turn every idle task into a spin, which is the exact
|
|
6739
|
+
* pathology the backoff exists to remove.
|
|
6740
|
+
*/
|
|
6741
|
+
export declare const readPollCeilingMs: (env?: {
|
|
6742
|
+
readonly VOLTRO_POLL_CEILING_MS?: string;
|
|
6743
|
+
}) => number;
|
|
6744
|
+
|
|
5882
6745
|
/** `admin:full` (mirrors `@voltro/protocol`'s ADMIN_SCOPE) bypasses ReBAC. */
|
|
5883
6746
|
export declare const REBAC_ADMIN_SCOPE = "admin:full";
|
|
5884
6747
|
|
|
@@ -5958,6 +6821,21 @@ export declare const recordEventPublished: (event: string) => void;
|
|
|
5958
6821
|
/** Subscriber attached (+1) or detached (-1). */
|
|
5959
6822
|
export declare const recordEventSubscribers: (event: string, delta: number) => void;
|
|
5960
6823
|
|
|
6824
|
+
/**
|
|
6825
|
+
* Record a raw read, opening a scope for the rest of this execution context if
|
|
6826
|
+
* none is open yet.
|
|
6827
|
+
*
|
|
6828
|
+
* Opening it HERE is what keeps the request path free of an async-local frame it
|
|
6829
|
+
* would almost never use — see `isolateRawReadScope`. The scope then lives for
|
|
6830
|
+
* the remainder of this context, which is the request, which is exactly long
|
|
6831
|
+
* enough for the subscribe that follows to read it.
|
|
6832
|
+
*
|
|
6833
|
+
* Bounded by `rawReadTrackingLimit`, and DEDUPED by sql text: a handler that
|
|
6834
|
+
* raw-reads in a loop must not turn a diagnostic into a leak, and the tenth
|
|
6835
|
+
* identical read tells the reader nothing the first did not.
|
|
6836
|
+
*/
|
|
6837
|
+
export declare const recordRawRead: (observation: RawReadObservation) => void;
|
|
6838
|
+
|
|
5961
6839
|
/**
|
|
5962
6840
|
* Record one framework sample into the Effect metric registry. Synchronous —
|
|
5963
6841
|
* metric updates are pure, so `runSync` is cheap and safe to call from the
|
|
@@ -6188,6 +7066,11 @@ export declare interface RegistryTableLike {
|
|
|
6188
7066
|
readonly validatePatchSchema?: Schema.Schema.Any;
|
|
6189
7067
|
}
|
|
6190
7068
|
|
|
7069
|
+
/** Rebuild checkable guards from the row. The stored resource id becomes a
|
|
7070
|
+
* constant extractor, so the decision is scoped to the SAME resource the
|
|
7071
|
+
* request was. */
|
|
7072
|
+
export declare const rehydrateGuards: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<AnyGuardSpec>;
|
|
7073
|
+
|
|
6191
7074
|
/** A relationship tuple — `subjectId` has `relation` on `<resourceType>:<resourceId>`.
|
|
6192
7075
|
* In practice these are rows in a relation table; the engine takes them as data. */
|
|
6193
7076
|
export declare interface RelationTuple {
|
|
@@ -6370,8 +7253,8 @@ export declare interface ResendOptions {
|
|
|
6370
7253
|
/** Exported for tests — the warn-once set is process-global by design. */
|
|
6371
7254
|
export declare const resetComputedQueryCacheWarnings: () => void;
|
|
6372
7255
|
|
|
6373
|
-
/** Test/dev-only — clear ALL
|
|
6374
|
-
export declare const
|
|
7256
|
+
/** Test/dev-only — clear ALL per-connection state. Don't call from app code. */
|
|
7257
|
+
export declare const _resetConnectionCredentialsForTest: () => void;
|
|
6375
7258
|
|
|
6376
7259
|
/** Test seams for the tag-cache guard — the cache is module-private otherwise. */
|
|
6377
7260
|
export declare const resetEventMetricTagCacheForTests: () => void;
|
|
@@ -6382,6 +7265,23 @@ export declare const resetObservedGraph: () => void;
|
|
|
6382
7265
|
/** Reset to the env default (tests). */
|
|
6383
7266
|
export declare const resetSecretsBackend: () => void;
|
|
6384
7267
|
|
|
7268
|
+
/**
|
|
7269
|
+
* The client address to rate-limit, geo-block and audit by.
|
|
7270
|
+
*
|
|
7271
|
+
* The rules, in the order they fire:
|
|
7272
|
+
*
|
|
7273
|
+
* 1. No `x-forwarded-for` → the socket address. (Nothing to decide.)
|
|
7274
|
+
* 2. `trustAll` → the leftmost forwarded token. The pre-fix behaviour, now an
|
|
7275
|
+
* explicit opt-in for an ingress that OVERWRITES the header.
|
|
7276
|
+
* 3. Hop mode (`trustedProxies: ['2']`) → walk `n` hops in from the right of
|
|
7277
|
+
* `[...forwarded, socket]`.
|
|
7278
|
+
* 4. Otherwise the immediate peer must itself be trusted; if it is not, the
|
|
7279
|
+
* chain is a client-supplied string and is ignored entirely.
|
|
7280
|
+
* 5. With a trusted peer, walk the chain right-to-left past every trusted hop;
|
|
7281
|
+
* the first address that is NOT a configured proxy is the client.
|
|
7282
|
+
*/
|
|
7283
|
+
export declare const resolveClientAddress: (input: ClientAddressInput) => string | undefined;
|
|
7284
|
+
|
|
6385
7285
|
/**
|
|
6386
7286
|
* Resolve the calling subject's credential for one connection, refreshed.
|
|
6387
7287
|
*
|
|
@@ -6478,6 +7378,11 @@ export declare interface ResolvedConnection {
|
|
|
6478
7378
|
*/
|
|
6479
7379
|
export declare const resolveDependentTables: (descriptor: QueryDescriptor) => ReadonlySet<string>;
|
|
6480
7380
|
|
|
7381
|
+
export declare interface ResolvedReactiveConfig {
|
|
7382
|
+
readonly deliveryConcurrency: number;
|
|
7383
|
+
readonly rawReadTrackingLimit: number;
|
|
7384
|
+
}
|
|
7385
|
+
|
|
6481
7386
|
/**
|
|
6482
7387
|
* A `WorkflowCallerContext` after the engine has filled in what a workflow
|
|
6483
7388
|
* always needs. A run started by a request carries the caller's subject; a
|
|
@@ -6500,8 +7405,39 @@ export declare interface ResolvedWorkflowCallerContext extends WorkflowCallerCon
|
|
|
6500
7405
|
readonly traceId: string;
|
|
6501
7406
|
}
|
|
6502
7407
|
|
|
7408
|
+
/**
|
|
7409
|
+
* Resolve the guard's configuration.
|
|
7410
|
+
*
|
|
7411
|
+
* Precedence: explicit `app.config.ts` values (threaded through
|
|
7412
|
+
* `RpcServerOptions.security`) → env override → safe default. An unrecognised
|
|
7413
|
+
* mode falls back to enforcement rather than to `off`: a typo in a security
|
|
7414
|
+
* knob must not silently disable it.
|
|
7415
|
+
*/
|
|
7416
|
+
export declare const resolveOriginGuardConfig: (configured?: {
|
|
7417
|
+
readonly mode?: OriginGuardMode | undefined;
|
|
7418
|
+
readonly allowedOrigins?: ReadonlyArray<string> | undefined;
|
|
7419
|
+
} | undefined, env?: Readonly<Record<string, string | undefined>>) => OriginGuardConfig;
|
|
7420
|
+
|
|
7421
|
+
export declare const resolveReactiveConfig: (config?: ReactiveConfigInput | undefined, env?: ReactiveEnv) => ResolvedReactiveConfig;
|
|
7422
|
+
|
|
6503
7423
|
export declare const resolveRedirectUri: (definition: OAuth2ConnectionDefinition, publicUrl: string) => string;
|
|
6504
7424
|
|
|
7425
|
+
/**
|
|
7426
|
+
* Whether the request reached the app over TLS.
|
|
7427
|
+
*
|
|
7428
|
+
* `x-forwarded-proto` is believed only from a trusted proxy, for exactly the
|
|
7429
|
+
* reason `x-forwarded-for` is: it is a client-writable header. Getting this
|
|
7430
|
+
* wrong in the permissive direction would let any client make the framework
|
|
7431
|
+
* emit HSTS (harmless) — and, more importantly, would let it claim `https` in
|
|
7432
|
+
* an audit record, so it is gated the same way.
|
|
7433
|
+
*/
|
|
7434
|
+
export declare const resolveRequestIsHttps: (input: {
|
|
7435
|
+
readonly socketEncrypted: boolean;
|
|
7436
|
+
readonly socketAddress: string | undefined;
|
|
7437
|
+
readonly xForwardedProto: string | undefined;
|
|
7438
|
+
readonly config: TrustedProxyConfig;
|
|
7439
|
+
}) => boolean;
|
|
7440
|
+
|
|
6505
7441
|
/** Rebuild the flagged groups from the (already-committed) base rows + merge —
|
|
6506
7442
|
* the caller's resolution of a min/max rescan signal. `baseRows` is the full
|
|
6507
7443
|
* current table (or at least every row in the rescan groups). */
|
|
@@ -6561,6 +7497,21 @@ export declare const resolveSecretsBackend: (config: SecretsBackendConfig | unde
|
|
|
6561
7497
|
* await (e.g. deriving a permission host from a key). Bypasses remote backends. */
|
|
6562
7498
|
export declare const resolveSecretSync: (key: string) => string | undefined;
|
|
6563
7499
|
|
|
7500
|
+
/**
|
|
7501
|
+
* Resolve the header configuration.
|
|
7502
|
+
*
|
|
7503
|
+
* Precedence: explicit `app.config.ts` values → env override → defaults. Env
|
|
7504
|
+
* knobs: `VOLTRO_SECURITY_HEADERS` (`off|default|strict`), `VOLTRO_CSP`,
|
|
7505
|
+
* `VOLTRO_CSP_HTML`, `VOLTRO_HSTS` — each accepting `off` to drop just that one.
|
|
7506
|
+
*/
|
|
7507
|
+
export declare const resolveSecurityHeaders: (configured?: SecurityHeadersOptions | undefined, env?: Readonly<Record<string, string | undefined>>) => SecurityHeadersConfig;
|
|
7508
|
+
|
|
7509
|
+
/** The app's composed strategy chain — `buildResolveSubject`'s return value. */
|
|
7510
|
+
export declare type ResolveSubjectFn = (input: {
|
|
7511
|
+
headers: Record<string, string | undefined>;
|
|
7512
|
+
clientId: number;
|
|
7513
|
+
}) => Promise<SubjectResolution>;
|
|
7514
|
+
|
|
6564
7515
|
/**
|
|
6565
7516
|
* Resolve the recording mode. `VOLTRO_TIMELINE=off|interesting|all` (also
|
|
6566
7517
|
* on/1 = interesting, 0 = off) overrides; default `interesting` outside
|
|
@@ -6570,6 +7521,18 @@ export declare const resolveSecretSync: (key: string) => string | undefined;
|
|
|
6570
7521
|
*/
|
|
6571
7522
|
export declare const resolveTimelineMode: (env?: Record<string, string | undefined>) => TimelineMode;
|
|
6572
7523
|
|
|
7524
|
+
/**
|
|
7525
|
+
* Resolve the trusted-proxy configuration.
|
|
7526
|
+
*
|
|
7527
|
+
* Precedence: explicit config (from `app.config.ts`, threaded through
|
|
7528
|
+
* `RpcServerOptions.security.trustedProxies`) beats the `VOLTRO_TRUSTED_PROXIES`
|
|
7529
|
+
* env override, which beats the safe default of trusting nothing. The env var
|
|
7530
|
+
* exists so an operator can correct a misconfigured deployment without a
|
|
7531
|
+
* rebuild; the config field exists because it is a tunable and tunables belong
|
|
7532
|
+
* in `app.config.ts`.
|
|
7533
|
+
*/
|
|
7534
|
+
export declare const resolveTrustedProxies: (configured?: ReadonlyArray<string> | undefined, env?: Readonly<Record<string, string | undefined>>) => TrustedProxyConfig;
|
|
7535
|
+
|
|
6573
7536
|
/** Per-resource-type policy. `actions` maps an action to the relations that
|
|
6574
7537
|
* grant it (ANY-of). `implies` is relation implication (owner ⇒ editor ⇒
|
|
6575
7538
|
* viewer), applied as a transitive closure before the action check. */
|
|
@@ -6582,6 +7545,10 @@ export declare interface ResourcePolicy {
|
|
|
6582
7545
|
/** The full ordered list of result variants for a definition, holdout last. */
|
|
6583
7546
|
export declare const resultVariantNames: (def: ExperimentDefinition) => ReadonlyArray<string>;
|
|
6584
7547
|
|
|
7548
|
+
export { RetentionConflict }
|
|
7549
|
+
|
|
7550
|
+
export { RetentionSource }
|
|
7551
|
+
|
|
6585
7552
|
export { RetentionSpec }
|
|
6586
7553
|
|
|
6587
7554
|
export { retentionTtlMsFromEnv }
|
|
@@ -6823,15 +7790,36 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
|
|
|
6823
7790
|
readonly ping?: () => Promise<boolean>;
|
|
6824
7791
|
};
|
|
6825
7792
|
/**
|
|
6826
|
-
* Max
|
|
6827
|
-
*
|
|
6828
|
-
*
|
|
6829
|
-
*
|
|
6830
|
-
*
|
|
6831
|
-
*
|
|
6832
|
-
*
|
|
7793
|
+
* Max body size (bytes) accepted on the buffered `POST /rpc` JSON endpoint. A
|
|
7794
|
+
* JSON rpc mutation/action/query envelope is never legitimately large, so an
|
|
7795
|
+
* oversized body is a memory-DoS attempt — refused with `413` and never
|
|
7796
|
+
* buffered past the cap. Applies ONLY to `/rpc` (JSON); file uploads ride
|
|
7797
|
+
* separate plugin routes with their own `limits.maxBytes`, and WS frames are
|
|
7798
|
+
* capped by the `ws` library default (100 MiB). Per-IP rate limiting + the
|
|
7799
|
+
* primary body cap belong at the ingress. Default
|
|
7800
|
+
* {@link DEFAULT_MAX_RPC_BODY_BYTES}.
|
|
7801
|
+
*
|
|
7802
|
+
* **Both shapes end in a `413`, and the byte counter is what enforces it.**
|
|
7803
|
+
* A declared `Content-Length` over the cap is refused first, before the client
|
|
7804
|
+
* uploads anything — a courtesy, not the enforcement, since a
|
|
7805
|
+
* `Transfer-Encoding: chunked` body declares no length. The counter runs over
|
|
7806
|
+
* the arriving bytes, stops accumulating the moment the running total crosses
|
|
7807
|
+
* the cap, drains the remainder and answers `413`.
|
|
7808
|
+
*
|
|
7809
|
+
* This paragraph used to claim the counter was the enforcement while the
|
|
7810
|
+
* declared check was the courtesy, and observably it was the other way round:
|
|
7811
|
+
* the counter bounded MEMORY correctly and produced no client-visible status
|
|
7812
|
+
* at all. It cut the body by destroying the request, that took the socket with
|
|
7813
|
+
* it, and the 413 it had just built was written into a dead connection —
|
|
7814
|
+
* `curl: (56) Recv failure: Connection reset by peer` where the same bytes
|
|
7815
|
+
* with a `Content-Length` got a clean 413. See `rpcBodyCap.ts`.
|
|
6833
7816
|
*/
|
|
6834
7817
|
readonly maxRpcBodyBytes?: number;
|
|
7818
|
+
/**
|
|
7819
|
+
* Transport-level security policy for this listener — see
|
|
7820
|
+
* {@link TransportSecurityOptions}. `undefined` → the safe defaults.
|
|
7821
|
+
*/
|
|
7822
|
+
readonly security?: TransportSecurityOptions;
|
|
6835
7823
|
}
|
|
6836
7824
|
|
|
6837
7825
|
/**
|
|
@@ -6972,6 +7960,16 @@ export declare const runWithDeadlockRetry: <T>(work: () => Promise<T>, options?:
|
|
|
6972
7960
|
*/
|
|
6973
7961
|
export declare const runWithObservedProcedure: <T>(procedure: ObservedProcedure, work: () => T) => T;
|
|
6974
7962
|
|
|
7963
|
+
/**
|
|
7964
|
+
* Run `fn` inside a FRESH raw-read scope, restoring whatever was current before.
|
|
7965
|
+
*
|
|
7966
|
+
* The wrapping form, for callers that have a callback: the dispatcher, which
|
|
7967
|
+
* wraps a computed query's recompute so a raw read issued by a LATER handler run
|
|
7968
|
+
* is attributed to that run rather than to whatever opened the request; and
|
|
7969
|
+
* tests, which need a scope that provably does not outlive them.
|
|
7970
|
+
*/
|
|
7971
|
+
export declare const runWithRawReadScope: <T>(fn: () => T) => T;
|
|
7972
|
+
|
|
6975
7973
|
/**
|
|
6976
7974
|
* Run `fn` with `loader` current, restoring whatever was current before.
|
|
6977
7975
|
*
|
|
@@ -7080,29 +8078,27 @@ export declare interface ScheduleContext {
|
|
|
7080
8078
|
}
|
|
7081
8079
|
|
|
7082
8080
|
/**
|
|
7083
|
-
* Run `effect`
|
|
8081
|
+
* Run `effect` on only ONE replica per tick, on a cadence that follows the
|
|
8082
|
+
* work rather than a fixed clock.
|
|
7084
8083
|
*
|
|
7085
|
-
*
|
|
7086
|
-
*
|
|
7087
|
-
*
|
|
7088
|
-
*
|
|
7089
|
-
*
|
|
7090
|
-
* the window and races on the identical claim key — the INSERT-wins
|
|
7091
|
-
* arbiter picks one.
|
|
8084
|
+
* Each tick floors the wall clock to an `intervalMs` bucket and asks the
|
|
8085
|
+
* coordinator to claim `(name, bucket)`. Only the replica that wins runs the
|
|
8086
|
+
* effect; the rest skip. Because the bucket comes from the shared wall clock
|
|
8087
|
+
* (not each replica's tick offset), every replica computes the same bucket
|
|
8088
|
+
* within the window and races on the identical claim key.
|
|
7092
8089
|
*
|
|
7093
|
-
* Non-dying: a throw inside `effect` is caught + logged
|
|
7094
|
-
* always re-armed. The timer is `unref`'d so it never keeps the process
|
|
7095
|
-
* alive on its own (mirrors the bare-`setInterval` behaviour it replaces).
|
|
8090
|
+
* Non-dying: a throw inside `effect` is caught + logged and the next tick is
|
|
8091
|
+
* always re-armed. The timer is `unref`'d so it never keeps the process alive.
|
|
7096
8092
|
*
|
|
7097
8093
|
* @param name Stable task name, namespaced by the caller (a plugin
|
|
7098
8094
|
* passes e.g. `presence.sweep`). Used as the claim key
|
|
7099
8095
|
* prefix + in logs.
|
|
7100
|
-
* @param intervalMs
|
|
7101
|
-
*
|
|
7102
|
-
*
|
|
7103
|
-
*
|
|
8096
|
+
* @param intervalMs The BASE period — the fastest this task ticks, the claim
|
|
8097
|
+
* bucket width, and the wake coalescing window.
|
|
8098
|
+
* @param effect The work. Return a {@link CoordinatedTickOutcome} to let
|
|
8099
|
+
* the runner back off when there is nothing to do.
|
|
7104
8100
|
*/
|
|
7105
|
-
export declare const scheduleCoordinated: (name: string, intervalMs: number, effect:
|
|
8101
|
+
export declare const scheduleCoordinated: (name: string, intervalMs: number, effect: CoordinatedEffect, deps: CoordinatedScheduleDeps) => CoordinatedScheduleHandle;
|
|
7106
8102
|
|
|
7107
8103
|
export declare interface ScheduleDefinition {
|
|
7108
8104
|
readonly name: string;
|
|
@@ -7443,6 +8439,9 @@ declare interface ScopeCtx {
|
|
|
7443
8439
|
readonly rowFilter?: RowFilterScope;
|
|
7444
8440
|
}
|
|
7445
8441
|
|
|
8442
|
+
/** The scope strings a UI shows ("ask someone with …"). */
|
|
8443
|
+
export declare const scopeStringsOf: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<string>;
|
|
8444
|
+
|
|
7446
8445
|
export declare interface SecretsBackend {
|
|
7447
8446
|
/** Resolve a secret by key. `undefined` when absent. */
|
|
7448
8447
|
readonly get: (key: string) => Promise<string | undefined>;
|
|
@@ -7457,6 +8456,49 @@ export declare type SecretsBackendConfig = 'env' | {
|
|
|
7457
8456
|
readonly backend: SecretsBackend;
|
|
7458
8457
|
};
|
|
7459
8458
|
|
|
8459
|
+
export declare interface SecurityHeadersConfig {
|
|
8460
|
+
readonly mode: SecurityHeadersMode;
|
|
8461
|
+
/** CSP for non-HTML responses. `false` → do not send one. */
|
|
8462
|
+
readonly csp: string | false;
|
|
8463
|
+
/** CSP for `text/html` responses. `false` → do not send one. */
|
|
8464
|
+
readonly cspHtml: string | false;
|
|
8465
|
+
/** `Strict-Transport-Security` value; only emitted over https. */
|
|
8466
|
+
readonly hsts: string | false;
|
|
8467
|
+
readonly frameOptions: string | false;
|
|
8468
|
+
readonly referrerPolicy: string | false;
|
|
8469
|
+
readonly contentTypeOptions: string | false;
|
|
8470
|
+
/** Anything else the app wants on every response (e.g. `Permissions-Policy`,
|
|
8471
|
+
* `Cross-Origin-Resource-Policy`). Deliberately not defaulted: both of those
|
|
8472
|
+
* break legitimate cross-origin dashboards when guessed wrong. */
|
|
8473
|
+
readonly extra: Readonly<Record<string, string>>;
|
|
8474
|
+
}
|
|
8475
|
+
|
|
8476
|
+
/**
|
|
8477
|
+
* The headers to ADD to one response. The caller must not overwrite a header
|
|
8478
|
+
* the route already set.
|
|
8479
|
+
*
|
|
8480
|
+
* `https` gates HSTS only: announcing a year of HTTPS-only from a plain-http
|
|
8481
|
+
* response is either ignored (per RFC 6797) or, on a dev box reached over
|
|
8482
|
+
* `http://localhost`, actively harmful.
|
|
8483
|
+
*/
|
|
8484
|
+
export declare const securityHeadersFor: (config: SecurityHeadersConfig, request: {
|
|
8485
|
+
readonly contentType: string | undefined;
|
|
8486
|
+
readonly https: boolean;
|
|
8487
|
+
}) => Readonly<Record<string, string>>;
|
|
8488
|
+
|
|
8489
|
+
export declare type SecurityHeadersMode = 'off' | 'default' | 'strict';
|
|
8490
|
+
|
|
8491
|
+
export declare interface SecurityHeadersOptions {
|
|
8492
|
+
readonly mode?: SecurityHeadersMode | undefined;
|
|
8493
|
+
readonly csp?: string | false | undefined;
|
|
8494
|
+
readonly cspHtml?: string | false | undefined;
|
|
8495
|
+
readonly hsts?: string | false | undefined;
|
|
8496
|
+
readonly frameOptions?: string | false | undefined;
|
|
8497
|
+
readonly referrerPolicy?: string | false | undefined;
|
|
8498
|
+
readonly contentTypeOptions?: string | false | undefined;
|
|
8499
|
+
readonly extra?: Readonly<Record<string, string>> | undefined;
|
|
8500
|
+
}
|
|
8501
|
+
|
|
7460
8502
|
export declare class SelectBuilder {
|
|
7461
8503
|
private readonly backend;
|
|
7462
8504
|
private readonly scope;
|
|
@@ -7508,6 +8550,25 @@ export declare class SelectBuilder {
|
|
|
7508
8550
|
get descriptor(): QueryDescriptor;
|
|
7509
8551
|
}
|
|
7510
8552
|
|
|
8553
|
+
/** A guard, flattened to something a row can hold. */
|
|
8554
|
+
export declare type SerializedGuard = {
|
|
8555
|
+
readonly kind: 'scope';
|
|
8556
|
+
readonly scope: ReadonlyArray<string>;
|
|
8557
|
+
readonly mode: 'all' | 'any';
|
|
8558
|
+
readonly resource: string | null;
|
|
8559
|
+
} | {
|
|
8560
|
+
readonly kind: 'policy';
|
|
8561
|
+
readonly action: string;
|
|
8562
|
+
readonly resourceType: string;
|
|
8563
|
+
readonly resource: string | null;
|
|
8564
|
+
};
|
|
8565
|
+
|
|
8566
|
+
/**
|
|
8567
|
+
* Flatten the descriptor's approver guards, resolving each `resource` extractor
|
|
8568
|
+
* against the REQUEST's input while it is still available.
|
|
8569
|
+
*/
|
|
8570
|
+
export declare const serializeGuards: (guards: ReadonlyArray<AnyGuardSpec>, input: unknown) => ReadonlyArray<SerializedGuard>;
|
|
8571
|
+
|
|
7511
8572
|
export declare interface ServeRequestContext {
|
|
7512
8573
|
readonly subject: unknown;
|
|
7513
8574
|
readonly traceId: string;
|
|
@@ -7531,6 +8592,33 @@ export declare interface ServeRequestContext {
|
|
|
7531
8592
|
*/
|
|
7532
8593
|
export declare const serverOnlyColumnNames: (tableName: string) => ReadonlyArray<string>;
|
|
7533
8594
|
|
|
8595
|
+
/**
|
|
8596
|
+
* A generated CRUD write (`crud.create` / `crud.update`) was handed a
|
|
8597
|
+
* `.serverOnly()` column in its INPUT.
|
|
8598
|
+
*
|
|
8599
|
+
* `.serverOnly()` is the WIRE-exposure axis: the column never crosses the
|
|
8600
|
+
* boundary in EITHER direction. Reads strip it; a write that accepts it is the
|
|
8601
|
+
* same violation mirrored — mass assignment of a column the schema declared the
|
|
8602
|
+
* client may not see, let alone set.
|
|
8603
|
+
*
|
|
8604
|
+
* Refused rather than silently stripped: a stripped field makes an attack
|
|
8605
|
+
* indistinguishable from a no-op and leaves an honest caller wondering why the
|
|
8606
|
+
* value it sent never landed. `columns` names what was rejected so the fix
|
|
8607
|
+
* (drop the field from the descriptor's input schema, or from the caller) is
|
|
8608
|
+
* mechanical.
|
|
8609
|
+
*/
|
|
8610
|
+
export declare class ServerOnlyColumnWrite extends ServerOnlyColumnWrite_base {
|
|
8611
|
+
}
|
|
8612
|
+
|
|
8613
|
+
declare const ServerOnlyColumnWrite_base: Schema.TaggedErrorClass<ServerOnlyColumnWrite, "ServerOnlyColumnWrite", {
|
|
8614
|
+
readonly _tag: Schema.tag<"ServerOnlyColumnWrite">;
|
|
8615
|
+
} & {
|
|
8616
|
+
/** The table the write targeted. */
|
|
8617
|
+
table: typeof Schema.String;
|
|
8618
|
+
/** The `.serverOnly()` columns the input tried to set. */
|
|
8619
|
+
columns: Schema.Array$<typeof Schema.String>;
|
|
8620
|
+
}>;
|
|
8621
|
+
|
|
7534
8622
|
/** One leak: a wire query that declares a serverOnly column in its output. */
|
|
7535
8623
|
export declare interface ServerOnlyLeak {
|
|
7536
8624
|
readonly query: string;
|
|
@@ -7809,7 +8897,7 @@ export declare interface StoreCredentialInput {
|
|
|
7809
8897
|
* a mutation's `error:` schema when you want every kind surfaced
|
|
7810
8898
|
* typed to the client.
|
|
7811
8899
|
*/
|
|
7812
|
-
export declare type StoreError = TenantScopeViolation | StoreOperationFailed | TableValidationFailed;
|
|
8900
|
+
export declare type StoreError = TenantScopeViolation | TenantRowNotFound | ServerOnlyColumnWrite | StoreOperationFailed | TableValidationFailed;
|
|
7813
8901
|
|
|
7814
8902
|
export declare interface StoreMiddlewareContext {
|
|
7815
8903
|
readonly subject: Subject;
|
|
@@ -8167,6 +9255,41 @@ export declare interface TenantCostState {
|
|
|
8167
9255
|
readonly lastUpdatedAt: number | null;
|
|
8168
9256
|
}
|
|
8169
9257
|
|
|
9258
|
+
/**
|
|
9259
|
+
* A keyed-by-primary-key write (`ctx.store.update(table, id, patch)`,
|
|
9260
|
+
* `delete(table, id)`, `hardDelete(table, id)`, `patchJson(table, id, …)`) on a
|
|
9261
|
+
* `tenant()`-scoped table did not resolve to a row inside the CALLER's tenant.
|
|
9262
|
+
*
|
|
9263
|
+
* **One error for two situations, on purpose.** It is raised identically when
|
|
9264
|
+
* the row does not exist at all and when it exists but belongs to another
|
|
9265
|
+
* tenant, and it carries no field that separates them. That is the whole point:
|
|
9266
|
+
*
|
|
9267
|
+
* - Reporting "forbidden" for a foreign row and "not found" for a missing one
|
|
9268
|
+
* turns any keyed write into a cross-tenant EXISTENCE ORACLE — an attacker
|
|
9269
|
+
* walks ids and learns which ones are real in someone else's tenant, which
|
|
9270
|
+
* is exactly the isolation the `tenant()` mixin exists to provide.
|
|
9271
|
+
* - Collapsing the other way — silently affecting zero rows — is worse than
|
|
9272
|
+
* either: the handler reads it as "the row is gone", not "you may not touch
|
|
9273
|
+
* it", so a genuine isolation breach shows up in an app as a confusing
|
|
9274
|
+
* absent-row branch and never as a security signal.
|
|
9275
|
+
*
|
|
9276
|
+
* So both cases fail LOUDLY and IDENTICALLY. `id` is the key the caller itself
|
|
9277
|
+
* supplied — never another tenant's data.
|
|
9278
|
+
*/
|
|
9279
|
+
export declare class TenantRowNotFound extends TenantRowNotFound_base {
|
|
9280
|
+
}
|
|
9281
|
+
|
|
9282
|
+
declare const TenantRowNotFound_base: Schema.TaggedErrorClass<TenantRowNotFound, "TenantRowNotFound", {
|
|
9283
|
+
readonly _tag: Schema.tag<"TenantRowNotFound">;
|
|
9284
|
+
} & {
|
|
9285
|
+
/** The table the keyed write targeted. */
|
|
9286
|
+
table: typeof Schema.String;
|
|
9287
|
+
/** The primary key the CALLER supplied. Echoing it leaks nothing. */
|
|
9288
|
+
id: typeof Schema.String;
|
|
9289
|
+
/** Human-readable explanation for diagnostics + UI. */
|
|
9290
|
+
reason: typeof Schema.String;
|
|
9291
|
+
}>;
|
|
9292
|
+
|
|
8170
9293
|
/**
|
|
8171
9294
|
* A write was attempted against a `tenant()`-scoped table, but the
|
|
8172
9295
|
* authenticated subject's `tenantId` is null — either anonymous, or
|
|
@@ -8390,6 +9513,41 @@ export declare interface TransactionalStore {
|
|
|
8390
9513
|
transactional<T>(work: (tx: unknown) => Promise<T>): Promise<T>;
|
|
8391
9514
|
}
|
|
8392
9515
|
|
|
9516
|
+
/**
|
|
9517
|
+
* Transport-level security policy for the api listener.
|
|
9518
|
+
*
|
|
9519
|
+
* Every field is a TUNABLE, so its home is the app's `app.config.ts`
|
|
9520
|
+
* (`security: { … }`, threaded to here by `voltro dev` and `voltro serve`
|
|
9521
|
+
* through one shared resolver); each also carries an env override, for
|
|
9522
|
+
* correcting a deployment without a rebuild. An embedder calling
|
|
9523
|
+
* `startRpcServer` directly passes the same object.
|
|
9524
|
+
*
|
|
9525
|
+
* `undefined` on every field → the safe defaults documented on each resolver:
|
|
9526
|
+
* origin checking ON, `x-forwarded-for` IGNORED, security headers ON.
|
|
9527
|
+
*/
|
|
9528
|
+
export declare interface TransportSecurityOptions {
|
|
9529
|
+
/** Cross-site protection for `POST /rpc` + the WS upgrade.
|
|
9530
|
+
* `'same-origin'` (default) accepts a browser request only when its
|
|
9531
|
+
* `Origin` matches the `Host` it was sent to or is in `allowedOrigins`;
|
|
9532
|
+
* a request with NO browser origin signal (the in-process SSR loader,
|
|
9533
|
+
* a mobile SDK, another service) is allowed — see `originGuard.ts`.
|
|
9534
|
+
* Env override: `VOLTRO_ORIGIN_GUARD=off`. */
|
|
9535
|
+
readonly originGuard?: OriginGuardMode;
|
|
9536
|
+
/** Extra origins to accept, for a split web/api deployment where the
|
|
9537
|
+
* browser's `Origin` is a different host than the api's `Host`.
|
|
9538
|
+
* Env override: `VOLTRO_ALLOWED_ORIGINS` (comma-separated). */
|
|
9539
|
+
readonly allowedOrigins?: ReadonlyArray<string>;
|
|
9540
|
+
/** Whose `x-forwarded-for` to believe. Empty (the default) → the header is
|
|
9541
|
+
* IGNORED and `socket.remoteAddress` is the client address. Accepts IPs,
|
|
9542
|
+
* CIDRs, the presets `loopback` / `private`, a hop count (`['2']`), or
|
|
9543
|
+
* `['*']` to trust any peer. Env override: `VOLTRO_TRUSTED_PROXIES`. */
|
|
9544
|
+
readonly trustedProxies?: ReadonlyArray<string>;
|
|
9545
|
+
/** Security response headers. Env override: `VOLTRO_SECURITY_HEADERS`
|
|
9546
|
+
* (`off|default|strict`), plus `VOLTRO_CSP` / `VOLTRO_CSP_HTML` /
|
|
9547
|
+
* `VOLTRO_HSTS`. */
|
|
9548
|
+
readonly headers?: SecurityHeadersOptions;
|
|
9549
|
+
}
|
|
9550
|
+
|
|
8393
9551
|
/**
|
|
8394
9552
|
* Declare a workflow trigger.
|
|
8395
9553
|
*
|
|
@@ -8421,6 +9579,16 @@ export declare const triggerWorkflow: <EventPayload = unknown, WorkflowPayload =
|
|
|
8421
9579
|
};
|
|
8422
9580
|
})) => WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>;
|
|
8423
9581
|
|
|
9582
|
+
/** How much of the forwarded chain we are willing to believe. */
|
|
9583
|
+
export declare interface TrustedProxyConfig {
|
|
9584
|
+
/** Literal IPs / CIDR blocks / presets (`loopback`, `private`) to trust. */
|
|
9585
|
+
readonly entries: ReadonlyArray<string>;
|
|
9586
|
+
/** `*` — believe the leftmost XFF token from ANY peer. */
|
|
9587
|
+
readonly trustAll: boolean;
|
|
9588
|
+
/** Hop count (express's numeric `trust proxy`). `undefined` → not hop mode. */
|
|
9589
|
+
readonly hops?: number;
|
|
9590
|
+
}
|
|
9591
|
+
|
|
8424
9592
|
/**
|
|
8425
9593
|
* Atomically claim a pending wakeup for waking — CAS `pending → fired`.
|
|
8426
9594
|
* Returns `true` iff THIS caller won the claim (the row was pending).
|
|
@@ -8466,10 +9634,10 @@ export declare type TupleSource = (req: {
|
|
|
8466
9634
|
}) => Promise<ReadonlyArray<RelationTuple>> | ReadonlyArray<RelationTuple>;
|
|
8467
9635
|
|
|
8468
9636
|
/**
|
|
8469
|
-
*
|
|
8470
|
-
*
|
|
9637
|
+
* Drop everything this connection accumulated. Called by the WS-close finalizer
|
|
9638
|
+
* and by explicit sign-out flows. Idempotent.
|
|
8471
9639
|
*/
|
|
8472
|
-
export declare const
|
|
9640
|
+
export declare const unbindConnection: (clientId: number) => void;
|
|
8473
9641
|
|
|
8474
9642
|
/** The store surface an inverse op needs (a subset of the framework DataStore). */
|
|
8475
9643
|
export declare interface UndoApplyStore {
|
|
@@ -8546,6 +9714,29 @@ export declare class UndoStack<E> {
|
|
|
8546
9714
|
};
|
|
8547
9715
|
}
|
|
8548
9716
|
|
|
9717
|
+
/**
|
|
9718
|
+
* Is the undo WIRE SURFACE present — the three `__voltro.undo.*` descriptors in
|
|
9719
|
+
* the client's generated rpc group, and the matching routes on the server?
|
|
9720
|
+
*
|
|
9721
|
+
* **Deliberately NOT `undoCaptureEnabled()`, and the difference is the whole
|
|
9722
|
+
* point.** The client half of that surface is a BUILD ARTEFACT:
|
|
9723
|
+
* `rpcGroup.generated.ts` is written by `voltro dev`'s codegen and `voltro
|
|
9724
|
+
* build` does not regenerate it. So the artefact froze whatever
|
|
9725
|
+
* `undoCaptureEnabled()` answered on the developer's machine — on, by its
|
|
9726
|
+
* NODE_ENV default — while the production process it ships to answers off and
|
|
9727
|
+
* binds nothing. The failure appears only after deploy, and only when someone
|
|
9728
|
+
* presses undo.
|
|
9729
|
+
*
|
|
9730
|
+
* The surface therefore reads ONLY the explicit declaration: `VOLTRO_UNDO=off`
|
|
9731
|
+
* removes it everywhere (codegen + both boot paths agree, because nothing in
|
|
9732
|
+
* the answer depends on when or where the code ran), and anything else keeps
|
|
9733
|
+
* it. Whether the mutations are actually RECORDED stays environment-aware —
|
|
9734
|
+
* that is the cost decision, and it is a genuine one. With capture off the
|
|
9735
|
+
* executors answer honestly rather than 404: the log is empty because nothing
|
|
9736
|
+
* was captured, and an apply/redo cannot find its row.
|
|
9737
|
+
*/
|
|
9738
|
+
export declare const undoSurfaceEnabled: (env?: Record<string, string | undefined>) => boolean;
|
|
9739
|
+
|
|
8549
9740
|
/** One `source:` entry that resolves to no declared table. */
|
|
8550
9741
|
export declare interface UnresolvedSource {
|
|
8551
9742
|
/** The procedure that declares it. */
|
|
@@ -8554,6 +9745,9 @@ export declare interface UnresolvedSource {
|
|
|
8554
9745
|
readonly source: string;
|
|
8555
9746
|
/** A declared table whose name is close — the rename case, usually. */
|
|
8556
9747
|
readonly didYouMean: string | undefined;
|
|
9748
|
+
/** True when the name is in the `channel:` namespace: it named a reactivity
|
|
9749
|
+
* channel nobody declared, which is a different repair from a stale table. */
|
|
9750
|
+
readonly channel: boolean;
|
|
8557
9751
|
}
|
|
8558
9752
|
|
|
8559
9753
|
/**
|
|
@@ -8567,6 +9761,14 @@ export declare const unresolvedSources: (procedures: ReadonlyArray<{
|
|
|
8567
9761
|
readonly source: string | ReadonlyArray<string> | undefined;
|
|
8568
9762
|
}>, declared: ReadonlySet<string>) => ReadonlyArray<UnresolvedSource>;
|
|
8569
9763
|
|
|
9764
|
+
/** Thrown at boot; the message is the whole point, so it names the path and the
|
|
9765
|
+
* three ways out. */
|
|
9766
|
+
export declare class UnverifiedWebhookRoute extends Error {
|
|
9767
|
+
readonly path: string;
|
|
9768
|
+
readonly name = "UnverifiedWebhookRoute";
|
|
9769
|
+
constructor(path: string);
|
|
9770
|
+
}
|
|
9771
|
+
|
|
8570
9772
|
export declare class UpdateBuilder {
|
|
8571
9773
|
private readonly backend;
|
|
8572
9774
|
private readonly scope;
|
|
@@ -8624,6 +9826,11 @@ export declare const useExperiment: (def: ExperimentDefinition) => Effect.Effect
|
|
|
8624
9826
|
|
|
8625
9827
|
declare type VaultStore = Pick<DataStore, 'query' | 'insert' | 'update' | 'updateMany' | 'delete' | 'upsert'>;
|
|
8626
9828
|
|
|
9829
|
+
/** A mounted incoming-webhook handler that has declared how it authenticates. */
|
|
9830
|
+
export declare type VerificationDeclared<T> = T & {
|
|
9831
|
+
readonly [WEBHOOK_VERIFICATION_PROPERTY]: WebhookVerification;
|
|
9832
|
+
};
|
|
9833
|
+
|
|
8627
9834
|
/** The pure core of live revocation: given a subject + action + a row-set,
|
|
8628
9835
|
* return ONLY the rows the subject may still see. The reactive matcher calls
|
|
8629
9836
|
* this on a permission-changing write and diffs against the prior visible set;
|
|
@@ -8654,6 +9861,16 @@ export declare const VOLTRO_SOFT_DELETE_MIXIN_ID: "voltro/softDelete";
|
|
|
8654
9861
|
|
|
8655
9862
|
export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
8656
9863
|
|
|
9864
|
+
/**
|
|
9865
|
+
* One pending (or historical) approval intent.
|
|
9866
|
+
*
|
|
9867
|
+
* Reactive on purpose — the requester's client subscribes to
|
|
9868
|
+
* `__voltro.approvals.pending` and sees its own row flip `pending → approved`
|
|
9869
|
+
* without polling, which is the whole answer to "what does the caller see
|
|
9870
|
+
* meanwhile".
|
|
9871
|
+
*/
|
|
9872
|
+
export declare const _voltroApprovalsTable: TableLike;
|
|
9873
|
+
|
|
8657
9874
|
/**
|
|
8658
9875
|
* `_voltro_connection_grants` — an IN-FLIGHT oauth2 handshake. Separate from
|
|
8659
9876
|
* the credential table on purpose: a handshake is a short-lived, single-use
|
|
@@ -8790,10 +10007,22 @@ export declare interface WakeupRef {
|
|
|
8790
10007
|
* next register cleanly overwrites it). */
|
|
8791
10008
|
export declare type WakeupStatus = 'pending' | 'fired';
|
|
8792
10009
|
|
|
10010
|
+
/** The property `mountIncomingWebhook` stamps onto the handler it returns.
|
|
10011
|
+
* A plain string property rather than a symbol so it survives the structural
|
|
10012
|
+
* hand-off between packages that do not import each other. */
|
|
10013
|
+
export declare const WEBHOOK_VERIFICATION_PROPERTY: "voltroWebhookVerification";
|
|
10014
|
+
|
|
8793
10015
|
/** Stage 4 webhook-route surface — kept transport-agnostic so the
|
|
8794
10016
|
* plugin can pipe its IncomingResponse through unchanged. The
|
|
8795
10017
|
* rpc server reads the request body (UTF-8 / binary) + headers
|
|
8796
|
-
* and dispatches by path.
|
|
10018
|
+
* and dispatches by path.
|
|
10019
|
+
*
|
|
10020
|
+
* `handle` MUST carry a `voltroWebhookVerification` declaration — the
|
|
10021
|
+
* property `@voltro/plugin-webhooks`' `mountIncomingWebhook` stamps onto
|
|
10022
|
+
* the handler it returns. `startRpcServer` refuses to mount a route
|
|
10023
|
+
* without one (see `webhookVerification.ts`): an incoming webhook is a
|
|
10024
|
+
* public POST that runs application code, so "nothing verifies it" has to
|
|
10025
|
+
* be a decision somebody wrote down, not the default. */
|
|
8797
10026
|
export declare interface WebhookRouteHandler {
|
|
8798
10027
|
readonly handle: (request: {
|
|
8799
10028
|
readonly method: string;
|
|
@@ -8809,6 +10038,21 @@ export declare interface WebhookRouteHandler {
|
|
|
8809
10038
|
|
|
8810
10039
|
export declare type WebhooksAppContext = unknown;
|
|
8811
10040
|
|
|
10041
|
+
/** How an incoming webhook authenticates its caller. */
|
|
10042
|
+
export declare type WebhookVerification =
|
|
10043
|
+
/** The framework verifies an HMAC signature (+ replay window) before the
|
|
10044
|
+
* handler runs, using the webhook's configured shared secret. */
|
|
10045
|
+
'signature'
|
|
10046
|
+
/** The handler / provider integration verifies with the provider's own SDK
|
|
10047
|
+
* (Stripe's `constructEvent`, etc.). The framework does not second-guess it. */
|
|
10048
|
+
| 'provider'
|
|
10049
|
+
/** Deliberately unverified — the endpoint is behind a separate trust boundary
|
|
10050
|
+
* (gateway + IP allow-list). A visible decision, never a default. */
|
|
10051
|
+
| 'none';
|
|
10052
|
+
|
|
10053
|
+
/** Read the declaration off a mounted handler. `undefined` → never declared. */
|
|
10054
|
+
export declare const webhookVerificationOf: (handle: unknown) => WebhookVerification | undefined;
|
|
10055
|
+
|
|
8812
10056
|
/**
|
|
8813
10057
|
* Comparison operators accepted by the ergonomic `.where(col, op, value)`
|
|
8814
10058
|
* form. `fts` falls back to a `contains` (LIKE) match here; the index-backed
|
|
@@ -9033,6 +10277,31 @@ export declare interface WorkflowLayerExecutionContext {
|
|
|
9033
10277
|
export declare interface WorkflowLayerOptions<Context> {
|
|
9034
10278
|
readonly buildContext: (callerContext: ResolvedWorkflowCallerContext, execution: WorkflowLayerExecutionContext) => Context;
|
|
9035
10279
|
readonly resolveStartContext?: (workflowName: string, executionId: string) => WorkflowCallerContext | undefined | Promise<WorkflowCallerContext | undefined>;
|
|
10280
|
+
/**
|
|
10281
|
+
* Put AUTHORITY back on a restored caller identity, at execution time
|
|
10282
|
+
* (REL-24).
|
|
10283
|
+
*
|
|
10284
|
+
* `resolveStartContext` returns IDENTITY — the start-context table strips
|
|
10285
|
+
* scopes on write and on read, because a `json()` column read by another
|
|
10286
|
+
* runner days later is authority frozen and made durable, which is REL-5's
|
|
10287
|
+
* cookie one layer down. This hook is what re-establishes the authority, from
|
|
10288
|
+
* the app's live source, on every execution attempt.
|
|
10289
|
+
*
|
|
10290
|
+
* Called ONLY for a run that has a recorded caller. A bootstrap run — no
|
|
10291
|
+
* recorded context — is `SYSTEM_SUBJECT` and is not put through it:
|
|
10292
|
+
* `SYSTEM_SUBJECT` already states its own authority, and running it through
|
|
10293
|
+
* an app resolver keyed on a null id would be asking a question with no
|
|
10294
|
+
* answer.
|
|
10295
|
+
*
|
|
10296
|
+
* Absent ⇒ the identity runs with no scopes. That is the fail-closed
|
|
10297
|
+
* direction and it matches what a cookie-authenticated request already gets
|
|
10298
|
+
* from an app that wires no resolver.
|
|
10299
|
+
*
|
|
10300
|
+
* Rejecting FAILS the attempt. Do not swallow it into "fewer scopes": a run
|
|
10301
|
+
* that skips the branch it was not allowed to take looks exactly like one
|
|
10302
|
+
* whose business logic said no.
|
|
10303
|
+
*/
|
|
10304
|
+
readonly resolveAuthority?: (identity: unknown) => Promise<unknown>;
|
|
9036
10305
|
}
|
|
9037
10306
|
|
|
9038
10307
|
/**
|