@voltro/runtime 0.33.0 → 0.35.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 +1968 -0
- package/dist/index.d.ts +1320 -72
- package/dist/index.js +2755 -1644
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
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';
|
|
10
15
|
import { createServer } from 'node:http';
|
|
11
16
|
import { Cron } from 'effect';
|
|
12
17
|
import { DataStore } from '@voltro/database';
|
|
18
|
+
import { DeclaredAccess } from '@voltro/protocol';
|
|
13
19
|
import { DialectId } from '@voltro/database';
|
|
14
20
|
import { DialectReplicationAdapter } from '@voltro/database';
|
|
15
21
|
import { Duration } from 'effect';
|
|
@@ -23,7 +29,6 @@ import { EventResumePoint } from '@voltro/protocol';
|
|
|
23
29
|
import { EventStreamEvent } from '@voltro/protocol';
|
|
24
30
|
import { Fiber } from 'effect';
|
|
25
31
|
import { FieldCipher } from '@voltro/database';
|
|
26
|
-
import { Guards } from '@voltro/protocol';
|
|
27
32
|
import * as http from 'node:http';
|
|
28
33
|
import { HttpClient } from '@effect/platform';
|
|
29
34
|
import { HttpRequestInterceptor } from '@voltro/protocol';
|
|
@@ -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';
|
|
@@ -106,6 +116,8 @@ export declare interface ActionRunnerDeps {
|
|
|
106
116
|
readonly runEffect: (effect: Effect.Effect<unknown, unknown, never>, store: unknown) => Promise<unknown>;
|
|
107
117
|
readonly interceptor?: ServeRpcInterceptor;
|
|
108
118
|
readonly recordMetric?: MutationRunnerDeps['recordMetric'];
|
|
119
|
+
/** See {@link MutationRunnerDeps.defaultDeny} — same flag, same refusal. */
|
|
120
|
+
readonly defaultDeny?: boolean;
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
/**
|
|
@@ -527,26 +539,24 @@ export declare type AnalyticsMetric = 'count' | 'unique' | {
|
|
|
527
539
|
readonly avg: string;
|
|
528
540
|
};
|
|
529
541
|
|
|
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
542
|
export declare interface AnalyticsMirrorHandle {
|
|
545
543
|
/** Tables actually being mirrored (the sink's `mirror.tables`). Empty
|
|
546
544
|
* when the sink declares no mirror. */
|
|
547
545
|
readonly tables: ReadonlyArray<string>;
|
|
548
|
-
/** Detach the `onChange` subscription. */
|
|
546
|
+
/** Detach the `onChange` subscription and stop the repair loop. */
|
|
549
547
|
readonly detach: () => void;
|
|
548
|
+
/** Settle every queued + in-flight write. Resolves when the mirror has
|
|
549
|
+
* nothing left to apply (or has queued it for repair). */
|
|
550
|
+
readonly flush: () => Promise<void>;
|
|
551
|
+
/**
|
|
552
|
+
* Re-drive every key awaiting repair: re-read its CURRENT row from the
|
|
553
|
+
* store and re-apply it (upsert when present, remove when gone) under
|
|
554
|
+
* a fresh version. Runs on the `repairIntervalMs` timer; exposed so an
|
|
555
|
+
* operator or a shutdown hook can force a pass. Resolves with the
|
|
556
|
+
* number of keys that landed.
|
|
557
|
+
*/
|
|
558
|
+
readonly repair: () => Promise<number>;
|
|
559
|
+
readonly stats: () => AnalyticsMirrorStats;
|
|
550
560
|
}
|
|
551
561
|
|
|
552
562
|
/**
|
|
@@ -561,6 +571,13 @@ export declare interface AnalyticsMirrorHandle {
|
|
|
561
571
|
* MUST be idempotent — the same change may be re-delivered after a
|
|
562
572
|
* reconnect, and the upsert is keyed on `primaryKey` so re-applying a
|
|
563
573
|
* row is a no-op-equivalent overwrite.
|
|
574
|
+
*
|
|
575
|
+
* Idempotent is not enough on its own: re-delivery can also arrive OUT
|
|
576
|
+
* OF ORDER, so both methods additionally receive a {@link MirrorVersion}
|
|
577
|
+
* and MUST ignore a write whose version is not greater than the version
|
|
578
|
+
* already stored for that key (ClickHouse gets this from
|
|
579
|
+
* `ReplacingMergeTree(version)`; DuckDB from an `ON CONFLICT … WHERE
|
|
580
|
+
* excluded.version > version` guard).
|
|
564
581
|
*/
|
|
565
582
|
export declare interface AnalyticsMirrorImpl {
|
|
566
583
|
/** Reactive tables this sink mirrors. The framework only forwards
|
|
@@ -569,10 +586,109 @@ export declare interface AnalyticsMirrorImpl {
|
|
|
569
586
|
/** Primary-key column the sink upserts/deletes by. Defaults to `'id'`
|
|
570
587
|
* when omitted. */
|
|
571
588
|
readonly primaryKey?: string;
|
|
572
|
-
/** Apply an insert/update. Idempotent upsert keyed on `primaryKey
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
589
|
+
/** Apply an insert/update. Idempotent upsert keyed on `primaryKey`,
|
|
590
|
+
* version-guarded by `write.version`. */
|
|
591
|
+
readonly upsert: (write: AnalyticsMirrorUpsert) => Effect.Effect<void, AnalyticsFailure>;
|
|
592
|
+
/** Apply a delete. Idempotent — removing an absent key is a no-op —
|
|
593
|
+
* and version-guarded by `write.version`. */
|
|
594
|
+
readonly remove: (write: AnalyticsMirrorRemove) => Effect.Effect<void, AnalyticsFailure>;
|
|
595
|
+
/**
|
|
596
|
+
* The highest version this sink has PERSISTED for one key — `null` when
|
|
597
|
+
* the key has never been written (a tombstoned key still reports its
|
|
598
|
+
* tombstone's version; both warehouse sinks keep tombstones for exactly
|
|
599
|
+
* this reason).
|
|
600
|
+
*
|
|
601
|
+
* This is the fleet-scope seed read: a replica that joins mid-stream has
|
|
602
|
+
* no per-key history of its own, so before its first write to a key it
|
|
603
|
+
* asks the warehouse where the fleet already is and continues from
|
|
604
|
+
* there. Without it a joiner would restart the key's versions low and
|
|
605
|
+
* every write it produced would be silently rejected by the sink's own
|
|
606
|
+
* `version >` guard. Called at most once per key per process (off the
|
|
607
|
+
* commit path, inside the per-key drain worker), never per change.
|
|
608
|
+
*/
|
|
609
|
+
readonly maxVersion: (input: {
|
|
610
|
+
readonly table: string;
|
|
611
|
+
readonly primaryKeyValue: unknown;
|
|
612
|
+
}) => Effect.Effect<MirrorVersion | null, AnalyticsFailure>;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** One delete to apply to the mirror. */
|
|
616
|
+
export declare interface AnalyticsMirrorRemove {
|
|
617
|
+
readonly table: string;
|
|
618
|
+
/** Primary-key value of the removed row. */
|
|
619
|
+
readonly primaryKeyValue: unknown;
|
|
620
|
+
/** See {@link MirrorVersion}. A delete is ordered against upserts of
|
|
621
|
+
* the same key by exactly this number. */
|
|
622
|
+
readonly version: MirrorVersion;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** Counters a caller (boot log, inspect endpoint, test) can read. */
|
|
626
|
+
export declare interface AnalyticsMirrorStats {
|
|
627
|
+
/** Writes the sink accepted. */
|
|
628
|
+
readonly forwarded: number;
|
|
629
|
+
/** Keys with a queued-but-not-yet-applied write. */
|
|
630
|
+
readonly pending: number;
|
|
631
|
+
/** Keys awaiting repair (retries exhausted). */
|
|
632
|
+
readonly awaitingRepair: number;
|
|
633
|
+
/** Changes provably lost — repair-queue overflow. */
|
|
634
|
+
readonly dropped: number;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Every number the mirror picks on the app's behalf, with its default.
|
|
639
|
+
* Each is also readable from the environment so an operator can tune a
|
|
640
|
+
* running deployment without a code change.
|
|
641
|
+
*/
|
|
642
|
+
export declare interface AnalyticsMirrorTunables {
|
|
643
|
+
/**
|
|
644
|
+
* Total attempts for one mirror write (1 = no retry).
|
|
645
|
+
* Default `5`. Env: `VOLTRO_ANALYTICS_MIRROR_RETRY_ATTEMPTS`.
|
|
646
|
+
*/
|
|
647
|
+
readonly retryAttempts: number;
|
|
648
|
+
/**
|
|
649
|
+
* First backoff delay; doubles per attempt.
|
|
650
|
+
* Default `100`ms. Env: `VOLTRO_ANALYTICS_MIRROR_RETRY_BASE_MS`.
|
|
651
|
+
*/
|
|
652
|
+
readonly retryBaseDelayMs: number;
|
|
653
|
+
/**
|
|
654
|
+
* Ceiling for the doubling backoff.
|
|
655
|
+
* Default `30_000`ms. Env: `VOLTRO_ANALYTICS_MIRROR_RETRY_MAX_MS`.
|
|
656
|
+
*/
|
|
657
|
+
readonly retryMaxDelayMs: number;
|
|
658
|
+
/**
|
|
659
|
+
* How often the repair loop re-drives changes whose retries ran out.
|
|
660
|
+
* `0` disables the loop (the queue then only drains via an explicit
|
|
661
|
+
* `handle.repair()`).
|
|
662
|
+
* Default `60_000`ms. Env: `VOLTRO_ANALYTICS_MIRROR_REPAIR_INTERVAL_MS`.
|
|
663
|
+
*/
|
|
664
|
+
readonly repairIntervalMs: number;
|
|
665
|
+
/**
|
|
666
|
+
* Maximum keys held for repair. Past this, the oldest queued key is
|
|
667
|
+
* dropped, counted and logged at error level — a bounded queue that
|
|
668
|
+
* says so beats an unbounded one that ends the process.
|
|
669
|
+
* Default `10_000`. Env: `VOLTRO_ANALYTICS_MIRROR_REPAIR_QUEUE_LIMIT`.
|
|
670
|
+
*/
|
|
671
|
+
readonly repairQueueLimit: number;
|
|
672
|
+
/**
|
|
673
|
+
* Maximum keys whose fleet-scope version state (warehouse baseline +
|
|
674
|
+
* per-key ordinal) is held in memory. Least-recently-changed keys past
|
|
675
|
+
* the limit are evicted — safe, because an evicted key that changes
|
|
676
|
+
* again simply re-seeds its baseline from the warehouse, which by then
|
|
677
|
+
* contains its landed writes (keys with queued / in-flight / repairing
|
|
678
|
+
* writes are never evicted). Unused under `changeScope: 'local'`.
|
|
679
|
+
* Default `100_000`. Env: `VOLTRO_ANALYTICS_MIRROR_VERSION_STATE_LIMIT`.
|
|
680
|
+
*/
|
|
681
|
+
readonly versionStateLimit: number;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** One insert/update to apply to the mirror. */
|
|
685
|
+
export declare interface AnalyticsMirrorUpsert {
|
|
686
|
+
readonly table: string;
|
|
687
|
+
/** Post-image of the changed row. */
|
|
688
|
+
readonly row: Readonly<Record<string, unknown>>;
|
|
689
|
+
/** See {@link MirrorVersion} — persist it, compare against it, never
|
|
690
|
+
* replace it with a local clock reading. */
|
|
691
|
+
readonly version: MirrorVersion;
|
|
576
692
|
}
|
|
577
693
|
|
|
578
694
|
/**
|
|
@@ -680,6 +796,9 @@ declare type AnyRow = Record<string, unknown>;
|
|
|
680
796
|
|
|
681
797
|
declare type AnyRow_2 = Record<string, unknown>;
|
|
682
798
|
|
|
799
|
+
/** Locked-down policy for an API response. Nothing loads, nothing frames it. */
|
|
800
|
+
export declare const API_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
|
|
801
|
+
|
|
683
802
|
export declare const API_KEYS_TABLE = "_voltro_api_keys";
|
|
684
803
|
|
|
685
804
|
/** Admin-gated management routes (issue / list / revoke) for the built-in keys.
|
|
@@ -924,6 +1043,21 @@ export declare interface AppContext {
|
|
|
924
1043
|
readonly loadMany: DataLoader['loadMany'];
|
|
925
1044
|
}
|
|
926
1045
|
|
|
1046
|
+
/**
|
|
1047
|
+
* The connection's headers as the auth chain should see them: the transport's
|
|
1048
|
+
* own headers with the bound credential patched over them.
|
|
1049
|
+
*
|
|
1050
|
+
* Returns the input UNCHANGED when nothing is bound — the overwhelmingly common
|
|
1051
|
+
* case is a connection that never re-authenticated, and it must not pay for a
|
|
1052
|
+
* clone.
|
|
1053
|
+
*
|
|
1054
|
+
* Header names are lowercased on the patched keys because the rest of the chain
|
|
1055
|
+
* reads them lowercase (`headers['cookie']`, `headers['authorization']`), and a
|
|
1056
|
+
* patch written as `Authorization` that lands beside an existing `authorization`
|
|
1057
|
+
* is a credential that is present and invisible.
|
|
1058
|
+
*/
|
|
1059
|
+
export declare const applyConnectionCredential: (clientId: number, headers: Record<string, string | undefined>) => Record<string, string | undefined>;
|
|
1060
|
+
|
|
927
1061
|
/**
|
|
928
1062
|
* Pure helper backing `ctx.store.applyDefined`. Picks the listed keys from
|
|
929
1063
|
* `input` whose value is not `undefined`, so a PATCH sets exactly the fields
|
|
@@ -987,6 +1121,72 @@ export declare const applyUndoInvocation: (input: {
|
|
|
987
1121
|
readonly ok: boolean;
|
|
988
1122
|
}>;
|
|
989
1123
|
|
|
1124
|
+
/** What the dispatch spine hands the gate. */
|
|
1125
|
+
export declare interface ApprovalAdmissionInput {
|
|
1126
|
+
readonly procedure: string;
|
|
1127
|
+
readonly kind: 'mutation' | 'action';
|
|
1128
|
+
readonly policy: AnyApprovalPolicy;
|
|
1129
|
+
readonly subject: Subject | null;
|
|
1130
|
+
readonly input: unknown;
|
|
1131
|
+
readonly traceId?: string | undefined;
|
|
1132
|
+
/** Distinguishes a deliberate repeat of an identical request. */
|
|
1133
|
+
readonly nonce?: string | undefined;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** The slice of AppContext the approval executors need. */
|
|
1137
|
+
export declare interface ApprovalExecutorCtx {
|
|
1138
|
+
readonly store: DataStore;
|
|
1139
|
+
readonly request?: {
|
|
1140
|
+
readonly subject?: Subject | null;
|
|
1141
|
+
} | undefined;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
export declare interface ApprovalGateApi {
|
|
1145
|
+
/**
|
|
1146
|
+
* ADMIT or REFUSE one approval-requiring call.
|
|
1147
|
+
*
|
|
1148
|
+
* Resolves when the call may proceed (an approval was found and CONSUMED, so
|
|
1149
|
+
* it cannot be spent twice). REJECTS with one of the typed approval errors
|
|
1150
|
+
* otherwise — the caller re-throws it and the rpc layer matches it against the
|
|
1151
|
+
* descriptor's `error:` union.
|
|
1152
|
+
*/
|
|
1153
|
+
readonly admit: (input: ApprovalAdmissionInput) => Promise<void>;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* The content-addressed identity of an intent.
|
|
1158
|
+
*
|
|
1159
|
+
* LENGTH-PREFIXED, not separator-joined, for the reason `promptVersionDigest`
|
|
1160
|
+
* records: no printable separator is injective over arbitrary prose, and the
|
|
1161
|
+
* repo's rule forbids the NUL that would be. A procedure named `a` with
|
|
1162
|
+
* requester `b:c` must not digest the same as `a:b` with requester `c`.
|
|
1163
|
+
*/
|
|
1164
|
+
export declare const approvalIntentKey: (input: {
|
|
1165
|
+
readonly procedure: string;
|
|
1166
|
+
readonly requestedBy: string | null;
|
|
1167
|
+
readonly tenantId: string | null;
|
|
1168
|
+
readonly payload: string;
|
|
1169
|
+
/** Distinguishes two DELIBERATELY identical requests. The caller states it;
|
|
1170
|
+
* the framework never invents one (that would defeat the whole idea). */
|
|
1171
|
+
readonly nonce?: string | undefined;
|
|
1172
|
+
}) => string;
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Mint a nonce for a DELIBERATE repeat of an identical request.
|
|
1176
|
+
*
|
|
1177
|
+
* Exported for callers that genuinely need two identical intents in flight (the
|
|
1178
|
+
* same refund, twice, on purpose). Never called by the framework: inventing one
|
|
1179
|
+
* per attempt is exactly the too-fine identity the header rejects.
|
|
1180
|
+
*/
|
|
1181
|
+
export declare const approvalNonce: () => string;
|
|
1182
|
+
|
|
1183
|
+
export declare const APPROVALS_TABLE = "_voltro_approvals";
|
|
1184
|
+
|
|
1185
|
+
/** `__voltro.approvals.pending` — the calling subject's approval work. */
|
|
1186
|
+
export declare const approvalsPendingExecutor: (input: {
|
|
1187
|
+
readonly limit?: number;
|
|
1188
|
+
}, ctx: ApprovalExecutorCtx) => Promise<ReadonlyArray<PendingApproval>>;
|
|
1189
|
+
|
|
990
1190
|
/**
|
|
991
1191
|
* Controls the lifecycle of the app process the orchestrator fronts.
|
|
992
1192
|
* Both `start` and `stop` are idempotent so the orchestrator can call
|
|
@@ -1021,6 +1221,14 @@ export declare const assertCan: (subject: RebacSubject, action: string, resource
|
|
|
1021
1221
|
*/
|
|
1022
1222
|
export declare const assertConnectionCipherConfigured: (definitions: ReadonlyArray<ConnectionDefinition>) => void;
|
|
1023
1223
|
|
|
1224
|
+
/**
|
|
1225
|
+
* Boot gate: every mounted incoming-webhook route must carry a verification
|
|
1226
|
+
* declaration. Throws `UnverifiedWebhookRoute` for the first that does not.
|
|
1227
|
+
*/
|
|
1228
|
+
export declare const assertWebhookRoutesDeclareVerification: (routes: ReadonlyMap<string, {
|
|
1229
|
+
readonly handle: unknown;
|
|
1230
|
+
}> | undefined) => void;
|
|
1231
|
+
|
|
1024
1232
|
/**
|
|
1025
1233
|
* Assign a subject to a variant — STABLE (same subject ⇒ same variant, always)
|
|
1026
1234
|
* and BALANCED (proportions converge to the weights over many subjects). The
|
|
@@ -1092,12 +1300,10 @@ export declare interface AsyncKv {
|
|
|
1092
1300
|
* Wire the sink's CDC-mirror to the store's change stream. No-op (and
|
|
1093
1301
|
* returns an empty handle) when the sink declares no `mirror` — so the
|
|
1094
1302
|
* caller can always call this unconditionally.
|
|
1095
|
-
*
|
|
1096
|
-
* Returns the set of mirrored tables (for boot logging) + a detach.
|
|
1097
1303
|
*/
|
|
1098
1304
|
export declare const attachAnalyticsMirror: (options: AttachAnalyticsMirrorOptions) => AnalyticsMirrorHandle;
|
|
1099
1305
|
|
|
1100
|
-
export declare interface AttachAnalyticsMirrorOptions {
|
|
1306
|
+
export declare interface AttachAnalyticsMirrorOptions extends Partial<AnalyticsMirrorTunables> {
|
|
1101
1307
|
readonly store: DataStore;
|
|
1102
1308
|
readonly sink: AnalyticsSinkImpl;
|
|
1103
1309
|
readonly log: SyncLogger;
|
|
@@ -1193,6 +1399,10 @@ export declare interface AuditableQuery {
|
|
|
1193
1399
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
1194
1400
|
}
|
|
1195
1401
|
|
|
1402
|
+
export declare interface AuthMiddlewareLayerOptions {
|
|
1403
|
+
readonly resolveSubject: ResolveSubjectFn;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1196
1406
|
/**
|
|
1197
1407
|
* An Effect that is ALSO awaitable.
|
|
1198
1408
|
*
|
|
@@ -1241,7 +1451,18 @@ export declare interface BeginOAuthResult {
|
|
|
1241
1451
|
readonly state: string;
|
|
1242
1452
|
}
|
|
1243
1453
|
|
|
1244
|
-
|
|
1454
|
+
/**
|
|
1455
|
+
* Present a new credential on an ALREADY-OPEN connection.
|
|
1456
|
+
*
|
|
1457
|
+
* Called from a handler that has just authenticated the caller — `auth.signin`,
|
|
1458
|
+
* a tenant switch — with the credential it minted (typically the session cookie
|
|
1459
|
+
* it is about to `Set-Cookie`). Every subsequent call on this connection is
|
|
1460
|
+
* resolved by the FULL auth chain against these headers, so the caller gets
|
|
1461
|
+
* exactly what a reconnect would give them and nothing more.
|
|
1462
|
+
*
|
|
1463
|
+
* Idempotent; a second bind replaces the first.
|
|
1464
|
+
*/
|
|
1465
|
+
export declare const bindConnectionCredential: (clientId: number, credential: ConnectionCredential) => void;
|
|
1245
1466
|
|
|
1246
1467
|
/**
|
|
1247
1468
|
* Bind one client's subscription to a declared event.
|
|
@@ -1269,9 +1490,11 @@ export declare interface BindEventInput {
|
|
|
1269
1490
|
*
|
|
1270
1491
|
* Passed in rather than read from a registry so there is exactly one way for a
|
|
1271
1492
|
* subscription to be authorised, and it is the same list the manifest, the
|
|
1272
|
-
* doctor and the dashboard report on.
|
|
1493
|
+
* doctor and the dashboard report on. `DeclaredAccess` rather than `Guards`:
|
|
1494
|
+
* an `openAccess:` event's erased `{ open }` entry rides the same array, and
|
|
1495
|
+
* this bridge must treat it as "no check", not as a guard.
|
|
1273
1496
|
*/
|
|
1274
|
-
readonly guards?:
|
|
1497
|
+
readonly guards?: DeclaredAccess<unknown> | undefined;
|
|
1275
1498
|
}
|
|
1276
1499
|
|
|
1277
1500
|
export declare const bindMutation: <Input, Output, E = never>(execute: (input: Input, context: RuntimeContext) => Output | Promise<Output> | Effect.Effect<Output, E, never>, input: Input, spanName?: string, codec?: MutationOutputCodec<unknown>, idempotency?: IdempotencyBinding,
|
|
@@ -1522,6 +1745,14 @@ export declare interface CandidateShape {
|
|
|
1522
1745
|
}
|
|
1523
1746
|
|
|
1524
1747
|
/* Excluded from this release type: canonicalize */
|
|
1748
|
+
|
|
1749
|
+
/**
|
|
1750
|
+
* Canonical JSON — object keys sorted recursively, so two structurally equal
|
|
1751
|
+
* inputs that differ only in key order digest identically. A retry from a
|
|
1752
|
+
* different client build must not mint a second approval.
|
|
1753
|
+
*/
|
|
1754
|
+
export declare const canonicalJson: (value: unknown) => string;
|
|
1755
|
+
|
|
1525
1756
|
export { CaughtUpVerdict }
|
|
1526
1757
|
|
|
1527
1758
|
/** A CDC change event, structurally — what the reactive engine already emits
|
|
@@ -1632,6 +1863,17 @@ export declare const CLAIM_RETENTION_BUCKETS = 64;
|
|
|
1632
1863
|
*/
|
|
1633
1864
|
export declare const claimGraceMs: (bucketWidthMs: number | undefined) => number;
|
|
1634
1865
|
|
|
1866
|
+
/**
|
|
1867
|
+
* Decide whether a state-changing request may proceed.
|
|
1868
|
+
*
|
|
1869
|
+
* `headers` keys must already be lowercased (the rpc server lowercases once per
|
|
1870
|
+
* request and passes the same bag to every guard).
|
|
1871
|
+
*/
|
|
1872
|
+
export declare const classifyRequestOrigin: (input: {
|
|
1873
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
1874
|
+
readonly config: OriginGuardConfig;
|
|
1875
|
+
}) => OriginDecision;
|
|
1876
|
+
|
|
1635
1877
|
/**
|
|
1636
1878
|
* Decide whether a candidate matches a maintainable shape. Conservative by
|
|
1637
1879
|
* design — anything not provably maintainable is rejected (→ full recompute),
|
|
@@ -1639,6 +1881,9 @@ export declare const claimGraceMs: (bucketWidthMs: number | undefined) => number
|
|
|
1639
1881
|
*/
|
|
1640
1882
|
export declare const classifyShape: (c: CandidateShape) => ShapeClassification;
|
|
1641
1883
|
|
|
1884
|
+
/** Test seam. */
|
|
1885
|
+
export declare const clearApprovalGate: () => void;
|
|
1886
|
+
|
|
1642
1887
|
/** Test seam — the in-flight map is module state; a test that asserts
|
|
1643
1888
|
* single-flight must be able to start from empty. */
|
|
1644
1889
|
export declare const clearConnectionRefreshFlights: () => void;
|
|
@@ -1651,6 +1896,14 @@ export { clearRetentions }
|
|
|
1651
1896
|
/** Clear the process-wide handle (test teardown). */
|
|
1652
1897
|
export declare const clearSystemStoreHandle: () => void;
|
|
1653
1898
|
|
|
1899
|
+
export declare interface ClientAddressInput {
|
|
1900
|
+
/** `socket.remoteAddress` — the only unforgeable value in the request. */
|
|
1901
|
+
readonly socketAddress: string | undefined;
|
|
1902
|
+
/** Raw `x-forwarded-for` header, if present. */
|
|
1903
|
+
readonly xForwardedFor: string | undefined;
|
|
1904
|
+
readonly config: TrustedProxyConfig;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1654
1907
|
/**
|
|
1655
1908
|
* Collect the declared rules from the discovered tables.
|
|
1656
1909
|
*
|
|
@@ -1867,13 +2120,6 @@ export declare interface ConnectionsFacadeDeps {
|
|
|
1867
2120
|
/** Test-only — number of streams currently registered for a client. */
|
|
1868
2121
|
export declare const _connectionStreamCount: (clientId: number) => number;
|
|
1869
2122
|
|
|
1870
|
-
/** Snapshot for diagnostics / dashboard. */
|
|
1871
|
-
export declare const connectionSubjectsSnapshot: () => ReadonlyArray<{
|
|
1872
|
-
clientId: number;
|
|
1873
|
-
subjectType: string;
|
|
1874
|
-
tenantId: string | null;
|
|
1875
|
-
}>;
|
|
1876
|
-
|
|
1877
2123
|
/** A resolved token set. `expiresAt` is an absolute epoch-ms deadline. */
|
|
1878
2124
|
export declare interface ConnectionTokens {
|
|
1879
2125
|
readonly accessToken: string;
|
|
@@ -2049,6 +2295,8 @@ export declare interface CostBudgetDefinition {
|
|
|
2049
2295
|
/** Resolved tumbling-window length in ms, or `null` for a cumulative budget. */
|
|
2050
2296
|
readonly windowMs: number | null;
|
|
2051
2297
|
readonly severity: CostBudgetSeverity;
|
|
2298
|
+
/** Resolved breach behaviour — `'observe'` unless the app opted in. */
|
|
2299
|
+
readonly onExceeded: CostBudgetOnExceeded;
|
|
2052
2300
|
readonly description?: string;
|
|
2053
2301
|
}
|
|
2054
2302
|
|
|
@@ -2073,14 +2321,50 @@ export declare interface CostBudgetDefinitionInput {
|
|
|
2073
2321
|
readonly window?: string;
|
|
2074
2322
|
/** Alerting priority carried on the breach signal. Default `'warn'`. */
|
|
2075
2323
|
readonly severity?: CostBudgetSeverity;
|
|
2324
|
+
/**
|
|
2325
|
+
* What a breach DOES. Default `'observe'` — signal only, the behaviour this
|
|
2326
|
+
* module shipped with.
|
|
2327
|
+
*
|
|
2328
|
+
* `'suspend'` opts the budget into stopping the next spend: a durable run
|
|
2329
|
+
* reaching a pre-spend gate for this budget parks on a
|
|
2330
|
+
* `_voltro_budget_holds` row and resumes when the budget recovers. Opt-in,
|
|
2331
|
+
* because parking a run is a change in what the app DOES and a default that
|
|
2332
|
+
* silently stalls work is worse than one that silently allows it.
|
|
2333
|
+
*/
|
|
2334
|
+
readonly onExceeded?: CostBudgetOnExceeded;
|
|
2076
2335
|
/** Human-facing note surfaced in devtools / the breach message. */
|
|
2077
2336
|
readonly description?: string;
|
|
2078
2337
|
}
|
|
2079
2338
|
|
|
2339
|
+
/**
|
|
2340
|
+
* What a breach DOES, as opposed to how loudly it is reported.
|
|
2341
|
+
*
|
|
2342
|
+
* This file used to state flatly that "the framework never BLOCKS compute on a
|
|
2343
|
+
* budget … an observability-grade signal over work that already happened", and
|
|
2344
|
+
* that was accurate: a cost event arrives after its compute, so a budget here
|
|
2345
|
+
* genuinely cannot refuse the work that produced it.
|
|
2346
|
+
*
|
|
2347
|
+
* What it can do — and now does — is stop the NEXT one. `'suspend'` marks the
|
|
2348
|
+
* budget as one that parks durable runs: a run reaching a pre-spend gate for
|
|
2349
|
+
* this budget SUSPENDS on a `_voltro_budget_holds` row (`@voltro/workflow`'s
|
|
2350
|
+
* `suspendForBudget`) instead of proceeding. The suspension is therefore still
|
|
2351
|
+
* not retroactive, and saying so is the honest version: it bounds future spend,
|
|
2352
|
+
* and the work already metered is already paid for.
|
|
2353
|
+
*
|
|
2354
|
+
* This flag is a DECLARATION, not a wiring. Nothing here subscribes the
|
|
2355
|
+
* accountant's `recovered` signal to `releaseBudgetHolds` — whether a recovered
|
|
2356
|
+
* compute budget should wake a run held on a DIFFERENT (e.g. AI-USD) budget is
|
|
2357
|
+
* an app decision, and a hold re-checks on its own durable clock regardless, so
|
|
2358
|
+
* no run is stranded by the absence. An app that wants the immediate release
|
|
2359
|
+
* subscribes: `registry.subscribe((s) => s.kind === 'recovered' && …)`.
|
|
2360
|
+
*
|
|
2361
|
+
* `'observe'` (default) keeps the previous behaviour exactly — signal only.
|
|
2362
|
+
*/
|
|
2363
|
+
export declare type CostBudgetOnExceeded = 'observe' | 'suspend';
|
|
2364
|
+
|
|
2080
2365
|
/** How loud a budget breach is. A classification carried on the signal for
|
|
2081
|
-
* alerting priority —
|
|
2082
|
-
*
|
|
2083
|
-
* budget is an observability-grade signal over work that already happened. */
|
|
2366
|
+
* alerting priority — orthogonal to {@link CostBudgetOnExceeded}, which decides
|
|
2367
|
+
* whether anything STOPS. */
|
|
2084
2368
|
export declare type CostBudgetSeverity = 'info' | 'warn' | 'critical';
|
|
2085
2369
|
|
|
2086
2370
|
/** Emitted when a budget crosses a threshold for a tenant. `warn` / `exceeded`
|
|
@@ -2107,6 +2391,9 @@ export declare interface CostBudgetState {
|
|
|
2107
2391
|
readonly warnThreshold: number;
|
|
2108
2392
|
/** The unit this budget meters, or `null` for a total-across-units budget. */
|
|
2109
2393
|
readonly unit: string | null;
|
|
2394
|
+
/** What a breach of this budget DOES — carried on the state so an operator
|
|
2395
|
+
* view can tell an observed breach from one that is holding runs. */
|
|
2396
|
+
readonly onExceeded: CostBudgetOnExceeded;
|
|
2110
2397
|
/** When the current window opened (windowed budgets), else `null`. */
|
|
2111
2398
|
readonly windowStartedAt: number | null;
|
|
2112
2399
|
/** When this budget last entered its current `status`. */
|
|
@@ -2205,6 +2492,18 @@ export declare const counter: (name: string, description?: string) => Metric.Met
|
|
|
2205
2492
|
*/
|
|
2206
2493
|
export declare const countRunningWorkflows: (store: DataStore) => Promise<number>;
|
|
2207
2494
|
|
|
2495
|
+
/**
|
|
2496
|
+
* Every firing instant of `def` in `(from, to]`, oldest first, bounded by
|
|
2497
|
+
* `cap + 1` entries.
|
|
2498
|
+
*
|
|
2499
|
+
* The `+ 1` is the honest part: a result LONGER than `cap` tells the caller
|
|
2500
|
+
* "there are more than cap", which is different from "there are exactly cap" —
|
|
2501
|
+
* a backfill verb that silently truncated would enqueue a prefix and report
|
|
2502
|
+
* completeness. Pure (no clock, no store), so the counting a confirmation
|
|
2503
|
+
* prompt needs and the walking the backfill does cannot disagree.
|
|
2504
|
+
*/
|
|
2505
|
+
export declare const cronOccurrences: (def: ScheduleDefinition, from: Date, to: Date, cap: number) => ReadonlyArray<Date>;
|
|
2506
|
+
|
|
2208
2507
|
/**
|
|
2209
2508
|
* Secure-default CRUD executor factories. Each takes the table NAME (not the
|
|
2210
2509
|
* table value — that would be a server import in a descriptor) and returns an
|
|
@@ -2222,10 +2521,14 @@ export declare const crud: {
|
|
|
2222
2521
|
readonly id: string;
|
|
2223
2522
|
}, ctx: AppContext) => Promise<Row | null>;
|
|
2224
2523
|
/** Insert the input as a new row (id / tenant / audit auto-stamped). The echoed
|
|
2225
|
-
* row is redacted
|
|
2524
|
+
* row is redacted, and an input that sets a `.serverOnly()` column is REFUSED
|
|
2525
|
+
* with `ServerOnlyColumnWrite`. Guard the DESCRIPTOR — this does not gate. */
|
|
2226
2526
|
create: (table: string, options?: CrudWriteOptions) => (input: Row, ctx: AppContext) => Promise<Row>;
|
|
2227
2527
|
/** Patch a row by id (`{ id, ...patch }`); returns the updated row or `null`.
|
|
2228
|
-
* Redacted
|
|
2528
|
+
* Redacted, and a patch that sets a `.serverOnly()` column is REFUSED with
|
|
2529
|
+
* `ServerOnlyColumnWrite`. On a `tenant()` table the keyed write resolves the
|
|
2530
|
+
* row inside the caller's tenant (`TenantRowNotFound` otherwise — see
|
|
2531
|
+
* `storeMiddleware`). Guard the DESCRIPTOR. */
|
|
2229
2532
|
update: (table: string, options?: CrudWriteOptions) => (input: {
|
|
2230
2533
|
readonly id: string;
|
|
2231
2534
|
} & Record<string, unknown>, ctx: AppContext) => Promise<Row | null>;
|
|
@@ -2362,6 +2665,9 @@ export declare interface CrudWriteOptions {
|
|
|
2362
2665
|
readonly redact?: ReadonlyArray<string>;
|
|
2363
2666
|
}
|
|
2364
2667
|
|
|
2668
|
+
/** Every raw read recorded in the current scope; empty outside one. */
|
|
2669
|
+
export declare const currentRawReads: () => ReadonlyArray<RawReadObservation>;
|
|
2670
|
+
|
|
2365
2671
|
/**
|
|
2366
2672
|
* The current request's loader, or `undefined` outside a request.
|
|
2367
2673
|
*
|
|
@@ -2404,6 +2710,37 @@ export declare const dataStoreIdempotencyStore: (store: DataStore) => Idempotenc
|
|
|
2404
2710
|
/** Build a durable `KvStoreShape` over a raw `DataStore`. */
|
|
2405
2711
|
export declare const dataStoreKvStore: (store: DataStore) => KvStoreShape;
|
|
2406
2712
|
|
|
2713
|
+
/**
|
|
2714
|
+
* Approve or reject ONE intent, applying every refusal in a fixed order.
|
|
2715
|
+
*
|
|
2716
|
+
* The order is asserted in the tests and is not arbitrary: identity checks come
|
|
2717
|
+
* before authority checks so a requester with the approver scope is told they
|
|
2718
|
+
* cannot approve their OWN request (the accurate reason) rather than being
|
|
2719
|
+
* silently allowed through on a scope they do hold.
|
|
2720
|
+
*/
|
|
2721
|
+
export declare const decideApproval: (store: DataStore, input: DecideApprovalInput) => Promise<{
|
|
2722
|
+
readonly approvalId: string;
|
|
2723
|
+
readonly status: "approved" | "rejected";
|
|
2724
|
+
}>;
|
|
2725
|
+
|
|
2726
|
+
export declare interface DecideApprovalInput {
|
|
2727
|
+
readonly approvalId: string;
|
|
2728
|
+
readonly decision: 'approve' | 'reject';
|
|
2729
|
+
readonly note?: string | undefined;
|
|
2730
|
+
readonly approver: Subject | null;
|
|
2731
|
+
readonly now?: () => number;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
/** `__voltro.approvals.decide` — approve or reject one pending intent. */
|
|
2735
|
+
export declare const decideApprovalInvocation: (input: {
|
|
2736
|
+
readonly approvalId: string;
|
|
2737
|
+
readonly decision: "approve" | "reject";
|
|
2738
|
+
readonly note?: string;
|
|
2739
|
+
}, ctx: ApprovalExecutorCtx) => Promise<{
|
|
2740
|
+
readonly approvalId: string;
|
|
2741
|
+
readonly status: "approved" | "rejected";
|
|
2742
|
+
}>;
|
|
2743
|
+
|
|
2407
2744
|
/** Decrypt a value produced by `encryptField` / an `.encrypted()` column. A
|
|
2408
2745
|
* value that is NOT ciphertext (`enc:v1:…`) is returned unchanged — so a
|
|
2409
2746
|
* raw-SQL read path can be switched to encryption while pre-existing plaintext
|
|
@@ -2411,10 +2748,35 @@ export declare const dataStoreKvStore: (store: DataStore) => KvStoreShape;
|
|
|
2411
2748
|
* cipher is registered (a genuine ciphertext with a wrong key throws GCM). */
|
|
2412
2749
|
export declare const decryptField: (value: string) => string;
|
|
2413
2750
|
|
|
2751
|
+
export declare const DEFAULT_ANALYTICS_MIRROR_TUNABLES: AnalyticsMirrorTunables;
|
|
2752
|
+
|
|
2753
|
+
/** Default life of a pending intent when neither the descriptor nor the app
|
|
2754
|
+
* says otherwise. 24 h: long enough to survive a weekend handoff being missed
|
|
2755
|
+
* by a few hours, short enough that a forgotten queue empties itself. */
|
|
2756
|
+
export declare const DEFAULT_APPROVAL_EXPIRY_MS: number;
|
|
2757
|
+
|
|
2414
2758
|
/** Assumed distance between buckets when a caller passes none — the cron
|
|
2415
2759
|
* engine's finest useful cadence. */
|
|
2416
2760
|
export declare const DEFAULT_CLAIM_BUCKET_MS = 60000;
|
|
2417
2761
|
|
|
2762
|
+
/**
|
|
2763
|
+
* How many subscribers one change event is delivered to CONCURRENTLY.
|
|
2764
|
+
*
|
|
2765
|
+
* 8 rather than 1 (fully serial, what this replaced) or unbounded. Serial makes
|
|
2766
|
+
* one slow guard's round-trip the prefix of every later subscriber's latency —
|
|
2767
|
+
* measured at 50 subscribers × a 5 ms guard, the last delivery landed 699 ms
|
|
2768
|
+
* after the write. Unbounded would open one DB round-trip per subscriber at the
|
|
2769
|
+
* same instant, which on a 10-connection pool is slower than serial AND starves
|
|
2770
|
+
* the request path that shares it. 8 sits under a default pool and still
|
|
2771
|
+
* collapses the serial chain by roughly its own factor.
|
|
2772
|
+
*/
|
|
2773
|
+
export declare const DEFAULT_DELIVERY_CONCURRENCY = 8;
|
|
2774
|
+
|
|
2775
|
+
/** 180 days + subdomains. Deliberately NOT `preload`: preload is effectively
|
|
2776
|
+
* irreversible for a domain, so it must be a deployment decision, never a
|
|
2777
|
+
* framework default. */
|
|
2778
|
+
export declare const DEFAULT_HSTS = "max-age=15552000; includeSubDomains";
|
|
2779
|
+
|
|
2418
2780
|
/** Ceiling the idle backoff climbs to. Deliberately short enough to be a
|
|
2419
2781
|
* FLOOR under a missed wake rather than a substitute for one. */
|
|
2420
2782
|
export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
@@ -2423,6 +2785,16 @@ export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
|
2423
2785
|
* small enough to stop a pathological body being buffered into memory. */
|
|
2424
2786
|
export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
2425
2787
|
|
|
2788
|
+
/**
|
|
2789
|
+
* How many raw-SQL reads one request records for the undeclared-`dependsOn`
|
|
2790
|
+
* diagnostic. A bound rather than none: the recording lives for as long as the
|
|
2791
|
+
* async context that opened it, and a long-lived non-request context (a
|
|
2792
|
+
* subscriber runner, a boot seed) would otherwise accumulate one entry per raw
|
|
2793
|
+
* read forever. Reads beyond the bound are dropped, not remembered — this is a
|
|
2794
|
+
* diagnostic, and a diagnostic that leaks memory is worse than a missing one.
|
|
2795
|
+
*/
|
|
2796
|
+
export declare const DEFAULT_RAW_READ_TRACKING_LIMIT = 32;
|
|
2797
|
+
|
|
2426
2798
|
export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
|
|
2427
2799
|
|
|
2428
2800
|
/**
|
|
@@ -2693,7 +3065,33 @@ export declare class Dispatcher {
|
|
|
2693
3065
|
*/
|
|
2694
3066
|
private readonly computedSubs;
|
|
2695
3067
|
private readonly computedByTable;
|
|
3068
|
+
/** Resolved delivery tunables. Read once here so `voltro dev` and
|
|
3069
|
+
* `voltro serve` cannot disagree about them — see `reactiveConfig.ts`. */
|
|
3070
|
+
private readonly reactive;
|
|
3071
|
+
/** Query labels already warned about for an undeclared raw read, so a hot
|
|
3072
|
+
* subscription logs once rather than once per subscriber. */
|
|
3073
|
+
private readonly rawReadWarned;
|
|
2696
3074
|
constructor(deps: DispatcherDependencies);
|
|
3075
|
+
/**
|
|
3076
|
+
* REL-14 — a raw read that nothing can invalidate, said out loud.
|
|
3077
|
+
*
|
|
3078
|
+
* `store.raw(...)` is opaque to every mechanism that makes a query live: the
|
|
3079
|
+
* matcher reads a predicate, the dependency graph reads an eager spec, and a
|
|
3080
|
+
* raw fragment is a string neither can parse. `dependsOn` is how an author
|
|
3081
|
+
* tells us; omitting it produces a subscription that opens, delivers once and
|
|
3082
|
+
* then never updates. Nothing throws, nothing logs, and the bug looks like
|
|
3083
|
+
* ours.
|
|
3084
|
+
*
|
|
3085
|
+
* Wired HERE, in the dispatcher, and called from BOTH `subscribe` and
|
|
3086
|
+
* `subscribeComputed` — the one place every reactive subscription passes
|
|
3087
|
+
* through, on both boot paths. `voltro dev` and `voltro serve` build their own
|
|
3088
|
+
* rpc bridges and have drifted before; a check that lives in one of them is
|
|
3089
|
+
* not a check. There is no second place to put this, which is the point.
|
|
3090
|
+
*
|
|
3091
|
+
* Once per query label. A per-subscriber warning on a hot query is its own
|
|
3092
|
+
* kind of useless.
|
|
3093
|
+
*/
|
|
3094
|
+
private warnUndeclaredRawReads;
|
|
2697
3095
|
/**
|
|
2698
3096
|
* Register a logical subscription. The caller's `emit` receives:
|
|
2699
3097
|
* - exactly one `snapshot` event with the initial query result,
|
|
@@ -2817,6 +3215,13 @@ export declare interface DispatcherDependencies {
|
|
|
2817
3215
|
* present → subscriptions whose descriptor opted in (a `cacheBinding`
|
|
2818
3216
|
* is passed to `subscribe`) share + cache their initial snapshot. */
|
|
2819
3217
|
readonly cache?: SnapshotCache;
|
|
3218
|
+
/**
|
|
3219
|
+
* Tunables of the delivery loop — see `reactiveConfig.ts`. Absent → the
|
|
3220
|
+
* documented defaults. Resolved ONCE, in the constructor, so the two boot
|
|
3221
|
+
* paths cannot each derive their own answer; an env override wins over
|
|
3222
|
+
* whatever is passed here.
|
|
3223
|
+
*/
|
|
3224
|
+
readonly reactive?: ReactiveConfigInput;
|
|
2820
3225
|
}
|
|
2821
3226
|
|
|
2822
3227
|
/** Bookkeeping tenant key for the `_voltro_wakeups` rows that dormant schedules
|
|
@@ -2882,6 +3287,25 @@ export declare interface DrainResult {
|
|
|
2882
3287
|
readonly failed: number;
|
|
2883
3288
|
readonly dead: number;
|
|
2884
3289
|
readonly skipped: number;
|
|
3290
|
+
/**
|
|
3291
|
+
* How many rows this pass had to look at. `0` means the queue is EMPTY, which
|
|
3292
|
+
* a caller cannot otherwise tell from "everything delivered" — both leave
|
|
3293
|
+
* `delivered` at whatever it was, and only one of them means the poll has
|
|
3294
|
+
* nothing left to do.
|
|
3295
|
+
*/
|
|
3296
|
+
readonly scanned: number;
|
|
3297
|
+
/**
|
|
3298
|
+
* When the earliest NOT-yet-due pending row comes due, as an epoch ms.
|
|
3299
|
+
*
|
|
3300
|
+
* `undefined` means nothing in the read window is waiting. A LOWER bound (the
|
|
3301
|
+
* window is capped by `batchSize`), which is the safe direction: understating
|
|
3302
|
+
* it costs one early pass, overstating it delays a delivery.
|
|
3303
|
+
*
|
|
3304
|
+
* This is what lets the runner arm for a backoff instead of polling until it
|
|
3305
|
+
* expires — the one thing in this loop that genuinely needs a clock, since a
|
|
3306
|
+
* retry becoming due is not a write anybody can be notified about.
|
|
3307
|
+
*/
|
|
3308
|
+
readonly nextAttemptAt?: number;
|
|
2885
3309
|
}
|
|
2886
3310
|
|
|
2887
3311
|
/**
|
|
@@ -2926,6 +3350,23 @@ export declare const enableGraphObservation: () => void;
|
|
|
2926
3350
|
* columns use). Throws if no cipher is registered. */
|
|
2927
3351
|
export declare const encryptField: (plaintext: string) => string;
|
|
2928
3352
|
|
|
3353
|
+
/**
|
|
3354
|
+
* The dispatch-spine entry point. A no-op for a descriptor with no policy; a
|
|
3355
|
+
* REFUSAL when a policy exists and no gate was installed.
|
|
3356
|
+
*
|
|
3357
|
+
* Fail-closed is the whole point: "the approvals store is not wired, so run the
|
|
3358
|
+
* refund" is precisely the hole the declaration exists to close, and it is the
|
|
3359
|
+
* shape a boot-path parity miss would produce.
|
|
3360
|
+
*/
|
|
3361
|
+
export declare const enforceApproval: (input: {
|
|
3362
|
+
readonly procedure: string;
|
|
3363
|
+
readonly kind: "mutation" | "action";
|
|
3364
|
+
readonly policy: AnyApprovalPolicy | undefined;
|
|
3365
|
+
readonly subject: unknown;
|
|
3366
|
+
readonly input: unknown;
|
|
3367
|
+
readonly traceId?: string | undefined;
|
|
3368
|
+
}) => Promise<void>;
|
|
3369
|
+
|
|
2929
3370
|
export declare interface EnqueueOptions {
|
|
2930
3371
|
/** Drop this enqueue if an undelivered row already carries the same key. */
|
|
2931
3372
|
readonly idempotencyKey?: string;
|
|
@@ -3577,8 +4018,12 @@ export declare interface ExperimentDefinition {
|
|
|
3577
4018
|
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3578
4019
|
};
|
|
3579
4020
|
/** Resolved to a function: reads the subject and normalises it to a non-empty
|
|
3580
|
-
* string, or `null` when the row carries no subject.
|
|
3581
|
-
|
|
4021
|
+
* string, or `null` when the row carries no subject. `null` for a
|
|
4022
|
+
* `variantFrom` experiment, which assigns nothing. */
|
|
4023
|
+
readonly subject: ((row: Readonly<Record<string, unknown>>) => string | null) | null;
|
|
4024
|
+
/** Resolved arm reader for a `variantFrom` experiment; `null` when the
|
|
4025
|
+
* experiment assigns its own arms. Exactly one of these two is non-null. */
|
|
4026
|
+
readonly variantFrom: ((row: Readonly<Record<string, unknown>>) => string | null) | null;
|
|
3582
4027
|
readonly variants: ReadonlyArray<ExperimentVariant>;
|
|
3583
4028
|
/** Resolved holdout fraction in [0, 1); `0` when none. */
|
|
3584
4029
|
readonly holdout: number;
|
|
@@ -3599,8 +4044,33 @@ export declare interface ExperimentDefinitionInput {
|
|
|
3599
4044
|
readonly table: string;
|
|
3600
4045
|
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3601
4046
|
};
|
|
3602
|
-
/**
|
|
3603
|
-
|
|
4047
|
+
/**
|
|
4048
|
+
* The stable assignment key extractor — the experiment ASSIGNS.
|
|
4049
|
+
*
|
|
4050
|
+
* Exactly one of `subject` / `variantFrom` is required. They are the two ways
|
|
4051
|
+
* an experiment can know which arm a row belongs to, and they are mutually
|
|
4052
|
+
* exclusive by construction: declaring both would mean two answers to one
|
|
4053
|
+
* question, which is the defect this exclusion exists to prevent.
|
|
4054
|
+
*/
|
|
4055
|
+
readonly subject?: ExperimentSubject;
|
|
4056
|
+
/**
|
|
4057
|
+
* READ the arm off the row instead of assigning it — for measuring an
|
|
4058
|
+
* assignment something ELSE already made and persisted.
|
|
4059
|
+
*
|
|
4060
|
+
* This is what makes `plugin-flags` + `defineExperiment` one product rather
|
|
4061
|
+
* than two that happen to sit in the same app. A flag serves a variant by its
|
|
4062
|
+
* own deterministic bucketing (FNV-1a over the flag key); an experiment in
|
|
4063
|
+
* `subject` mode buckets independently (FNV-1a over the EXPERIMENT name). Both
|
|
4064
|
+
* are stable, both are uniform, and they DISAGREE — so measuring a flag's
|
|
4065
|
+
* rollout with a `subject`-mode experiment reports the uplift of an arm
|
|
4066
|
+
* assignment nobody was ever served. Reading the arm the flag actually served
|
|
4067
|
+
* (write `flagVariant(ctx, f)` onto the row, point `variantFrom` at that
|
|
4068
|
+
* column) makes the served arm and the measured arm the same fact.
|
|
4069
|
+
*
|
|
4070
|
+
* Persisting the arm is also the only version that survives a weight change:
|
|
4071
|
+
* a re-hash at read time silently re-labels every historical row.
|
|
4072
|
+
*/
|
|
4073
|
+
readonly variantFrom?: ExperimentVariantSource;
|
|
3604
4074
|
/** The arms. At least two (a control + one treatment). */
|
|
3605
4075
|
readonly variants: ReadonlyArray<ExperimentVariantSpec>;
|
|
3606
4076
|
/** Fraction (0..1) of subjects held out ENTIRELY — assigned to the reserved
|
|
@@ -3630,9 +4100,23 @@ export declare class ExperimentEvaluator {
|
|
|
3630
4100
|
private readonly now;
|
|
3631
4101
|
private seeded;
|
|
3632
4102
|
private lastUpdatedAt;
|
|
4103
|
+
/** The declared arm names — the admissible set in `variantFrom` mode. */
|
|
4104
|
+
private readonly declaredVariants;
|
|
3633
4105
|
constructor(def: ExperimentDefinition, deps: ExperimentEvaluatorDeps);
|
|
3634
|
-
/**
|
|
3635
|
-
*
|
|
4106
|
+
/**
|
|
4107
|
+
* The variant an in-scope row belongs to, or `null` when the row is out of the
|
|
4108
|
+
* `where` population, carries no subject, or (in `variantFrom` mode) carries
|
|
4109
|
+
* an arm this experiment does not declare.
|
|
4110
|
+
*
|
|
4111
|
+
* The two modes meet here and only here — the maintainers behind this are
|
|
4112
|
+
* identical either way, which is the point: an experiment that MEASURES an
|
|
4113
|
+
* arm somebody else assigned rides the same per-write IVM aggregate as one
|
|
4114
|
+
* that assigns its own.
|
|
4115
|
+
*
|
|
4116
|
+
* An unrecognised arm name is EXCLUDED, never folded into a neighbour. A typo
|
|
4117
|
+
* that silently landed in the baseline would corrupt the one number every
|
|
4118
|
+
* other row's lift is divided by.
|
|
4119
|
+
*/
|
|
3636
4120
|
private variantOf;
|
|
3637
4121
|
private inScope;
|
|
3638
4122
|
/** Seed both maintainers from the current base rows. Never emits (there is no
|
|
@@ -3778,6 +4262,17 @@ export declare interface ExperimentVariantResult {
|
|
|
3778
4262
|
readonly diff: number | null;
|
|
3779
4263
|
}
|
|
3780
4264
|
|
|
4265
|
+
/**
|
|
4266
|
+
* Where the ARM comes from when the experiment did not assign it.
|
|
4267
|
+
*
|
|
4268
|
+
* A column NAME whose value IS the variant, or a function returning it.
|
|
4269
|
+
* `null` / `undefined` / a name that is not one of the declared variants ⇒ the
|
|
4270
|
+
* row is UNASSIGNED and excluded from every arm — an unrecognised arm name is
|
|
4271
|
+
* never folded into a neighbour, because a typo that silently lands in
|
|
4272
|
+
* `control` corrupts the baseline the whole result is measured against.
|
|
4273
|
+
*/
|
|
4274
|
+
declare type ExperimentVariantSource = string | ((row: Readonly<Record<string, unknown>>) => string | null | undefined);
|
|
4275
|
+
|
|
3781
4276
|
/** A named arm of the experiment. `weight` skews the split (default 1 = equal);
|
|
3782
4277
|
* a subject is assigned in proportion to its weight over the total. */
|
|
3783
4278
|
export declare interface ExperimentVariantSpec {
|
|
@@ -3980,12 +4475,15 @@ export declare const gauge: (name: string, description?: string) => Metric.Metri
|
|
|
3980
4475
|
/** Generate a fresh token `<prefix><40-char-base64url>`. */
|
|
3981
4476
|
export declare const generateApiKeyToken: (prefix?: string) => string;
|
|
3982
4477
|
|
|
4478
|
+
export declare const getApprovalGate: () => ApprovalGateApi | undefined;
|
|
4479
|
+
|
|
4480
|
+
/** The credential bound to a connection, if any. Exposed for diagnostics and
|
|
4481
|
+
* for `applyConnectionCredential`; the auth chain goes through the latter. */
|
|
4482
|
+
export declare const getConnectionCredential: (clientId: number) => ConnectionCredential | undefined;
|
|
4483
|
+
|
|
3983
4484
|
/** The registered resolver, if the app declared any connections. */
|
|
3984
4485
|
export declare const getConnectionResolver: () => ConnectionResolver | undefined;
|
|
3985
4486
|
|
|
3986
|
-
/** Look up the override for a connection. Returns `undefined` if no override is set. */
|
|
3987
|
-
export declare const getConnectionSubject: (clientId: number) => Subject | undefined;
|
|
3988
|
-
|
|
3989
4487
|
export declare const getCredentialExpiry: (clientId: number) => number | undefined;
|
|
3990
4488
|
|
|
3991
4489
|
/** The registered field cipher, if any. The store middleware reads this. */
|
|
@@ -4058,6 +4556,10 @@ export declare const historyProvenance: (history: ReadonlyArray<RowHistoryEntry>
|
|
|
4058
4556
|
* this name when `holdout > 0`. */
|
|
4059
4557
|
export declare const HOLDOUT_VARIANT = "holdout";
|
|
4060
4558
|
|
|
4559
|
+
/** Safe-for-any-page policy: closes clickjacking + base-tag injection + plugin
|
|
4560
|
+
* embedding without constraining what the page itself loads. */
|
|
4561
|
+
export declare const HTML_CSP = "frame-ancestors 'none'; base-uri 'none'; object-src 'none'";
|
|
4562
|
+
|
|
4061
4563
|
/** A secrets backend over a plain JSON HTTP API — no SDK. Wraps fetch + cache.
|
|
4062
4564
|
* Vault (`/v1/secret/data/...`), Doppler, and the cloud control-plane all fit. */
|
|
4063
4565
|
export declare const httpSecretsBackend: (opts: HttpSecretsOptions) => SecretsBackend;
|
|
@@ -4255,6 +4757,8 @@ export declare interface InspectStream {
|
|
|
4255
4757
|
|
|
4256
4758
|
export { inspectWorkflow }
|
|
4257
4759
|
|
|
4760
|
+
export declare const installApprovalGate: (gate: ApprovalGateApi) => void;
|
|
4761
|
+
|
|
4258
4762
|
/**
|
|
4259
4763
|
* Install the resolver that answers `guards: [{ action, resourceType }]`.
|
|
4260
4764
|
*
|
|
@@ -4341,6 +4845,9 @@ export declare type InverseOp = {
|
|
|
4341
4845
|
* inverse needs (e.g. a hard-delete with no captured `prev`). */
|
|
4342
4846
|
export declare const invertChange: (c: ForwardChange) => InverseOp | null;
|
|
4343
4847
|
|
|
4848
|
+
/** Parse an already-normalized address into its bytes (4 for v4, 16 for v6). */
|
|
4849
|
+
export declare const ipToBytes: (ip: string) => Uint8Array | undefined;
|
|
4850
|
+
|
|
4344
4851
|
export declare const isAggregateDefinition: (value: unknown) => value is AggregateDefinition;
|
|
4345
4852
|
|
|
4346
4853
|
export declare const isCostBudgetDefinition: (value: unknown) => value is CostBudgetDefinition;
|
|
@@ -4367,6 +4874,34 @@ export declare const isInfraError: (e: unknown) => boolean;
|
|
|
4367
4874
|
|
|
4368
4875
|
export declare const isObservingGraph: () => boolean;
|
|
4369
4876
|
|
|
4877
|
+
/**
|
|
4878
|
+
* Shadow an INHERITED scope with a fresh one; do nothing when there is none.
|
|
4879
|
+
*
|
|
4880
|
+
* Two decisions here, and both were measured rather than reasoned.
|
|
4881
|
+
*
|
|
4882
|
+
* WHY IT ISOLATES INSTEAD OF REUSING. Nothing stops a non-request caller from
|
|
4883
|
+
* building a handler-facing store on a long-lived context — a boot seed,
|
|
4884
|
+
* `runAsSystem`, a subscriber runner. A scope opened there is found by every
|
|
4885
|
+
* request that runs underneath it (async-local lookup walks outward), so reusing
|
|
4886
|
+
* it would collect every request's raw reads into one bag and let a warning name
|
|
4887
|
+
* a query that never issued the read. A diagnostic that names the WRONG query is
|
|
4888
|
+
* worse than a missing one.
|
|
4889
|
+
*
|
|
4890
|
+
* WHY IT DOES NOTHING WHEN THERE IS NO SCOPE, rather than opening one eagerly.
|
|
4891
|
+
* `enterWith` is not free after it returns: each live frame taxes every
|
|
4892
|
+
* subsequent `await` in that context. Measured — a bare `await Promise.resolve()`
|
|
4893
|
+
* costs 0.165 µs with no frame, 0.256 µs with one, and 0.524 µs with six; they
|
|
4894
|
+
* STACK. Opening a scope for every request would put that on the request path in
|
|
4895
|
+
* exchange for nothing on the overwhelming majority of requests, which never
|
|
4896
|
+
* touch raw SQL at all. `recordRawRead` opens one on demand instead, so the cost
|
|
4897
|
+
* lands only on a request that already paid for a database round-trip.
|
|
4898
|
+
*
|
|
4899
|
+
* What this costs is a raw read inside `store.transactional(...)`: the
|
|
4900
|
+
* transaction's re-wrap isolates, so the read lands in the transaction's scope
|
|
4901
|
+
* and the subscribe never sees it. A missed warning, not a wrong one.
|
|
4902
|
+
*/
|
|
4903
|
+
export declare const isolateRawReadScope: () => void;
|
|
4904
|
+
|
|
4370
4905
|
/**
|
|
4371
4906
|
* A subject that AUTHENTICATED but resolved with NO active org — a `user` (not
|
|
4372
4907
|
* `anonymous`/`system`) carrying the empty-string tenant sentinel. Every
|
|
@@ -4415,6 +4950,10 @@ export declare const isScheduleDefinition: (v: unknown) => v is BrandedScheduleD
|
|
|
4415
4950
|
*/
|
|
4416
4951
|
export declare const isSoftDelete: (change: ChangeEvent) => boolean;
|
|
4417
4952
|
|
|
4953
|
+
/** The methods that can change state. A GET cannot be a CSRF write, and the WS
|
|
4954
|
+
* upgrade is a GET — so the upgrade is guarded by PATH, not by method. */
|
|
4955
|
+
export declare const isStateChangingMethod: (method: string) => boolean;
|
|
4956
|
+
|
|
4418
4957
|
/** A freshly issued key — `token` is shown ONCE and never stored in clear. */
|
|
4419
4958
|
export declare interface IssuedApiKey {
|
|
4420
4959
|
readonly id: string;
|
|
@@ -4442,6 +4981,9 @@ export declare interface IssueInput {
|
|
|
4442
4981
|
readonly metadata?: Readonly<Record<string, unknown>> | null;
|
|
4443
4982
|
}
|
|
4444
4983
|
|
|
4984
|
+
/** Is `address` one of the peers we are willing to take a forwarded chain from? */
|
|
4985
|
+
export declare const isTrustedProxy: (address: string | undefined, config: TrustedProxyConfig) => boolean;
|
|
4986
|
+
|
|
4445
4987
|
export declare const isWorkflowEventTriggerDefinition: (value: unknown) => value is WorkflowEventTriggerDefinition;
|
|
4446
4988
|
|
|
4447
4989
|
/** A maintainable aggregate op. (countDistinct / window funcs are NOT here —
|
|
@@ -4551,6 +5093,8 @@ export declare const listConnectionStates: (store: VaultStore, registry: Connect
|
|
|
4551
5093
|
|
|
4552
5094
|
export declare const listResourcePolicies: () => ReadonlyArray<ResourcePolicy>;
|
|
4553
5095
|
|
|
5096
|
+
export { listRetentionConflicts }
|
|
5097
|
+
|
|
4554
5098
|
export { listRetentions }
|
|
4555
5099
|
|
|
4556
5100
|
export declare interface LoaderDeps {
|
|
@@ -4704,10 +5248,44 @@ export declare const makeApiKeyUsageFlusher: (store: ApiKeyStore) => (id: string
|
|
|
4704
5248
|
* the test-context factory so `ctx.access` can't drift between them. */
|
|
4705
5249
|
export declare const makeAppAccess: (subject: Subject) => AppAccess;
|
|
4706
5250
|
|
|
5251
|
+
/**
|
|
5252
|
+
* Build the gate over a live store. ONE construction, called by the shared cli
|
|
5253
|
+
* builder both boot paths use — the dev/serve parity rule applied to a control
|
|
5254
|
+
* whose absence is a silent open door rather than a crash.
|
|
5255
|
+
*/
|
|
5256
|
+
export declare const makeApprovalGate: (options: MakeApprovalGateOptions) => ApprovalGateApi;
|
|
5257
|
+
|
|
5258
|
+
export declare interface MakeApprovalGateOptions {
|
|
5259
|
+
readonly store: DataStore;
|
|
5260
|
+
/** App-level default expiry (the `approvals.expiresIn` tunable), already
|
|
5261
|
+
* resolved to ms by the caller. Falls back to 24 h. */
|
|
5262
|
+
readonly defaultExpiryMs?: number;
|
|
5263
|
+
readonly now?: () => number;
|
|
5264
|
+
}
|
|
5265
|
+
|
|
4707
5266
|
/** Build the `ctx.kv` facade. `KV_BACKEND` picks the backend; unknown values
|
|
4708
5267
|
* fall back to `database`. */
|
|
4709
5268
|
export declare const makeAsyncKv: ({ store, env }: KvFacadeOptions) => Promise<KvFacade>;
|
|
4710
5269
|
|
|
5270
|
+
/**
|
|
5271
|
+
* The `AuthMiddleware` + `ConnectionInfoMiddleware` pair every rpc surface
|
|
5272
|
+
* needs, wired to one app's strategy chain.
|
|
5273
|
+
*
|
|
5274
|
+
* Per call, in this order:
|
|
5275
|
+
*
|
|
5276
|
+
* 1. patch the connection's soft-reauth credential over the transport headers
|
|
5277
|
+
* (a no-op for a connection that never re-authenticated);
|
|
5278
|
+
* 2. run the FULL chain on the result — strategies, session revocation,
|
|
5279
|
+
* `resolveScopes`, the scope cache;
|
|
5280
|
+
* 3. record what the strategy verified about the credential's lifetime, so
|
|
5281
|
+
* `ConnectionInfo.credentialExpiresAt` reports the token's own `exp` rather
|
|
5282
|
+
* than a cookie derivation that only ever saw one auth shape.
|
|
5283
|
+
*
|
|
5284
|
+
* Step 1 is the only thing a re-authenticated connection gets. It changes WHICH
|
|
5285
|
+
* credential is presented and nothing about how it is judged.
|
|
5286
|
+
*/
|
|
5287
|
+
export declare const makeAuthMiddlewareLayers: (options: AuthMiddlewareLayerOptions) => Layer.Layer<AuthMiddleware | ConnectionInfoMiddleware>;
|
|
5288
|
+
|
|
4711
5289
|
/**
|
|
4712
5290
|
* Detect a serial jump per origin, and report it as a proven count.
|
|
4713
5291
|
*
|
|
@@ -4838,6 +5416,35 @@ export declare const makeMutationRunner: (deps: MutationRunnerDeps) => (mutation
|
|
|
4838
5416
|
*/
|
|
4839
5417
|
export declare const makeOneShotQueryRunner: <D>(deps: OneShotQueryRunnerDeps<D>) => (query: MutationLike, input: unknown, requestContext: ServeRequestContext) => Promise<unknown>;
|
|
4840
5418
|
|
|
5419
|
+
/**
|
|
5420
|
+
* Build the per-boot "is this path outside the origin check" predicate.
|
|
5421
|
+
*
|
|
5422
|
+
* Everything not named here is guarded. The three inputs, and why each is a
|
|
5423
|
+
* different KIND of exemption:
|
|
5424
|
+
*
|
|
5425
|
+
* - `INSPECT_PREFIX` — structural, above.
|
|
5426
|
+
* - `webhookPaths` — the `*.webhook.tsx` mounts. Their caller is a third
|
|
5427
|
+
* party and `assertWebhookRoutesDeclareVerification` refuses to boot one
|
|
5428
|
+
* that has not declared how it authenticates that caller, so the signature
|
|
5429
|
+
* — not the cookie — is the authority. (Most send no `Origin` and would
|
|
5430
|
+
* pass anyway; naming them means a provider that DOES send one is not a
|
|
5431
|
+
* mystery 403 in production.)
|
|
5432
|
+
* - `exemptRoutePaths` — DECLARED, per route, via
|
|
5433
|
+
* `PluginHttpRoute.originGuard: 'exempt'`. The claim and the test to apply
|
|
5434
|
+
* are documented on that field.
|
|
5435
|
+
*
|
|
5436
|
+
* A path shared by SEVERAL routes is exempt only when EVERY route on it
|
|
5437
|
+
* declares the exemption — the pre-routing check cannot know which route in the
|
|
5438
|
+
* group will end up owning the method, so the strictest member decides.
|
|
5439
|
+
*/
|
|
5440
|
+
export declare const makeOriginGuardExemption: (input: {
|
|
5441
|
+
readonly webhookPaths?: Iterable<string> | undefined;
|
|
5442
|
+
readonly httpRoutes?: Iterable<{
|
|
5443
|
+
readonly path: string;
|
|
5444
|
+
readonly originGuard?: "exempt" | undefined;
|
|
5445
|
+
}> | undefined;
|
|
5446
|
+
}) => ((path: string) => boolean);
|
|
5447
|
+
|
|
4841
5448
|
export declare const makeOutboxFacade: (deps: OutboxFacadeDeps) => OutboxFacade;
|
|
4842
5449
|
|
|
4843
5450
|
export declare const makePostCommitWorkflowFacade: (base: WorkflowsAppContext, afterCommit: (work: () => Promise<unknown>) => void) => WorkflowsAppContext;
|
|
@@ -4880,10 +5487,12 @@ export declare const makeQueryFinalizer: (options: QueryFinalizeOptions) => ((de
|
|
|
4880
5487
|
* subject still passes, or the typed `ScopeError` that denied it.
|
|
4881
5488
|
*
|
|
4882
5489
|
* Returns a closure that always resolves `null` when the descriptor carries
|
|
4883
|
-
* no guards, so the caller needs no branch and an
|
|
4884
|
-
* a resolved promise.
|
|
5490
|
+
* no guards AND default-deny is off, so the caller needs no branch and an
|
|
5491
|
+
* unguarded query pays only a resolved promise. Under `defaultDeny` an
|
|
5492
|
+
* UNDECIDED descriptor resolves the same typed refusal on every delivery —
|
|
5493
|
+
* fail closed on the re-check too, not only at subscribe.
|
|
4885
5494
|
*/
|
|
4886
|
-
export declare const makeQueryReauthorizer: (query: MutationLike, input: unknown) => (subject: Subject) => () => Promise<unknown>;
|
|
5495
|
+
export declare const makeQueryReauthorizer: (query: MutationLike, input: unknown, defaultDeny?: boolean) => (subject: Subject) => () => Promise<unknown>;
|
|
4887
5496
|
|
|
4888
5497
|
/**
|
|
4889
5498
|
* Subscribe a QUERY and stream its events to a non-rpc consumer — the SSE
|
|
@@ -5137,6 +5746,17 @@ declare interface MemoryRywOptions {
|
|
|
5137
5746
|
*/
|
|
5138
5747
|
export declare const memoryRywPositionStore: (options?: MemoryRywOptions) => RywPositionStore;
|
|
5139
5748
|
|
|
5749
|
+
/**
|
|
5750
|
+
* Merge a name→value map into a `Cookie` header string, replacing same-named
|
|
5751
|
+
* pairs and preserving every other cookie the connection carries.
|
|
5752
|
+
*
|
|
5753
|
+
* Replacing the whole header would be simpler and wrong: a rebinder knows its
|
|
5754
|
+
* own cookie and nothing about the CSRF token, the locale, or the second app on
|
|
5755
|
+
* the same host. Values are percent-encoded on the way in because `readCookie`
|
|
5756
|
+
* percent-decodes on the way out, and the round trip has to be lossless.
|
|
5757
|
+
*/
|
|
5758
|
+
export declare const mergeCookieHeader: (existing: string | undefined, patch: Readonly<Record<string, string>>) => string;
|
|
5759
|
+
|
|
5140
5760
|
export { MetricBoundaries }
|
|
5141
5761
|
|
|
5142
5762
|
export declare interface MetricBucketPoint {
|
|
@@ -5174,6 +5794,32 @@ export declare type MetricSampleType = 'counter' | 'gauge' | 'histogram' | 'summ
|
|
|
5174
5794
|
* into the OTel MeterProvider when a `metricReader` is configured. */
|
|
5175
5795
|
export declare type MetricsMode = 'off' | 'console' | 'otlp' | 'memory';
|
|
5176
5796
|
|
|
5797
|
+
/**
|
|
5798
|
+
* Ordering token for one mirrored write.
|
|
5799
|
+
*
|
|
5800
|
+
* **The sink MUST persist this and MUST NOT invent its own.** It is
|
|
5801
|
+
* assigned by the framework at the moment the change leaves the store's
|
|
5802
|
+
* change channel — i.e. in COMMIT order — so a write with a lower
|
|
5803
|
+
* version is, by construction, an older image of that row. Stamping a
|
|
5804
|
+
* version at sink-call time instead (a `Date.now()` inside `upsert`)
|
|
5805
|
+
* makes a late-arriving STALE image outrank the fresh one and win
|
|
5806
|
+
* permanently; that is the bug this type exists to make unrepresentable.
|
|
5807
|
+
*
|
|
5808
|
+
* How the number is derived depends on the store's `changeScope`:
|
|
5809
|
+
*
|
|
5810
|
+
* - `'local'` (one process observes each change): a hybrid clock —
|
|
5811
|
+
* wall-clock microseconds forced strictly upward, so it orders within
|
|
5812
|
+
* a millisecond and keeps increasing across a restart.
|
|
5813
|
+
* - `'fleet'` (every replica observes every change): derived from the
|
|
5814
|
+
* CHANGE, not from the receiving process — the warehouse's own max
|
|
5815
|
+
* version for the key (read once via {@link AnalyticsMirrorImpl.maxVersion})
|
|
5816
|
+
* plus the change's per-key position in the totally-ordered fleet
|
|
5817
|
+
* stream. N replicas mirroring the same change therefore stamp the
|
|
5818
|
+
* SAME number, and the sink's version guard dedupes the N duplicate
|
|
5819
|
+
* writes for free — no leader, no clock, no skew hazard.
|
|
5820
|
+
*/
|
|
5821
|
+
export declare type MirrorVersion = number;
|
|
5822
|
+
|
|
5177
5823
|
export declare interface MutationLifecycle {
|
|
5178
5824
|
readonly afterCommit: (work: () => Promise<unknown>) => void;
|
|
5179
5825
|
}
|
|
@@ -5183,6 +5829,9 @@ export declare interface MutationLike {
|
|
|
5183
5829
|
readonly name: string;
|
|
5184
5830
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
5185
5831
|
readonly guards?: ReadonlyArray<AnyCheckSpec> | undefined;
|
|
5832
|
+
/** The second-human control (mutation/action only). Enforced by
|
|
5833
|
+
* `enforceApprovalGate`, immediately after `enforceGuards`. */
|
|
5834
|
+
readonly requiresApproval?: AnyApprovalPolicy | undefined;
|
|
5186
5835
|
readonly target?: {
|
|
5187
5836
|
readonly table: string;
|
|
5188
5837
|
} | ReadonlyArray<{
|
|
@@ -5208,6 +5857,14 @@ export declare interface MutationOutputCodec<Output> {
|
|
|
5208
5857
|
|
|
5209
5858
|
export declare interface MutationRunnerDeps {
|
|
5210
5859
|
readonly store: TransactionalStore;
|
|
5860
|
+
/**
|
|
5861
|
+
* `security.defaultDeny`, threaded to the dispatch spine: `true` refuses a
|
|
5862
|
+
* descriptor with NO access decision per-request (typed `ScopeError`, the
|
|
5863
|
+
* `missingAccessDecision` shape) — see `enforceGuards`. Both boot paths pass
|
|
5864
|
+
* the app's resolved value; an embedder that omits it keeps plain guard
|
|
5865
|
+
* enforcement only, exactly as the boot gate's carve-outs describe.
|
|
5866
|
+
*/
|
|
5867
|
+
readonly defaultDeny?: boolean;
|
|
5211
5868
|
/** Build the per-call `AppContext` bound to the transactional `tx`. */
|
|
5212
5869
|
readonly buildContext: (request: ServeRequestContext, tx: unknown, lifecycle?: MutationLifecycle) => {
|
|
5213
5870
|
readonly store: unknown;
|
|
@@ -5278,6 +5935,9 @@ export declare const nextWakeup: (store: DataStore) => Promise<Wakeup | null>;
|
|
|
5278
5935
|
* registered, or for a system subject. */
|
|
5279
5936
|
export declare const NO_ROW_FILTER: RowFilterScope;
|
|
5280
5937
|
|
|
5938
|
+
/** Nothing trusted: the socket address is the client address. The default. */
|
|
5939
|
+
export declare const NO_TRUSTED_PROXIES: TrustedProxyConfig;
|
|
5940
|
+
|
|
5281
5941
|
/**
|
|
5282
5942
|
* The real single-node supervisor: `spawn`s the app command and considers
|
|
5283
5943
|
* it healthy once `healthUrl` returns 2xx. Stopping sends `stopSignal`
|
|
@@ -5346,6 +6006,15 @@ export declare const noopAnalyticsSpec: AnalyticsSinkSpec;
|
|
|
5346
6006
|
* misses, writes are dropped. Mirrors the cache's `noopCache`. */
|
|
5347
6007
|
export declare const noopKv: AsyncKv;
|
|
5348
6008
|
|
|
6009
|
+
/** Strip the decorations a real-world address string arrives with:
|
|
6010
|
+
* `[::1]:443` → `::1`, `::ffff:10.0.0.1` → `10.0.0.1`, `fe80::1%eth0` → `fe80::1`. */
|
|
6011
|
+
export declare const normalizeIp: (raw: string) => string | undefined;
|
|
6012
|
+
|
|
6013
|
+
/** `HTTPS://Example.com:443/x` → `https://example.com`. Returns the lowercased
|
|
6014
|
+
* input when it is not a parseable absolute URL, so an unparseable Origin can
|
|
6015
|
+
* never accidentally compare equal to a real one. */
|
|
6016
|
+
export declare const normalizeOrigin: (raw: string) => string;
|
|
6017
|
+
|
|
5349
6018
|
/**
|
|
5350
6019
|
* `.one()` matched a number of rows other than exactly one.
|
|
5351
6020
|
*
|
|
@@ -5442,14 +6111,6 @@ export declare interface ObservedProcedure {
|
|
|
5442
6111
|
*/
|
|
5443
6112
|
export declare const observeStore: <S extends object>(store: S) => S;
|
|
5444
6113
|
|
|
5445
|
-
/**
|
|
5446
|
-
* Listen for re-bind events. Returns an unsubscribe function. Used by
|
|
5447
|
-
* the dispatcher to re-scope active subscriptions when a connection's
|
|
5448
|
-
* subject changes (e.g. anonymous → authenticated user on a different
|
|
5449
|
-
* tenant — subscriptions filtered by tenantId need re-evaluation).
|
|
5450
|
-
*/
|
|
5451
|
-
export declare const onBindConnectionSubject: (listener: (clientId: number, subject: Subject) => void) => () => void;
|
|
5452
|
-
|
|
5453
6114
|
export declare interface OneShotQueryRunnerDeps<D> extends QueryProducerDeps<D> {
|
|
5454
6115
|
/**
|
|
5455
6116
|
* Execute a FINALIZED (tenant-scoped) descriptor → rows. On the WS path the
|
|
@@ -5499,6 +6160,26 @@ export declare interface OrchestratorTickDeps {
|
|
|
5499
6160
|
readonly log?: WakeOrchestratorLogger;
|
|
5500
6161
|
}
|
|
5501
6162
|
|
|
6163
|
+
export declare type OriginDecision = {
|
|
6164
|
+
readonly allowed: true;
|
|
6165
|
+
readonly reason: 'guard-off' | 'same-origin' | 'loopback' | 'allowlisted' | 'no-browser-origin';
|
|
6166
|
+
} | {
|
|
6167
|
+
readonly allowed: false;
|
|
6168
|
+
readonly reason: 'cross-origin' | 'opaque-origin' | 'cross-site-metadata';
|
|
6169
|
+
/** What the request claimed, for the log line. Never echoed to the client. */
|
|
6170
|
+
readonly origin: string;
|
|
6171
|
+
};
|
|
6172
|
+
|
|
6173
|
+
export declare interface OriginGuardConfig {
|
|
6174
|
+
readonly mode: OriginGuardMode;
|
|
6175
|
+
/** Extra origins to accept, normalised to `scheme://host[:port]`. Needed for
|
|
6176
|
+
* a split deployment where the web app and the api sit on different hosts. */
|
|
6177
|
+
readonly allowedOrigins: ReadonlyArray<string>;
|
|
6178
|
+
}
|
|
6179
|
+
|
|
6180
|
+
/** `off` disables the check entirely; `same-origin` (default) enforces it. */
|
|
6181
|
+
export declare type OriginGuardMode = 'off' | 'same-origin';
|
|
6182
|
+
|
|
5502
6183
|
/**
|
|
5503
6184
|
* Map an `import('@effect/opentelemetry')` rejection to what the reader needs.
|
|
5504
6185
|
*
|
|
@@ -5648,6 +6329,13 @@ declare interface P2COptions {
|
|
|
5648
6329
|
readonly random?: () => number;
|
|
5649
6330
|
}
|
|
5650
6331
|
|
|
6332
|
+
/**
|
|
6333
|
+
* Parse an interval string (`'30m'`, `'4h'`, `'7d'`) to ms, or `null`.
|
|
6334
|
+
* A local copy for the same reason `finops.ts` keeps one — this module must not
|
|
6335
|
+
* grow a dependency for five lines of regex.
|
|
6336
|
+
*/
|
|
6337
|
+
export declare const parseApprovalInterval: (spec: string) => number | null;
|
|
6338
|
+
|
|
5651
6339
|
/** Parse a `traceparent` header. Returns null when malformed or when
|
|
5652
6340
|
* the all-zero invalid ids are present (per the spec those are
|
|
5653
6341
|
* treated as "no parent"). */
|
|
@@ -5683,6 +6371,19 @@ export declare const payloadPropertyNames: (schema: Schema.Schema.Any) => {
|
|
|
5683
6371
|
readonly optional: ReadonlyArray<string>;
|
|
5684
6372
|
};
|
|
5685
6373
|
|
|
6374
|
+
/**
|
|
6375
|
+
* The calling subject's approval work: intents they may DECIDE, plus intents
|
|
6376
|
+
* they REQUESTED that are still live.
|
|
6377
|
+
*
|
|
6378
|
+
* Subject-scoped in here rather than by a descriptor guard — see the descriptor
|
|
6379
|
+
* comment for why. An anonymous caller gets an empty list, which is the correct
|
|
6380
|
+
* answer and not a refusal: they have no work.
|
|
6381
|
+
*/
|
|
6382
|
+
export declare const pendingApprovalsFor: (store: DataStore, subject: Subject | null, options?: {
|
|
6383
|
+
readonly limit?: number | undefined;
|
|
6384
|
+
readonly now?: () => number;
|
|
6385
|
+
}) => Promise<ReadonlyArray<PendingApproval>>;
|
|
6386
|
+
|
|
5686
6387
|
/** One resolved rule: "when <targetTable> loses a row, do <policy> to <table.column>". */
|
|
5687
6388
|
export declare interface PluginRefRule {
|
|
5688
6389
|
readonly table: string;
|
|
@@ -5884,6 +6585,10 @@ export declare interface QueryProducerDeps<D> {
|
|
|
5884
6585
|
* executor (EffectStore + the action base layer). */
|
|
5885
6586
|
readonly provideEffect: (effect: Effect.Effect<unknown, unknown, never>, store: unknown) => Effect.Effect<unknown, unknown, never>;
|
|
5886
6587
|
readonly interceptor?: ServeRpcInterceptor;
|
|
6588
|
+
/** See {@link MutationRunnerDeps.defaultDeny} — same flag; an undecided
|
|
6589
|
+
* query fails the subscribe AND every recompute/reauth with the same typed
|
|
6590
|
+
* `ScopeError`. */
|
|
6591
|
+
readonly defaultDeny?: boolean;
|
|
5887
6592
|
}
|
|
5888
6593
|
|
|
5889
6594
|
export declare interface QuerySubscriberDeps<D> extends QueryProducerDeps<D> {
|
|
@@ -5893,6 +6598,27 @@ export declare interface QuerySubscriberDeps<D> extends QueryProducerDeps<D> {
|
|
|
5893
6598
|
readonly subscribeComputed: (computed: ComputedQuery, emit: (event: unknown) => void, context: ServeRequestContext) => Promise<() => void>;
|
|
5894
6599
|
}
|
|
5895
6600
|
|
|
6601
|
+
/** One `store.raw(...)` call, as the reactive engine needs to see it. */
|
|
6602
|
+
export declare interface RawReadObservation {
|
|
6603
|
+
/** The fragment's STATIC text, joined with `?` where a value was bound.
|
|
6604
|
+
* Never the values — they are user data and this string reaches a log. */
|
|
6605
|
+
readonly sql: string;
|
|
6606
|
+
/** The tables the caller declared, from `opts.dependsOn` or the fragment.
|
|
6607
|
+
* Empty is the whole point of this module. */
|
|
6608
|
+
readonly dependsOn: ReadonlyArray<string>;
|
|
6609
|
+
}
|
|
6610
|
+
|
|
6611
|
+
/**
|
|
6612
|
+
* The static text of a captured fragment, with every bound value rendered as
|
|
6613
|
+
* `?`.
|
|
6614
|
+
*
|
|
6615
|
+
* The values are deliberately NOT included. This string is written to a log and
|
|
6616
|
+
* a raw fragment's values are the caller's data — a customer email, a token
|
|
6617
|
+
* being looked up. The static text is what identifies the query to its author,
|
|
6618
|
+
* which is all the warning needs.
|
|
6619
|
+
*/
|
|
6620
|
+
export declare const rawSqlPreview: (strings: ReadonlyArray<string>) => string;
|
|
6621
|
+
|
|
5896
6622
|
/** What a reaction does when it fires — run an agent or start a workflow. Both
|
|
5897
6623
|
* identified by name; the serve layer resolves + runs them as `agentActor`. */
|
|
5898
6624
|
export declare type ReactionAct = {
|
|
@@ -6026,6 +6752,32 @@ export declare interface ReactionRunDeps {
|
|
|
6026
6752
|
readonly now?: () => number;
|
|
6027
6753
|
}
|
|
6028
6754
|
|
|
6755
|
+
/** The reactive tunables a caller may declare. Every field optional; every
|
|
6756
|
+
* default above. */
|
|
6757
|
+
export declare interface ReactiveConfigInput {
|
|
6758
|
+
/**
|
|
6759
|
+
* How many subscribers a single change event is delivered to at once.
|
|
6760
|
+
*
|
|
6761
|
+
* Raise it when deliveries are dominated by I/O the framework performs on the
|
|
6762
|
+
* subscriber's behalf — a `guards:` resource resolver or a row-filter loader
|
|
6763
|
+
* that hits the database once per subscriber. Lower it (to `1`, fully serial)
|
|
6764
|
+
* only if you have measured your connection pool being starved by reactive
|
|
6765
|
+
* traffic; the delivery loop shares the pool with the request path.
|
|
6766
|
+
*/
|
|
6767
|
+
readonly deliveryConcurrency?: number;
|
|
6768
|
+
/**
|
|
6769
|
+
* How many raw-SQL reads one request records for the `dependsOn` diagnostic.
|
|
6770
|
+
* Only worth raising if a handler issues many raw reads and you want the
|
|
6771
|
+
* warning to name a later one.
|
|
6772
|
+
*/
|
|
6773
|
+
readonly rawReadTrackingLimit?: number;
|
|
6774
|
+
}
|
|
6775
|
+
|
|
6776
|
+
export declare interface ReactiveEnv {
|
|
6777
|
+
readonly VOLTRO_REACTIVE_DELIVERY_CONCURRENCY?: string;
|
|
6778
|
+
readonly VOLTRO_RAW_READ_TRACKING_LIMIT?: string;
|
|
6779
|
+
}
|
|
6780
|
+
|
|
6029
6781
|
/** A reactive query returns a builder descriptor instead of a value; the store
|
|
6030
6782
|
* runs it and streams the rows. Only queries may do this. */
|
|
6031
6783
|
export declare type ReactiveReturn<D extends ExecutorDescriptor> = D extends {
|
|
@@ -6136,6 +6888,21 @@ export declare const recordEventPublished: (event: string) => void;
|
|
|
6136
6888
|
/** Subscriber attached (+1) or detached (-1). */
|
|
6137
6889
|
export declare const recordEventSubscribers: (event: string, delta: number) => void;
|
|
6138
6890
|
|
|
6891
|
+
/**
|
|
6892
|
+
* Record a raw read, opening a scope for the rest of this execution context if
|
|
6893
|
+
* none is open yet.
|
|
6894
|
+
*
|
|
6895
|
+
* Opening it HERE is what keeps the request path free of an async-local frame it
|
|
6896
|
+
* would almost never use — see `isolateRawReadScope`. The scope then lives for
|
|
6897
|
+
* the remainder of this context, which is the request, which is exactly long
|
|
6898
|
+
* enough for the subscribe that follows to read it.
|
|
6899
|
+
*
|
|
6900
|
+
* Bounded by `rawReadTrackingLimit`, and DEDUPED by sql text: a handler that
|
|
6901
|
+
* raw-reads in a loop must not turn a diagnostic into a leak, and the tenth
|
|
6902
|
+
* identical read tells the reader nothing the first did not.
|
|
6903
|
+
*/
|
|
6904
|
+
export declare const recordRawRead: (observation: RawReadObservation) => void;
|
|
6905
|
+
|
|
6139
6906
|
/**
|
|
6140
6907
|
* Record one framework sample into the Effect metric registry. Synchronous —
|
|
6141
6908
|
* metric updates are pure, so `runSync` is cheap and safe to call from the
|
|
@@ -6366,6 +7133,11 @@ export declare interface RegistryTableLike {
|
|
|
6366
7133
|
readonly validatePatchSchema?: Schema.Schema.Any;
|
|
6367
7134
|
}
|
|
6368
7135
|
|
|
7136
|
+
/** Rebuild checkable guards from the row. The stored resource id becomes a
|
|
7137
|
+
* constant extractor, so the decision is scoped to the SAME resource the
|
|
7138
|
+
* request was. */
|
|
7139
|
+
export declare const rehydrateGuards: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<AnyGuardSpec>;
|
|
7140
|
+
|
|
6369
7141
|
/** A relationship tuple — `subjectId` has `relation` on `<resourceType>:<resourceId>`.
|
|
6370
7142
|
* In practice these are rows in a relation table; the engine takes them as data. */
|
|
6371
7143
|
export declare interface RelationTuple {
|
|
@@ -6548,8 +7320,8 @@ export declare interface ResendOptions {
|
|
|
6548
7320
|
/** Exported for tests — the warn-once set is process-global by design. */
|
|
6549
7321
|
export declare const resetComputedQueryCacheWarnings: () => void;
|
|
6550
7322
|
|
|
6551
|
-
/** Test/dev-only — clear ALL
|
|
6552
|
-
export declare const
|
|
7323
|
+
/** Test/dev-only — clear ALL per-connection state. Don't call from app code. */
|
|
7324
|
+
export declare const _resetConnectionCredentialsForTest: () => void;
|
|
6553
7325
|
|
|
6554
7326
|
/** Test seams for the tag-cache guard — the cache is module-private otherwise. */
|
|
6555
7327
|
export declare const resetEventMetricTagCacheForTests: () => void;
|
|
@@ -6560,6 +7332,23 @@ export declare const resetObservedGraph: () => void;
|
|
|
6560
7332
|
/** Reset to the env default (tests). */
|
|
6561
7333
|
export declare const resetSecretsBackend: () => void;
|
|
6562
7334
|
|
|
7335
|
+
/**
|
|
7336
|
+
* The client address to rate-limit, geo-block and audit by.
|
|
7337
|
+
*
|
|
7338
|
+
* The rules, in the order they fire:
|
|
7339
|
+
*
|
|
7340
|
+
* 1. No `x-forwarded-for` → the socket address. (Nothing to decide.)
|
|
7341
|
+
* 2. `trustAll` → the leftmost forwarded token. The pre-fix behaviour, now an
|
|
7342
|
+
* explicit opt-in for an ingress that OVERWRITES the header.
|
|
7343
|
+
* 3. Hop mode (`trustedProxies: ['2']`) → walk `n` hops in from the right of
|
|
7344
|
+
* `[...forwarded, socket]`.
|
|
7345
|
+
* 4. Otherwise the immediate peer must itself be trusted; if it is not, the
|
|
7346
|
+
* chain is a client-supplied string and is ignored entirely.
|
|
7347
|
+
* 5. With a trusted peer, walk the chain right-to-left past every trusted hop;
|
|
7348
|
+
* the first address that is NOT a configured proxy is the client.
|
|
7349
|
+
*/
|
|
7350
|
+
export declare const resolveClientAddress: (input: ClientAddressInput) => string | undefined;
|
|
7351
|
+
|
|
6563
7352
|
/**
|
|
6564
7353
|
* Resolve the calling subject's credential for one connection, refreshed.
|
|
6565
7354
|
*
|
|
@@ -6656,6 +7445,11 @@ export declare interface ResolvedConnection {
|
|
|
6656
7445
|
*/
|
|
6657
7446
|
export declare const resolveDependentTables: (descriptor: QueryDescriptor) => ReadonlySet<string>;
|
|
6658
7447
|
|
|
7448
|
+
export declare interface ResolvedReactiveConfig {
|
|
7449
|
+
readonly deliveryConcurrency: number;
|
|
7450
|
+
readonly rawReadTrackingLimit: number;
|
|
7451
|
+
}
|
|
7452
|
+
|
|
6659
7453
|
/**
|
|
6660
7454
|
* A `WorkflowCallerContext` after the engine has filled in what a workflow
|
|
6661
7455
|
* always needs. A run started by a request carries the caller's subject; a
|
|
@@ -6678,8 +7472,39 @@ export declare interface ResolvedWorkflowCallerContext extends WorkflowCallerCon
|
|
|
6678
7472
|
readonly traceId: string;
|
|
6679
7473
|
}
|
|
6680
7474
|
|
|
7475
|
+
/**
|
|
7476
|
+
* Resolve the guard's configuration.
|
|
7477
|
+
*
|
|
7478
|
+
* Precedence: explicit `app.config.ts` values (threaded through
|
|
7479
|
+
* `RpcServerOptions.security`) → env override → safe default. An unrecognised
|
|
7480
|
+
* mode falls back to enforcement rather than to `off`: a typo in a security
|
|
7481
|
+
* knob must not silently disable it.
|
|
7482
|
+
*/
|
|
7483
|
+
export declare const resolveOriginGuardConfig: (configured?: {
|
|
7484
|
+
readonly mode?: OriginGuardMode | undefined;
|
|
7485
|
+
readonly allowedOrigins?: ReadonlyArray<string> | undefined;
|
|
7486
|
+
} | undefined, env?: Readonly<Record<string, string | undefined>>) => OriginGuardConfig;
|
|
7487
|
+
|
|
7488
|
+
export declare const resolveReactiveConfig: (config?: ReactiveConfigInput | undefined, env?: ReactiveEnv) => ResolvedReactiveConfig;
|
|
7489
|
+
|
|
6681
7490
|
export declare const resolveRedirectUri: (definition: OAuth2ConnectionDefinition, publicUrl: string) => string;
|
|
6682
7491
|
|
|
7492
|
+
/**
|
|
7493
|
+
* Whether the request reached the app over TLS.
|
|
7494
|
+
*
|
|
7495
|
+
* `x-forwarded-proto` is believed only from a trusted proxy, for exactly the
|
|
7496
|
+
* reason `x-forwarded-for` is: it is a client-writable header. Getting this
|
|
7497
|
+
* wrong in the permissive direction would let any client make the framework
|
|
7498
|
+
* emit HSTS (harmless) — and, more importantly, would let it claim `https` in
|
|
7499
|
+
* an audit record, so it is gated the same way.
|
|
7500
|
+
*/
|
|
7501
|
+
export declare const resolveRequestIsHttps: (input: {
|
|
7502
|
+
readonly socketEncrypted: boolean;
|
|
7503
|
+
readonly socketAddress: string | undefined;
|
|
7504
|
+
readonly xForwardedProto: string | undefined;
|
|
7505
|
+
readonly config: TrustedProxyConfig;
|
|
7506
|
+
}) => boolean;
|
|
7507
|
+
|
|
6683
7508
|
/** Rebuild the flagged groups from the (already-committed) base rows + merge —
|
|
6684
7509
|
* the caller's resolution of a min/max rescan signal. `baseRows` is the full
|
|
6685
7510
|
* current table (or at least every row in the rescan groups). */
|
|
@@ -6739,6 +7564,21 @@ export declare const resolveSecretsBackend: (config: SecretsBackendConfig | unde
|
|
|
6739
7564
|
* await (e.g. deriving a permission host from a key). Bypasses remote backends. */
|
|
6740
7565
|
export declare const resolveSecretSync: (key: string) => string | undefined;
|
|
6741
7566
|
|
|
7567
|
+
/**
|
|
7568
|
+
* Resolve the header configuration.
|
|
7569
|
+
*
|
|
7570
|
+
* Precedence: explicit `app.config.ts` values → env override → defaults. Env
|
|
7571
|
+
* knobs: `VOLTRO_SECURITY_HEADERS` (`off|default|strict`), `VOLTRO_CSP`,
|
|
7572
|
+
* `VOLTRO_CSP_HTML`, `VOLTRO_HSTS` — each accepting `off` to drop just that one.
|
|
7573
|
+
*/
|
|
7574
|
+
export declare const resolveSecurityHeaders: (configured?: SecurityHeadersOptions | undefined, env?: Readonly<Record<string, string | undefined>>) => SecurityHeadersConfig;
|
|
7575
|
+
|
|
7576
|
+
/** The app's composed strategy chain — `buildResolveSubject`'s return value. */
|
|
7577
|
+
export declare type ResolveSubjectFn = (input: {
|
|
7578
|
+
headers: Record<string, string | undefined>;
|
|
7579
|
+
clientId: number;
|
|
7580
|
+
}) => Promise<SubjectResolution>;
|
|
7581
|
+
|
|
6742
7582
|
/**
|
|
6743
7583
|
* Resolve the recording mode. `VOLTRO_TIMELINE=off|interesting|all` (also
|
|
6744
7584
|
* on/1 = interesting, 0 = off) overrides; default `interesting` outside
|
|
@@ -6748,6 +7588,18 @@ export declare const resolveSecretSync: (key: string) => string | undefined;
|
|
|
6748
7588
|
*/
|
|
6749
7589
|
export declare const resolveTimelineMode: (env?: Record<string, string | undefined>) => TimelineMode;
|
|
6750
7590
|
|
|
7591
|
+
/**
|
|
7592
|
+
* Resolve the trusted-proxy configuration.
|
|
7593
|
+
*
|
|
7594
|
+
* Precedence: explicit config (from `app.config.ts`, threaded through
|
|
7595
|
+
* `RpcServerOptions.security.trustedProxies`) beats the `VOLTRO_TRUSTED_PROXIES`
|
|
7596
|
+
* env override, which beats the safe default of trusting nothing. The env var
|
|
7597
|
+
* exists so an operator can correct a misconfigured deployment without a
|
|
7598
|
+
* rebuild; the config field exists because it is a tunable and tunables belong
|
|
7599
|
+
* in `app.config.ts`.
|
|
7600
|
+
*/
|
|
7601
|
+
export declare const resolveTrustedProxies: (configured?: ReadonlyArray<string> | undefined, env?: Readonly<Record<string, string | undefined>>) => TrustedProxyConfig;
|
|
7602
|
+
|
|
6751
7603
|
/** Per-resource-type policy. `actions` maps an action to the relations that
|
|
6752
7604
|
* grant it (ANY-of). `implies` is relation implication (owner ⇒ editor ⇒
|
|
6753
7605
|
* viewer), applied as a transitive closure before the action check. */
|
|
@@ -6760,6 +7612,10 @@ export declare interface ResourcePolicy {
|
|
|
6760
7612
|
/** The full ordered list of result variants for a definition, holdout last. */
|
|
6761
7613
|
export declare const resultVariantNames: (def: ExperimentDefinition) => ReadonlyArray<string>;
|
|
6762
7614
|
|
|
7615
|
+
export { RetentionConflict }
|
|
7616
|
+
|
|
7617
|
+
export { RetentionSource }
|
|
7618
|
+
|
|
6763
7619
|
export { RetentionSpec }
|
|
6764
7620
|
|
|
6765
7621
|
export { retentionTtlMsFromEnv }
|
|
@@ -6875,6 +7731,24 @@ export declare interface RowFilter<Ctx = unknown> {
|
|
|
6875
7731
|
readonly onLoadError?: 'fail' | 'deny';
|
|
6876
7732
|
}
|
|
6877
7733
|
|
|
7734
|
+
/**
|
|
7735
|
+
* Is a row filter registered right now — asked by a path about to serve an
|
|
7736
|
+
* UNFILTERED read.
|
|
7737
|
+
*
|
|
7738
|
+
* The seam that turns the silent failure into a loud one. It is deliberately
|
|
7739
|
+
* the CURRENT registration rather than a sticky "was one ever set" flag, and
|
|
7740
|
+
* the difference matters in both directions.
|
|
7741
|
+
*
|
|
7742
|
+
* A sticky flag was the first version, on the reasoning that it would also
|
|
7743
|
+
* catch the instance split. It does not, and cannot: in that failure the
|
|
7744
|
+
* pipeline's copy of this module never saw the registration at all, so a flag
|
|
7745
|
+
* living beside it is just as blind as the filter is. The instance split is
|
|
7746
|
+
* fixed above, at the cell, which is the only place it CAN be fixed — and once
|
|
7747
|
+
* it is, the current registration is visible everywhere and the flag adds
|
|
7748
|
+
* nothing but a way to poison every later test in a worker.
|
|
7749
|
+
*/
|
|
7750
|
+
export declare const rowFilterRegistered: () => boolean;
|
|
7751
|
+
|
|
6878
7752
|
/** What a scoped store needs: a sync `table → Predicate?` lookup. */
|
|
6879
7753
|
export declare type RowFilterScope = (table: string) => Predicate | undefined;
|
|
6880
7754
|
|
|
@@ -7001,15 +7875,36 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
|
|
|
7001
7875
|
readonly ping?: () => Promise<boolean>;
|
|
7002
7876
|
};
|
|
7003
7877
|
/**
|
|
7004
|
-
* Max
|
|
7005
|
-
*
|
|
7006
|
-
*
|
|
7007
|
-
*
|
|
7008
|
-
*
|
|
7009
|
-
*
|
|
7010
|
-
*
|
|
7878
|
+
* Max body size (bytes) accepted on the buffered `POST /rpc` JSON endpoint. A
|
|
7879
|
+
* JSON rpc mutation/action/query envelope is never legitimately large, so an
|
|
7880
|
+
* oversized body is a memory-DoS attempt — refused with `413` and never
|
|
7881
|
+
* buffered past the cap. Applies ONLY to `/rpc` (JSON); file uploads ride
|
|
7882
|
+
* separate plugin routes with their own `limits.maxBytes`, and WS frames are
|
|
7883
|
+
* capped by the `ws` library default (100 MiB). Per-IP rate limiting + the
|
|
7884
|
+
* primary body cap belong at the ingress. Default
|
|
7885
|
+
* {@link DEFAULT_MAX_RPC_BODY_BYTES}.
|
|
7886
|
+
*
|
|
7887
|
+
* **Both shapes end in a `413`, and the byte counter is what enforces it.**
|
|
7888
|
+
* A declared `Content-Length` over the cap is refused first, before the client
|
|
7889
|
+
* uploads anything — a courtesy, not the enforcement, since a
|
|
7890
|
+
* `Transfer-Encoding: chunked` body declares no length. The counter runs over
|
|
7891
|
+
* the arriving bytes, stops accumulating the moment the running total crosses
|
|
7892
|
+
* the cap, drains the remainder and answers `413`.
|
|
7893
|
+
*
|
|
7894
|
+
* This paragraph used to claim the counter was the enforcement while the
|
|
7895
|
+
* declared check was the courtesy, and observably it was the other way round:
|
|
7896
|
+
* the counter bounded MEMORY correctly and produced no client-visible status
|
|
7897
|
+
* at all. It cut the body by destroying the request, that took the socket with
|
|
7898
|
+
* it, and the 413 it had just built was written into a dead connection —
|
|
7899
|
+
* `curl: (56) Recv failure: Connection reset by peer` where the same bytes
|
|
7900
|
+
* with a `Content-Length` got a clean 413. See `rpcBodyCap.ts`.
|
|
7011
7901
|
*/
|
|
7012
7902
|
readonly maxRpcBodyBytes?: number;
|
|
7903
|
+
/**
|
|
7904
|
+
* Transport-level security policy for this listener — see
|
|
7905
|
+
* {@link TransportSecurityOptions}. `undefined` → the safe defaults.
|
|
7906
|
+
*/
|
|
7907
|
+
readonly security?: TransportSecurityOptions;
|
|
7013
7908
|
}
|
|
7014
7909
|
|
|
7015
7910
|
/**
|
|
@@ -7150,6 +8045,16 @@ export declare const runWithDeadlockRetry: <T>(work: () => Promise<T>, options?:
|
|
|
7150
8045
|
*/
|
|
7151
8046
|
export declare const runWithObservedProcedure: <T>(procedure: ObservedProcedure, work: () => T) => T;
|
|
7152
8047
|
|
|
8048
|
+
/**
|
|
8049
|
+
* Run `fn` inside a FRESH raw-read scope, restoring whatever was current before.
|
|
8050
|
+
*
|
|
8051
|
+
* The wrapping form, for callers that have a callback: the dispatcher, which
|
|
8052
|
+
* wraps a computed query's recompute so a raw read issued by a LATER handler run
|
|
8053
|
+
* is attributed to that run rather than to whatever opened the request; and
|
|
8054
|
+
* tests, which need a scope that provably does not outlive them.
|
|
8055
|
+
*/
|
|
8056
|
+
export declare const runWithRawReadScope: <T>(fn: () => T) => T;
|
|
8057
|
+
|
|
7153
8058
|
/**
|
|
7154
8059
|
* Run `fn` with `loader` current, restoring whatever was current before.
|
|
7155
8060
|
*
|
|
@@ -7423,6 +8328,32 @@ export declare interface SchedulerHandle {
|
|
|
7423
8328
|
* with `coordinationOutcome: 'cluster'`. Overlap policy still
|
|
7424
8329
|
* applies. */
|
|
7425
8330
|
fireFromCluster(name: string): Promise<string | undefined>;
|
|
8331
|
+
/**
|
|
8332
|
+
* WF-14 — fire every occurrence of a schedule in `(from, to]`, SEQUENTIALLY
|
|
8333
|
+
* and in order, each recorded against its own cron-derived `scheduledAt`.
|
|
8334
|
+
*
|
|
8335
|
+
* The verb behind "backfill this range": boot backfill walks forward from
|
|
8336
|
+
* the last recorded run only, and cluster cron caps its own catch-up at one
|
|
8337
|
+
* day — a longer outage, or a schedule added after the fact, needs an
|
|
8338
|
+
* explicit operator instruction naming the range.
|
|
8339
|
+
*
|
|
8340
|
+
* Fired as `trigger: 'manual'` — an explicit operator action, so it bypasses
|
|
8341
|
+
* the coordinator (the operator invoked ONE process) and the overlap policy
|
|
8342
|
+
* (sequential awaiting means the backfill never overlaps itself; a live
|
|
8343
|
+
* firing racing it is the same race a manual "Run now" already has).
|
|
8344
|
+
*
|
|
8345
|
+
* `cap` is a hard bound: MORE than `cap` occurrences refuses up front rather
|
|
8346
|
+
* than enqueueing a prefix — a partial backfill that reports success is how
|
|
8347
|
+
* a range gets "filled" twice. Confirmation thresholds live one layer up
|
|
8348
|
+
* (the inspect hook / CLI), where the human is.
|
|
8349
|
+
*/
|
|
8350
|
+
backfillRange(name: string, from: Date, to: Date, options: {
|
|
8351
|
+
readonly cap: number;
|
|
8352
|
+
}): Promise<{
|
|
8353
|
+
readonly occurrences: number;
|
|
8354
|
+
readonly fired: number;
|
|
8355
|
+
readonly runIds: ReadonlyArray<string | null>;
|
|
8356
|
+
}>;
|
|
7426
8357
|
/** Stop all timers + abort in-flight watchdogs. */
|
|
7427
8358
|
shutdown(): Promise<void>;
|
|
7428
8359
|
}
|
|
@@ -7619,6 +8550,9 @@ declare interface ScopeCtx {
|
|
|
7619
8550
|
readonly rowFilter?: RowFilterScope;
|
|
7620
8551
|
}
|
|
7621
8552
|
|
|
8553
|
+
/** The scope strings a UI shows ("ask someone with …"). */
|
|
8554
|
+
export declare const scopeStringsOf: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<string>;
|
|
8555
|
+
|
|
7622
8556
|
export declare interface SecretsBackend {
|
|
7623
8557
|
/** Resolve a secret by key. `undefined` when absent. */
|
|
7624
8558
|
readonly get: (key: string) => Promise<string | undefined>;
|
|
@@ -7633,6 +8567,49 @@ export declare type SecretsBackendConfig = 'env' | {
|
|
|
7633
8567
|
readonly backend: SecretsBackend;
|
|
7634
8568
|
};
|
|
7635
8569
|
|
|
8570
|
+
export declare interface SecurityHeadersConfig {
|
|
8571
|
+
readonly mode: SecurityHeadersMode;
|
|
8572
|
+
/** CSP for non-HTML responses. `false` → do not send one. */
|
|
8573
|
+
readonly csp: string | false;
|
|
8574
|
+
/** CSP for `text/html` responses. `false` → do not send one. */
|
|
8575
|
+
readonly cspHtml: string | false;
|
|
8576
|
+
/** `Strict-Transport-Security` value; only emitted over https. */
|
|
8577
|
+
readonly hsts: string | false;
|
|
8578
|
+
readonly frameOptions: string | false;
|
|
8579
|
+
readonly referrerPolicy: string | false;
|
|
8580
|
+
readonly contentTypeOptions: string | false;
|
|
8581
|
+
/** Anything else the app wants on every response (e.g. `Permissions-Policy`,
|
|
8582
|
+
* `Cross-Origin-Resource-Policy`). Deliberately not defaulted: both of those
|
|
8583
|
+
* break legitimate cross-origin dashboards when guessed wrong. */
|
|
8584
|
+
readonly extra: Readonly<Record<string, string>>;
|
|
8585
|
+
}
|
|
8586
|
+
|
|
8587
|
+
/**
|
|
8588
|
+
* The headers to ADD to one response. The caller must not overwrite a header
|
|
8589
|
+
* the route already set.
|
|
8590
|
+
*
|
|
8591
|
+
* `https` gates HSTS only: announcing a year of HTTPS-only from a plain-http
|
|
8592
|
+
* response is either ignored (per RFC 6797) or, on a dev box reached over
|
|
8593
|
+
* `http://localhost`, actively harmful.
|
|
8594
|
+
*/
|
|
8595
|
+
export declare const securityHeadersFor: (config: SecurityHeadersConfig, request: {
|
|
8596
|
+
readonly contentType: string | undefined;
|
|
8597
|
+
readonly https: boolean;
|
|
8598
|
+
}) => Readonly<Record<string, string>>;
|
|
8599
|
+
|
|
8600
|
+
export declare type SecurityHeadersMode = 'off' | 'default' | 'strict';
|
|
8601
|
+
|
|
8602
|
+
export declare interface SecurityHeadersOptions {
|
|
8603
|
+
readonly mode?: SecurityHeadersMode | undefined;
|
|
8604
|
+
readonly csp?: string | false | undefined;
|
|
8605
|
+
readonly cspHtml?: string | false | undefined;
|
|
8606
|
+
readonly hsts?: string | false | undefined;
|
|
8607
|
+
readonly frameOptions?: string | false | undefined;
|
|
8608
|
+
readonly referrerPolicy?: string | false | undefined;
|
|
8609
|
+
readonly contentTypeOptions?: string | false | undefined;
|
|
8610
|
+
readonly extra?: Readonly<Record<string, string>> | undefined;
|
|
8611
|
+
}
|
|
8612
|
+
|
|
7636
8613
|
export declare class SelectBuilder {
|
|
7637
8614
|
private readonly backend;
|
|
7638
8615
|
private readonly scope;
|
|
@@ -7684,6 +8661,25 @@ export declare class SelectBuilder {
|
|
|
7684
8661
|
get descriptor(): QueryDescriptor;
|
|
7685
8662
|
}
|
|
7686
8663
|
|
|
8664
|
+
/** A guard, flattened to something a row can hold. */
|
|
8665
|
+
export declare type SerializedGuard = {
|
|
8666
|
+
readonly kind: 'scope';
|
|
8667
|
+
readonly scope: ReadonlyArray<string>;
|
|
8668
|
+
readonly mode: 'all' | 'any';
|
|
8669
|
+
readonly resource: string | null;
|
|
8670
|
+
} | {
|
|
8671
|
+
readonly kind: 'policy';
|
|
8672
|
+
readonly action: string;
|
|
8673
|
+
readonly resourceType: string;
|
|
8674
|
+
readonly resource: string | null;
|
|
8675
|
+
};
|
|
8676
|
+
|
|
8677
|
+
/**
|
|
8678
|
+
* Flatten the descriptor's approver guards, resolving each `resource` extractor
|
|
8679
|
+
* against the REQUEST's input while it is still available.
|
|
8680
|
+
*/
|
|
8681
|
+
export declare const serializeGuards: (guards: ReadonlyArray<AnyGuardSpec>, input: unknown) => ReadonlyArray<SerializedGuard>;
|
|
8682
|
+
|
|
7687
8683
|
export declare interface ServeRequestContext {
|
|
7688
8684
|
readonly subject: unknown;
|
|
7689
8685
|
readonly traceId: string;
|
|
@@ -7707,6 +8703,33 @@ export declare interface ServeRequestContext {
|
|
|
7707
8703
|
*/
|
|
7708
8704
|
export declare const serverOnlyColumnNames: (tableName: string) => ReadonlyArray<string>;
|
|
7709
8705
|
|
|
8706
|
+
/**
|
|
8707
|
+
* A generated CRUD write (`crud.create` / `crud.update`) was handed a
|
|
8708
|
+
* `.serverOnly()` column in its INPUT.
|
|
8709
|
+
*
|
|
8710
|
+
* `.serverOnly()` is the WIRE-exposure axis: the column never crosses the
|
|
8711
|
+
* boundary in EITHER direction. Reads strip it; a write that accepts it is the
|
|
8712
|
+
* same violation mirrored — mass assignment of a column the schema declared the
|
|
8713
|
+
* client may not see, let alone set.
|
|
8714
|
+
*
|
|
8715
|
+
* Refused rather than silently stripped: a stripped field makes an attack
|
|
8716
|
+
* indistinguishable from a no-op and leaves an honest caller wondering why the
|
|
8717
|
+
* value it sent never landed. `columns` names what was rejected so the fix
|
|
8718
|
+
* (drop the field from the descriptor's input schema, or from the caller) is
|
|
8719
|
+
* mechanical.
|
|
8720
|
+
*/
|
|
8721
|
+
export declare class ServerOnlyColumnWrite extends ServerOnlyColumnWrite_base {
|
|
8722
|
+
}
|
|
8723
|
+
|
|
8724
|
+
declare const ServerOnlyColumnWrite_base: Schema.TaggedErrorClass<ServerOnlyColumnWrite, "ServerOnlyColumnWrite", {
|
|
8725
|
+
readonly _tag: Schema.tag<"ServerOnlyColumnWrite">;
|
|
8726
|
+
} & {
|
|
8727
|
+
/** The table the write targeted. */
|
|
8728
|
+
table: typeof Schema.String;
|
|
8729
|
+
/** The `.serverOnly()` columns the input tried to set. */
|
|
8730
|
+
columns: Schema.Array$<typeof Schema.String>;
|
|
8731
|
+
}>;
|
|
8732
|
+
|
|
7710
8733
|
/** One leak: a wire query that declares a serverOnly column in its output. */
|
|
7711
8734
|
export declare interface ServerOnlyLeak {
|
|
7712
8735
|
readonly query: string;
|
|
@@ -7985,7 +9008,7 @@ export declare interface StoreCredentialInput {
|
|
|
7985
9008
|
* a mutation's `error:` schema when you want every kind surfaced
|
|
7986
9009
|
* typed to the client.
|
|
7987
9010
|
*/
|
|
7988
|
-
export declare type StoreError = TenantScopeViolation | StoreOperationFailed | TableValidationFailed;
|
|
9011
|
+
export declare type StoreError = TenantScopeViolation | TenantRowNotFound | ServerOnlyColumnWrite | StoreOperationFailed | TableValidationFailed;
|
|
7989
9012
|
|
|
7990
9013
|
export declare interface StoreMiddlewareContext {
|
|
7991
9014
|
readonly subject: Subject;
|
|
@@ -8343,6 +9366,41 @@ export declare interface TenantCostState {
|
|
|
8343
9366
|
readonly lastUpdatedAt: number | null;
|
|
8344
9367
|
}
|
|
8345
9368
|
|
|
9369
|
+
/**
|
|
9370
|
+
* A keyed-by-primary-key write (`ctx.store.update(table, id, patch)`,
|
|
9371
|
+
* `delete(table, id)`, `hardDelete(table, id)`, `patchJson(table, id, …)`) on a
|
|
9372
|
+
* `tenant()`-scoped table did not resolve to a row inside the CALLER's tenant.
|
|
9373
|
+
*
|
|
9374
|
+
* **One error for two situations, on purpose.** It is raised identically when
|
|
9375
|
+
* the row does not exist at all and when it exists but belongs to another
|
|
9376
|
+
* tenant, and it carries no field that separates them. That is the whole point:
|
|
9377
|
+
*
|
|
9378
|
+
* - Reporting "forbidden" for a foreign row and "not found" for a missing one
|
|
9379
|
+
* turns any keyed write into a cross-tenant EXISTENCE ORACLE — an attacker
|
|
9380
|
+
* walks ids and learns which ones are real in someone else's tenant, which
|
|
9381
|
+
* is exactly the isolation the `tenant()` mixin exists to provide.
|
|
9382
|
+
* - Collapsing the other way — silently affecting zero rows — is worse than
|
|
9383
|
+
* either: the handler reads it as "the row is gone", not "you may not touch
|
|
9384
|
+
* it", so a genuine isolation breach shows up in an app as a confusing
|
|
9385
|
+
* absent-row branch and never as a security signal.
|
|
9386
|
+
*
|
|
9387
|
+
* So both cases fail LOUDLY and IDENTICALLY. `id` is the key the caller itself
|
|
9388
|
+
* supplied — never another tenant's data.
|
|
9389
|
+
*/
|
|
9390
|
+
export declare class TenantRowNotFound extends TenantRowNotFound_base {
|
|
9391
|
+
}
|
|
9392
|
+
|
|
9393
|
+
declare const TenantRowNotFound_base: Schema.TaggedErrorClass<TenantRowNotFound, "TenantRowNotFound", {
|
|
9394
|
+
readonly _tag: Schema.tag<"TenantRowNotFound">;
|
|
9395
|
+
} & {
|
|
9396
|
+
/** The table the keyed write targeted. */
|
|
9397
|
+
table: typeof Schema.String;
|
|
9398
|
+
/** The primary key the CALLER supplied. Echoing it leaks nothing. */
|
|
9399
|
+
id: typeof Schema.String;
|
|
9400
|
+
/** Human-readable explanation for diagnostics + UI. */
|
|
9401
|
+
reason: typeof Schema.String;
|
|
9402
|
+
}>;
|
|
9403
|
+
|
|
8346
9404
|
/**
|
|
8347
9405
|
* A write was attempted against a `tenant()`-scoped table, but the
|
|
8348
9406
|
* authenticated subject's `tenantId` is null — either anonymous, or
|
|
@@ -8566,6 +9624,41 @@ export declare interface TransactionalStore {
|
|
|
8566
9624
|
transactional<T>(work: (tx: unknown) => Promise<T>): Promise<T>;
|
|
8567
9625
|
}
|
|
8568
9626
|
|
|
9627
|
+
/**
|
|
9628
|
+
* Transport-level security policy for the api listener.
|
|
9629
|
+
*
|
|
9630
|
+
* Every field is a TUNABLE, so its home is the app's `app.config.ts`
|
|
9631
|
+
* (`security: { … }`, threaded to here by `voltro dev` and `voltro serve`
|
|
9632
|
+
* through one shared resolver); each also carries an env override, for
|
|
9633
|
+
* correcting a deployment without a rebuild. An embedder calling
|
|
9634
|
+
* `startRpcServer` directly passes the same object.
|
|
9635
|
+
*
|
|
9636
|
+
* `undefined` on every field → the safe defaults documented on each resolver:
|
|
9637
|
+
* origin checking ON, `x-forwarded-for` IGNORED, security headers ON.
|
|
9638
|
+
*/
|
|
9639
|
+
export declare interface TransportSecurityOptions {
|
|
9640
|
+
/** Cross-site protection for `POST /rpc` + the WS upgrade.
|
|
9641
|
+
* `'same-origin'` (default) accepts a browser request only when its
|
|
9642
|
+
* `Origin` matches the `Host` it was sent to or is in `allowedOrigins`;
|
|
9643
|
+
* a request with NO browser origin signal (the in-process SSR loader,
|
|
9644
|
+
* a mobile SDK, another service) is allowed — see `originGuard.ts`.
|
|
9645
|
+
* Env override: `VOLTRO_ORIGIN_GUARD=off`. */
|
|
9646
|
+
readonly originGuard?: OriginGuardMode;
|
|
9647
|
+
/** Extra origins to accept, for a split web/api deployment where the
|
|
9648
|
+
* browser's `Origin` is a different host than the api's `Host`.
|
|
9649
|
+
* Env override: `VOLTRO_ALLOWED_ORIGINS` (comma-separated). */
|
|
9650
|
+
readonly allowedOrigins?: ReadonlyArray<string>;
|
|
9651
|
+
/** Whose `x-forwarded-for` to believe. Empty (the default) → the header is
|
|
9652
|
+
* IGNORED and `socket.remoteAddress` is the client address. Accepts IPs,
|
|
9653
|
+
* CIDRs, the presets `loopback` / `private`, a hop count (`['2']`), or
|
|
9654
|
+
* `['*']` to trust any peer. Env override: `VOLTRO_TRUSTED_PROXIES`. */
|
|
9655
|
+
readonly trustedProxies?: ReadonlyArray<string>;
|
|
9656
|
+
/** Security response headers. Env override: `VOLTRO_SECURITY_HEADERS`
|
|
9657
|
+
* (`off|default|strict`), plus `VOLTRO_CSP` / `VOLTRO_CSP_HTML` /
|
|
9658
|
+
* `VOLTRO_HSTS`. */
|
|
9659
|
+
readonly headers?: SecurityHeadersOptions;
|
|
9660
|
+
}
|
|
9661
|
+
|
|
8569
9662
|
/**
|
|
8570
9663
|
* Declare a workflow trigger.
|
|
8571
9664
|
*
|
|
@@ -8597,6 +9690,16 @@ export declare const triggerWorkflow: <EventPayload = unknown, WorkflowPayload =
|
|
|
8597
9690
|
};
|
|
8598
9691
|
})) => WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>;
|
|
8599
9692
|
|
|
9693
|
+
/** How much of the forwarded chain we are willing to believe. */
|
|
9694
|
+
export declare interface TrustedProxyConfig {
|
|
9695
|
+
/** Literal IPs / CIDR blocks / presets (`loopback`, `private`) to trust. */
|
|
9696
|
+
readonly entries: ReadonlyArray<string>;
|
|
9697
|
+
/** `*` — believe the leftmost XFF token from ANY peer. */
|
|
9698
|
+
readonly trustAll: boolean;
|
|
9699
|
+
/** Hop count (express's numeric `trust proxy`). `undefined` → not hop mode. */
|
|
9700
|
+
readonly hops?: number;
|
|
9701
|
+
}
|
|
9702
|
+
|
|
8600
9703
|
/**
|
|
8601
9704
|
* Atomically claim a pending wakeup for waking — CAS `pending → fired`.
|
|
8602
9705
|
* Returns `true` iff THIS caller won the claim (the row was pending).
|
|
@@ -8642,10 +9745,10 @@ export declare type TupleSource = (req: {
|
|
|
8642
9745
|
}) => Promise<ReadonlyArray<RelationTuple>> | ReadonlyArray<RelationTuple>;
|
|
8643
9746
|
|
|
8644
9747
|
/**
|
|
8645
|
-
*
|
|
8646
|
-
*
|
|
9748
|
+
* Drop everything this connection accumulated. Called by the WS-close finalizer
|
|
9749
|
+
* and by explicit sign-out flows. Idempotent.
|
|
8647
9750
|
*/
|
|
8648
|
-
export declare const
|
|
9751
|
+
export declare const unbindConnection: (clientId: number) => void;
|
|
8649
9752
|
|
|
8650
9753
|
/** The store surface an inverse op needs (a subset of the framework DataStore). */
|
|
8651
9754
|
export declare interface UndoApplyStore {
|
|
@@ -8722,6 +9825,29 @@ export declare class UndoStack<E> {
|
|
|
8722
9825
|
};
|
|
8723
9826
|
}
|
|
8724
9827
|
|
|
9828
|
+
/**
|
|
9829
|
+
* Is the undo WIRE SURFACE present — the three `__voltro.undo.*` descriptors in
|
|
9830
|
+
* the client's generated rpc group, and the matching routes on the server?
|
|
9831
|
+
*
|
|
9832
|
+
* **Deliberately NOT `undoCaptureEnabled()`, and the difference is the whole
|
|
9833
|
+
* point.** The client half of that surface is a BUILD ARTEFACT:
|
|
9834
|
+
* `rpcGroup.generated.ts` is written by `voltro dev`'s codegen and `voltro
|
|
9835
|
+
* build` does not regenerate it. So the artefact froze whatever
|
|
9836
|
+
* `undoCaptureEnabled()` answered on the developer's machine — on, by its
|
|
9837
|
+
* NODE_ENV default — while the production process it ships to answers off and
|
|
9838
|
+
* binds nothing. The failure appears only after deploy, and only when someone
|
|
9839
|
+
* presses undo.
|
|
9840
|
+
*
|
|
9841
|
+
* The surface therefore reads ONLY the explicit declaration: `VOLTRO_UNDO=off`
|
|
9842
|
+
* removes it everywhere (codegen + both boot paths agree, because nothing in
|
|
9843
|
+
* the answer depends on when or where the code ran), and anything else keeps
|
|
9844
|
+
* it. Whether the mutations are actually RECORDED stays environment-aware —
|
|
9845
|
+
* that is the cost decision, and it is a genuine one. With capture off the
|
|
9846
|
+
* executors answer honestly rather than 404: the log is empty because nothing
|
|
9847
|
+
* was captured, and an apply/redo cannot find its row.
|
|
9848
|
+
*/
|
|
9849
|
+
export declare const undoSurfaceEnabled: (env?: Record<string, string | undefined>) => boolean;
|
|
9850
|
+
|
|
8725
9851
|
/** One `source:` entry that resolves to no declared table. */
|
|
8726
9852
|
export declare interface UnresolvedSource {
|
|
8727
9853
|
/** The procedure that declares it. */
|
|
@@ -8730,6 +9856,9 @@ export declare interface UnresolvedSource {
|
|
|
8730
9856
|
readonly source: string;
|
|
8731
9857
|
/** A declared table whose name is close — the rename case, usually. */
|
|
8732
9858
|
readonly didYouMean: string | undefined;
|
|
9859
|
+
/** True when the name is in the `channel:` namespace: it named a reactivity
|
|
9860
|
+
* channel nobody declared, which is a different repair from a stale table. */
|
|
9861
|
+
readonly channel: boolean;
|
|
8733
9862
|
}
|
|
8734
9863
|
|
|
8735
9864
|
/**
|
|
@@ -8743,6 +9872,14 @@ export declare const unresolvedSources: (procedures: ReadonlyArray<{
|
|
|
8743
9872
|
readonly source: string | ReadonlyArray<string> | undefined;
|
|
8744
9873
|
}>, declared: ReadonlySet<string>) => ReadonlyArray<UnresolvedSource>;
|
|
8745
9874
|
|
|
9875
|
+
/** Thrown at boot; the message is the whole point, so it names the path and the
|
|
9876
|
+
* three ways out. */
|
|
9877
|
+
export declare class UnverifiedWebhookRoute extends Error {
|
|
9878
|
+
readonly path: string;
|
|
9879
|
+
readonly name = "UnverifiedWebhookRoute";
|
|
9880
|
+
constructor(path: string);
|
|
9881
|
+
}
|
|
9882
|
+
|
|
8746
9883
|
export declare class UpdateBuilder {
|
|
8747
9884
|
private readonly backend;
|
|
8748
9885
|
private readonly scope;
|
|
@@ -8800,6 +9937,11 @@ export declare const useExperiment: (def: ExperimentDefinition) => Effect.Effect
|
|
|
8800
9937
|
|
|
8801
9938
|
declare type VaultStore = Pick<DataStore, 'query' | 'insert' | 'update' | 'updateMany' | 'delete' | 'upsert'>;
|
|
8802
9939
|
|
|
9940
|
+
/** A mounted incoming-webhook handler that has declared how it authenticates. */
|
|
9941
|
+
export declare type VerificationDeclared<T> = T & {
|
|
9942
|
+
readonly [WEBHOOK_VERIFICATION_PROPERTY]: WebhookVerification;
|
|
9943
|
+
};
|
|
9944
|
+
|
|
8803
9945
|
/** The pure core of live revocation: given a subject + action + a row-set,
|
|
8804
9946
|
* return ONLY the rows the subject may still see. The reactive matcher calls
|
|
8805
9947
|
* this on a permission-changing write and diffs against the prior visible set;
|
|
@@ -8830,6 +9972,16 @@ export declare const VOLTRO_SOFT_DELETE_MIXIN_ID: "voltro/softDelete";
|
|
|
8830
9972
|
|
|
8831
9973
|
export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
8832
9974
|
|
|
9975
|
+
/**
|
|
9976
|
+
* One pending (or historical) approval intent.
|
|
9977
|
+
*
|
|
9978
|
+
* Reactive on purpose — the requester's client subscribes to
|
|
9979
|
+
* `__voltro.approvals.pending` and sees its own row flip `pending → approved`
|
|
9980
|
+
* without polling, which is the whole answer to "what does the caller see
|
|
9981
|
+
* meanwhile".
|
|
9982
|
+
*/
|
|
9983
|
+
export declare const _voltroApprovalsTable: TableLike;
|
|
9984
|
+
|
|
8833
9985
|
/**
|
|
8834
9986
|
* `_voltro_connection_grants` — an IN-FLIGHT oauth2 handshake. Separate from
|
|
8835
9987
|
* the credential table on purpose: a handshake is a short-lived, single-use
|
|
@@ -8966,10 +10118,22 @@ export declare interface WakeupRef {
|
|
|
8966
10118
|
* next register cleanly overwrites it). */
|
|
8967
10119
|
export declare type WakeupStatus = 'pending' | 'fired';
|
|
8968
10120
|
|
|
10121
|
+
/** The property `mountIncomingWebhook` stamps onto the handler it returns.
|
|
10122
|
+
* A plain string property rather than a symbol so it survives the structural
|
|
10123
|
+
* hand-off between packages that do not import each other. */
|
|
10124
|
+
export declare const WEBHOOK_VERIFICATION_PROPERTY: "voltroWebhookVerification";
|
|
10125
|
+
|
|
8969
10126
|
/** Stage 4 webhook-route surface — kept transport-agnostic so the
|
|
8970
10127
|
* plugin can pipe its IncomingResponse through unchanged. The
|
|
8971
10128
|
* rpc server reads the request body (UTF-8 / binary) + headers
|
|
8972
|
-
* and dispatches by path.
|
|
10129
|
+
* and dispatches by path.
|
|
10130
|
+
*
|
|
10131
|
+
* `handle` MUST carry a `voltroWebhookVerification` declaration — the
|
|
10132
|
+
* property `@voltro/plugin-webhooks`' `mountIncomingWebhook` stamps onto
|
|
10133
|
+
* the handler it returns. `startRpcServer` refuses to mount a route
|
|
10134
|
+
* without one (see `webhookVerification.ts`): an incoming webhook is a
|
|
10135
|
+
* public POST that runs application code, so "nothing verifies it" has to
|
|
10136
|
+
* be a decision somebody wrote down, not the default. */
|
|
8973
10137
|
export declare interface WebhookRouteHandler {
|
|
8974
10138
|
readonly handle: (request: {
|
|
8975
10139
|
readonly method: string;
|
|
@@ -8985,6 +10149,21 @@ export declare interface WebhookRouteHandler {
|
|
|
8985
10149
|
|
|
8986
10150
|
export declare type WebhooksAppContext = unknown;
|
|
8987
10151
|
|
|
10152
|
+
/** How an incoming webhook authenticates its caller. */
|
|
10153
|
+
export declare type WebhookVerification =
|
|
10154
|
+
/** The framework verifies an HMAC signature (+ replay window) before the
|
|
10155
|
+
* handler runs, using the webhook's configured shared secret. */
|
|
10156
|
+
'signature'
|
|
10157
|
+
/** The handler / provider integration verifies with the provider's own SDK
|
|
10158
|
+
* (Stripe's `constructEvent`, etc.). The framework does not second-guess it. */
|
|
10159
|
+
| 'provider'
|
|
10160
|
+
/** Deliberately unverified — the endpoint is behind a separate trust boundary
|
|
10161
|
+
* (gateway + IP allow-list). A visible decision, never a default. */
|
|
10162
|
+
| 'none';
|
|
10163
|
+
|
|
10164
|
+
/** Read the declaration off a mounted handler. `undefined` → never declared. */
|
|
10165
|
+
export declare const webhookVerificationOf: (handle: unknown) => WebhookVerification | undefined;
|
|
10166
|
+
|
|
8988
10167
|
/**
|
|
8989
10168
|
* Comparison operators accepted by the ergonomic `.where(col, op, value)`
|
|
8990
10169
|
* form. `fts` falls back to a `contains` (LIKE) match here; the index-backed
|
|
@@ -9197,6 +10376,26 @@ export declare interface WorkflowFacadeOptions {
|
|
|
9197
10376
|
* workflow that does not batch, which is the common case and costs nothing.
|
|
9198
10377
|
*/
|
|
9199
10378
|
readonly startPayloadSchema?: (workflowName: string) => unknown | undefined;
|
|
10379
|
+
/**
|
|
10380
|
+
* Park a `start(..., { at })` as a durable pending row — see
|
|
10381
|
+
* {@link WorkflowStartOptions.at}.
|
|
10382
|
+
*
|
|
10383
|
+
* Injected for the same reason `admitStart` is: the runtime stays
|
|
10384
|
+
* storage-agnostic and the CLI owns the pending table. Absent ⇒ `{ at }`
|
|
10385
|
+
* throws rather than degrading to an in-process timer, because a timer dies
|
|
10386
|
+
* with the process and a delayed start that silently became a maybe-start is
|
|
10387
|
+
* the failure this option exists to rule out.
|
|
10388
|
+
*/
|
|
10389
|
+
readonly parkDelayedStart?: (input: {
|
|
10390
|
+
readonly workflowName: string;
|
|
10391
|
+
readonly payload: unknown;
|
|
10392
|
+
readonly callerContext: WorkflowCallerContext | undefined;
|
|
10393
|
+
/** Epoch ms. Always strictly in the future — the facade handles the past. */
|
|
10394
|
+
readonly at: number;
|
|
10395
|
+
}) => Promise<{
|
|
10396
|
+
readonly intentId: string;
|
|
10397
|
+
readonly dueAt: number;
|
|
10398
|
+
}>;
|
|
9200
10399
|
}
|
|
9201
10400
|
|
|
9202
10401
|
export declare interface WorkflowLayerExecutionContext {
|
|
@@ -9209,6 +10408,31 @@ export declare interface WorkflowLayerExecutionContext {
|
|
|
9209
10408
|
export declare interface WorkflowLayerOptions<Context> {
|
|
9210
10409
|
readonly buildContext: (callerContext: ResolvedWorkflowCallerContext, execution: WorkflowLayerExecutionContext) => Context;
|
|
9211
10410
|
readonly resolveStartContext?: (workflowName: string, executionId: string) => WorkflowCallerContext | undefined | Promise<WorkflowCallerContext | undefined>;
|
|
10411
|
+
/**
|
|
10412
|
+
* Put AUTHORITY back on a restored caller identity, at execution time
|
|
10413
|
+
* (REL-24).
|
|
10414
|
+
*
|
|
10415
|
+
* `resolveStartContext` returns IDENTITY — the start-context table strips
|
|
10416
|
+
* scopes on write and on read, because a `json()` column read by another
|
|
10417
|
+
* runner days later is authority frozen and made durable, which is REL-5's
|
|
10418
|
+
* cookie one layer down. This hook is what re-establishes the authority, from
|
|
10419
|
+
* the app's live source, on every execution attempt.
|
|
10420
|
+
*
|
|
10421
|
+
* Called ONLY for a run that has a recorded caller. A bootstrap run — no
|
|
10422
|
+
* recorded context — is `SYSTEM_SUBJECT` and is not put through it:
|
|
10423
|
+
* `SYSTEM_SUBJECT` already states its own authority, and running it through
|
|
10424
|
+
* an app resolver keyed on a null id would be asking a question with no
|
|
10425
|
+
* answer.
|
|
10426
|
+
*
|
|
10427
|
+
* Absent ⇒ the identity runs with no scopes. That is the fail-closed
|
|
10428
|
+
* direction and it matches what a cookie-authenticated request already gets
|
|
10429
|
+
* from an app that wires no resolver.
|
|
10430
|
+
*
|
|
10431
|
+
* Rejecting FAILS the attempt. Do not swallow it into "fewer scopes": a run
|
|
10432
|
+
* that skips the branch it was not allowed to take looks exactly like one
|
|
10433
|
+
* whose business logic said no.
|
|
10434
|
+
*/
|
|
10435
|
+
readonly resolveAuthority?: (identity: unknown) => Promise<unknown>;
|
|
9212
10436
|
}
|
|
9213
10437
|
|
|
9214
10438
|
/**
|
|
@@ -9416,6 +10640,30 @@ export declare class WorkflowSingletonHeldError extends Error {
|
|
|
9416
10640
|
|
|
9417
10641
|
export declare interface WorkflowStartOptions {
|
|
9418
10642
|
readonly wait?: boolean;
|
|
10643
|
+
/**
|
|
10644
|
+
* Delayed one-off start: park this start DURABLY and let it arrive at `at`.
|
|
10645
|
+
*
|
|
10646
|
+
* `at` is an absolute instant, deliberately — not a `delay` duration. A delay
|
|
10647
|
+
* is measured "from when?" (enqueue? admission? retry?), and every queueing
|
|
10648
|
+
* system answers that differently (Temporal spells it `startDelay`, BullMQ
|
|
10649
|
+
* `delay`, each from its own clock); an instant has no such ambiguity, it
|
|
10650
|
+
* survives the park/restart boundary byte-identically, and it composes with
|
|
10651
|
+
* the schedule/backfill surfaces, which are also instant-based. A relative
|
|
10652
|
+
* delay is one line at the call site: `at: new Date(Date.now() + ms)`.
|
|
10653
|
+
*
|
|
10654
|
+
* Semantics: the start is a durable `_voltro_workflow_pending` row
|
|
10655
|
+
* (`mode: 'delayed'`) fired by the coordinated drainer — it survives restarts
|
|
10656
|
+
* and fires on whichever replica drains. At `at` it becomes an ordinary
|
|
10657
|
+
* ARRIVAL, so declared flow control (debounce, singleton, rateLimit, …)
|
|
10658
|
+
* judges it as of that moment; `at` never bypasses a control. The returned
|
|
10659
|
+
* handle has `status: 'queued'` with `deferral: { mode: 'delayed', dueAt }`.
|
|
10660
|
+
*
|
|
10661
|
+
* An `at` in the past (or now) starts immediately — the caller said "no
|
|
10662
|
+
* earlier than", and that is already true. Combining `at` with
|
|
10663
|
+
* `wait: true` is refused: there is no result to block on for a start that
|
|
10664
|
+
* exists only as a future row.
|
|
10665
|
+
*/
|
|
10666
|
+
readonly at?: Date;
|
|
9419
10667
|
/* Excluded from this release type: callerContext */
|
|
9420
10668
|
/* Excluded from this release type: admitted */
|
|
9421
10669
|
}
|