@voltro/runtime 0.67.0 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -163,6 +163,11 @@ export declare interface ActivitySignals {
163
163
  readonly activeWorkflowExecutions: () => number;
164
164
  }
165
165
 
166
+ /** The framework's universal audit-subject table (`@voltro/database`'s core
167
+ * `actors`). An issued key gets a row here so `audit()`'s `createdBy` foreign
168
+ * key resolves for a key-authenticated caller. */
169
+ export declare const ACTORS_TABLE = "actors";
170
+
166
171
  /**
167
172
  * Adopt a detached subscription for a resuming client at `fromRevision`.
168
173
  * Undefined — identity unknown, entry dead (an error/revocation while
@@ -949,6 +954,28 @@ export declare interface ApiKeyStore {
949
954
  readonly findById: (id: string) => Promise<ApiKeyRow | null>;
950
955
  readonly listByTenant: (tenantId: string | null) => Promise<ReadonlyArray<ApiKeyRow>>;
951
956
  readonly patch: (id: string, fields: Partial<ApiKeyRow>) => Promise<void>;
957
+ /**
958
+ * Plant the key's row in `actors` — the audit subject every `createdBy` /
959
+ * `updatedBy` / `deletedBy` points at.
960
+ *
961
+ * REQUIRED, not optional, and that is the whole point. `actors.kind` is
962
+ * declared by the framework as `'user' | 'serviceAccount' | 'apiKey' |
963
+ * 'system'`, so an API key is explicitly an audit subject — and issuing wrote
964
+ * only `_voltro_api_keys`, so the row never existed. Measured in a consumer's
965
+ * database: `actors with kind='apiKey': 0`. The first audit-stamped insert by
966
+ * a key-authenticated caller then died on the foreign key, which means
967
+ * `apiKeys:` and `audit()` were not combinable and both are on by default.
968
+ *
969
+ * An optional hook would have been the same defect with a nicer name — the
970
+ * mistake was omission, and an optional field is omissible. Same reasoning as
971
+ * `startOutboxRunner`'s required `onShutdown`.
972
+ *
973
+ * Idempotent by contract: it is called on every issue, including a rotation.
974
+ */
975
+ readonly ensureActor: (actor: {
976
+ readonly id: string;
977
+ readonly displayName: string | null;
978
+ }) => Promise<void>;
952
979
  }
953
980
 
