@voltro/runtime 0.55.0 → 0.57.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 +691 -0
- package/dist/index.d.ts +341 -37
- package/dist/index.js +2092 -1830
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ import { Sampler } from '@opentelemetry/sdk-trace-base';
|
|
|
78
78
|
import { Schedule } from 'effect';
|
|
79
79
|
import { Schema } from 'effect';
|
|
80
80
|
import { ScopeError } from '@voltro/protocol';
|
|
81
|
+
import { Server } from 'node:http';
|
|
81
82
|
import { ServerOnlyColumnWrite } from '@voltro/protocol';
|
|
82
83
|
import { SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
83
84
|
import { spawn } from 'node:child_process';
|
|
@@ -1171,6 +1172,9 @@ export declare const applyInverse: (store: UndoApplyStore, op: InverseOp) => Pro
|
|
|
1171
1172
|
* `synthesizeInverse`), as the caller's transaction wraps them. */
|
|
1172
1173
|
export declare const applyInverses: (store: UndoApplyStore, ops: ReadonlyArray<InverseOp>) => Promise<void>;
|
|
1173
1174
|
|
|
1175
|
+
/** Apply the timeouts to a node HTTP server. */
|
|
1176
|
+
export declare const applyKeepAlive: (server: Server, t: KeepAliveTimeouts) => void;
|
|
1177
|
+
|
|
1174
1178
|
/**
|
|
1175
1179
|
* Apply every rule matching `change`.
|
|
1176
1180
|
*
|
|
@@ -2947,6 +2951,18 @@ export declare const DEFAULT_DELIVERY_CONCURRENCY = 8;
|
|
|
2947
2951
|
* framework default. */
|
|
2948
2952
|
export declare const DEFAULT_HSTS = "max-age=15552000; includeSubDomains";
|
|
2949
2953
|
|
|
2954
|
+
/**
|
|
2955
|
+
* 72 seconds.
|
|
2956
|
+
*
|
|
2957
|
+
* The bound that matters is the PROXY's idle timeout, and the defaults cluster
|
|
2958
|
+
* at 60s (ALB, Envoy) and 75s (nginx). 72s clears the 60s group with margin and
|
|
2959
|
+
* sits just under nginx's — a deployment on nginx should raise this, and the
|
|
2960
|
+
* doc says so. Picking 76s instead would leave every ALB user holding a
|
|
2961
|
+
* connection the load balancer has already dropped, which is the same defect
|
|
2962
|
+
* mirrored; the 60s group is the larger one.
|
|
2963
|
+
*/
|
|
2964
|
+
export declare const DEFAULT_KEEP_ALIVE_MS = 72000;
|
|
2965
|
+
|
|
2950
2966
|
/** Ceiling the idle backoff climbs to. Deliberately short enough to be a
|
|
2951
2967
|
* FLOOR under a missed wake rather than a substitute for one. */
|
|
2952
2968
|
export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
@@ -2955,6 +2971,15 @@ export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
|
2955
2971
|
* small enough to stop a pathological body being buffered into memory. */
|
|
2956
2972
|
export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
2957
2973
|
|
|
2974
|
+
/** The watchdog bound a schedule gets when it declares none. Exported
|
|
2975
|
+
* because the overlap guard dates an un-beating run against it — see
|
|
2976
|
+
* `peerRunIsAlive`; a second copy of the number there would be a second
|
|
2977
|
+
* definition nothing keeps in step. */
|
|
2978
|
+
export declare const DEFAULT_MAX_RUNTIME_MS: number;
|
|
2979
|
+
|
|
2980
|
+
/** See {@link DrainDeps.claimLeaseMs}. */
|
|
2981
|
+
export declare const DEFAULT_OUTBOX_CLAIM_LEASE_MS: number;
|
|
2982
|
+
|
|
2958
2983
|
/**
|
|
2959
2984
|
* How many raw-SQL reads one request records for the undeclared-`dependsOn`
|
|
2960
2985
|
* diagnostic. A bound rather than none: the recording lives for as long as the
|
|
@@ -2983,6 +3008,25 @@ export declare const DEFAULT_RESUME_WINDOW_MS = 60000;
|
|
|
2983
3008
|
*/
|
|
2984
3009
|
export declare const DEFAULT_ROW_FILTER_RETRY: Schedule.Schedule<unknown, unknown>;
|
|
2985
3010
|
|
|
3011
|
+
/**
|
|
3012
|
+
* How often a run in flight bumps `heartbeatAt`.
|
|
3013
|
+
*
|
|
3014
|
+
* The default lives HERE, next to the only code that reads it, and
|
|
3015
|
+
* `cli/src/schedulingConfig.ts` imports it as the default of the
|
|
3016
|
+
* `scheduling.scheduleHeartbeatMs` field. One definition — a second one in the
|
|
3017
|
+
* resolver is how a config default and a library default come to disagree
|
|
3018
|
+
* about a number neither of them prints.
|
|
3019
|
+
*
|
|
3020
|
+
* 30 s rather than the couple of seconds `_voltro_replace_in_progress` beats
|
|
3021
|
+
* at, because the two are bounding different things. A bulk load's beat decides
|
|
3022
|
+
* whether a boot may DROP staging tables, so its cost is worth paying to keep
|
|
3023
|
+
* the window tight. This one decides how long a dead run holds its schedule
|
|
3024
|
+
* shut, measured against a cron period — a minute and a half of latency on a
|
|
3025
|
+
* half-hourly job is one slot, and a run shorter than one interval writes no
|
|
3026
|
+
* beat at all and costs nothing.
|
|
3027
|
+
*/
|
|
3028
|
+
export declare const DEFAULT_SCHEDULE_HEARTBEAT_MS = 30000;
|
|
3029
|
+
|
|
2986
3030
|
/** Defaults, overridable via `app.config.ts` `reactive.socket.*` (+ env) —
|
|
2987
3031
|
* resolved by the boot paths' tunables resolver. */
|
|
2988
3032
|
export declare const DEFAULT_SOCKET_MAX_BUFFERED_BYTES = 1048576;
|
|
@@ -2991,6 +3035,15 @@ export declare const DEFAULT_SOCKET_OVERRUN_AFTER_MS = 10000;
|
|
|
2991
3035
|
|
|
2992
3036
|
export declare const DEFAULT_SOCKET_OVERSIZED_EVENT_BYTES = 262144;
|
|
2993
3037
|
|
|
3038
|
+
/**
|
|
3039
|
+
* How old a row may be before a reader should stop trusting it as current.
|
|
3040
|
+
*
|
|
3041
|
+
* Tunable rather than a constant because the write cadence is: a fleet that
|
|
3042
|
+
* writes every 30s and one that writes every 5m disagree about what "stale"
|
|
3043
|
+
* means, and a single number would be wrong for one of them.
|
|
3044
|
+
*/
|
|
3045
|
+
export declare const DEFAULT_STALE_AFTER_MS = 120000;
|
|
3046
|
+
|
|
2994
3047
|
/**
|
|
2995
3048
|
* The message of an UNDECLARED throw, bounded, with nothing else attached.
|
|
2996
3049
|
*
|
|
@@ -3436,13 +3489,39 @@ export declare interface DispatcherDependencies {
|
|
|
3436
3489
|
export declare const DORMANCY_WAKEUP_TENANT = "default";
|
|
3437
3490
|
|
|
3438
3491
|
export declare interface DrainDeps {
|
|
3439
|
-
readonly store: Pick<DataStore, 'query' | 'update' | 'insert' | 'delete'>;
|
|
3492
|
+
readonly store: Pick<DataStore, 'query' | 'update' | 'updateMany' | 'insert' | 'delete'>;
|
|
3440
3493
|
readonly handlers: ReadonlyMap<string, OutboxHandlerDefinition>;
|
|
3441
3494
|
readonly now?: () => Date;
|
|
3442
3495
|
/** Max rows per drain pass. */
|
|
3443
3496
|
readonly batchSize?: number;
|
|
3444
3497
|
/** Per-entry cap on retained attempt rows. See {@link OUTBOX_ATTEMPT_LOG_CAP}. */
|
|
3445
3498
|
readonly attemptLogCap?: number;
|
|
3499
|
+
/**
|
|
3500
|
+
* Who this process is, stamped on every row it claims.
|
|
3501
|
+
*
|
|
3502
|
+
* REQUIRED, and deliberately so. The first version made it optional "so an
|
|
3503
|
+
* embedder driving a single process is not forced to invent an identity",
|
|
3504
|
+
* which meant a caller who omitted it silently got the old behaviour: every
|
|
3505
|
+
* replica delivers every row. An exactly-once gate that can be switched off by
|
|
3506
|
+
* leaving a field out is not a gate — it is a default, and the wrong one.
|
|
3507
|
+
*
|
|
3508
|
+
* The INSTANCE rather than the replica name: a pod that restarts under a
|
|
3509
|
+
* stable name must not look like it still holds the claim it died inside, or
|
|
3510
|
+
* the lease is reading its own ghost.
|
|
3511
|
+
*/
|
|
3512
|
+
readonly claimedBy: string;
|
|
3513
|
+
/**
|
|
3514
|
+
* How long a claim stands before another replica may take the row.
|
|
3515
|
+
*
|
|
3516
|
+
* It is a LEASE, not a lock: the holder can die, and nothing it left behind
|
|
3517
|
+
* may block delivery forever. It must therefore exceed the slowest handler
|
|
3518
|
+
* you are willing to wait for — a lease shorter than a real delivery hands
|
|
3519
|
+
* the same row to a second replica while the first is still working, which is
|
|
3520
|
+
* the duplicate this whole mechanism removes, reintroduced by a number.
|
|
3521
|
+
*
|
|
3522
|
+
* Default 5 minutes.
|
|
3523
|
+
*/
|
|
3524
|
+
readonly claimLeaseMs?: number;
|
|
3446
3525
|
}
|
|
3447
3526
|
|
|
3448
3527
|
/**
|
|
@@ -3474,14 +3553,6 @@ export declare const drainForShutdown: (options: {
|
|
|
3474
3553
|
}) => void;
|
|
3475
3554
|
}) => void;
|
|
3476
3555
|
|
|
3477
|
-
/**
|
|
3478
|
-
* One drain pass: claim due rows, run their handler, record the outcome.
|
|
3479
|
-
*
|
|
3480
|
-
* A row whose handler is unknown is left PENDING rather than dead-lettered —
|
|
3481
|
-
* the usual cause is a deploy where the enqueuing code shipped before the
|
|
3482
|
-
* handler, and discarding those would turn a rollout ordering detail into
|
|
3483
|
-
* permanent data loss.
|
|
3484
|
-
*/
|
|
3485
3556
|
export declare const drainOutbox: (deps: DrainDeps) => Promise<DrainResult>;
|
|
3486
3557
|
|
|
3487
3558
|
export declare interface DrainResult {
|
|
@@ -4544,6 +4615,38 @@ export declare interface FinopsRunnerHandle {
|
|
|
4544
4615
|
readonly runTick: () => void;
|
|
4545
4616
|
}
|
|
4546
4617
|
|
|
4618
|
+
/** A member of the fleet, as membership sees it. The shape this module needs
|
|
4619
|
+
* from `instanceMembership`, named so the merge can be tested without one. */
|
|
4620
|
+
export declare interface FleetMember {
|
|
4621
|
+
readonly instanceId: string;
|
|
4622
|
+
readonly startedAt: number;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
export declare interface FleetMergeResult<T> {
|
|
4626
|
+
/** One entry per replica that has written, newest generation only. */
|
|
4627
|
+
readonly byReplica: ReadonlyArray<{
|
|
4628
|
+
readonly replicaId: string;
|
|
4629
|
+
readonly version: string | undefined;
|
|
4630
|
+
readonly ageMs: number;
|
|
4631
|
+
/** Where a peer can reach it, or `undefined`. */
|
|
4632
|
+
readonly reachableAt: string | undefined;
|
|
4633
|
+
readonly payload: T;
|
|
4634
|
+
}>;
|
|
4635
|
+
/** Replicas membership knows about that have written nothing readable. */
|
|
4636
|
+
readonly missing: ReadonlyArray<string>;
|
|
4637
|
+
readonly responded: number;
|
|
4638
|
+
readonly expected: number;
|
|
4639
|
+
/** Rows old enough that a reader should not treat them as current. */
|
|
4640
|
+
readonly stale: ReadonlyArray<{
|
|
4641
|
+
readonly replicaId: string;
|
|
4642
|
+
readonly ageMs: number;
|
|
4643
|
+
}>;
|
|
4644
|
+
/** Framework versions among the responders, counted. Present only when they
|
|
4645
|
+
* DIFFER — a single-version fleet needs no such note, and a note nobody
|
|
4646
|
+
* needs is one everybody learns to skip. */
|
|
4647
|
+
readonly versions: Readonly<Record<string, number>> | undefined;
|
|
4648
|
+
}
|
|
4649
|
+
|
|
4547
4650
|
export declare interface FluentStore extends Omit<MutationStore, 'update' | 'delete' | 'query'> {
|
|
4548
4651
|
/**
|
|
4549
4652
|
* Diff-based many-to-many link writer for a junction table. `anchor` names the
|
|
@@ -4665,6 +4768,17 @@ export declare const forbiddenColumnsForSubject: (tableName: string, subject: Su
|
|
|
4665
4768
|
/** Forget the calling subject's credential. Returns whether a row was removed. */
|
|
4666
4769
|
export declare const forgetCredential: (store: VaultStore, connectionId: string, subjectId: string) => Promise<boolean>;
|
|
4667
4770
|
|
|
4771
|
+
/**
|
|
4772
|
+
* Drop rows for a replica GENERATION that is gone.
|
|
4773
|
+
*
|
|
4774
|
+
* Not a retention sweep — the table has no history to age out. This removes
|
|
4775
|
+
* the row of a process that restarted, because the replica's NEW row is
|
|
4776
|
+
* written under the same `(replicaId, kind)` key and the old generation is
|
|
4777
|
+
* simply overwritten. What this handles is a replica that went away for good
|
|
4778
|
+
* (a scale-down), whose row would otherwise be counted forever.
|
|
4779
|
+
*/
|
|
4780
|
+
export declare const forgetReplicaObservations: (store: DataStore, replicaId: string) => Promise<void>;
|
|
4781
|
+
|
|
4668
4782
|
/** A boot-ready message for a set of cache-scope leaks (empty → `undefined`). */
|
|
4669
4783
|
export declare const formatCacheScopeLeaks: (leaks: ReadonlyArray<CacheScopeLeak>) => string | undefined;
|
|
4670
4784
|
|
|
@@ -4776,6 +4890,9 @@ export declare type HandlerBody = void | Promise<unknown> | Effect.Effect<unknow
|
|
|
4776
4890
|
*/
|
|
4777
4891
|
export declare const hashUnitInterval: (s: string) => number;
|
|
4778
4892
|
|
|
4893
|
+
/** How much longer than `keepAliveTimeout` the headers deadline runs. */
|
|
4894
|
+
export declare const HEADERS_TIMEOUT_MARGIN_MS = 5000;
|
|
4895
|
+
|
|
4779
4896
|
/** Define a histogram. `boundaries` default to the framework duration buckets. */
|
|
4780
4897
|
export declare const histogramMetric: (name: string, boundaries?: MetricBoundaries.MetricBoundaries, description?: string) => Metric.Metric.Histogram<number>;
|
|
4781
4898
|
|
|
@@ -5294,6 +5411,23 @@ export declare interface JunctionLinks {
|
|
|
5294
5411
|
}>;
|
|
5295
5412
|
}
|
|
5296
5413
|
|
|
5414
|
+
/** The `http:` block's keep-alive knobs. */
|
|
5415
|
+
export declare interface KeepAliveConfig {
|
|
5416
|
+
readonly http?: {
|
|
5417
|
+
/** Idle keep-alive lifetime, ms. Default 72000. Raise it above YOUR
|
|
5418
|
+
* proxy's idle timeout — see `DEFAULT_KEEP_ALIVE_MS` for why 72s. */
|
|
5419
|
+
readonly keepAliveTimeoutMs?: number;
|
|
5420
|
+
/** Header-completion deadline, ms. Must exceed `keepAliveTimeoutMs`.
|
|
5421
|
+
* Default `keepAliveTimeoutMs + 5000`. */
|
|
5422
|
+
readonly headersTimeoutMs?: number;
|
|
5423
|
+
};
|
|
5424
|
+
}
|
|
5425
|
+
|
|
5426
|
+
export declare interface KeepAliveTimeouts {
|
|
5427
|
+
readonly keepAliveTimeoutMs: number;
|
|
5428
|
+
readonly headersTimeoutMs: number;
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5297
5431
|
export declare interface KvFacade {
|
|
5298
5432
|
readonly kv: AsyncKv;
|
|
5299
5433
|
/** The resolved Effect-native `Kv` service instance. Its methods close over
|
|
@@ -6029,6 +6163,25 @@ export declare const memoryRywPositionStore: (options?: MemoryRywOptions) => Ryw
|
|
|
6029
6163
|
*/
|
|
6030
6164
|
export declare const mergeCookieHeader: (existing: string | undefined, patch: Readonly<Record<string, string>>) => string;
|
|
6031
6165
|
|
|
6166
|
+
/**
|
|
6167
|
+
* Merge rows into a fleet view, and say what is missing from it.
|
|
6168
|
+
*
|
|
6169
|
+
* The three things that make this honest, each of which a naive `rows.map()`
|
|
6170
|
+
* gets wrong:
|
|
6171
|
+
*
|
|
6172
|
+
* 1. **A replica membership knows about but that has no row is MISSING**, not
|
|
6173
|
+
* absent. Dropping it silently is the unmarked sample this whole feature
|
|
6174
|
+
* exists to end — one level up, and more expensive, because the reader now
|
|
6175
|
+
* believes they asked everybody.
|
|
6176
|
+
* 2. **A stale row is reported WITH its age** rather than filtered out.
|
|
6177
|
+
* Removing it would make a partial answer look complete; keeping it
|
|
6178
|
+
* unmarked would make fiction look current. Neither is available.
|
|
6179
|
+
* 3. **Mixed versions are named.** During a rolling deploy the fleet genuinely
|
|
6180
|
+
* spans two shapes, and an aggregate that averages them silently is wrong
|
|
6181
|
+
* in a way nothing downstream can detect.
|
|
6182
|
+
*/
|
|
6183
|
+
export declare const mergeFleetObservations: <T>(rows: ReadonlyArray<ReplicaObservationRow>, members: ReadonlyArray<FleetMember>, staleAfterMs?: number) => FleetMergeResult<T>;
|
|
6184
|
+
|
|
6032
6185
|
export { MetricBoundaries }
|
|
6033
6186
|
|
|
6034
6187
|
export declare interface MetricBucketPoint {
|
|
@@ -6328,6 +6481,15 @@ export declare const noteRead: (descriptor: QueryDescriptor) => void;
|
|
|
6328
6481
|
*/
|
|
6329
6482
|
export declare const noteResumeVerdict: (rawLabel: string, verdict: true | ResumeExclusion) => void;
|
|
6330
6483
|
|
|
6484
|
+
/**
|
|
6485
|
+
* Note that a row filter was resolved on `arm`, and say so the FIRST time.
|
|
6486
|
+
*
|
|
6487
|
+
* Exported so the arms that are not in this module (the serve pipeline) report
|
|
6488
|
+
* through the same counter — four arms reporting four ways is how a reader comes
|
|
6489
|
+
* to believe three of them are covered.
|
|
6490
|
+
*/
|
|
6491
|
+
export declare const noteRowFilterResolved: (arm: string, tables: number | undefined) => void;
|
|
6492
|
+
|
|
6331
6493
|
export declare interface Notification {
|
|
6332
6494
|
readonly subscriberId: string;
|
|
6333
6495
|
readonly fingerprint: string;
|
|
@@ -6665,6 +6827,42 @@ export declare const payloadPropertyNames: (schema: Schema.Schema.Any) => {
|
|
|
6665
6827
|
readonly optional: ReadonlyArray<string>;
|
|
6666
6828
|
};
|
|
6667
6829
|
|
|
6830
|
+
/**
|
|
6831
|
+
* Is a `running` row on ANOTHER replica a live run, or a corpse?
|
|
6832
|
+
*
|
|
6833
|
+
* Pure, and separated from the query for the same reason
|
|
6834
|
+
* `stagingLeftovers` is: the rule is four lines and each of them has a way to
|
|
6835
|
+
* be quietly wrong in a direction nobody sees. Wrong towards "alive" and a
|
|
6836
|
+
* schedule stops firing with `overlap skip` in the log; wrong towards "dead"
|
|
6837
|
+
* and two replicas run the same occurrence.
|
|
6838
|
+
*
|
|
6839
|
+
* ── The two cases, and why there are two ───────────────────────────────────
|
|
6840
|
+
*
|
|
6841
|
+
* **A beat is present.** Then the answer is the beat and nothing else: a live
|
|
6842
|
+
* handler bumps it every `heartbeatMs`, so silence longer than `STALE_BEATS`
|
|
6843
|
+
* of them is a process that is not coming back.
|
|
6844
|
+
*
|
|
6845
|
+
* **No beat.** A row written before this column existed — i.e. by a process
|
|
6846
|
+
* still running the previous version during a rolling deploy. `firedAt` is all
|
|
6847
|
+
* there is, and the honest bound on it is the schedule's OWN `maxRuntimeMs`:
|
|
6848
|
+
* the watchdog records a run that exceeds it as `failed`, so a row older than
|
|
6849
|
+
* that plus the stale window cannot belong to a process that is still going to
|
|
6850
|
+
* write a terminal status.
|
|
6851
|
+
*
|
|
6852
|
+
* **Neither is a usable instant** → NOT alive. Fail-open, deliberately, and it
|
|
6853
|
+
* is the pre-existing reading (`firedAt != null &&`). A row nobody can date
|
|
6854
|
+
* must not hold a schedule shut forever, and the counterweight is the claim
|
|
6855
|
+
* coordinator, which is what actually guarantees once-only for a clock firing —
|
|
6856
|
+
* this guard is the second line, not the first.
|
|
6857
|
+
*/
|
|
6858
|
+
export declare const peerRunIsAlive: (row: {
|
|
6859
|
+
readonly firedAt: Date | string | null;
|
|
6860
|
+
readonly heartbeatAt?: Date | string | null;
|
|
6861
|
+
}, now: number, bounds: {
|
|
6862
|
+
readonly heartbeatMs: number;
|
|
6863
|
+
readonly maxRuntimeMs: number;
|
|
6864
|
+
}) => boolean;
|
|
6865
|
+
|
|
6668
6866
|
/**
|
|
6669
6867
|
* The calling subject's approval work: intents they may DECIDE, plus intents
|
|
6670
6868
|
* they REQUESTED that are still live.
|
|
@@ -7044,10 +7242,21 @@ export declare class ReactionRateLimiter {
|
|
|
7044
7242
|
}
|
|
7045
7243
|
|
|
7046
7244
|
export declare interface ReactionRunDeps {
|
|
7047
|
-
/**
|
|
7245
|
+
/**
|
|
7246
|
+
* Atomically claim ONE logical change. `true` = ours, act. `false` = someone
|
|
7247
|
+
* already has it (or the claim could not be taken — fail-closed, like the
|
|
7248
|
+
* rate slot and the budget beside it).
|
|
7249
|
+
*
|
|
7250
|
+
* ONE operation, not `has` then `add`, and the difference is the whole
|
|
7251
|
+
* guarantee. A read-then-write cannot be won by exactly one caller: two
|
|
7252
|
+
* replicas both read "not seen", both act, and `dedupeKey` — the field
|
|
7253
|
+
* `defineReaction` REQUIRES precisely so an act cannot repeat — delivers N
|
|
7254
|
+
* acts on N replicas. It was worse than a missing gate, because it read as a
|
|
7255
|
+
* present one: the in-memory set behind it was per-process, so every test and
|
|
7256
|
+
* every single-instance run confirmed it worked.
|
|
7257
|
+
*/
|
|
7048
7258
|
readonly dedupe: {
|
|
7049
|
-
readonly
|
|
7050
|
-
readonly add: (key: string) => void | Promise<void>;
|
|
7259
|
+
readonly claim: (key: string) => boolean | Promise<boolean>;
|
|
7051
7260
|
};
|
|
7052
7261
|
/** Resolve + run the reaction's act AS the agent actor. Injected by the serve
|
|
7053
7262
|
* layer (it owns agent/workflow execution). */
|
|
@@ -7166,6 +7375,16 @@ export declare const readPollCeilingMs: (env?: {
|
|
|
7166
7375
|
/** Is anything listening? Call sites skip their work when nothing is. */
|
|
7167
7376
|
export declare const readRecordingActive: () => boolean;
|
|
7168
7377
|
|
|
7378
|
+
/**
|
|
7379
|
+
* Every replica's rows for one kind.
|
|
7380
|
+
*
|
|
7381
|
+
* `now` is injected so a caller can compute ages against one instant rather
|
|
7382
|
+
* than a moving clock — two rows compared against two `Date.now()` calls are
|
|
7383
|
+
* two measurements, and the difference between them is noise a reader would
|
|
7384
|
+
* read as signal.
|
|
7385
|
+
*/
|
|
7386
|
+
export declare const readReplicaObservations: (store: DataStore, kind: ReplicaObservationKind, now?: number) => Promise<ReadonlyArray<ReplicaObservationRow>>;
|
|
7387
|
+
|
|
7169
7388
|
/** `admin:full` (mirrors `@voltro/protocol`'s ADMIN_SCOPE) bypasses ReBAC. */
|
|
7170
7389
|
export declare const REBAC_ADMIN_SCOPE = "admin:full";
|
|
7171
7390
|
|
|
@@ -7591,6 +7810,8 @@ export declare interface RelationTuple {
|
|
|
7591
7810
|
*/
|
|
7592
7811
|
export declare const releaseWakeup: (store: DataStore, ref: WakeupRef) => Promise<void>;
|
|
7593
7812
|
|
|
7813
|
+
export declare const REPLICA_OBSERVATIONS_TABLE = "_voltro_replica_observations";
|
|
7814
|
+
|
|
7594
7815
|
export declare interface ReplicaHealth {
|
|
7595
7816
|
/** Whether this replica is currently eligible for reads. */
|
|
7596
7817
|
isHealthy(replica: DataStore): boolean;
|
|
@@ -7620,6 +7841,27 @@ export declare interface ReplicaHealthOptions {
|
|
|
7620
7841
|
readonly now?: () => number;
|
|
7621
7842
|
}
|
|
7622
7843
|
|
|
7844
|
+
/** Which observation a row carries. A closed set — a new kind is a deliberate
|
|
7845
|
+
* addition with a reader, not a free-form label. */
|
|
7846
|
+
export declare type ReplicaObservationKind = 'resume' | 'subscriptions';
|
|
7847
|
+
|
|
7848
|
+
/** One replica's row, as a reader gets it back. */
|
|
7849
|
+
export declare interface ReplicaObservationRow {
|
|
7850
|
+
readonly replicaId: string;
|
|
7851
|
+
readonly startedAt: number;
|
|
7852
|
+
readonly version: string | undefined;
|
|
7853
|
+
readonly kind: ReplicaObservationKind;
|
|
7854
|
+
readonly payload: unknown;
|
|
7855
|
+
/** Where a peer can reach it, or `undefined` — never a guess. */
|
|
7856
|
+
readonly reachableAt: string | undefined;
|
|
7857
|
+
readonly writtenAt: number;
|
|
7858
|
+
/** How old this row is, on the READER's clock. Approximate by construction —
|
|
7859
|
+
* it compares two machines' clocks, which is exactly what
|
|
7860
|
+
* `instanceMembership` refuses to do for liveness. It is good enough to
|
|
7861
|
+
* say "this is minutes old", and must never be used to order two rows. */
|
|
7862
|
+
readonly ageMs: number;
|
|
7863
|
+
}
|
|
7864
|
+
|
|
7623
7865
|
/** Captured per-read for observability. Pushed into trace buffers
|
|
7624
7866
|
* by the runtime so `voltro traces` shows the routing decision +
|
|
7625
7867
|
* any RYW wait latency. */
|
|
@@ -7773,6 +8015,11 @@ export declare const resetResumeCensus: () => void;
|
|
|
7773
8015
|
/** Test seam. */
|
|
7774
8016
|
export declare const resetResumeRingsForTest: () => void;
|
|
7775
8017
|
|
|
8018
|
+
/** Test helper: forget which arms have reported. Not for production — the set
|
|
8019
|
+
* is a record of what this PROCESS has proven, and forgetting it would make
|
|
8020
|
+
* the line repeat and the list lie. */
|
|
8021
|
+
export declare const resetRowFilterResolvedArms: () => void;
|
|
8022
|
+
|
|
7776
8023
|
/** Reset to the env default (tests). */
|
|
7777
8024
|
export declare const resetSecretsBackend: () => void;
|
|
7778
8025
|
|
|
@@ -7919,6 +8166,8 @@ export declare interface ResolvedWorkflowCallerContext extends WorkflowCallerCon
|
|
|
7919
8166
|
readonly traceId: string;
|
|
7920
8167
|
}
|
|
7921
8168
|
|
|
8169
|
+
export declare const resolveKeepAlive: (config: KeepAliveConfig | null | undefined) => KeepAliveTimeouts;
|
|
8170
|
+
|
|
7922
8171
|
/**
|
|
7923
8172
|
* Resolve the guard's configuration.
|
|
7924
8173
|
*
|
|
@@ -7957,31 +8206,6 @@ export declare const resolveRequestIsHttps: (input: {
|
|
|
7957
8206
|
* current table (or at least every row in the rescan groups). */
|
|
7958
8207
|
export declare const resolveRescan: (shape: AggregateShape, state: IvmState, rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<Row_3>) => IvmState;
|
|
7959
8208
|
|
|
7960
|
-
/**
|
|
7961
|
-
* Resolve the request-scoped filter for `subject`.
|
|
7962
|
-
*
|
|
7963
|
-
* Three stages, in order, and the order is the design:
|
|
7964
|
-
*
|
|
7965
|
-
* 1. **Bypass** — no registered filter, or a `system` subject: `NO_ROW_FILTER`,
|
|
7966
|
-
* zero cost, no `load` call.
|
|
7967
|
-
* 2. **Retry** — `load` runs under the filter's `retry` schedule
|
|
7968
|
-
* (`DEFAULT_ROW_FILTER_RETRY` unless overridden). A transient failure must
|
|
7969
|
-
* never reach stage 3, because at stage 3 it is indistinguishable from an
|
|
7970
|
-
* authorization answer.
|
|
7971
|
-
* 3. **Refuse** — a failure that survived retry is reported through `onError`
|
|
7972
|
-
* and then, per `onLoadError`, either FAILS with `RowFilterUnavailable`
|
|
7973
|
-
* (default) or degrades to `DENY_ALL`.
|
|
7974
|
-
*
|
|
7975
|
-
* Fails CLOSED, never open: a `load` failure never yields an unfiltered read.
|
|
7976
|
-
* A row filter that degrades to "no filter" under load failure is worse than
|
|
7977
|
-
* none, because the system keeps serving and nothing looks wrong.
|
|
7978
|
-
*
|
|
7979
|
-
* And refusal is an ERROR by default, not an empty result. An empty result for
|
|
7980
|
-
* an infrastructure failure is the most misleading outcome available: it is
|
|
7981
|
-
* byte-identical to legitimate emptiness, so the user reads "you have no
|
|
7982
|
-
* tickets" and the operator reads a healthy 200. Every constrained page IS
|
|
7983
|
-
* broken when this happens; the honest thing is to say so.
|
|
7984
|
-
*/
|
|
7985
8209
|
export declare const resolveRowFilterScope: (subject: Subject, onError?: (error: unknown) => void) => Effect.Effect<RowFilterScope, RowFilterUnavailable>;
|
|
7986
8210
|
|
|
7987
8211
|
/**
|
|
@@ -8440,6 +8664,10 @@ export declare const rowFilterMayNarrow: (tables: ReadonlyArray<string>) => bool
|
|
|
8440
8664
|
*/
|
|
8441
8665
|
export declare const rowFilterRegistered: () => boolean;
|
|
8442
8666
|
|
|
8667
|
+
/** Which arms have been proven to resolve the filter in this process. Read by
|
|
8668
|
+
* the inspect surface; a test reads it to assert an arm was exercised. */
|
|
8669
|
+
export declare const rowFilterResolvedArms: () => ReadonlyArray<string>;
|
|
8670
|
+
|
|
8443
8671
|
/** What a scoped store needs: a sync `table → Predicate?` lookup. */
|
|
8444
8672
|
export declare type RowFilterScope = (table: string) => Predicate | undefined;
|
|
8445
8673
|
|
|
@@ -9020,6 +9248,16 @@ export declare interface SchedulerDeps {
|
|
|
9020
9248
|
/** Stable id of this process/replica (pod name, pid, …) recorded on
|
|
9021
9249
|
* every run row for debugging "which instance fired this." */
|
|
9022
9250
|
readonly replicaId: string;
|
|
9251
|
+
/**
|
|
9252
|
+
* How often a run in flight bumps `heartbeatAt` — the signal the
|
|
9253
|
+
* cross-instance overlap guard reads to tell a live run from a corpse.
|
|
9254
|
+
*
|
|
9255
|
+
* Omitted → {@link DEFAULT_SCHEDULE_HEARTBEAT_MS}. Both boot paths pass the
|
|
9256
|
+
* resolved `scheduling.scheduleHeartbeatMs`, so a deployment changes it in
|
|
9257
|
+
* one place; a non-positive value falls back to the default rather than
|
|
9258
|
+
* turning the beat into a spin.
|
|
9259
|
+
*/
|
|
9260
|
+
readonly runHeartbeatMs?: number;
|
|
9023
9261
|
readonly log: SchedulerLogger;
|
|
9024
9262
|
/** Notify on every run-row insert/update so the SSE bridge can
|
|
9025
9263
|
* mirror schedule runs to the cloud dashboard in real time. */
|
|
@@ -9900,6 +10138,61 @@ export declare interface SubscriberDefinition {
|
|
|
9900
10138
|
readonly table: string;
|
|
9901
10139
|
/** Default `'any'`. */
|
|
9902
10140
|
readonly on?: SubscribeOp | ReadonlyArray<Exclude<SubscribeOp, 'any'>>;
|
|
10141
|
+
/**
|
|
10142
|
+
* Run the handler ONCE PER CHANGE across the whole fleet, instead of once on
|
|
10143
|
+
* every replica that sees it.
|
|
10144
|
+
*
|
|
10145
|
+
* **Without it, the handler runs on EVERY api instance.** That is the correct
|
|
10146
|
+
* default and not a gap: a subscriber that refreshes a search index, drops a
|
|
10147
|
+
* process-local cache entry or updates in-memory state has to run everywhere,
|
|
10148
|
+
* and a fleet-wide gate would leave every other replica stale. The default
|
|
10149
|
+
* assumes the handler is IDEMPOTENT.
|
|
10150
|
+
*
|
|
10151
|
+
* It is the wrong default for an EFFECT — a notification, a mail, a webhook, a
|
|
10152
|
+
* payment — because there is nothing to make idempotent: the effect IS the
|
|
10153
|
+
* write, so each run produces another one. Two replicas send two mails.
|
|
10154
|
+
*
|
|
10155
|
+
* `once` returns a deterministic key for the change, and exactly one replica
|
|
10156
|
+
* wins the claim on it (`_voltro_change_claims`, INSERT-wins on a UNIQUE — the
|
|
10157
|
+
* same arbiter the cron scheduler uses for a firing). The key is namespaced by
|
|
10158
|
+
* this subscriber's file id, so two subscribers on one table never lock each
|
|
10159
|
+
* other out.
|
|
10160
|
+
*
|
|
10161
|
+
* ```ts
|
|
10162
|
+
* export default defineSubscriber({
|
|
10163
|
+
* table: 'absence_requests',
|
|
10164
|
+
* on: ['insert'],
|
|
10165
|
+
* once: true,
|
|
10166
|
+
* handler: notifyApprovers,
|
|
10167
|
+
* })
|
|
10168
|
+
* ```
|
|
10169
|
+
*
|
|
10170
|
+
* **`once: true` derives the key for you**, and is the right default choice.
|
|
10171
|
+
* It names the change by its CONTENT plus its position among content-identical
|
|
10172
|
+
* repeats (`changeDigest` + `OccurrenceCounter`, `@voltro/database`) — which is
|
|
10173
|
+
* the only thing two replicas provably agree on, since a fleet change carries
|
|
10174
|
+
* no LSN, no commit id and no `traceId`. `A→B`, then `B→A`, then `A→B` again is
|
|
10175
|
+
* three changes, not two.
|
|
10176
|
+
*
|
|
10177
|
+
* **A function is for when you want to be COARSER than one-per-change.** Two
|
|
10178
|
+
* updates that differ only in a field you do not care about are two changes to
|
|
10179
|
+
* `once: true` and can be one to a key function that ignores that field.
|
|
10180
|
+
*
|
|
10181
|
+
* **If you write one, it must tell two genuine changes apart.** A row id is
|
|
10182
|
+
* enough for `insert` and `delete`, where a row changes state once. It is NOT
|
|
10183
|
+
* enough for `update`: two edits to the same row produce the same id, and the
|
|
10184
|
+
* second would be dropped as a duplicate of the first. Put something that moves
|
|
10185
|
+
* in the key — `` `${row.id}:${row.updatedAt}` `` — or use `once: true`.
|
|
10186
|
+
*
|
|
10187
|
+
* **AT MOST once, not exactly once.** The claim is taken BEFORE the handler
|
|
10188
|
+
* runs, so a replica that wins and then dies takes the event with it, and a
|
|
10189
|
+
* claim that cannot be written (database unreachable) is not taken by anyone.
|
|
10190
|
+
* Both are loud in the log and neither is retried — a subscriber is
|
|
10191
|
+
* best-effort by construction. When the effect must not be lost, the change
|
|
10192
|
+
* stream is the wrong seam for it: run it in the mutation, or start a workflow
|
|
10193
|
+
* from a `*.reaction.tsx`, where the durability is the primitive's job.
|
|
10194
|
+
*/
|
|
10195
|
+
readonly once?: true | ((event: SubscribeEvent) => string);
|
|
9903
10196
|
readonly handler: SubscribeHandler;
|
|
9904
10197
|
}
|
|
9905
10198
|
|
|
@@ -10736,6 +11029,8 @@ export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
|
10736
11029
|
*/
|
|
10737
11030
|
export declare const _voltroApprovalsTable: TableLike;
|
|
10738
11031
|
|
|
11032
|
+
export declare const _voltroChangeClaimsTable: TableLike;
|
|
11033
|
+
|
|
10739
11034
|
/**
|
|
10740
11035
|
* `_voltro_connection_grants` — an IN-FLIGHT oauth2 handshake. Separate from
|
|
10741
11036
|
* the credential table on purpose: a handshake is a short-lived, single-use
|
|
@@ -11489,4 +11784,13 @@ export declare const wrapStoreWithBootCodec: (underlying: DataStore, dialectId:
|
|
|
11489
11784
|
|
|
11490
11785
|
export declare const wrapStoreWithMixinBehaviour: (rawUnderlying: DataStore, ctx: StoreMiddlewareContext) => FluentStore;
|
|
11491
11786
|
|
|
11787
|
+
/**
|
|
11788
|
+
* Write (upsert) this replica's counters for one kind.
|
|
11789
|
+
*
|
|
11790
|
+
* Best-effort throughout: a diagnostic that can fail a request, or a boot, is a
|
|
11791
|
+
* new way to be down. A failure logs at debug and leaves the previous row —
|
|
11792
|
+
* which then reads as stale, which is the truth.
|
|
11793
|
+
*/
|
|
11794
|
+
export declare const writeReplicaObservation: (store: DataStore, kind: ReplicaObservationKind, payload: unknown) => Promise<void>;
|
|
11795
|
+
|
|
11492
11796
|
export { }
|