@voltro/runtime 0.8.0 → 0.10.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
@@ -1662,6 +1662,17 @@ export declare class DeleteBuilder {
1662
1662
  hard(): Promise<number>;
1663
1663
  }
1664
1664
 
1665
+ /** Row count for a delivered payload, for the trace/metric tap.
1666
+ *
1667
+ * A computed query's value is arbitrary — a scalar, an object, an array, or
1668
+ * nothing at all. Reporting a fixed 1 for all of them (which this used to do)
1669
+ * turned the `rows` attribute into a liveness signal that reads like a data
1670
+ * signal: an `employees.me` that resolved to `null` still traced `rows=1`, and
1671
+ * a downstream consumer used exactly that span to conclude the server had
1672
+ * produced a value when it had not. Absent is 0, a single value is 1, an array
1673
+ * is its length. */
1674
+ export declare const deliveredRowCount: (value: unknown) => number;
1675
+
1665
1676
  /**
1666
1677
  * Derive a 32-byte AES-256 key from a passphrase via scrypt. A fixed
1667
1678
  * salt keeps derivation deterministic across processes (the passphrase
@@ -1798,6 +1809,15 @@ export declare interface DispatcherDependencies {
1798
1809
  readonly cache?: SnapshotCache;
1799
1810
  }
1800
1811
 
1812
+ /** Bookkeeping tenant key for the `_voltro_wakeups` rows that dormant schedules
1813
+ * and dormant workflow waits register when their subject carries no tenant.
1814
+ *
1815
+ * NOT a security scope — but it drifted exactly like the subject did: dev keyed
1816
+ * these rows under `$TENANT ?? 'acme'` and serve under `'default'`, in BOTH the
1817
+ * schedule and the workflow wiring, so a wakeup written by one boot path was
1818
+ * invisible to a waker looking under the other's key. */
1819
+ export declare const DORMANCY_WAKEUP_TENANT = "default";
1820
+
1801
1821
  export declare interface DrainDeps {
1802
1822
  readonly store: Pick<DataStore, 'query' | 'update' | 'insert' | 'delete'>;
1803
1823
  readonly handlers: ReadonlyMap<string, OutboxHandlerDefinition>;
@@ -2752,7 +2772,7 @@ export declare const makeWorkflowFacadeWithCallerContext: (base: WorkflowsAppCon
2752
2772
 
2753
2773
  export declare const makeWorkflowHandlers: (workflows: ReadonlyArray<DiscoveredWorkflowLike>, facade?: WorkflowsAppContext) => Record<string, (input: unknown) => Effect.Effect<WorkflowRunHandle, unknown, unknown>>;
2754
2774
 
2755
- export declare const makeWorkflowLayers: <Context>(workflows: ReadonlyArray<DiscoveredWorkflowLike<Context>>, options: WorkflowLayerOptions<Context>, wrapExecute?: (workflow: DiscoveredWorkflowLike<Context>, execute: (payload: never, executionId: string) => Effect.Effect<unknown, unknown, unknown>, callerContext: WorkflowCallerContext | undefined) => (payload: never, executionId: string) => Effect.Effect<unknown, unknown, unknown>) => ReadonlyArray<Layer.Layer<unknown, unknown, unknown>>;
2775
+ export declare const makeWorkflowLayers: <Context>(workflows: ReadonlyArray<DiscoveredWorkflowLike<Context>>, options: WorkflowLayerOptions<Context>, wrapExecute?: (workflow: DiscoveredWorkflowLike<Context>, execute: (payload: never, executionId: string) => Effect.Effect<unknown, unknown, unknown>, callerContext: ResolvedWorkflowCallerContext) => (payload: never, executionId: string) => Effect.Effect<unknown, unknown, unknown>) => ReadonlyArray<Layer.Layer<unknown, unknown, unknown>>;
2756
2776
 
2757
2777
  export declare const makeWorkflowRunHandle: (workflowName: string, executionId: string) => WorkflowRunHandle;
2758
2778
 
@@ -3972,6 +3992,28 @@ export declare interface ResolvedConnection {
3972
3992
  */
3973
3993
  export declare const resolveDependentTables: (descriptor: QueryDescriptor) => ReadonlySet<string>;
3974
3994
 
3995
+ /**
3996
+ * A `WorkflowCallerContext` after the engine has filled in what a workflow
3997
+ * always needs. A run started by a request carries the caller's subject; a
3998
+ * BOOTSTRAP run — one resumed after a restart with no recorded start context —
3999
+ * carries `SYSTEM_SUBJECT`.
4000
+ *
4001
+ * The engine resolves this so `buildContext` never has to. When each boot path
4002
+ * supplied its own fallback, `voltro dev` used the dev REQUEST fallback
4003
+ * (`anonymousSubject($TENANT ?? 'acme')`) and `voltro serve` used a tenant-less
4004
+ * subject — the same resumed workflow read one tenant's rows in dev and every
4005
+ * tenant's in production, with no error on either side. Same defect as the
4006
+ * schedule path; found while fixing it.
4007
+ */
4008
+ export declare interface ResolvedWorkflowCallerContext extends WorkflowCallerContext {
4009
+ /** Never absent — the caller's subject, or `SYSTEM_SUBJECT` for a bootstrap
4010
+ * run. Typed `unknown` to match `WorkflowCallerContext.subject`; the CLI
4011
+ * narrows it to `Subject` at the one place it builds the AppContext. */
4012
+ readonly subject: unknown;
4013
+ /** Never absent — the caller's trace id, or `'workflow-bootstrap'`. */
4014
+ readonly traceId: string;
4015
+ }
4016
+
3975
4017
  export declare const resolveRedirectUri: (definition: OAuth2ConnectionDefinition, publicUrl: string) => string;
3976
4018
 
3977
4019
  /** Rebuild the flagged groups from the (already-committed) base rows + merge —
@@ -4456,6 +4498,19 @@ export declare const sanitizeRedirectTo: (value: string | undefined) => string;
4456
4498
  * default export without importing the concrete class. */
4457
4499
  declare const SCHEDULE_BRAND: unique symbol;
4458
4500
 
4501
+ /**
4502
+ * Who a schedule runs as — the shared `SYSTEM_SUBJECT` (see its doc comment for
4503
+ * why one definition exists at all).
4504
+ *
4505
+ * What matters HERE is that the ENGINE owns it, not whoever wires the engine.
4506
+ * The two callers owning it separately is exactly what went wrong: `voltro
4507
+ * serve` passed a tenant-less subject while `voltro dev` passed the dev REQUEST
4508
+ * fallback (`anonymousSubject($TENANT ?? 'acme')`) — a value meant for a
4509
+ * login-less HTTP call, which a schedule never is. A `buildContext` that
4510
+ * RECEIVES the subject cannot make that mistake; one that invents it can.
4511
+ */
4512
+ export declare const SCHEDULE_SUBJECT: Subject;
4513
+
4459
4514
  /** What happens to firings missed during downtime (computed on boot
4460
4515
  * from the last-fired-at row). */
4461
4516
  export declare type ScheduleBackfillPolicy = 'skip' | 'latest' | 'all';
@@ -4575,14 +4630,17 @@ export declare interface SchedulerDeps {
4575
4630
  * whose effective trigger is `external` are NOT armed by the timer
4576
4631
  * loop — they fire via the HTTP endpoint. */
4577
4632
  readonly defaultTrigger: ScheduleTrigger;
4578
- /** Builds the per-firing AppContext (store + anonymous subject +
4579
- * webhooks). The CLI supplies this — it knows how to wrap the
4580
- * store with mixin behaviour. */
4633
+ /** Builds the per-firing AppContext (store + webhooks). The CLI supplies
4634
+ * this — it knows how to wrap the store with mixin behaviour — but it does
4635
+ * NOT choose the subject: `SCHEDULE_SUBJECT` is handed in, so `voltro dev`
4636
+ * and `voltro serve` cannot disagree about who a cron runs as. */
4581
4637
  readonly buildContext: (input: {
4582
4638
  readonly traceId: string;
4583
4639
  readonly scheduleName: string;
4584
4640
  readonly trigger: ScheduleTrigger | 'manual';
4585
4641
  readonly runId: string | undefined;
4642
+ /** Always `SCHEDULE_SUBJECT`. Forward it — do not construct one. */
4643
+ readonly subject: Subject;
4586
4644
  }) => ScheduleContext['app'];
4587
4645
  /** Stable id of this process/replica (pod name, pid, …) recorded on
4588
4646
  * every run row for debugging "which instance fired this." */
@@ -5074,6 +5132,26 @@ export declare interface SubscribeContext {
5074
5132
  readonly log: SyncLogger;
5075
5133
  /** Stable id derived from the file name (no `.subscribe.ts` suffix). */
5076
5134
  readonly id: string;
5135
+ /**
5136
+ * The framework's store, for reading or writing in reaction to the commit.
5137
+ *
5138
+ * **Runs as `SYSTEM_SUBJECT` — it is NOT tenant-scoped.** A subscriber fires
5139
+ * from the change stream, not from a request, so there is no subject to scope
5140
+ * to and no tenant to infer; queries see every tenant's rows. When the
5141
+ * reaction is per-tenant, take the tenant from the row that changed
5142
+ * (`event.new?.tenantId ?? event.old?.tenantId`) and filter on it explicitly.
5143
+ * Writes are mixin-stamped exactly as a request-path write is, so `tenantId`
5144
+ * on an inserted row is yours to set — nothing fills it in for you.
5145
+ *
5146
+ * This is the same posture a `*.cron.tsx` handler's `ctx.app.store` has, on
5147
+ * purpose: both are post-request system work, and having them differ is how
5148
+ * the schedule path drifted between `voltro dev` and `voltro serve`.
5149
+ *
5150
+ * Still absent, still deliberate: there is no workflow handle here. A
5151
+ * subscriber is best-effort and non-durable, so "a row changed → start a
5152
+ * workflow" belongs in a `*.reaction.tsx`, whose `act` is crash-safe.
5153
+ */
5154
+ readonly store: FluentStore;
5077
5155
  }
5078
5156
 
5079
5157
  /** Event shape passed to a subscriber handler. Same as the
@@ -5113,6 +5191,12 @@ export declare interface SubscriptionDelivery {
5113
5191
  readonly startMs: number;
5114
5192
  readonly endMs: number;
5115
5193
  readonly durationMs: number;
5194
+ /** How many rows this delivery carried. A row-set subscription reports its
5195
+ * real length; a COMPUTED query has no row set, so it reports 1 for a
5196
+ * delivered value and **0 for `null`/`undefined`** — see `deliveredRowCount`.
5197
+ * It must never be a constant: this number is both the `rows` span attribute
5198
+ * and the Prometheus row histogram, and a hardcoded 1 makes "the handler
5199
+ * returned null" look identical to "the handler returned a value". */
5116
5200
  readonly rowCount: number;
5117
5201
  }
5118
5202
 
@@ -5198,6 +5282,13 @@ export { sweepRetention }
5198
5282
  */
5199
5283
  export declare const synthesizeInverse: (cs: ChangeSet) => ReadonlyArray<InverseOp>;
5200
5284
 
5285
+ /** The Subject non-request work runs as: a schedule firing, a workflow resumed
5286
+ * after a restart, a `*.subscribe.ts` handler. `tenantId: null` means
5287
+ * `applyTenantScope` adds no tenant filter, so system work sees every tenant
5288
+ * rather than one arbitrary one. See this file's header for why it is a single
5289
+ * shared value. */
5290
+ export declare const SYSTEM_SUBJECT: Subject;
5291
+
5201
5292
  /** The context handed to a `runAsSystem` block. Mirrors the handler-facing
5202
5293
  * shape: a fluent, system-scoped `store` + the resolved `subject`. */
5203
5294
  export declare interface SystemContext {
@@ -5936,7 +6027,7 @@ export declare interface WorkflowLayerExecutionContext {
5936
6027
  }
5937
6028
 
5938
6029
  export declare interface WorkflowLayerOptions<Context> {
5939
- readonly buildContext: (callerContext: WorkflowCallerContext | undefined, execution: WorkflowLayerExecutionContext) => Context;
6030
+ readonly buildContext: (callerContext: ResolvedWorkflowCallerContext, execution: WorkflowLayerExecutionContext) => Context;
5940
6031
  readonly resolveStartContext?: (workflowName: string, executionId: string) => WorkflowCallerContext | undefined | Promise<WorkflowCallerContext | undefined>;
5941
6032
  }
5942
6033