954
981
  export declare interface ApiKeyUsageBuffer {
@@ -4758,6 +4785,12 @@ export declare interface FleetMergeResult<T> {
4758
4785
  *
4759
4786
  * Empty when no membership is wired: with no roster there is no evidence of
4760
4787
  * departure, and assuming it would empty a single-process answer.
4788
+ *
4789
+ * A row is departed only when it is BOTH unrostered AND older than the
4790
+ * freshness window. Roster-absence alone is not evidence — a membership view
4791
+ * is per-process, so replicas sharing a database but no bus each hold a roster
4792
+ * of one, and the earlier rule had two live replicas calling each other gone
4793
+ * while reporting an age of under two seconds.
4761
4794
  */
4762
4795
  readonly departed: ReadonlyArray<{
4763
4796
  readonly replicaId: string;
@@ -6280,8 +6313,18 @@ export declare interface MemberSnapshot {
6280
6313
  readonly self: boolean;
6281
6314
  }
6282
6315
 
6283
- /** In-memory store (tests + single-process dev). */
6284
- export declare const memoryApiKeyStore: () => ApiKeyStore;
6316
+ /** The planted audit-subject row, as the memory store records it. */
6317
+ export declare interface MemoryActorRow {
6318
+ readonly id: string;
6319
+ readonly kind: 'apiKey';
6320
+ readonly displayName: string | null;
6321
+ }
6322
+
6323
+ /** In-memory store (tests + single-process dev). The `actors` map is EXPOSED so
6324
+ * a test can assert the row was planted — see `ApiKeyStore.ensureActor`. */
6325
+ export declare const memoryApiKeyStore: () => ApiKeyStore & {
6326
+ readonly actors: ReadonlyMap<string, MemoryActorRow>;
6327
+ };
6285
6328
 
6286
6329
  /**
6287
6330
  * Adapter for in-memory testing. The "position" is a monotonically-
@@ -6814,6 +6857,43 @@ export declare interface OrchestratorTickDeps {
6814
6857
  readonly log?: WakeOrchestratorLogger;
6815
6858
  }
6816
6859
 
6860
+ /**
6861
+ * Referrer policies that make a browser send `Origin: null` on a SAME-ORIGIN
6862
+ * state-changing request — which `classifyRequestOrigin` refuses as opaque.
6863
+ *
6864
+ * This is not a lint about tidiness. `no-referrer` was the default here, and
6865
+ * under it every no-JS `<form method="post">` in every Voltro app answered 403,
6866
+ * on both boot paths, for every user. Two security defaults, each defensible
6867
+ * alone, cancelling each other: we asked the browser to withhold provenance and
6868
+ * then refused the request for lacking it.
6869
+ *
6870
+ * Measured in a real headless chromium against a bare `node:http` server —
6871
+ * ONE header changed between the rows, nothing else:
6872
+ *
6873
+ * Referrer-Policy: (none) → Origin: http://127.0.0.1:45412
6874
+ * Referrer-Policy: no-referrer → Origin: null ← and no Referer
6875
+ * Referrer-Policy: strict-origin-when-cross-origin → Origin: http://127.0.0.1:45412
6876
+ * Referrer-Policy: same-origin → Origin: http://127.0.0.1:45412
6877
+ *
6878
+ * It is the Fetch spec's behaviour, not a chromium quirk: when appending the
6879
+ * `Origin` header, a request whose referrer policy is `no-referrer` serialises
6880
+ * its origin as `null`. `origin-when-cross-origin`'s sibling `unsafe-url` does
6881
+ * not, so the set below is exactly the two that null it unconditionally.
6882
+ *
6883
+ * `same-origin` is NOT in the set — it preserves the Origin for same-origin
6884
+ * requests — but it is also not the default, because it nulls the Origin for a
6885
+ * legitimate CROSS-origin post to an allowlisted api, which is precisely the
6886
+ * split-deployment `VOLTRO_ALLOWED_ORIGINS` exists to permit.
6887
+ * `strict-origin-when-cross-origin` nulls it only on an https→http downgrade,
6888
+ * which is the one case where withholding it is the point. It is also what every
6889
+ * current browser does with no header at all, so it surprises nobody.
6890
+ *
6891
+ * The second casualty of `no-referrer` is in the same request: the Referer is
6892
+ * gone too, and `/form/*` reads it to decide where the PRG redirect and the 422
6893
+ * re-render go back to.
6894
+ */
6895
+ export declare const ORIGIN_NULLING_REFERRER_POLICIES: ReadonlySet<string>;
6896
+
6817
6897
  export declare type OriginDecision = {
6818
6898
  readonly allowed: true;
6819
6899
  readonly reason: 'guard-off' | 'same-origin' | 'loopback' | 'allowlisted' | 'no-browser-origin';
@@ -6945,7 +7025,7 @@ export declare interface OutboxFacade {
6945
7025
  export declare interface OutboxFacadeDeps {
6946
7026
  /** The REQUEST's store. Inside a mutation this is the transactional view —
6947
7027
  * which is what makes the enqueue atomic with the domain write. */
6948
- readonly store: Pick<DataStore, 'insert' | 'query' | 'update'>;
7028
+ readonly store: Pick<DataStore, 'insert' | 'query' | 'updateMany'>;
6949
7029
  readonly subject: Subject;
6950
7030
  readonly traceId: string | null;
6951
7031
  /** Nudge the drain worker once the transaction commits. Optional: without
@@ -7108,6 +7188,15 @@ export declare type PluginScheduleCoordinated = (name: string, intervalMs: numbe
7108
7188
 
7109
7189
  export declare const powerOfTwoSelector: (options?: P2COptions) => ReplicaSelector;
7110
7190
 
7191
+ /**
7192
+ * Whether a `Referrer-Policy` value leaves the `Origin` header intact on a
7193
+ * same-origin state-changing request.
7194
+ *
7195
+ * Reads the LAST supported token, which is what a browser honours: the header
7196
+ * is a fallback list, and every token after a supported one still overrides it.
7197
+ */
7198
+ export declare const preservesSameOriginHeader: (value: string | false) => boolean;
7199
+
7111
7200
  export declare interface PreviewDiff {
7112
7201
  readonly rows: ReadonlyArray<RowDiff>;
7113
7202
  readonly summary: {
@@ -9042,6 +9131,20 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
9042
9131
  * runtime capability so clients replay only when the deployed server proves
9043
9132
  * the same key will be deduplicated. */
9044
9133
  readonly mutationReplay?: boolean;
9134
+ /**
9135
+ * Publish per-rpc-tag SCHEMA SHAPE digests alongside the capabilities, so a
9136
+ * connecting client can tell whether the copy of the api client it loaded is
9137
+ * the one this server binds.
9138
+ *
9139
+ * Dev only, and set by `voltro dev` alone. The staleness it detects is a
9140
+ * pre-bundled browser chunk that Vite did not re-optimize; `voltro serve`
9141
+ * ships a built bundle where that cannot arise, and there a rolling deploy
9142
+ * would make a transient mismatch NORMAL — reporting it would be noise on
9143
+ * exactly the path where noise is expensive. So this is a deliberate
9144
+ * dev/serve asymmetry rather than an oversight, which is the distinction the
9145
+ * boot-path parity rule asks for.
9146
+ */
9147
+ readonly publishSurfaceDigest?: boolean;
9045
9148
  /** Non-sensitive fleet identity written on the rpc WebSocket open/close
9046
9149
  * records. Defaults to the process identity resolver (POD_NAME/HOSTNAME).
9047
9150
  * Embedders may state it explicitly when they own process identity. */