@voltro/runtime 0.28.0 → 0.30.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 +655 -0
- package/THIRD-PARTY-NOTICES.md +87 -1
- package/dist/index.d.ts +1276 -14
- package/dist/index.js +2295 -1187
- package/package.json +7 -6
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { Row } from '@voltro/database';
|
|
|
56
56
|
import { Rpc } from '@effect/rpc';
|
|
57
57
|
import { RpcGroup } from '@effect/rpc';
|
|
58
58
|
import { RpcInterceptor } from '@voltro/protocol';
|
|
59
|
+
import { RuleReadStore } from '@voltro/database';
|
|
59
60
|
import { Sampler } from '@opentelemetry/sdk-trace-base';
|
|
60
61
|
import { Schedule } from 'effect';
|
|
61
62
|
import { Schema } from 'effect';
|
|
@@ -677,6 +678,8 @@ export declare interface AnalyticsTopEntry {
|
|
|
677
678
|
|
|
678
679
|
declare type AnyRow = Record<string, unknown>;
|
|
679
680
|
|
|
681
|
+
declare type AnyRow_2 = Record<string, unknown>;
|
|
682
|
+
|
|
680
683
|
export declare const API_KEYS_TABLE = "_voltro_api_keys";
|
|
681
684
|
|
|
682
685
|
/** Admin-gated management routes (issue / list / revoke) for the built-in keys.
|
|
@@ -1018,6 +1021,17 @@ export declare const assertCan: (subject: RebacSubject, action: string, resource
|
|
|
1018
1021
|
*/
|
|
1019
1022
|
export declare const assertConnectionCipherConfigured: (definitions: ReadonlyArray<ConnectionDefinition>) => void;
|
|
1020
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* Assign a subject to a variant — STABLE (same subject ⇒ same variant, always)
|
|
1026
|
+
* and BALANCED (proportions converge to the weights over many subjects). The
|
|
1027
|
+
* assignment is salted with the experiment `name`, so one subject buckets
|
|
1028
|
+
* independently across different experiments.
|
|
1029
|
+
*
|
|
1030
|
+
* A `holdout` fraction is carved off the top of the interval FIRST; the
|
|
1031
|
+
* remaining space is split by variant weight. Pure — a client can reproduce it.
|
|
1032
|
+
*/
|
|
1033
|
+
export declare const assignVariant: (def: ExperimentDefinition, subject: string) => string;
|
|
1034
|
+
|
|
1021
1035
|
/**
|
|
1022
1036
|
* Async cache surface exposed as `ctx.cache` to non-Effect handlers — the
|
|
1023
1037
|
* facade over the Effect-native `Cache` service from `@voltro/cache`
|
|
@@ -1095,6 +1109,82 @@ export declare interface AttachAnalyticsMirrorOptions {
|
|
|
1095
1109
|
readonly run?: (effect: Effect.Effect<void, unknown>) => Promise<unknown>;
|
|
1096
1110
|
}
|
|
1097
1111
|
|
|
1112
|
+
/**
|
|
1113
|
+
* Wire every discovered expectation onto the store's CDC channel + a clock tick,
|
|
1114
|
+
* and expose the live signal via a registry. Returns the registry impl + a
|
|
1115
|
+
* detach for graceful shutdown — the SAME shape as the aggregate runner.
|
|
1116
|
+
*
|
|
1117
|
+
* Both boot paths (`voltro dev`, `voltro serve`) must call this and provide the
|
|
1118
|
+
* resulting `ExpectationRegistry` layer, exactly like `AggregateRegistry`.
|
|
1119
|
+
*/
|
|
1120
|
+
export declare const attachExpectations: (options: AttachExpectationsOptions) => ExpectationRunnerHandle;
|
|
1121
|
+
|
|
1122
|
+
export declare interface AttachExpectationsOptions {
|
|
1123
|
+
readonly discovered: ReadonlyArray<{
|
|
1124
|
+
readonly definition: ExpectationDefinition;
|
|
1125
|
+
}>;
|
|
1126
|
+
readonly store: DataStore;
|
|
1127
|
+
readonly log: SyncLogger;
|
|
1128
|
+
/** Wall clock. Default `Date.now`. */
|
|
1129
|
+
readonly now?: () => number;
|
|
1130
|
+
/** How often the time-dependent invariants (freshness) are re-checked against
|
|
1131
|
+
* the clock. Default 30s. Ignored when no expectation is time-dependent. */
|
|
1132
|
+
readonly tickIntervalMs?: number;
|
|
1133
|
+
/** Periodic full re-seed interval (drift healing), mirroring the aggregate
|
|
1134
|
+
* runner's reconcile. Default 5m. */
|
|
1135
|
+
readonly reconcileIntervalMs?: number;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Wire every discovered experiment onto the store's CDC channel and expose the
|
|
1140
|
+
* live results via a registry. Returns the registry impl + a detach for graceful
|
|
1141
|
+
* shutdown — the SAME shape as `attachExpectations` / `attachFinops`.
|
|
1142
|
+
*
|
|
1143
|
+
* Both boot paths (`voltro dev`, `voltro serve`) must call this and provide the
|
|
1144
|
+
* resulting `ExperimentRegistry` layer, exactly like `AggregateRegistry`
|
|
1145
|
+
* (deferred cli work — see the NOTE at the bottom of this file).
|
|
1146
|
+
*/
|
|
1147
|
+
export declare const attachExperiments: (options: AttachExperimentsOptions) => ExperimentRunnerHandle;
|
|
1148
|
+
|
|
1149
|
+
export declare interface AttachExperimentsOptions {
|
|
1150
|
+
readonly discovered: ReadonlyArray<{
|
|
1151
|
+
readonly definition: ExperimentDefinition;
|
|
1152
|
+
}>;
|
|
1153
|
+
readonly store: DataStore;
|
|
1154
|
+
readonly log: SyncLogger;
|
|
1155
|
+
/** Wall clock. Default `Date.now`. */
|
|
1156
|
+
readonly now?: () => number;
|
|
1157
|
+
/** Periodic full re-seed interval (drift healing), mirroring the aggregate
|
|
1158
|
+
* runner's reconcile. Default 5m. */
|
|
1159
|
+
readonly reconcileIntervalMs?: number;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Wire every discovered cost budget into a `CostAccountant` + a clock tick, and
|
|
1164
|
+
* expose the live attribution + budget signal via a `CostRegistry`. Returns the
|
|
1165
|
+
* registry impl + the `record` ingestion entry point + a detach — the SAME shape
|
|
1166
|
+
* as `attachExpectations`.
|
|
1167
|
+
*
|
|
1168
|
+
* Both boot paths (`voltro dev`, `voltro serve`) must call this and provide the
|
|
1169
|
+
* resulting `CostRegistry` layer, exactly like `ExpectationRegistry`; the
|
|
1170
|
+
* `record` returned here is the seam the dispatcher's recompute + the query path
|
|
1171
|
+
* call to emit cost events (see the NOTE at the bottom of this file — that call-
|
|
1172
|
+
* site instrumentation + `*.budget.ts` discovery are the deferred cli work).
|
|
1173
|
+
*/
|
|
1174
|
+
export declare const attachFinops: (options: AttachFinopsOptions) => FinopsRunnerHandle;
|
|
1175
|
+
|
|
1176
|
+
export declare interface AttachFinopsOptions {
|
|
1177
|
+
readonly discovered: ReadonlyArray<{
|
|
1178
|
+
readonly definition: CostBudgetDefinition;
|
|
1179
|
+
}>;
|
|
1180
|
+
readonly log: SyncLogger;
|
|
1181
|
+
/** Wall clock. Default `Date.now`. */
|
|
1182
|
+
readonly now?: () => number;
|
|
1183
|
+
/** How often windowed budgets are re-checked against the clock for a rollover
|
|
1184
|
+
* recovery. Default 30s. Ignored when no budget is windowed. */
|
|
1185
|
+
readonly tickIntervalMs?: number;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1098
1188
|
/** The descriptor shape the audit needs — a `defineQuery` result carries it. */
|
|
1099
1189
|
export declare interface AuditableQuery {
|
|
1100
1190
|
readonly name: string;
|
|
@@ -1184,7 +1274,30 @@ export declare interface BindEventInput {
|
|
|
1184
1274
|
readonly guards?: Guards<unknown> | undefined;
|
|
1185
1275
|
}
|
|
1186
1276
|
|
|
1187
|
-
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
|
|
1277
|
+
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,
|
|
1278
|
+
/**
|
|
1279
|
+
* This procedure's DECLARED error union (`descriptor.error`).
|
|
1280
|
+
*
|
|
1281
|
+
* The third category of failure, and the one two guards below could not see.
|
|
1282
|
+
* A value that is TAGGED but not representable by this union is undeclared by
|
|
1283
|
+
* construction: the untagged catch skips it (it has a `_tag`), the infra list
|
|
1284
|
+
* skips it (it is not on a curated list), and the rpc encoder then cannot
|
|
1285
|
+
* match it and ships the whole `ExitEncoded<…>` decode tree to the browser —
|
|
1286
|
+
* ~2 KB for a one-line cause, with the message at the END so every tool that
|
|
1287
|
+
* truncates shows the useless half.
|
|
1288
|
+
*
|
|
1289
|
+
* A consumer met it with `TenantScopeViolation`. Adding that tag to
|
|
1290
|
+
* `INFRA_ERROR_TAGS` would have been wrong: `effectStore.ts` documents
|
|
1291
|
+
* `error: Schema.Union(TenantScopeViolation, StoreOperationFailed, …)` as a
|
|
1292
|
+
* supported declaration, so an app that DECLARES it must still receive it
|
|
1293
|
+
* typed. The question is therefore not "is this tag infra" but "can THIS
|
|
1294
|
+
* descriptor represent it" — which only the descriptor can answer.
|
|
1295
|
+
*
|
|
1296
|
+
* Omitted ⇒ the check is skipped entirely. A call site that cannot supply a
|
|
1297
|
+
* schema keeps exactly the old behaviour rather than collapsing errors it
|
|
1298
|
+
* cannot classify.
|
|
1299
|
+
*/
|
|
1300
|
+
declaredError?: Schema.Schema.All) => Effect.Effect<Output, E, SubjectService | ConnectionInfo>;
|
|
1188
1301
|
|
|
1189
1302
|
/**
|
|
1190
1303
|
* Bind a non-reactive server→client stream (A3). The executor builds a
|
|
@@ -1770,6 +1883,206 @@ export declare interface Coordinator {
|
|
|
1770
1883
|
tryClaim(scheduleName: string, scheduledAt: Date): Promise<boolean>;
|
|
1771
1884
|
}
|
|
1772
1885
|
|
|
1886
|
+
/**
|
|
1887
|
+
* The standing compute-cost accountant. Fold `CostEvent`s in with `record`;
|
|
1888
|
+
* read attribution + budget state through the getters; roll windows with `tick`.
|
|
1889
|
+
* Every method is synchronous and allocation-light — it is on the reactive
|
|
1890
|
+
* recompute hot path.
|
|
1891
|
+
*/
|
|
1892
|
+
export declare class CostAccountant {
|
|
1893
|
+
private readonly now;
|
|
1894
|
+
private readonly attribution;
|
|
1895
|
+
private readonly counters;
|
|
1896
|
+
private readonly budgetsByName;
|
|
1897
|
+
private readonly totalBudgets;
|
|
1898
|
+
private readonly unitBudgets;
|
|
1899
|
+
constructor(definitions: ReadonlyArray<CostBudgetDefinition>, deps?: CostAccountantDeps);
|
|
1900
|
+
private counterFor;
|
|
1901
|
+
/** Attribute one cost event and evaluate every budget that watches its unit.
|
|
1902
|
+
* Returns the threshold-crossing signals produced (empty in the common,
|
|
1903
|
+
* no-crossing case). */
|
|
1904
|
+
record(event: CostEvent): ReadonlyArray<CostBudgetSignal>;
|
|
1905
|
+
/** Roll every windowed budget whose window has elapsed, with no event —
|
|
1906
|
+
* a budget recovers when its window passes even if the tenant went quiet.
|
|
1907
|
+
* Returns the `recovered` signals produced. */
|
|
1908
|
+
tick(): ReadonlyArray<CostBudgetSignal>;
|
|
1909
|
+
/** Reset attribution + every budget counter for one tenant, or ALL tenants
|
|
1910
|
+
* when `tenantId` is `undefined` (the system tenant is `null`, and IS
|
|
1911
|
+
* targetable). Returns the `recovered` signals for any budget that was
|
|
1912
|
+
* breached. Use for a chargeback-period rollover the windows don't express,
|
|
1913
|
+
* or a test reset. */
|
|
1914
|
+
reset(tenantId?: string | null): ReadonlyArray<CostBudgetSignal>;
|
|
1915
|
+
private allCounterTenants;
|
|
1916
|
+
attributionSnapshot(): ReadonlyArray<TenantCostState>;
|
|
1917
|
+
tenantState(tenantId: string | null): TenantCostState | null;
|
|
1918
|
+
budgetStates(): ReadonlyArray<CostBudgetState>;
|
|
1919
|
+
budgetState(name: string, tenantId: string | null): CostBudgetState | null;
|
|
1920
|
+
breaches(): ReadonlyArray<CostBudgetState>;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
export declare interface CostAccountantDeps {
|
|
1924
|
+
/** Wall clock. Injectable for deterministic tests. Default `Date.now`. */
|
|
1925
|
+
readonly now?: () => number;
|
|
1926
|
+
readonly log?: SyncLogger;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
export declare interface CostBudgetDefinition {
|
|
1930
|
+
/** Brand marker — lets the cli discriminate default-exported budget
|
|
1931
|
+
* definitions from arbitrary objects during file discovery. */
|
|
1932
|
+
readonly _voltroCostBudget: true;
|
|
1933
|
+
readonly name: string;
|
|
1934
|
+
readonly limit: number;
|
|
1935
|
+
/** Resolved cost unit, or `null` for a total-across-all-units budget. */
|
|
1936
|
+
readonly unit: string | null;
|
|
1937
|
+
readonly warnAt: number;
|
|
1938
|
+
/** Resolved tumbling-window length in ms, or `null` for a cumulative budget. */
|
|
1939
|
+
readonly windowMs: number | null;
|
|
1940
|
+
readonly severity: CostBudgetSeverity;
|
|
1941
|
+
readonly description?: string;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
export declare interface CostBudgetDefinitionInput {
|
|
1945
|
+
/** Stable id within the app. Unique across all cost budgets. */
|
|
1946
|
+
readonly name: string;
|
|
1947
|
+
/** The per-tenant ceiling, in the budget's `unit` (or the tenant's grand
|
|
1948
|
+
* total when `unit` is omitted). A tenant crossing this is `exceeded`. */
|
|
1949
|
+
readonly limit: number;
|
|
1950
|
+
/** Which cost unit this budget meters. Omitted ⇒ the tenant's TOTAL across
|
|
1951
|
+
* every unit (the common "total compute per tenant" cap). When set, only
|
|
1952
|
+
* `CostEvent`s of this unit count toward the budget. */
|
|
1953
|
+
readonly unit?: string;
|
|
1954
|
+
/** Fraction of `limit` (0..1) at which the budget goes `warn` — the early
|
|
1955
|
+
* signal before the hard cap. Default `0.8`. Set `1` to disable the warn
|
|
1956
|
+
* band (straight ok→exceeded). */
|
|
1957
|
+
readonly warnAt?: number;
|
|
1958
|
+
/** Tumbling window as an interval string (`'1h'`, `'24h'`). The per-tenant
|
|
1959
|
+
* counter resets at each window boundary (and a breached budget RECOVERS).
|
|
1960
|
+
* Omitted ⇒ a cumulative budget that only resets on an explicit
|
|
1961
|
+
* `reset(tenantId)` — the since-boot chargeback ceiling. */
|
|
1962
|
+
readonly window?: string;
|
|
1963
|
+
/** Alerting priority carried on the breach signal. Default `'warn'`. */
|
|
1964
|
+
readonly severity?: CostBudgetSeverity;
|
|
1965
|
+
/** Human-facing note surfaced in devtools / the breach message. */
|
|
1966
|
+
readonly description?: string;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
/** How loud a budget breach is. A classification carried on the signal for
|
|
1970
|
+
* alerting priority — the framework never BLOCKS compute on a budget here
|
|
1971
|
+
* (that is a caller's choice, the way `requireAiBudget` fails a call); a cost
|
|
1972
|
+
* budget is an observability-grade signal over work that already happened. */
|
|
1973
|
+
export declare type CostBudgetSeverity = 'info' | 'warn' | 'critical';
|
|
1974
|
+
|
|
1975
|
+
/** Emitted when a budget crosses a threshold for a tenant. `warn` / `exceeded`
|
|
1976
|
+
* are upward crossings (event-driven, with a cause); `recovered` is a window
|
|
1977
|
+
* rollover / reset back to `ok` (no cause). */
|
|
1978
|
+
export declare interface CostBudgetSignal {
|
|
1979
|
+
readonly kind: 'warn' | 'exceeded' | 'recovered';
|
|
1980
|
+
readonly state: CostBudgetState;
|
|
1981
|
+
readonly cause: CostCause | null;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/** The live state of one budget for one tenant — the row an inspect endpoint /
|
|
1985
|
+
* dashboard reads. */
|
|
1986
|
+
export declare interface CostBudgetState {
|
|
1987
|
+
readonly budget: string;
|
|
1988
|
+
readonly tenantId: string | null;
|
|
1989
|
+
readonly status: CostBudgetStatus;
|
|
1990
|
+
readonly severity: CostBudgetSeverity;
|
|
1991
|
+
/** The metered cost against this budget in the current window. */
|
|
1992
|
+
readonly spent: number;
|
|
1993
|
+
/** The hard ceiling. */
|
|
1994
|
+
readonly limit: number;
|
|
1995
|
+
/** The absolute warn ceiling (`warnAt * limit`). */
|
|
1996
|
+
readonly warnThreshold: number;
|
|
1997
|
+
/** The unit this budget meters, or `null` for a total-across-units budget. */
|
|
1998
|
+
readonly unit: string | null;
|
|
1999
|
+
/** When the current window opened (windowed budgets), else `null`. */
|
|
2000
|
+
readonly windowStartedAt: number | null;
|
|
2001
|
+
/** When this budget last entered its current `status`. */
|
|
2002
|
+
readonly since: number | null;
|
|
2003
|
+
readonly lastUpdatedAt: number | null;
|
|
2004
|
+
/** The most recent breach's cause, retained across recovery for the audit
|
|
2005
|
+
* trail. `null` if it has never been breached. */
|
|
2006
|
+
readonly lastCause: CostCause | null;
|
|
2007
|
+
readonly description?: string;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/** A budget's status for one tenant. `ok` below the warn band, `warn` at/over
|
|
2011
|
+
* `warnAt * limit`, `exceeded` at/over `limit`. Monotonic within a window;
|
|
2012
|
+
* resets to `ok` on window rollover / explicit reset. */
|
|
2013
|
+
export declare type CostBudgetStatus = 'ok' | 'warn' | 'exceeded';
|
|
2014
|
+
|
|
2015
|
+
/** What incurred the cost that tipped a budget across a threshold — resolved
|
|
2016
|
+
* from the `CostEvent`. `null` when the transition was window-driven (a budget
|
|
2017
|
+
* RECOVERING because its window rolled over, with no event). */
|
|
2018
|
+
export declare interface CostCause {
|
|
2019
|
+
readonly unit: string;
|
|
2020
|
+
readonly subscriptionId?: string | null;
|
|
2021
|
+
readonly procedure?: string | null;
|
|
2022
|
+
readonly traceId?: string | null;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* One unit of attributed compute. A compute site (a reactive recompute in the
|
|
2027
|
+
* dispatcher, a query, a fan-out delivery, or any pluggable cost source) emits
|
|
2028
|
+
* one of these tagged with the tenant/subscription that caused it, and the
|
|
2029
|
+
* accountant folds it into the standing per-tenant total AND every budget that
|
|
2030
|
+
* watches its `unit`.
|
|
2031
|
+
*
|
|
2032
|
+
* `amount` is a NORMALISED cost figure in whatever unit the caller chose — 1 per
|
|
2033
|
+
* recompute, rows scanned, milliseconds elapsed, a priced micro-USD. The
|
|
2034
|
+
* framework does not impose a cost model; `unit` names which one this event is
|
|
2035
|
+
* denominated in, and a budget either targets that named unit or the tenant's
|
|
2036
|
+
* grand total. This is the "pluggable cost unit" seam.
|
|
2037
|
+
*/
|
|
2038
|
+
export declare interface CostEvent {
|
|
2039
|
+
/** Active org id the compute is attributed to. `null` = system / untenanted
|
|
2040
|
+
* work (a schedule, a resumed workflow — `SYSTEM_SUBJECT.tenantId`). */
|
|
2041
|
+
readonly tenantId: string | null;
|
|
2042
|
+
/** The live subscription that drove this compute, when the cost came from a
|
|
2043
|
+
* reactive recompute — the per-subscription attribution the plan calls for.
|
|
2044
|
+
* Absent for non-subscription compute (a one-shot query). */
|
|
2045
|
+
readonly subscriptionId?: string | null;
|
|
2046
|
+
/** Which cost unit `amount` is denominated in — `'recompute'`, `'query'`,
|
|
2047
|
+
* `'fanout'`, or any custom unit the caller meters. */
|
|
2048
|
+
readonly unit: string;
|
|
2049
|
+
/** The normalised cost contribution in `unit`. Non-finite / negative amounts
|
|
2050
|
+
* are ignored by the accountant (a cost cannot be negative). */
|
|
2051
|
+
readonly amount: number;
|
|
2052
|
+
/** The rpc tag of the call that incurred the cost, for the "why is my bill
|
|
2053
|
+
* high" breakdown + the breach cause. */
|
|
2054
|
+
readonly procedure?: string | null;
|
|
2055
|
+
/** The trace the offending compute ran under — joins to `voltro logs --trace`. */
|
|
2056
|
+
readonly traceId?: string | null;
|
|
2057
|
+
/** Wall-clock ms the cost was incurred. Default: the accountant's clock. */
|
|
2058
|
+
readonly at?: number;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
/**
|
|
2062
|
+
* Framework-provided registry of per-tenant compute-cost attribution + every
|
|
2063
|
+
* budget the cli discovered. Handler / inspect code reads the live signal here,
|
|
2064
|
+
* exactly as it reads `ExpectationRegistry`.
|
|
2065
|
+
*/
|
|
2066
|
+
export declare class CostRegistry extends CostRegistry_base {
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
declare const CostRegistry_base: Context.TagClass<CostRegistry, "@voltro/CostRegistry", CostRegistryApi>;
|
|
2070
|
+
|
|
2071
|
+
export declare interface CostRegistryApi {
|
|
2072
|
+
/** Per-tenant compute-cost attribution — the chargeback feed. */
|
|
2073
|
+
readonly attribution: () => ReadonlyArray<TenantCostState>;
|
|
2074
|
+
/** One tenant's attribution, or `null` when nothing has been attributed. */
|
|
2075
|
+
readonly tenant: (tenantId: string | null) => TenantCostState | null;
|
|
2076
|
+
/** Every (budget, tenant) state — the full budget snapshot. */
|
|
2077
|
+
readonly budgets: () => ReadonlyArray<CostBudgetState>;
|
|
2078
|
+
/** One budget's state for one tenant, or `null` when unseen. */
|
|
2079
|
+
readonly budget: (name: string, tenantId: string | null) => CostBudgetState | null;
|
|
2080
|
+
/** Only the currently warn/exceeded budget states — the alerting view. */
|
|
2081
|
+
readonly breaches: () => ReadonlyArray<CostBudgetState>;
|
|
2082
|
+
/** Subscribe to budget threshold crossings. Returns an unsubscribe fn. */
|
|
2083
|
+
readonly subscribe: (listener: (s: CostBudgetSignal) => void) => () => void;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
1773
2086
|
/** Define a cumulative counter. Increment with `incrementMetric` / `Metric.increment`. */
|
|
1774
2087
|
export declare const counter: (name: string, description?: string) => Metric.Metric.Counter<number>;
|
|
1775
2088
|
|
|
@@ -2005,6 +2318,32 @@ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
|
|
|
2005
2318
|
*/
|
|
2006
2319
|
export declare const DEFAULT_ROW_FILTER_RETRY: Schedule.Schedule<unknown, unknown>;
|
|
2007
2320
|
|
|
2321
|
+
/**
|
|
2322
|
+
* The message of an UNDECLARED throw, bounded, with nothing else attached.
|
|
2323
|
+
*
|
|
2324
|
+
* An executor that throws a plain `TypeError` produces a DEFECT, not a typed
|
|
2325
|
+
* failure — so `catchIf(isInfraError)` above never sees it (that guards the E
|
|
2326
|
+
* channel) and the rpc encoder tried to match it against the descriptor's
|
|
2327
|
+
* `error:` union. It cannot match, by definition: a defect is precisely the
|
|
2328
|
+
* thing that is not in the union. What reached the browser was the entire
|
|
2329
|
+
* decode tree — every union member, the full `ExitEncoded<…>` type, and the
|
|
2330
|
+
* real cause on the last line. ~2 KB of type names, which one consumer's
|
|
2331
|
+
* account page rendered verbatim where a reason belonged, and which every app
|
|
2332
|
+
* otherwise has to condense heuristically to avoid putting a schema on screen.
|
|
2333
|
+
*
|
|
2334
|
+
* `message` ONLY — no stack, no `cause` chain, no own fields. The same
|
|
2335
|
+
* reasoning as `wireErrorFromCause`: a nested object can carry a DSN or a
|
|
2336
|
+
* token. A message is what the server already logged and what a human needs;
|
|
2337
|
+
* the full original stays in the server log via `logHandlerFailure`.
|
|
2338
|
+
*
|
|
2339
|
+
* Note the deliberate asymmetry with `isInfraError`, which collapses to the
|
|
2340
|
+
* generic text instead: a `SqlError`'s message names internal `table.column`
|
|
2341
|
+
* detail, so its text is withheld on purpose. An arbitrary app defect has no
|
|
2342
|
+
* such known shape — withholding it too would leave the app exactly where it
|
|
2343
|
+
* started, with a reason it cannot show.
|
|
2344
|
+
*/
|
|
2345
|
+
export declare const defectMessage: (defect: unknown) => string;
|
|
2346
|
+
|
|
2008
2347
|
/**
|
|
2009
2348
|
* Construct an aggregate definition. The returned object brands itself
|
|
2010
2349
|
* so the cli's file-discovery pass picks it up from default exports.
|
|
@@ -2043,6 +2382,27 @@ export declare const defineAggregate: <Row>(input: AggregateDefinitionInput<Row>
|
|
|
2043
2382
|
*/
|
|
2044
2383
|
export declare const defineConnection: <D extends ConnectionDefinition>(definition: D) => D;
|
|
2045
2384
|
|
|
2385
|
+
/**
|
|
2386
|
+
* Declare a per-tenant compute-cost budget. Validates the shape at declaration
|
|
2387
|
+
* time so a malformed budget fails LOUD at boot rather than silently never
|
|
2388
|
+
* firing (the same discipline as `defineExpectation`).
|
|
2389
|
+
*
|
|
2390
|
+
* ```ts
|
|
2391
|
+
* // apps/api/budgets/tenantCompute.budget.ts
|
|
2392
|
+
* import { defineCostBudget } from '@voltro/runtime'
|
|
2393
|
+
*
|
|
2394
|
+
* export default defineCostBudget({
|
|
2395
|
+
* name: 'tenant-recompute-hourly',
|
|
2396
|
+
* unit: 'recompute',
|
|
2397
|
+
* limit: 100_000, // 100k recomputes per tenant per hour
|
|
2398
|
+
* warnAt: 0.8, // warn at 80k
|
|
2399
|
+
* window: '1h',
|
|
2400
|
+
* severity: 'warn',
|
|
2401
|
+
* })
|
|
2402
|
+
* ```
|
|
2403
|
+
*/
|
|
2404
|
+
export declare const defineCostBudget: (input: CostBudgetDefinitionInput) => CostBudgetDefinition;
|
|
2405
|
+
|
|
2046
2406
|
export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayload = EventPayload>(spec: Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag"> | (Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag" | "event"> & {
|
|
2047
2407
|
/** A declared event. Its `name` becomes the matched event. */
|
|
2048
2408
|
readonly on: {
|
|
@@ -2058,6 +2418,46 @@ export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayloa
|
|
|
2058
2418
|
*/
|
|
2059
2419
|
export declare const defineExecutor: <D extends ExecutorDescriptor, F extends (input: ExecutorInput<D>, ctx: AppContext) => ExecutorReturn<D, unknown, unknown>>(_descriptor: D, fn: F) => ((input: ExecutorInput<D>, ctx: AppContext) => ReturnType<F>);
|
|
2060
2420
|
|
|
2421
|
+
/**
|
|
2422
|
+
* Declare a data-quality expectation. Validates the shape at declaration time so
|
|
2423
|
+
* a malformed contract fails LOUD at boot rather than silently never firing.
|
|
2424
|
+
*
|
|
2425
|
+
* ```ts
|
|
2426
|
+
* // apps/api/expectations/orderFreshness.expectation.ts
|
|
2427
|
+
* import { defineExpectation } from '@voltro/runtime'
|
|
2428
|
+
*
|
|
2429
|
+
* export default defineExpectation({
|
|
2430
|
+
* name: 'orders-fresh',
|
|
2431
|
+
* on: { table: 'orders' },
|
|
2432
|
+
* invariant: { kind: 'freshness', column: 'createdAt', maxAgeMs: 10 * 60_000 },
|
|
2433
|
+
* severity: 'critical',
|
|
2434
|
+
* })
|
|
2435
|
+
* ```
|
|
2436
|
+
*/
|
|
2437
|
+
export declare const defineExpectation: (input: ExpectationDefinitionInput) => ExpectationDefinition;
|
|
2438
|
+
|
|
2439
|
+
/**
|
|
2440
|
+
* Declare an online experiment. Validates the shape at declaration time so a
|
|
2441
|
+
* malformed experiment fails LOUD at boot rather than silently never producing a
|
|
2442
|
+
* result (the same discipline as `defineExpectation` / `defineCostBudget`).
|
|
2443
|
+
*
|
|
2444
|
+
* ```ts
|
|
2445
|
+
* // apps/api/experiments/checkoutColor.experiment.ts
|
|
2446
|
+
* import { defineExperiment } from '@voltro/runtime'
|
|
2447
|
+
*
|
|
2448
|
+
* export default defineExperiment({
|
|
2449
|
+
* name: 'checkout-button-color',
|
|
2450
|
+
* on: { table: 'orders' },
|
|
2451
|
+
* subject: 'userId', // stable per-user bucketing
|
|
2452
|
+
* variants: [{ name: 'control' }, { name: 'green' }],
|
|
2453
|
+
* holdout: 0.1, // 10% see nothing, for a clean baseline
|
|
2454
|
+
* metric: { kind: 'conversionRate', column: 'completed' },
|
|
2455
|
+
* baseline: 'control',
|
|
2456
|
+
* })
|
|
2457
|
+
* ```
|
|
2458
|
+
*/
|
|
2459
|
+
export declare const defineExperiment: (input: ExperimentDefinitionInput) => ExperimentDefinition;
|
|
2460
|
+
|
|
2061
2461
|
/**
|
|
2062
2462
|
* Declare who delivers an effect. One per `*.outbox.ts` file.
|
|
2063
2463
|
*
|
|
@@ -2129,6 +2529,21 @@ export { DialectReplicationAdapter }
|
|
|
2129
2529
|
* delete → every field `value→undefined`; update → only the changed fields. */
|
|
2130
2530
|
export declare const diffChange: (c: CdcChange) => RowDiff;
|
|
2131
2531
|
|
|
2532
|
+
export declare interface DiscoveredCostBudget {
|
|
2533
|
+
readonly file: string;
|
|
2534
|
+
readonly definition: CostBudgetDefinition;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
export declare interface DiscoveredExpectation {
|
|
2538
|
+
readonly file: string;
|
|
2539
|
+
readonly definition: ExpectationDefinition;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
export declare interface DiscoveredExperiment {
|
|
2543
|
+
readonly file: string;
|
|
2544
|
+
readonly definition: ExperimentDefinition;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2132
2547
|
export declare interface DiscoveredWorkflowLike<Context = unknown> {
|
|
2133
2548
|
readonly definition: WorkflowDefinitionLike;
|
|
2134
2549
|
readonly buildExecute: (ctx: Context) => (payload: never, executionId: string) => Effect.Effect<unknown, unknown, unknown>;
|
|
@@ -2266,6 +2681,19 @@ export declare interface DispatcherDependencies {
|
|
|
2266
2681
|
* the trace buffer so the dashboards show each data transfer's latency.
|
|
2267
2682
|
* No-op when absent (tests, embedders that don't trace). */
|
|
2268
2683
|
readonly recordDelivery?: (delivery: SubscriptionDelivery) => void;
|
|
2684
|
+
/**
|
|
2685
|
+
* Optional reactive-compute cost sink (the finops runner's `record`). Called
|
|
2686
|
+
* once per reactive RECOMPUTE delivery — a change re-ran an affected
|
|
2687
|
+
* subscription and pushed it a delta — with `unit: 'recompute', amount: 1`
|
|
2688
|
+
* attributed to the subscription's tenant. Sibling to `recordDelivery`: same
|
|
2689
|
+
* call sites, a different ledger (chargeback / budget vs latency trace).
|
|
2690
|
+
*
|
|
2691
|
+
* No-op when absent (tests, embedders, apps with no `*.budget.ts`). The hot
|
|
2692
|
+
* path pays nothing in that case — the `CostEvent` is only allocated inside
|
|
2693
|
+
* the `recordCost !== undefined` guard, never eagerly. Wired by both boot
|
|
2694
|
+
* paths ONLY when finops is attached; see `attachFinops`.
|
|
2695
|
+
*/
|
|
2696
|
+
readonly recordCost?: (event: CostEvent) => void;
|
|
2269
2697
|
/** Optional snapshot cache (Layer 3). Absent → queries are never cached;
|
|
2270
2698
|
* present → subscriptions whose descriptor opted in (a `cacheBinding`
|
|
2271
2699
|
* is passed to `subscribe`) share + cache their initial snapshot. */
|
|
@@ -2402,6 +2830,21 @@ export declare const enterRequestLoader: (loader: DataLoader) => void;
|
|
|
2402
2830
|
* Promise-wrapped to satisfy the async contract. */
|
|
2403
2831
|
export declare const envSecretsBackend: SecretsBackend;
|
|
2404
2832
|
|
|
2833
|
+
/**
|
|
2834
|
+
* Evaluate every declared rule of every touched table's rows. Throws the first
|
|
2835
|
+
* error-severity `BusinessRuleViolation` (rolling back the transaction);
|
|
2836
|
+
* warning-severity violations log and continue.
|
|
2837
|
+
*
|
|
2838
|
+
* A predicate that itself throws a `BusinessRuleViolation` (the author built one
|
|
2839
|
+
* directly) is re-thrown as-is; any OTHER thrown value propagates unchanged — a
|
|
2840
|
+
* real failure (a DB error) must not be silently reclassified as a violation.
|
|
2841
|
+
*/
|
|
2842
|
+
export declare const evaluateTouchedRules: (params: {
|
|
2843
|
+
readonly touched: ReadonlyArray<TouchedRow>;
|
|
2844
|
+
readonly store: RuleReadStore;
|
|
2845
|
+
readonly subject: unknown;
|
|
2846
|
+
}) => Promise<void>;
|
|
2847
|
+
|
|
2405
2848
|
/**
|
|
2406
2849
|
* How many deliveries one client may fall behind before the oldest are dropped.
|
|
2407
2850
|
*
|
|
@@ -2778,6 +3221,451 @@ export declare type ExecutorOutput<D extends ExecutorDescriptor> = Schema.Schema
|
|
|
2778
3221
|
*/
|
|
2779
3222
|
export declare type ExecutorReturn<D extends ExecutorDescriptor, E, R> = ExecutorOutput<D> | Promise<ExecutorOutput<D>> | Effect.Effect<ExecutorOutput<D>, E, R> | ReactiveReturn<D>;
|
|
2780
3223
|
|
|
3224
|
+
/** The write that tipped an expectation into violation, resolved from the CDC
|
|
3225
|
+
* `ChangeEvent` that caused it — the provenance link the plan calls for. `null`
|
|
3226
|
+
* when the transition was CLOCK-driven (a freshness SLA aging out with no
|
|
3227
|
+
* write) rather than write-driven: the honest answer is "no write caused this;
|
|
3228
|
+
* the ABSENCE of writes did". */
|
|
3229
|
+
export declare interface ExpectationCause {
|
|
3230
|
+
readonly table: string;
|
|
3231
|
+
readonly op: 'insert' | 'update' | 'delete';
|
|
3232
|
+
/** The trace the offending write ran under — joins to `voltro logs --trace`
|
|
3233
|
+
* and the audit sink. */
|
|
3234
|
+
readonly traceId?: string | null;
|
|
3235
|
+
/** The acting identity behind the offending write. */
|
|
3236
|
+
readonly subjectId?: string | null;
|
|
3237
|
+
/** The rpc tag of the call that made the offending write, when carried. */
|
|
3238
|
+
readonly procedure?: string | null;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
export declare interface ExpectationDefinition {
|
|
3242
|
+
/** Brand marker — lets the cli discriminate default-exported expectation
|
|
3243
|
+
* definitions from arbitrary objects during file discovery. */
|
|
3244
|
+
readonly _voltroExpectation: true;
|
|
3245
|
+
readonly name: string;
|
|
3246
|
+
readonly on: {
|
|
3247
|
+
readonly table: string;
|
|
3248
|
+
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3249
|
+
};
|
|
3250
|
+
readonly invariant: ExpectationInvariant;
|
|
3251
|
+
readonly severity: ExpectationSeverity;
|
|
3252
|
+
readonly description?: string;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
export declare interface ExpectationDefinitionInput {
|
|
3256
|
+
/** Stable id within the app. Unique across all expectations. */
|
|
3257
|
+
readonly name: string;
|
|
3258
|
+
/** The watched table. Its CDC deltas drive re-evaluation. An optional `where`
|
|
3259
|
+
* narrows the population the invariant is asserted over — a pure predicate
|
|
3260
|
+
* evaluated server-side per row (rows failing it are excluded from the
|
|
3261
|
+
* metric). It never leaves the server, so it can be any predicate. */
|
|
3262
|
+
readonly on: {
|
|
3263
|
+
readonly table: string;
|
|
3264
|
+
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3265
|
+
};
|
|
3266
|
+
readonly invariant: ExpectationInvariant;
|
|
3267
|
+
/** Alerting priority carried on the violation signal. Default `'warn'`. */
|
|
3268
|
+
readonly severity?: ExpectationSeverity;
|
|
3269
|
+
/** Human-facing note surfaced in devtools / the violation message. */
|
|
3270
|
+
readonly description?: string;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
/**
|
|
3274
|
+
* Evaluates ONE expectation. Seed once, then feed CDC deltas via `applyChange`
|
|
3275
|
+
* and (for a time-dependent invariant) clock ticks via `tick`. Each call
|
|
3276
|
+
* re-reads the maintained metric, updates the live state, and returns a
|
|
3277
|
+
* transition when the holding/violated boundary is crossed.
|
|
3278
|
+
*/
|
|
3279
|
+
export declare class ExpectationEvaluator {
|
|
3280
|
+
private readonly def;
|
|
3281
|
+
private readonly deps;
|
|
3282
|
+
private readonly engine;
|
|
3283
|
+
private readonly now;
|
|
3284
|
+
private seeded;
|
|
3285
|
+
private status;
|
|
3286
|
+
private metric;
|
|
3287
|
+
private threshold;
|
|
3288
|
+
private since;
|
|
3289
|
+
private lastEvaluatedAt;
|
|
3290
|
+
private lastCause;
|
|
3291
|
+
constructor(def: ExpectationDefinition, deps: ExpectationEvaluatorDeps);
|
|
3292
|
+
get timeDependent(): boolean;
|
|
3293
|
+
/** Whether a row is in the asserted population (honours `on.where`). */
|
|
3294
|
+
private inScope;
|
|
3295
|
+
/** Seed the metric from the current base rows and evaluate the initial state.
|
|
3296
|
+
* Never emits a transition (there is no prior state to cross from). */
|
|
3297
|
+
seed(): Promise<void>;
|
|
3298
|
+
/**
|
|
3299
|
+
* Apply one CDC change and re-evaluate. Returns a transition if the change
|
|
3300
|
+
* (or the reshaped population) crossed the boundary. A change to a row that is
|
|
3301
|
+
* out of scope on BOTH images is ignored.
|
|
3302
|
+
*/
|
|
3303
|
+
applyChange(event: ChangeEvent): Promise<ExpectationTransition | null>;
|
|
3304
|
+
/** Re-evaluate against the current clock WITHOUT a write — for a
|
|
3305
|
+
* time-dependent invariant (freshness) whose metric ages on its own. Returns
|
|
3306
|
+
* a transition if the clock alone crossed the boundary. */
|
|
3307
|
+
tick(): ExpectationTransition | null;
|
|
3308
|
+
private commitReading;
|
|
3309
|
+
state(): ExpectationState;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
export declare interface ExpectationEvaluatorDeps {
|
|
3313
|
+
/** Read the full current base rows — used to SEED and to resolve a min/max
|
|
3314
|
+
* rescan (freshness) from the post-commit table. */
|
|
3315
|
+
readonly queryBase: () => Promise<ReadonlyArray<Row_5>>;
|
|
3316
|
+
/** Wall clock. Injectable for deterministic tests. Default `Date.now`. */
|
|
3317
|
+
readonly now?: () => number;
|
|
3318
|
+
readonly log?: SyncLogger;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
/**
|
|
3322
|
+
* The data-quality invariant an expectation asserts. Each variant is chosen so
|
|
3323
|
+
* it reduces the watched table to a SINGLE incrementally-maintained metric that
|
|
3324
|
+
* rides the IVM engine (or a delta-driven filtered count), then compares that
|
|
3325
|
+
* metric against a threshold:
|
|
3326
|
+
*
|
|
3327
|
+
* - `freshness` — max(column) → `age = now - max`; violated when
|
|
3328
|
+
* `age > maxAgeMs`. Rides the IVM `max` maintainer (incl. its
|
|
3329
|
+
* rescan-on-extreme-delete protocol). TIME-DEPENDENT: it is
|
|
3330
|
+
* re-checked on a clock tick as well as on write, because the
|
|
3331
|
+
* metric ages with the wall clock even when no write arrives —
|
|
3332
|
+
* which is exactly when a freshness SLA matters.
|
|
3333
|
+
* - `nullRate` — fraction of rows whose `column` is null; violated when
|
|
3334
|
+
* `rate > maxRate`. Delta-maintained filtered count.
|
|
3335
|
+
* - `rowCount` — COUNT(*) of the table; violated when out of `[min, max]`.
|
|
3336
|
+
* - `valueBounds` — fraction of rows whose numeric `column` falls outside
|
|
3337
|
+
* `[min, max]` (the distribution / range check). Violated when
|
|
3338
|
+
* that fraction exceeds `maxViolationRate`, or — when no rate
|
|
3339
|
+
* is given — when ANY row is out of bounds.
|
|
3340
|
+
*/
|
|
3341
|
+
export declare type ExpectationInvariant = {
|
|
3342
|
+
readonly kind: 'freshness';
|
|
3343
|
+
readonly column: string;
|
|
3344
|
+
readonly maxAgeMs: number;
|
|
3345
|
+
} | {
|
|
3346
|
+
readonly kind: 'nullRate';
|
|
3347
|
+
readonly column: string;
|
|
3348
|
+
readonly maxRate: number;
|
|
3349
|
+
} | {
|
|
3350
|
+
readonly kind: 'rowCount';
|
|
3351
|
+
readonly min?: number;
|
|
3352
|
+
readonly max?: number;
|
|
3353
|
+
} | {
|
|
3354
|
+
readonly kind: 'valueBounds';
|
|
3355
|
+
readonly column: string;
|
|
3356
|
+
readonly min?: number;
|
|
3357
|
+
readonly max?: number;
|
|
3358
|
+
/** When set, the expectation tolerates up to this FRACTION (0..1) of
|
|
3359
|
+
* out-of-bounds rows before firing. Omitted ⇒ a single out-of-bounds row
|
|
3360
|
+
* violates. */
|
|
3361
|
+
readonly maxViolationRate?: number;
|
|
3362
|
+
};
|
|
3363
|
+
|
|
3364
|
+
/**
|
|
3365
|
+
* Framework-provided registry of every expectation the cli discovered + the
|
|
3366
|
+
* evaluator maintains. Handler / inspect code reads the live signal here.
|
|
3367
|
+
*/
|
|
3368
|
+
export declare class ExpectationRegistry extends ExpectationRegistry_base {
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
declare const ExpectationRegistry_base: Context.TagClass<ExpectationRegistry, "@voltro/ExpectationRegistry", ExpectationRegistryApi>;
|
|
3372
|
+
|
|
3373
|
+
export declare interface ExpectationRegistryApi {
|
|
3374
|
+
/** Snapshot every registered expectation's current state — the inspect feed. */
|
|
3375
|
+
readonly snapshot: () => ReadonlyArray<ExpectationState>;
|
|
3376
|
+
/** One expectation's state by name, or `null` when unknown. */
|
|
3377
|
+
readonly get: (name: string) => ExpectationState | null;
|
|
3378
|
+
/** Only the currently-violated expectations — the alerting view. */
|
|
3379
|
+
readonly violations: () => ReadonlyArray<ExpectationState>;
|
|
3380
|
+
/** Subscribe to holding/violated transitions. Returns an unsubscribe fn. */
|
|
3381
|
+
readonly subscribe: (listener: (t: ExpectationTransition) => void) => () => void;
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
export declare interface ExpectationRunnerHandle {
|
|
3385
|
+
readonly registry: ExpectationRegistryApi;
|
|
3386
|
+
readonly detach: () => void;
|
|
3387
|
+
readonly registered: ReadonlyArray<string>;
|
|
3388
|
+
/** Test/debug seam: run one clock re-check of the time-dependent
|
|
3389
|
+
* expectations at `nowMs` (or the injected clock) and return the snapshot.
|
|
3390
|
+
* This is what the periodic tick timer calls. */
|
|
3391
|
+
readonly runTick: () => void;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
/** How loud a violation is. Purely a classification carried on the signal —
|
|
3395
|
+
* the framework never blocks a write on any severity (that is a business
|
|
3396
|
+
* rule's job); severity drives alerting priority downstream. */
|
|
3397
|
+
export declare type ExpectationSeverity = 'info' | 'warn' | 'critical';
|
|
3398
|
+
|
|
3399
|
+
/** The live state of one expectation — the row an inspect endpoint / dashboard
|
|
3400
|
+
* reads. */
|
|
3401
|
+
export declare interface ExpectationState {
|
|
3402
|
+
readonly name: string;
|
|
3403
|
+
readonly table: string;
|
|
3404
|
+
readonly invariant: ExpectationInvariant['kind'];
|
|
3405
|
+
readonly severity: ExpectationSeverity;
|
|
3406
|
+
readonly status: ExpectationStatus;
|
|
3407
|
+
/** The measured metric: freshness → age in ms; nullRate / valueBounds(rate) →
|
|
3408
|
+
* a fraction; rowCount → the count; valueBounds(count) → number of bad rows.
|
|
3409
|
+
* `null` when the metric is undefined over the current data. */
|
|
3410
|
+
readonly metric: number | null;
|
|
3411
|
+
/** The threshold the metric is compared against (for display). */
|
|
3412
|
+
readonly threshold: number | null;
|
|
3413
|
+
/** When the expectation entered its current `status`. */
|
|
3414
|
+
readonly since: Date | null;
|
|
3415
|
+
/** When it was last (re-)evaluated. */
|
|
3416
|
+
readonly lastEvaluatedAt: Date | null;
|
|
3417
|
+
/** The most recent violation's provenance, retained across recovery for the
|
|
3418
|
+
* audit trail. `null` if it has never been violated. */
|
|
3419
|
+
readonly lastCause: ExpectationCause | null;
|
|
3420
|
+
readonly description?: string;
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
/** Whether an expectation currently HOLDS. `unknown` = not yet seeded, or a
|
|
3424
|
+
* metric that is undefined over the current data (e.g. freshness of an empty
|
|
3425
|
+
* table — neither fresh nor stale). Transitions are emitted only into/out of
|
|
3426
|
+
* `violated`. */
|
|
3427
|
+
export declare type ExpectationStatus = 'holding' | 'violated' | 'unknown';
|
|
3428
|
+
|
|
3429
|
+
/** Emitted when an expectation crosses the holding/violated boundary. */
|
|
3430
|
+
export declare interface ExpectationTransition {
|
|
3431
|
+
readonly kind: 'violated' | 'recovered';
|
|
3432
|
+
readonly state: ExpectationState;
|
|
3433
|
+
/** The causing write (present for a write-driven violation; `null` for a
|
|
3434
|
+
* clock-driven one or a recovery). */
|
|
3435
|
+
readonly cause: ExpectationCause | null;
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
/** What caused a live recompute — resolved from the CDC `ChangeEvent`. `null`
|
|
3439
|
+
* for a non-write recompute (a reconcile re-seed). */
|
|
3440
|
+
export declare interface ExperimentCause {
|
|
3441
|
+
readonly table: string;
|
|
3442
|
+
readonly op: 'insert' | 'update' | 'delete';
|
|
3443
|
+
/** The trace the write ran under — joins to `voltro logs --trace`. */
|
|
3444
|
+
readonly traceId?: string | null;
|
|
3445
|
+
/** The acting identity behind the write. */
|
|
3446
|
+
readonly subjectId?: string | null;
|
|
3447
|
+
/** The rpc tag of the call that made the write, when carried. */
|
|
3448
|
+
readonly procedure?: string | null;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
export declare interface ExperimentDefinition {
|
|
3452
|
+
/** Brand marker — lets the cli discriminate default-exported experiment
|
|
3453
|
+
* definitions from arbitrary objects during file discovery. */
|
|
3454
|
+
readonly _voltroExperiment: true;
|
|
3455
|
+
readonly name: string;
|
|
3456
|
+
readonly on: {
|
|
3457
|
+
readonly table: string;
|
|
3458
|
+
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3459
|
+
};
|
|
3460
|
+
/** Resolved to a function: reads the subject and normalises it to a non-empty
|
|
3461
|
+
* string, or `null` when the row carries no subject. */
|
|
3462
|
+
readonly subject: (row: Readonly<Record<string, unknown>>) => string | null;
|
|
3463
|
+
readonly variants: ReadonlyArray<ExperimentVariant>;
|
|
3464
|
+
/** Resolved holdout fraction in [0, 1); `0` when none. */
|
|
3465
|
+
readonly holdout: number;
|
|
3466
|
+
readonly metric: ExperimentMetric;
|
|
3467
|
+
readonly baseline: string;
|
|
3468
|
+
readonly description?: string;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
export declare interface ExperimentDefinitionInput {
|
|
3472
|
+
/** Stable id within the app. Unique across all experiments. Also the
|
|
3473
|
+
* assignment SALT, so the same subject buckets independently per experiment. */
|
|
3474
|
+
readonly name: string;
|
|
3475
|
+
/** The watched table. Its CDC deltas drive the live recompute. An optional
|
|
3476
|
+
* `where` narrows the population the experiment observes — a pure predicate
|
|
3477
|
+
* evaluated server-side per row (rows failing it are excluded). It never
|
|
3478
|
+
* leaves the server, so it can be any predicate. */
|
|
3479
|
+
readonly on: {
|
|
3480
|
+
readonly table: string;
|
|
3481
|
+
readonly where?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
3482
|
+
};
|
|
3483
|
+
/** The stable assignment key extractor. */
|
|
3484
|
+
readonly subject: ExperimentSubject;
|
|
3485
|
+
/** The arms. At least two (a control + one treatment). */
|
|
3486
|
+
readonly variants: ReadonlyArray<ExperimentVariantSpec>;
|
|
3487
|
+
/** Fraction (0..1) of subjects held out ENTIRELY — assigned to the reserved
|
|
3488
|
+
* `holdout` variant and never to a treatment. Omitted ⇒ no holdout. */
|
|
3489
|
+
readonly holdout?: number;
|
|
3490
|
+
/** The success metric maintained per variant. */
|
|
3491
|
+
readonly metric: ExperimentMetric;
|
|
3492
|
+
/** Which variant lift/difference is computed AGAINST. Must name one of
|
|
3493
|
+
* `variants`. Default: the first variant (by convention the control). */
|
|
3494
|
+
readonly baseline?: string;
|
|
3495
|
+
/** Human-facing note surfaced in devtools / the results view. */
|
|
3496
|
+
readonly description?: string;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
/**
|
|
3500
|
+
* Evaluates ONE experiment. Seed once from the base rows, then feed CDC deltas
|
|
3501
|
+
* via `applyChange`; each call re-maintains the per-variant metric + exposure and
|
|
3502
|
+
* recomputes the live result. Returns an `ExperimentUpdate` for any relevant
|
|
3503
|
+
* write (one whose old OR new image is assigned), else `null`.
|
|
3504
|
+
*/
|
|
3505
|
+
export declare class ExperimentEvaluator {
|
|
3506
|
+
private readonly def;
|
|
3507
|
+
private readonly deps;
|
|
3508
|
+
private readonly exposure;
|
|
3509
|
+
private readonly metric;
|
|
3510
|
+
private readonly metricIsCount;
|
|
3511
|
+
private readonly now;
|
|
3512
|
+
private seeded;
|
|
3513
|
+
private lastUpdatedAt;
|
|
3514
|
+
constructor(def: ExperimentDefinition, deps: ExperimentEvaluatorDeps);
|
|
3515
|
+
/** The variant an in-scope row is assigned to, or `null` when the row is out
|
|
3516
|
+
* of the `where` population or carries no subject. */
|
|
3517
|
+
private variantOf;
|
|
3518
|
+
private inScope;
|
|
3519
|
+
/** Seed both maintainers from the current base rows. Never emits (there is no
|
|
3520
|
+
* prior result to compare). */
|
|
3521
|
+
seed(): Promise<void>;
|
|
3522
|
+
/**
|
|
3523
|
+
* Apply one CDC change and recompute. Returns an update when the change was
|
|
3524
|
+
* relevant (assigned on either image), else `null`. A change to a row that is
|
|
3525
|
+
* out of scope on BOTH images is ignored.
|
|
3526
|
+
*/
|
|
3527
|
+
applyChange(event: ChangeEvent): ExperimentUpdate | null;
|
|
3528
|
+
/** The live result — per-variant metric + lift/diff vs the baseline. */
|
|
3529
|
+
result(): ExperimentResult;
|
|
3530
|
+
/** Resolve one variant's metric value, filling the "no group yet" gap: `count`
|
|
3531
|
+
* → 0, `sum` → 0 (empty sum), `avg` / `conversionRate` → `null` (undefined
|
|
3532
|
+
* over no samples). */
|
|
3533
|
+
private metricValueFor;
|
|
3534
|
+
get isSeeded(): boolean;
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
export declare interface ExperimentEvaluatorDeps {
|
|
3538
|
+
/** Read the full current base rows — used to SEED and to reconcile. */
|
|
3539
|
+
readonly queryBase: () => Promise<ReadonlyArray<Row_6>>;
|
|
3540
|
+
/** Wall clock. Injectable for deterministic tests. Default `Date.now`. */
|
|
3541
|
+
readonly now?: () => number;
|
|
3542
|
+
readonly log?: SyncLogger;
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
/** The success metric an experiment maintains PER VARIANT. Every kind reduces to
|
|
3546
|
+
* a bounded IVM aggregate grouped by the assigned variant, so it rides the same
|
|
3547
|
+
* incremental engine as `defineAggregate` (no batch re-scan):
|
|
3548
|
+
*
|
|
3549
|
+
* - `count` — rows assigned to the variant. The metric IS the
|
|
3550
|
+
* exposure (sample size).
|
|
3551
|
+
* - `sum` — Σ of a numeric `column` over the variant's rows.
|
|
3552
|
+
* - `avg` — mean of a numeric `column` — the per-observation
|
|
3553
|
+
* average (revenue-per-order, latency, …). Comparable
|
|
3554
|
+
* across variants of unequal size.
|
|
3555
|
+
* - `conversionRate` — the FRACTION of the variant's rows that "converted":
|
|
3556
|
+
* `row[column]` is truthy, or `=== equals` when given.
|
|
3557
|
+
* Modelled as `avg` of a 0/1 projection, so it too is a
|
|
3558
|
+
* live IVM aggregate. The classic A/B success metric.
|
|
3559
|
+
*
|
|
3560
|
+
* `count` / `sum` reflect BOTH exposure and effect, so their cross-variant
|
|
3561
|
+
* comparison only means "more/less total"; `avg` / `conversionRate` are
|
|
3562
|
+
* per-subject rates and are the ones a lift reads honestly. The result always
|
|
3563
|
+
* carries each variant's `exposure`, so an unequal split is visible either way. */
|
|
3564
|
+
export declare type ExperimentMetric = {
|
|
3565
|
+
readonly kind: 'count';
|
|
3566
|
+
} | {
|
|
3567
|
+
readonly kind: 'sum';
|
|
3568
|
+
readonly column: string;
|
|
3569
|
+
} | {
|
|
3570
|
+
readonly kind: 'avg';
|
|
3571
|
+
readonly column: string;
|
|
3572
|
+
} | {
|
|
3573
|
+
readonly kind: 'conversionRate';
|
|
3574
|
+
readonly column: string;
|
|
3575
|
+
/** When set, a row converted iff `row[column] === equals`. Omitted ⇒
|
|
3576
|
+
* converted iff `row[column]` is truthy. */
|
|
3577
|
+
readonly equals?: unknown;
|
|
3578
|
+
};
|
|
3579
|
+
|
|
3580
|
+
export declare type ExperimentMetricKind = ExperimentMetric['kind'];
|
|
3581
|
+
|
|
3582
|
+
/**
|
|
3583
|
+
* Framework-provided registry of every experiment the cli discovered + the
|
|
3584
|
+
* evaluator maintains. Handler / inspect code reads the live results here,
|
|
3585
|
+
* exactly as it reads `ExpectationRegistry` / `CostRegistry`.
|
|
3586
|
+
*/
|
|
3587
|
+
export declare class ExperimentRegistry extends ExperimentRegistry_base {
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
declare const ExperimentRegistry_base: Context.TagClass<ExperimentRegistry, "@voltro/ExperimentRegistry", ExperimentRegistryApi>;
|
|
3591
|
+
|
|
3592
|
+
export declare interface ExperimentRegistryApi {
|
|
3593
|
+
/** Snapshot every registered experiment's current result — the inspect feed. */
|
|
3594
|
+
readonly snapshot: () => ReadonlyArray<ExperimentResult>;
|
|
3595
|
+
/** One experiment's result by name, or `null` when unknown. */
|
|
3596
|
+
readonly get: (name: string) => ExperimentResult | null;
|
|
3597
|
+
/** Subscribe to live result recomputes. Returns an unsubscribe fn. */
|
|
3598
|
+
readonly subscribe: (listener: (u: ExperimentUpdate) => void) => () => void;
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
/** The live state of one experiment — the row an inspect endpoint / results
|
|
3602
|
+
* dashboard reads. Recomputed on every relevant write. */
|
|
3603
|
+
export declare interface ExperimentResult {
|
|
3604
|
+
readonly name: string;
|
|
3605
|
+
readonly table: string;
|
|
3606
|
+
readonly metric: ExperimentMetricKind;
|
|
3607
|
+
readonly baseline: string;
|
|
3608
|
+
/** Rows assigned to ANY variant (Σ exposure). */
|
|
3609
|
+
readonly totalExposure: number;
|
|
3610
|
+
readonly variants: ReadonlyArray<ExperimentVariantResult>;
|
|
3611
|
+
/** When the result was last recomputed (a relevant write arrived). */
|
|
3612
|
+
readonly lastUpdatedAt: Date | null;
|
|
3613
|
+
readonly description?: string;
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
export declare interface ExperimentRunnerHandle {
|
|
3617
|
+
readonly registry: ExperimentRegistryApi;
|
|
3618
|
+
readonly detach: () => void;
|
|
3619
|
+
readonly registered: ReadonlyArray<string>;
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
/** How the assignment key is read from a row. A column NAME (its value is the
|
|
3623
|
+
* subject) or a function returning the subject (a composite key, a tenant id,
|
|
3624
|
+
* a hashed cookie). Returning `null` / `undefined` / an empty value ⇒ the row
|
|
3625
|
+
* is UNASSIGNED and excluded from every variant. */
|
|
3626
|
+
export declare type ExperimentSubject = string | ((row: Readonly<Record<string, unknown>>) => string | number | null | undefined);
|
|
3627
|
+
|
|
3628
|
+
/** Emitted whenever an experiment's result recomputes on a relevant write. The
|
|
3629
|
+
* results view subscribes to this to update in real time. */
|
|
3630
|
+
export declare interface ExperimentUpdate {
|
|
3631
|
+
readonly result: ExperimentResult;
|
|
3632
|
+
readonly cause: ExperimentCause | null;
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
/** Resolved variant — weight defaulted. */
|
|
3636
|
+
export declare interface ExperimentVariant {
|
|
3637
|
+
readonly name: string;
|
|
3638
|
+
readonly weight: number;
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
/** One variant's live result within an experiment. */
|
|
3642
|
+
export declare interface ExperimentVariantResult {
|
|
3643
|
+
readonly variant: string;
|
|
3644
|
+
/** True for the variant lift/diff is measured against. */
|
|
3645
|
+
readonly isBaseline: boolean;
|
|
3646
|
+
/** True for the reserved holdout arm. */
|
|
3647
|
+
readonly isHoldout: boolean;
|
|
3648
|
+
/** Sample size — rows assigned to this variant (the exposure). */
|
|
3649
|
+
readonly exposure: number;
|
|
3650
|
+
/** The maintained metric value. `null` when the metric is undefined over the
|
|
3651
|
+
* variant's current data (no valid samples for an avg / rate). */
|
|
3652
|
+
readonly metric: number | null;
|
|
3653
|
+
/** Relative lift vs the baseline: `(metric − baseMetric) / baseMetric`. `null`
|
|
3654
|
+
* when either value is null or the baseline metric is 0 (undefined lift).
|
|
3655
|
+
* Always `null` on the baseline row itself. */
|
|
3656
|
+
readonly lift: number | null;
|
|
3657
|
+
/** Absolute difference vs the baseline: `metric − baseMetric`. `null` when
|
|
3658
|
+
* either value is null. Always `null` on the baseline row. */
|
|
3659
|
+
readonly diff: number | null;
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
/** A named arm of the experiment. `weight` skews the split (default 1 = equal);
|
|
3663
|
+
* a subject is assigned in proportion to its weight over the total. */
|
|
3664
|
+
export declare interface ExperimentVariantSpec {
|
|
3665
|
+
readonly name: string;
|
|
3666
|
+
readonly weight?: number;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
2781
3669
|
/** Build an Effect `ExternalSpan` parent from a `traceparent` header so
|
|
2782
3670
|
* inbound HTTP work continues the caller's trace. Returns undefined
|
|
2783
3671
|
* when the header is missing/invalid (no parent → a fresh root span). */
|
|
@@ -2800,6 +3688,20 @@ export declare interface FieldChange {
|
|
|
2800
3688
|
*/
|
|
2801
3689
|
export declare const fingerprintFor: (table: string, predicate: Predicate, indexHint?: string) => string;
|
|
2802
3690
|
|
|
3691
|
+
export declare interface FinopsRunnerHandle {
|
|
3692
|
+
readonly registry: CostRegistryApi;
|
|
3693
|
+
/** The ingestion entry point compute sites call to attribute one unit of
|
|
3694
|
+
* cost. Emits any budget-crossing signals to the registry's subscribers. */
|
|
3695
|
+
readonly record: (event: CostEvent) => void;
|
|
3696
|
+
/** Reset attribution + budgets for one tenant (or all). */
|
|
3697
|
+
readonly reset: (tenantId?: string | null) => void;
|
|
3698
|
+
readonly detach: () => void;
|
|
3699
|
+
readonly registered: ReadonlyArray<string>;
|
|
3700
|
+
/** Test/debug seam: run one clock re-check of the windowed budgets. This is
|
|
3701
|
+
* what the periodic tick timer calls. */
|
|
3702
|
+
readonly runTick: () => void;
|
|
3703
|
+
}
|
|
3704
|
+
|
|
2803
3705
|
export declare interface FluentStore extends Omit<MutationStore, 'update' | 'delete' | 'query'> {
|
|
2804
3706
|
/**
|
|
2805
3707
|
* Diff-based many-to-many link writer for a junction table. `anchor` names the
|
|
@@ -2828,7 +3730,7 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
|
|
|
2828
3730
|
* SPI every dialect store implements, and it genuinely does return untyped
|
|
2829
3731
|
* rows off the wire. The type is re-applied here, at the handler boundary.
|
|
2830
3732
|
*/
|
|
2831
|
-
query<R =
|
|
3733
|
+
query<R = Row_7>(descriptor: QueryDescriptor<R>): Promise<ReadonlyArray<R>>;
|
|
2832
3734
|
/**
|
|
2833
3735
|
* Terminal: EXACTLY one row, TYPED. Fails with `NoRowFound` on zero matches
|
|
2834
3736
|
* and equally on two or more.
|
|
@@ -2849,18 +3751,18 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
|
|
|
2849
3751
|
* IS `query()` underneath — the probe just asks for one row more than it
|
|
2850
3752
|
* needs so "the first of several" cannot masquerade as "the one you meant".
|
|
2851
3753
|
*/
|
|
2852
|
-
one<R =
|
|
3754
|
+
one<R = Row_7>(query: QueryLike<R>): Promise<R>;
|
|
2853
3755
|
/** Terminal: the first matching row or `null`, TYPED. Use when "any match" is
|
|
2854
3756
|
* genuinely what you mean. */
|
|
2855
|
-
first<R =
|
|
3757
|
+
first<R = Row_7>(query: QueryLike<R>): Promise<R | null>;
|
|
2856
3758
|
/** Alias of `first` — the first matching row or `null`, TYPED. */
|
|
2857
|
-
maybeOne<R =
|
|
3759
|
+
maybeOne<R = Row_7>(query: QueryLike<R>): Promise<R | null>;
|
|
2858
3760
|
/** Fluent, scope-applying read builder: `select('notes').where(...).all()`. */
|
|
2859
3761
|
select(table: string): SelectBuilder;
|
|
2860
3762
|
/** Fluent predicate update: `update('notes').where('id', id).set({...})`. */
|
|
2861
3763
|
update(table: string): UpdateBuilder;
|
|
2862
3764
|
/** Keyed update by primary key — returns the post-image. */
|
|
2863
|
-
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<
|
|
3765
|
+
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row_7 | null>;
|
|
2864
3766
|
/** Fluent predicate delete: `delete('notes').where('id', id).soft()`. */
|
|
2865
3767
|
delete(table: string): DeleteBuilder;
|
|
2866
3768
|
/** Keyed delete by primary key. */
|
|
@@ -2885,6 +3787,31 @@ export declare interface FluentStoreBackend {
|
|
|
2885
3787
|
readonly delete: DataStore['delete'];
|
|
2886
3788
|
}
|
|
2887
3789
|
|
|
3790
|
+
/**
|
|
3791
|
+
* The columns a given SUBJECT may not see on the wire for `tableName`: the
|
|
3792
|
+
* union of the table's `.serverOnly()` columns (hidden from EVERYONE, so
|
|
3793
|
+
* subject-independent) and its `.readableBy(...)` columns whose scope set the
|
|
3794
|
+
* subject does NOT intersect (Part B — field-level read permissions).
|
|
3795
|
+
*
|
|
3796
|
+
* A subject sees a `.readableBy(...)` column iff it holds AT LEAST ONE of the
|
|
3797
|
+
* declared scopes, checked against its EFFECTIVE scope set (raw subject scopes ∪
|
|
3798
|
+
* rbac role-derived scopes) via `hasEffectiveScope` — so `admin:full` sees every
|
|
3799
|
+
* such column, exactly as it satisfies every guard.
|
|
3800
|
+
*
|
|
3801
|
+
* SUBJECT-INDEPENDENCE is deliberate and load-bearing: when the table declares
|
|
3802
|
+
* NO `.readableBy(...)` column, this returns exactly `serverOnlyColumnNames` —
|
|
3803
|
+
* the same set for every subject — so the Dispatcher's per-subscriber strip
|
|
3804
|
+
* collapses to Part A's behaviour and its read/diff memo still shares one array
|
|
3805
|
+
* across all subscribers of a change. Only a table that actually carries a
|
|
3806
|
+
* `.readableBy(...)` column pays the per-subject cost (different subjects get
|
|
3807
|
+
* different stripped arrays, the same accepted cost `.serverOnly()` already has
|
|
3808
|
+
* whenever it strips anything).
|
|
3809
|
+
*
|
|
3810
|
+
* An unknown / unregistered table forbids nothing (returns `[]`) rather than
|
|
3811
|
+
* throwing — same posture as `serverOnlyColumnNames`.
|
|
3812
|
+
*/
|
|
3813
|
+
export declare const forbiddenColumnsForSubject: (tableName: string, subject: Subject) => ReadonlyArray<string>;
|
|
3814
|
+
|
|
2888
3815
|
/** Forget the calling subject's credential. Returns whether a row was removed. */
|
|
2889
3816
|
export declare const forgetCredential: (store: VaultStore, connectionId: string, subjectId: string) => Promise<boolean>;
|
|
2890
3817
|
|
|
@@ -2988,6 +3915,14 @@ export declare interface GroupState {
|
|
|
2988
3915
|
/** What a user handler may return. */
|
|
2989
3916
|
export declare type HandlerBody = void | Promise<unknown> | Effect.Effect<unknown, unknown, never>;
|
|
2990
3917
|
|
|
3918
|
+
/**
|
|
3919
|
+
* Deterministic hash of a string into the unit interval `[0, 1)` — FNV-1a
|
|
3920
|
+
* (32-bit), no `node:crypto`, so it runs identically on server and client. The
|
|
3921
|
+
* distribution is uniform enough for balanced bucketing; it is NOT a
|
|
3922
|
+
* cryptographic hash and is not meant to be one (an assignment is not a secret).
|
|
3923
|
+
*/
|
|
3924
|
+
export declare const hashUnitInterval: (s: string) => number;
|
|
3925
|
+
|
|
2991
3926
|
/** Define a histogram. `boundaries` default to the framework duration buckets. */
|
|
2992
3927
|
export declare const histogramMetric: (name: string, boundaries?: MetricBoundaries.MetricBoundaries, description?: string) => Metric.Metric.Histogram<number>;
|
|
2993
3928
|
|
|
@@ -2998,6 +3933,12 @@ export declare const histogramMetric: (name: string, boundaries?: MetricBoundari
|
|
|
2998
3933
|
*/
|
|
2999
3934
|
export declare const historyProvenance: (history: ReadonlyArray<RowHistoryEntry>, key: ProvenanceKey) => ProvenanceResult;
|
|
3000
3935
|
|
|
3936
|
+
/** The reserved variant name a `holdout` fraction is assigned to. A holdout is
|
|
3937
|
+
* carved off the TOP of the unit interval BEFORE the weighted split, so it is
|
|
3938
|
+
* never influenced by adding/removing a treatment. No user variant may take
|
|
3939
|
+
* this name when `holdout > 0`. */
|
|
3940
|
+
export declare const HOLDOUT_VARIANT = "holdout";
|
|
3941
|
+
|
|
3001
3942
|
/** A secrets backend over a plain JSON HTTP API — no SDK. Wraps fetch + cache.
|
|
3002
3943
|
* Vault (`/v1/secret/data/...`), Doppler, and the cloud control-plane all fit. */
|
|
3003
3944
|
export declare const httpSecretsBackend: (opts: HttpSecretsOptions) => SecretsBackend;
|
|
@@ -3283,8 +4224,14 @@ export declare const invertChange: (c: ForwardChange) => InverseOp | null;
|
|
|
3283
4224
|
|
|
3284
4225
|
export declare const isAggregateDefinition: (value: unknown) => value is AggregateDefinition;
|
|
3285
4226
|
|
|
4227
|
+
export declare const isCostBudgetDefinition: (value: unknown) => value is CostBudgetDefinition;
|
|
4228
|
+
|
|
3286
4229
|
export declare const isEmptyTenantScopedRead: (descriptor: QueryDescriptor, tenantScopedTables: ReadonlySet<string>, tenantId: string | null | undefined) => boolean;
|
|
3287
4230
|
|
|
4231
|
+
export declare const isExpectationDefinition: (value: unknown) => value is ExpectationDefinition;
|
|
4232
|
+
|
|
4233
|
+
export declare const isExperimentDefinition: (value: unknown) => value is ExperimentDefinition;
|
|
4234
|
+
|
|
3288
4235
|
/** Foreign-key-violation SQLSTATE / errno across dialects (pg 23503, mysql/maria
|
|
3289
4236
|
* 1452, mssql 547, sqlite). Used to turn an opaque tenant-FK SqlError into a
|
|
3290
4237
|
* typed error. */
|
|
@@ -3828,6 +4775,18 @@ export declare const makeQuerySubscriber: <D>(deps: QuerySubscriberDeps<D>) => (
|
|
|
3828
4775
|
|
|
3829
4776
|
export declare const makeRouterActivity: () => RouterActivity;
|
|
3830
4777
|
|
|
4778
|
+
/**
|
|
4779
|
+
* Wrap a transactional store so single-row writes on USER tables are recorded
|
|
4780
|
+
* as their POST-WRITE row. Inserts and updates are captured; a delete removes
|
|
4781
|
+
* the row (there is no post-write state to validate), so — like undo's bulk
|
|
4782
|
+
* boundary — deletes are NOT re-validated in v1. Everything else delegates
|
|
4783
|
+
* untouched. Multiple writes to the same (table, id) keep the latest row.
|
|
4784
|
+
*/
|
|
4785
|
+
export declare const makeRuleCapture: (tx: unknown) => {
|
|
4786
|
+
readonly tx: unknown;
|
|
4787
|
+
drain(): ReadonlyArray<TouchedRow>;
|
|
4788
|
+
};
|
|
4789
|
+
|
|
3831
4790
|
/**
|
|
3832
4791
|
* Build the registry from a list of table descriptors. Cheap — runs once
|
|
3833
4792
|
* at boot. The returned object is read-only.
|
|
@@ -4093,6 +5052,11 @@ export declare interface MutationLike {
|
|
|
4093
5052
|
readonly name: string;
|
|
4094
5053
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
4095
5054
|
readonly guards?: ReadonlyArray<AnyCheckSpec> | undefined;
|
|
5055
|
+
readonly target?: {
|
|
5056
|
+
readonly table: string;
|
|
5057
|
+
} | ReadonlyArray<{
|
|
5058
|
+
readonly table: string;
|
|
5059
|
+
}> | undefined;
|
|
4096
5060
|
};
|
|
4097
5061
|
executor(input: unknown, ctx: unknown): unknown;
|
|
4098
5062
|
}
|
|
@@ -4347,9 +5311,13 @@ export declare interface OneShotQueryRunnerDeps<D> extends QueryProducerDeps<D>
|
|
|
4347
5311
|
/**
|
|
4348
5312
|
* Execute a FINALIZED (tenant-scoped) descriptor → rows. On the WS path the
|
|
4349
5313
|
* dispatcher owns this step; a ONE-SHOT read (a `publicApi` REST GET, where
|
|
4350
|
-
* there is no subscription to drive) needs it inline.
|
|
5314
|
+
* there is no subscription to drive) needs it inline. The resolved request
|
|
5315
|
+
* `context` is handed alongside so the caller can apply the SAME
|
|
5316
|
+
* subject-aware wire-column strip the dispatcher does (`.serverOnly()` ∪ the
|
|
5317
|
+
* `.readableBy(...)` columns this subject lacks) — the parity that keeps the
|
|
5318
|
+
* REST projection from shipping a column the socket path would strip.
|
|
4351
5319
|
*/
|
|
4352
|
-
readonly queryRows: (descriptor: D) => Promise<ReadonlyArray<unknown>>;
|
|
5320
|
+
readonly queryRows: (descriptor: D, context: ServeRequestContext) => Promise<ReadonlyArray<unknown>>;
|
|
4353
5321
|
}
|
|
4354
5322
|
|
|
4355
5323
|
/**
|
|
@@ -4606,7 +5574,7 @@ export declare interface PreviewStore {
|
|
|
4606
5574
|
/** Project a stored row into the wire shape. Deliberately total over the
|
|
4607
5575
|
* columns the descriptor declares — and deliberately silent about the token
|
|
4608
5576
|
* columns, which have no wire representation at all. */
|
|
4609
|
-
export declare const projectConnectionState: (definition: ConnectionDefinition, row:
|
|
5577
|
+
export declare const projectConnectionState: (definition: ConnectionDefinition, row: AnyRow_2 | undefined) => ConnectionState;
|
|
4610
5578
|
|
|
4611
5579
|
/** Exact-match property filter. Values are AND'd. */
|
|
4612
5580
|
export declare type PropertyFilter = Readonly<Record<string, string | number | boolean | null>>;
|
|
@@ -4746,7 +5714,7 @@ export declare interface QueryFinalizeOptions {
|
|
|
4746
5714
|
|
|
4747
5715
|
/** A typed builder or its descriptor — the single-row terminals accept either,
|
|
4748
5716
|
* so a call site never has to reach for `.descriptor` just to use them. */
|
|
4749
|
-
export declare type QueryLike<R =
|
|
5717
|
+
export declare type QueryLike<R = Row_7> = QueryDescriptor<R> | {
|
|
4750
5718
|
readonly descriptor: QueryDescriptor<R>;
|
|
4751
5719
|
};
|
|
4752
5720
|
|
|
@@ -4778,6 +5746,26 @@ export declare type ReactionAct = {
|
|
|
4778
5746
|
} | {
|
|
4779
5747
|
readonly kind: 'workflow';
|
|
4780
5748
|
readonly workflow: string;
|
|
5749
|
+
/**
|
|
5750
|
+
* Shape the workflow's payload from the change. Omit and the CHANGED ROW is
|
|
5751
|
+
* the payload, as before.
|
|
5752
|
+
*
|
|
5753
|
+
* Without this the workflow's payload schema is dictated by the watched
|
|
5754
|
+
* TABLE's row shape rather than by what the workflow needs, which drags two
|
|
5755
|
+
* problems into every reaction-started workflow:
|
|
5756
|
+
*
|
|
5757
|
+
* • every column travels, including ones the workflow has no business
|
|
5758
|
+
* seeing — a reaction on a users table hands the whole row to a
|
|
5759
|
+
* narration workflow;
|
|
5760
|
+
* • the row shape is DIALECT-dependent at the edges. A `timestamp()`
|
|
5761
|
+
* column arrives as a `Date` on MariaDB and a number elsewhere, so an
|
|
5762
|
+
* app normalising on both sides of an idempotency key is doing the
|
|
5763
|
+
* framework's job.
|
|
5764
|
+
*
|
|
5765
|
+
* A mapper fixes both at the declaration: the workflow declares the payload
|
|
5766
|
+
* it wants and the reaction adapts.
|
|
5767
|
+
*/
|
|
5768
|
+
readonly payload?: (event: ReactionEvent) => unknown;
|
|
4781
5769
|
};
|
|
4782
5770
|
|
|
4783
5771
|
export declare interface ReactionDefinition {
|
|
@@ -4804,10 +5792,35 @@ export declare interface ReactionGuards {
|
|
|
4804
5792
|
/** Per-tenant AI spend ceiling (USD). Checked via the injected `checkBudget`
|
|
4805
5793
|
* (the app wires `requireAiBudget`). Over budget → the reaction refuses. */
|
|
4806
5794
|
readonly costBudgetUsd?: number;
|
|
4807
|
-
/**
|
|
5795
|
+
/**
|
|
5796
|
+
* Rate cap — at most `limit` firings per `windowMs`, PER KEY.
|
|
5797
|
+
*
|
|
5798
|
+
* ── Two defects this shape replaces, both reported from production ────────
|
|
5799
|
+
*
|
|
5800
|
+
* It read as a per-key cap and was neither. The runner keyed the limiter on
|
|
5801
|
+
* the REACTION NAME, so one cap covered every row and every tenant the
|
|
5802
|
+
* reaction watched: an app with a hundred tenants got a hundredth of the
|
|
5803
|
+
* throughput it declared, and the busiest tenant starved the rest.
|
|
5804
|
+
*
|
|
5805
|
+
* And the limiter was in-memory, per process. With three replicas the
|
|
5806
|
+
* effective cap was 3×, and nothing about the declaration said so — the same
|
|
5807
|
+
* config produced a different limit depending on how many pods happened to be
|
|
5808
|
+
* running.
|
|
5809
|
+
*
|
|
5810
|
+
* `key` is now REQUIRED to get per-entity behaviour, and the limiter is
|
|
5811
|
+
* DURABLE (a claim in the shared store, the same INSERT-wins arbiter the cron
|
|
5812
|
+
* scheduler uses), so the cap is the cap regardless of replica count.
|
|
5813
|
+
*
|
|
5814
|
+
* Omitting `key` keeps the old GLOBAL meaning — which is a legitimate thing to
|
|
5815
|
+
* want (a cap on a scarce downstream), just not what the old field appeared to
|
|
5816
|
+
* offer.
|
|
5817
|
+
*/
|
|
4808
5818
|
readonly rateLimit?: {
|
|
4809
5819
|
readonly limit: number;
|
|
4810
5820
|
readonly windowMs: number;
|
|
5821
|
+
/** Partitions the cap. `(event) => event.new.tenantId` for per-tenant,
|
|
5822
|
+
* `(event) => String(event.new.id)` for per-row. */
|
|
5823
|
+
readonly key?: (event: ReactionEvent) => string;
|
|
4811
5824
|
};
|
|
4812
5825
|
}
|
|
4813
5826
|
|
|
@@ -4815,7 +5828,17 @@ export declare type ReactionOp = 'insert' | 'update' | 'delete';
|
|
|
4815
5828
|
|
|
4816
5829
|
export declare type ReactionOutcome = 'acted' | 'skipped-op' | 'skipped-when' | 'skipped-dedupe' | 'skipped-ratelimit' | 'skipped-budget';
|
|
4817
5830
|
|
|
4818
|
-
/**
|
|
5831
|
+
/**
|
|
5832
|
+
* A per-key sliding-window rate limiter — IN-MEMORY, PER PROCESS.
|
|
5833
|
+
*
|
|
5834
|
+
* Correct for a single-process deployment and wrong for every other one: with N
|
|
5835
|
+
* replicas the effective cap is N×, silently. It stays as the FALLBACK the
|
|
5836
|
+
* runner uses when no durable claimer is wired (dev on the memory store), and
|
|
5837
|
+
* the runner logs once when it falls back, because "my limit is 3× what I
|
|
5838
|
+
* declared" is not something anyone discovers by reading config.
|
|
5839
|
+
*
|
|
5840
|
+
* Prefer {@link ReactionRunDeps.claimRateSlot}.
|
|
5841
|
+
*/
|
|
4819
5842
|
export declare class ReactionRateLimiter {
|
|
4820
5843
|
private readonly hits;
|
|
4821
5844
|
allow(key: string, limit: number, windowMs: number, now: number): boolean;
|
|
@@ -4836,6 +5859,14 @@ export declare interface ReactionRunDeps {
|
|
|
4836
5859
|
/** Per-tenant AI budget check — true = within budget. Injected (wraps
|
|
4837
5860
|
* requireAiBudget). Only consulted when `guards.costBudgetUsd` is set. */
|
|
4838
5861
|
readonly checkBudget?: (tenantId: string | null, budgetUsd: number) => boolean | Promise<boolean>;
|
|
5862
|
+
/**
|
|
5863
|
+
* Atomically claim ONE rate slot. `true` = we got it and may fire.
|
|
5864
|
+
*
|
|
5865
|
+
* Injected by the serve layer over the shared store, so the cap holds across
|
|
5866
|
+
* replicas. Absent → the runner falls back to {@link ReactionRateLimiter},
|
|
5867
|
+
* which is per-process and therefore N× on N replicas.
|
|
5868
|
+
*/
|
|
5869
|
+
readonly claimRateSlot?: (key: string) => Promise<boolean>;
|
|
4839
5870
|
readonly rateLimiter?: ReactionRateLimiter;
|
|
4840
5871
|
readonly now?: () => number;
|
|
4841
5872
|
}
|
|
@@ -5124,6 +6155,8 @@ export declare interface RegistryTableLike {
|
|
|
5124
6155
|
readonly expr: string;
|
|
5125
6156
|
readonly stored: boolean;
|
|
5126
6157
|
};
|
|
6158
|
+
/** `crdtText()` column — server-merged on write (see crdtColumns above). */
|
|
6159
|
+
readonly crdtManaged?: boolean;
|
|
5127
6160
|
}>;
|
|
5128
6161
|
readonly appliedMixins?: ReadonlyArray<{
|
|
5129
6162
|
readonly id?: string;
|
|
@@ -5546,6 +6579,9 @@ export declare interface ResourcePolicy {
|
|
|
5546
6579
|
readonly implies?: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
5547
6580
|
}
|
|
5548
6581
|
|
|
6582
|
+
/** The full ordered list of result variants for a definition, holdout last. */
|
|
6583
|
+
export declare const resultVariantNames: (def: ExperimentDefinition) => ReadonlyArray<string>;
|
|
6584
|
+
|
|
5549
6585
|
export { RetentionSpec }
|
|
5550
6586
|
|
|
5551
6587
|
export { retentionTtlMsFromEnv }
|
|
@@ -5605,6 +6641,10 @@ declare type Row_4 = Readonly<Record<string, unknown>>;
|
|
|
5605
6641
|
|
|
5606
6642
|
declare type Row_5 = Readonly<Record<string, unknown>>;
|
|
5607
6643
|
|
|
6644
|
+
declare type Row_6 = Readonly<Record<string, unknown>>;
|
|
6645
|
+
|
|
6646
|
+
declare type Row_7 = Readonly<Record<string, unknown>>;
|
|
6647
|
+
|
|
5608
6648
|
export declare interface RowDiff {
|
|
5609
6649
|
readonly table: string;
|
|
5610
6650
|
readonly op: 'insert' | 'update' | 'delete';
|
|
@@ -6273,6 +7313,12 @@ export declare interface SchemaInfo {
|
|
|
6273
7313
|
* them and the dialect rejects an explicit value.
|
|
6274
7314
|
*/
|
|
6275
7315
|
readonly generatedColumns: ReadonlySet<string>;
|
|
7316
|
+
/**
|
|
7317
|
+
* CRDT-managed columns (`crdtText()`). The MutationStore folds an incoming
|
|
7318
|
+
* encoded update into the stored state (`mergeCrdtStates`) before the write,
|
|
7319
|
+
* so concurrent clients converge. Empty set for the common non-CRDT table.
|
|
7320
|
+
*/
|
|
7321
|
+
readonly crdtColumns: ReadonlySet<string>;
|
|
6276
7322
|
/**
|
|
6277
7323
|
* Columns that a query predicate can hit and benefit from an index:
|
|
6278
7324
|
* - column-level `.index()`-flagged columns
|
|
@@ -6321,6 +7367,10 @@ export declare interface SchemaRegistry {
|
|
|
6321
7367
|
* optional half. */
|
|
6322
7368
|
hasExpires(table: string): boolean;
|
|
6323
7369
|
hasTenant(table: string): boolean;
|
|
7370
|
+
/** True when the table carries `localFirst()` — it is mirrored to the client,
|
|
7371
|
+
* synced bi-directionally, and its `crdtText()` fields converge via CRDT
|
|
7372
|
+
* merge. The discovery signal for the client-sync-set builder. */
|
|
7373
|
+
hasLocalFirst(table: string): boolean;
|
|
6324
7374
|
/** Field-existence check — used to skip stamping a column that the
|
|
6325
7375
|
* table doesn't actually declare (extra defensive). */
|
|
6326
7376
|
hasField(table: string, field: string): boolean;
|
|
@@ -6362,6 +7412,12 @@ export declare interface SchemaRegistry {
|
|
|
6362
7412
|
* Postgres reject an explicit value.
|
|
6363
7413
|
*/
|
|
6364
7414
|
generatedColumns(table: string): ReadonlySet<string>;
|
|
7415
|
+
/**
|
|
7416
|
+
* CRDT-managed columns (`crdtText()`) for a table. Empty set if none.
|
|
7417
|
+
* Read by the MutationStore write path to run the authoritative server-side
|
|
7418
|
+
* CRDT merge (`mergeCrdtStates`) before an INSERT/UPDATE persists.
|
|
7419
|
+
*/
|
|
7420
|
+
crdtColumns(table: string): ReadonlySet<string>;
|
|
6365
7421
|
/**
|
|
6366
7422
|
* Optional pre-INSERT schema decoder for a table. `undefined` when
|
|
6367
7423
|
* the table didn't ship `.validate(schema)`. The MutationStore runs
|
|
@@ -6468,6 +7524,13 @@ export declare interface ServeRequestContext {
|
|
|
6468
7524
|
readonly rowFilter?: RowFilterScope;
|
|
6469
7525
|
}
|
|
6470
7526
|
|
|
7527
|
+
/**
|
|
7528
|
+
* The `.serverOnly()` column names of a table by NAME, or `[]` when the table is
|
|
7529
|
+
* not registered (a computed query, a raw-SQL descriptor, an unknown source) —
|
|
7530
|
+
* an unknown table strips nothing rather than throwing.
|
|
7531
|
+
*/
|
|
7532
|
+
export declare const serverOnlyColumnNames: (tableName: string) => ReadonlyArray<string>;
|
|
7533
|
+
|
|
6471
7534
|
/** One leak: a wire query that declares a serverOnly column in its output. */
|
|
6472
7535
|
export declare interface ServerOnlyLeak {
|
|
6473
7536
|
readonly query: string;
|
|
@@ -6490,6 +7553,18 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
|
|
|
6490
7553
|
readonly subject: unknown;
|
|
6491
7554
|
readonly traceId: string;
|
|
6492
7555
|
readonly spanId?: string;
|
|
7556
|
+
/**
|
|
7557
|
+
* The declared write-target TABLE names of this mutation/action, derived
|
|
7558
|
+
* from the descriptor's `target:` block. Absent when the descriptor
|
|
7559
|
+
* declares no target (and always absent for queries).
|
|
7560
|
+
*
|
|
7561
|
+
* Carried so an interceptor — the audit sink is the motivating consumer —
|
|
7562
|
+
* can see WHICH tables an operation writes without reaching for the
|
|
7563
|
+
* descriptor registry. Purely additive: an interceptor that ignores it is
|
|
7564
|
+
* unaffected. It is the descriptor's DECLARED target, not the rows actually
|
|
7565
|
+
* written; a change-set carry (the rows/ids) is a separate, heavier seam.
|
|
7566
|
+
*/
|
|
7567
|
+
readonly target?: ReadonlyArray<string>;
|
|
6493
7568
|
}) => Effect.Effect<unknown, unknown, never>;
|
|
6494
7569
|
|
|
6495
7570
|
/** Register the process-wide connection resolver (or clear with `undefined`).
|
|
@@ -6780,6 +7855,45 @@ declare const StoreOperationFailed_base: Schema.TaggedErrorClass<StoreOperationF
|
|
|
6780
7855
|
cause: typeof Schema.String;
|
|
6781
7856
|
}>;
|
|
6782
7857
|
|
|
7858
|
+
/**
|
|
7859
|
+
* Drop `columns` from every row. Preserves array AND per-row object identity when
|
|
7860
|
+
* nothing is actually removed: an empty column set returns the input array
|
|
7861
|
+
* unchanged, and a row that carries none of the columns is returned as-is. That
|
|
7862
|
+
* identity preservation is load-bearing for the dispatcher's diff memo, which
|
|
7863
|
+
* keys on `prev` object identity — a gratuitous clone would defeat it.
|
|
7864
|
+
*/
|
|
7865
|
+
export declare const stripColumnsFromRows: <R extends Row>(rows: ReadonlyArray<R>, columns: ReadonlyArray<string>) => ReadonlyArray<R>;
|
|
7866
|
+
|
|
7867
|
+
/**
|
|
7868
|
+
* Strip a source table's columns this SUBJECT may not see — `.serverOnly()` ∪
|
|
7869
|
+
* the `.readableBy(...)` columns the subject lacks a scope for — from a row set
|
|
7870
|
+
* produced for the wire. The subject-aware counterpart of
|
|
7871
|
+
* {@link stripServerOnlyForWire}; the two share the same value-level
|
|
7872
|
+
* {@link stripColumnsFromRows} pass (and its array/row identity preservation),
|
|
7873
|
+
* so a table with no `.serverOnly()` and no `.readableBy(...)` column returns the
|
|
7874
|
+
* input array unchanged for every subject.
|
|
7875
|
+
*
|
|
7876
|
+
* Same ACCEPTED GAP as `stripServerOnlyForWire`: only TOP-LEVEL columns of
|
|
7877
|
+
* `tableName` are matched — an eager-loaded relation or a renamed projection
|
|
7878
|
+
* changes the shape and is not covered here.
|
|
7879
|
+
*/
|
|
7880
|
+
export declare const stripForbiddenForWire: <R extends Row>(tableName: string, rows: ReadonlyArray<R>, subject: Subject) => ReadonlyArray<R>;
|
|
7881
|
+
|
|
7882
|
+
/**
|
|
7883
|
+
* Strip a source table's `.serverOnly()` columns from a row set produced for the
|
|
7884
|
+
* wire. The runtime half of the exposure policy: declare `.serverOnly()` ONCE at
|
|
7885
|
+
* the schema and every query/subscription output respects it, regardless of
|
|
7886
|
+
* whether the handler was hand-written or a `crud.*` helper.
|
|
7887
|
+
*
|
|
7888
|
+
* ACCEPTED GAP (documented): only TOP-LEVEL columns of `tableName` are matched.
|
|
7889
|
+
* An eager-loaded relation nests another table's columns under a relation key,
|
|
7890
|
+
* and a computed/renamed projection changes the shape — neither is covered here
|
|
7891
|
+
* (mark the nested column `.serverOnly()` on ITS table and read it through a
|
|
7892
|
+
* `crud.*`/redacted path, or omit it from the output schema so the boot audit
|
|
7893
|
+
* blocks it). The marker + `crud.*` path covers the common flat-row case.
|
|
7894
|
+
*/
|
|
7895
|
+
export declare const stripServerOnlyForWire: <R extends Row>(tableName: string, rows: ReadonlyArray<R>) => ReadonlyArray<R>;
|
|
7896
|
+
|
|
6783
7897
|
export declare interface SubscribeContext {
|
|
6784
7898
|
/** Logger scoped to the subscriber file (`subscribe:<filename>`). */
|
|
6785
7899
|
readonly log: SyncLogger;
|
|
@@ -7037,6 +8151,22 @@ declare const TableValidationFailed_base: Schema.TaggedErrorClass<TableValidatio
|
|
|
7037
8151
|
}>>;
|
|
7038
8152
|
}>;
|
|
7039
8153
|
|
|
8154
|
+
/** The standing compute-cost attribution for one tenant — the chargeback /
|
|
8155
|
+
* showback row + the "why is my bill high" breakdown. Maintained incrementally:
|
|
8156
|
+
* every `CostEvent` folds into `total`, `byUnit`, and `bySubscription` in O(1).
|
|
8157
|
+
* Cumulative since boot (or since the last `reset(tenantId)`). */
|
|
8158
|
+
export declare interface TenantCostState {
|
|
8159
|
+
readonly tenantId: string | null;
|
|
8160
|
+
/** Grand total across every unit. */
|
|
8161
|
+
readonly total: number;
|
|
8162
|
+
/** Per-unit breakdown (`recompute` → n, `query` → n, …). */
|
|
8163
|
+
readonly byUnit: Readonly<Record<string, number>>;
|
|
8164
|
+
/** Per-subscription breakdown — the per-subscription attribution. Only
|
|
8165
|
+
* subscription-tagged events contribute. */
|
|
8166
|
+
readonly bySubscription: Readonly<Record<string, number>>;
|
|
8167
|
+
readonly lastUpdatedAt: number | null;
|
|
8168
|
+
}
|
|
8169
|
+
|
|
7040
8170
|
/**
|
|
7041
8171
|
* A write was attempted against a `tenant()`-scoped table, but the
|
|
7042
8172
|
* authenticated subject's `tenantId` is null — either anonymous, or
|
|
@@ -7138,6 +8268,14 @@ export declare interface TopNQuery {
|
|
|
7138
8268
|
readonly tenantId?: string | null;
|
|
7139
8269
|
}
|
|
7140
8270
|
|
|
8271
|
+
/** A row a mutation wrote, carried to rule evaluation. `row` is the POST-WRITE
|
|
8272
|
+
* state (insert result / update result). */
|
|
8273
|
+
declare interface TouchedRow {
|
|
8274
|
+
readonly table: string;
|
|
8275
|
+
readonly id: string;
|
|
8276
|
+
readonly row: AnyRow;
|
|
8277
|
+
}
|
|
8278
|
+
|
|
7141
8279
|
export declare interface TraceContext {
|
|
7142
8280
|
readonly traceId: string;
|
|
7143
8281
|
readonly spanId: string;
|
|
@@ -7472,6 +8610,18 @@ export declare const useAggregate: <Row>(def: AggregateDefinition<Row>) => Effec
|
|
|
7472
8610
|
*/
|
|
7473
8611
|
export declare const useAnalytics: () => Effect.Effect<AnalyticsSinkImpl, never, AnalyticsSink>;
|
|
7474
8612
|
|
|
8613
|
+
/** Sugar: read one budget's current state for a tenant from a handler. Returns
|
|
8614
|
+
* `null` when the budget name is not registered (none attached, or a typo). */
|
|
8615
|
+
export declare const useCostBudget: (def: CostBudgetDefinition, tenantId: string | null) => Effect.Effect<CostBudgetState | null, never, CostRegistry>;
|
|
8616
|
+
|
|
8617
|
+
/** Sugar: read one expectation's current state from a handler. Returns `null`
|
|
8618
|
+
* when the name is not registered (no expectations attached, or a typo). */
|
|
8619
|
+
export declare const useExpectation: (def: ExpectationDefinition) => Effect.Effect<ExpectationState | null, never, ExpectationRegistry>;
|
|
8620
|
+
|
|
8621
|
+
/** Sugar: read one experiment's current result from a handler. Returns `null`
|
|
8622
|
+
* when the name is not registered (none attached, or a typo). */
|
|
8623
|
+
export declare const useExperiment: (def: ExperimentDefinition) => Effect.Effect<ExperimentResult | null, never, ExperimentRegistry>;
|
|
8624
|
+
|
|
7475
8625
|
declare type VaultStore = Pick<DataStore, 'query' | 'insert' | 'update' | 'updateMany' | 'delete' | 'upsert'>;
|
|
7476
8626
|
|
|
7477
8627
|
/** The pure core of live revocation: given a subject + action + a row-set,
|
|
@@ -7496,6 +8646,10 @@ export declare const VOLTRO_AUDIT_MIXIN_ID: "voltro/audit";
|
|
|
7496
8646
|
* consumer). Kept in step by `expiresMixinId.test.ts`. */
|
|
7497
8647
|
export declare const VOLTRO_EXPIRES_MIXIN_ID: "voltro/expires";
|
|
7498
8648
|
|
|
8649
|
+
/** Re-declared here for the same reason as the ids above (no mixin import).
|
|
8650
|
+
* Kept in step by `localFirstMixinId.test.ts`. */
|
|
8651
|
+
export declare const VOLTRO_LOCAL_FIRST_MIXIN_ID: "voltro/localFirst";
|
|
8652
|
+
|
|
7499
8653
|
export declare const VOLTRO_SOFT_DELETE_MIXIN_ID: "voltro/softDelete";
|
|
7500
8654
|
|
|
7501
8655
|
export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
@@ -7689,6 +8843,44 @@ export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in'
|
|
|
7689
8843
|
*/
|
|
7690
8844
|
export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
|
|
7691
8845
|
|
|
8846
|
+
/**
|
|
8847
|
+
* What the gate decided. `passthrough` is the common case — no controls
|
|
8848
|
+
* declared — and is distinct from `start` so the facade can skip the commit
|
|
8849
|
+
* bookkeeping entirely rather than calling a no-op.
|
|
8850
|
+
*/
|
|
8851
|
+
export declare type WorkflowAdmissionOutcome = {
|
|
8852
|
+
readonly kind: 'passthrough';
|
|
8853
|
+
} | {
|
|
8854
|
+
readonly kind: 'start';
|
|
8855
|
+
/** What to start WITH. Differs from the caller's payload only for a batch
|
|
8856
|
+
* that filled up on this arrival. */
|
|
8857
|
+
readonly payload: unknown;
|
|
8858
|
+
/** Called once the engine returns an execution id: links the ledger row,
|
|
8859
|
+
* consumes any batched intents, evicts a singleton incumbent. */
|
|
8860
|
+
readonly commit: (executionId: string) => Promise<void>;
|
|
8861
|
+
} | {
|
|
8862
|
+
readonly kind: 'queued';
|
|
8863
|
+
readonly intentId: string;
|
|
8864
|
+
readonly mode: string;
|
|
8865
|
+
readonly dueAt: number;
|
|
8866
|
+
} | {
|
|
8867
|
+
readonly kind: 'dropped';
|
|
8868
|
+
readonly retryAfterMs: number;
|
|
8869
|
+
} | {
|
|
8870
|
+
readonly kind: 'skipped';
|
|
8871
|
+
readonly executionId: string;
|
|
8872
|
+
};
|
|
8873
|
+
|
|
8874
|
+
export declare interface WorkflowAdmissionRequest {
|
|
8875
|
+
readonly workflowName: string;
|
|
8876
|
+
readonly payload: unknown;
|
|
8877
|
+
readonly callerContext: WorkflowCallerContext | undefined;
|
|
8878
|
+
/** `true` for `run(...)` and `start(..., { wait: true })` — a caller blocking
|
|
8879
|
+
* for the RESULT. A deferring control has no coherent answer for one, so the
|
|
8880
|
+
* gate refuses it by name rather than doing something surprising. */
|
|
8881
|
+
readonly waiting: boolean;
|
|
8882
|
+
}
|
|
8883
|
+
|
|
7692
8884
|
export declare interface WorkflowCallerContext {
|
|
7693
8885
|
readonly subject?: unknown;
|
|
7694
8886
|
readonly traceId?: string | null;
|
|
@@ -7703,6 +8895,19 @@ export declare interface WorkflowChildOptions {
|
|
|
7703
8895
|
/* Excluded from this release type: callerContext */
|
|
7704
8896
|
}
|
|
7705
8897
|
|
|
8898
|
+
/**
|
|
8899
|
+
* Reject a blocking caller on a workflow whose flow control can DEFER.
|
|
8900
|
+
*
|
|
8901
|
+
* `run(...)` and `start({ wait: true })` block for the run's RESULT, and there
|
|
8902
|
+
* is no result to return for a start that was collapsed into a future run.
|
|
8903
|
+
* Silently starting it anyway would break the declared limit; silently
|
|
8904
|
+
* returning nothing would break the caller's type. So it is an error that names
|
|
8905
|
+
* both halves.
|
|
8906
|
+
*/
|
|
8907
|
+
export declare class WorkflowDeferredResultError extends Error {
|
|
8908
|
+
constructor(workflowName: string, method: string, mode: string);
|
|
8909
|
+
}
|
|
8910
|
+
|
|
7706
8911
|
export declare interface WorkflowDefinitionLike {
|
|
7707
8912
|
readonly name: string;
|
|
7708
8913
|
readonly payloadSchema: Schema.Schema.Any;
|
|
@@ -7790,6 +8995,17 @@ export declare interface WorkflowFacadeOptions {
|
|
|
7790
8995
|
* waiting for its next storage tick. No-op when unset (single replica / no
|
|
7791
8996
|
* broker → the poll interval covers it). */
|
|
7792
8997
|
readonly onEnqueue?: () => void;
|
|
8998
|
+
/**
|
|
8999
|
+
* Declarative flow control — the admission boundary every start passes
|
|
9000
|
+
* through when the workflow declares `debounce` / `singleton` / `concurrency`
|
|
9001
|
+
* / `throttle` / `rateLimit` / `batch`, or when an operator paused it.
|
|
9002
|
+
*
|
|
9003
|
+
* Injected, and absent by default, for the same reason `listRuns` is: the
|
|
9004
|
+
* runtime stays storage-agnostic and the CLI owns the tables. Absent ⇒ every
|
|
9005
|
+
* start takes exactly the path it took before flow control existed, which is
|
|
9006
|
+
* what makes this feature free for a workflow that declares none.
|
|
9007
|
+
*/
|
|
9008
|
+
readonly admitStart?: (input: WorkflowAdmissionRequest) => Promise<WorkflowAdmissionOutcome>;
|
|
7793
9009
|
}
|
|
7794
9010
|
|
|
7795
9011
|
export declare interface WorkflowLayerExecutionContext {
|
|
@@ -7830,6 +9046,20 @@ export declare class WorkflowPayloadError extends Error {
|
|
|
7830
9046
|
missingFields: ReadonlyArray<string>, detail: string);
|
|
7831
9047
|
}
|
|
7832
9048
|
|
|
9049
|
+
/**
|
|
9050
|
+
* A blocking caller hit a `rateLimit` cap.
|
|
9051
|
+
*
|
|
9052
|
+
* `start()` reports a drop through the handle, because a fire-and-forget caller
|
|
9053
|
+
* has somewhere to put it. `run()` has no such place — its return type is the
|
|
9054
|
+
* workflow's success value — so the drop has to be an error, or it would look
|
|
9055
|
+
* like a run that returned `undefined`.
|
|
9056
|
+
*/
|
|
9057
|
+
export declare class WorkflowRateLimitedError extends Error {
|
|
9058
|
+
readonly workflowName: string;
|
|
9059
|
+
readonly retryAfterMs: number;
|
|
9060
|
+
constructor(workflowName: string, retryAfterMs: number);
|
|
9061
|
+
}
|
|
9062
|
+
|
|
7833
9063
|
/** Result of {@link WorkflowsAppContext.redrive} — whether the failed run's
|
|
7834
9064
|
* durable journal was re-driven, how many failed step attempts were reset so
|
|
7835
9065
|
* they re-execute, and a `reason` when it declined (no journal / still
|
|
@@ -7861,6 +9091,24 @@ export declare interface WorkflowRunListFilter {
|
|
|
7861
9091
|
readonly workflowName?: string;
|
|
7862
9092
|
readonly tag?: string;
|
|
7863
9093
|
readonly status?: WorkflowRunRecordStatus;
|
|
9094
|
+
/** Several statuses at once (`['failed', 'cancelled']`). Ignored when the
|
|
9095
|
+
* single `status` is also set — one of them has to win, and the singular,
|
|
9096
|
+
* older spelling is the one existing callers already rely on. */
|
|
9097
|
+
readonly statuses?: ReadonlyArray<WorkflowRunRecordStatus>;
|
|
9098
|
+
/** Case-insensitive substring match on the workflow tag — the search-box
|
|
9099
|
+
* semantic, where `tag`/`workflowName` are exact. */
|
|
9100
|
+
readonly tagContains?: string;
|
|
9101
|
+
/** Exact match on the run's recorded `source` (`workflow-rpc`,
|
|
9102
|
+
* `app-context`, `inspect`, `schedule:<name>`, …). */
|
|
9103
|
+
readonly source?: string;
|
|
9104
|
+
/** Prefix match against the run id OR the execution id — what a human
|
|
9105
|
+
* pastes from a log line. Case-sensitive, because ids are. */
|
|
9106
|
+
readonly idPrefix?: string;
|
|
9107
|
+
/** Only runs started at/after this instant. With `startedBefore` this is
|
|
9108
|
+
* the time-range view; each bound works alone too. */
|
|
9109
|
+
readonly startedAfter?: Date;
|
|
9110
|
+
/** Only runs started strictly before this instant. */
|
|
9111
|
+
readonly startedBefore?: Date;
|
|
7864
9112
|
/** The DEAD-LETTER view: failed runs an operator has NOT yet discarded
|
|
7865
9113
|
* (`status = 'failed' AND discardedAt IS NULL`). Since the framework applies no
|
|
7866
9114
|
* retry, a `failed` run is terminal — this is the queue of unhandled failures.
|
|
@@ -7967,6 +9215,14 @@ export declare interface WorkflowSignalTarget {
|
|
|
7967
9215
|
readonly workflowName?: string;
|
|
7968
9216
|
}
|
|
7969
9217
|
|
|
9218
|
+
/** A blocking caller lost a `singleton: { mode: 'skip' }` race. Carries the
|
|
9219
|
+
* incumbent's execution id, so the caller can wait on THAT run instead. */
|
|
9220
|
+
export declare class WorkflowSingletonHeldError extends Error {
|
|
9221
|
+
readonly workflowName: string;
|
|
9222
|
+
readonly executionId: string;
|
|
9223
|
+
constructor(workflowName: string, executionId: string);
|
|
9224
|
+
}
|
|
9225
|
+
|
|
7970
9226
|
export declare interface WorkflowStartOptions {
|
|
7971
9227
|
readonly wait?: boolean;
|
|
7972
9228
|
/* Excluded from this release type: callerContext */
|
|
@@ -7975,8 +9231,14 @@ export declare interface WorkflowStartOptions {
|
|
|
7975
9231
|
export declare const workflowToRpc: (workflow: WorkflowDefinitionLike) => Rpc.Rpc<string, Schema.Schema.Any, Schema.Struct<{
|
|
7976
9232
|
id: typeof Schema.String;
|
|
7977
9233
|
workflowName: typeof Schema.String;
|
|
7978
|
-
executionId: typeof Schema.String
|
|
7979
|
-
status: Schema.Literal<["running"]>;
|
|
9234
|
+
executionId: Schema.NullOr<typeof Schema.String>;
|
|
9235
|
+
status: Schema.Literal<["running", "queued", "dropped", "skipped"]>;
|
|
9236
|
+
deferral: Schema.optional<Schema.Struct<{
|
|
9237
|
+
mode: typeof Schema.String;
|
|
9238
|
+
dueAt: Schema.NullOr<typeof Schema.Number>;
|
|
9239
|
+
retryAfterMs: Schema.NullOr<typeof Schema.Number>;
|
|
9240
|
+
intentId: Schema.NullOr<typeof Schema.String>;
|
|
9241
|
+
}>>;
|
|
7980
9242
|
}>, Schema.Schema.All, never>;
|
|
7981
9243
|
|
|
7982
9244
|
export declare interface WorkflowUpdateOptions {
|