@voltro/runtime 0.71.1 → 0.72.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
@@ -57,6 +57,7 @@ import { KvStoreShape } from '@voltro/kv';
57
57
  import { Layer } from 'effect';
58
58
  import { listRetentionConflicts } from '@voltro/database';
59
59
  import { listRetentions } from '@voltro/database';
60
+ import { Membership } from '@voltro/protocol';
60
61
  import { Metric } from 'effect';
61
62
  import { MetricBoundaries } from 'effect';
62
63
  import { MetricReader } from '@opentelemetry/sdk-metrics';
@@ -107,8 +108,11 @@ import { TableLike } from '@voltro/database';
107
108
  import { TableName } from '@voltro/protocol';
108
109
  import { TableValidationFailed } from '@voltro/protocol';
109
110
  import { TargetRelation } from '@voltro/protocol';
111
+ import { TENANT_SELECTIONS_TABLE } from '@voltro/database';
110
112
  import { TenantRowNotFound } from '@voltro/protocol';
111
113
  import { TenantScopeViolation } from '@voltro/protocol';
114
+ import { tenantSelectionId } from '@voltro/database';
115
+ import { TenantSelectionStore } from '@voltro/protocol';
112
116
  import { Tracer } from 'effect';
113
117
  import { Unauthenticated } from '@voltro/protocol';
114
118
  import { UpsertOptions } from '@voltro/database';
@@ -2134,6 +2138,11 @@ export { clearRetentions }
2134
2138
  /** Clear the process-wide handle (test teardown). */
2135
2139
  export declare const clearSystemStoreHandle: () => void;
2136
2140
 
2141
+ /** Forget the subject's selection: the next request resolves in the tenant
2142
+ * its credential names. A no-op for a subject with none, and for the types
2143
+ * that cannot hold one. */
2144
+ export declare const clearTenantSelection: (store: DataStore, subject: Subject) => Promise<void>;
2145
+
2137
2146
  export declare interface ClientAddressInput {
2138
2147
  /** `socket.remoteAddress` — the only unforgeable value in the request. */
2139
2148
  readonly socketAddress: string | undefined;
@@ -3054,6 +3063,8 @@ export declare interface DataStoreApiKeyStoreOptions {
3054
3063
  readonly ensureActor?: ((actor: ApiKeyActor, store: DataStore) => Promise<void>) | undefined;
3055
3064
  }
3056
3065
 
3066
+ export declare const dataStoreDeviceStore: (store: DataStore) => DeviceStore;
3067
+
3057
3068
  export declare const dataStoreIdempotencyStore: (store: DataStore) => IdempotencyStore;
3058
3069
 
3059
3070
  /** Build a durable `KvStoreShape` over a raw `DataStore`. */
@@ -3474,6 +3485,128 @@ export declare const detachForResume: (identity: ResumeIdentity, handle: {
3474
3485
  readonly unsubscribe: () => void;
3475
3486
  }, tunables?: ResumeTunables, now?: () => number) => void;
3476
3487
 
3488
+ export declare interface DeviceActor {
3489
+ readonly id: string;
3490
+ readonly displayName: string;
3491
+ }
3492
+
3493
+ /** The device routes. Mounted under `basePath` (default `/v1/devices`) when
3494
+ * `devices` is on. `pair`, list and `revoke` need `admin:full` or
3495
+ * `devices:manage`; `exchange` and `rotate` are the device's own calls and
3496
+ * carry no session — the one-time code and the refresh token ARE the
3497
+ * credential, and a wrong one is `401` with one reason for every cause. */
3498
+ export declare const deviceManagementRoutes: (service: DeviceServiceShape, basePath?: string) => ReadonlyArray<RestRouteDescriptor<any, any>>;
3499
+
3500
+ export declare interface DeviceRow {
3501
+ readonly id: string;
3502
+ readonly tenantId: string;
3503
+ readonly name: string;
3504
+ readonly scopes: ReadonlyArray<string> | null;
3505
+ readonly pairingHash: string | null;
3506
+ readonly pairingExpiresAt: number | null;
3507
+ readonly accessHash: string | null;
3508
+ readonly accessExpiresAt: number | null;
3509
+ readonly refreshHash: string | null;
3510
+ readonly refreshExpiresAt: number | null;
3511
+ /** The refresh hash rotated OUT by the last rotation — what a replay
3512
+ * presents. One generation back is what a stolen-then-rotated token looks
3513
+ * like; older replays fail as unknown, which is refusal enough. */
3514
+ readonly previousRefreshHash: string | null;
3515
+ readonly generation: number;
3516
+ readonly pairedAt: number | null;
3517
+ readonly lastSeenAt: number | null;
3518
+ readonly revokedAt: number | null;
3519
+ readonly revokedReason: string | null;
3520
+ readonly createdBy: string | null;
3521
+ readonly createdAt: number;
3522
+ readonly metadata?: Readonly<Record<string, unknown>> | null;
3523
+ }
3524
+
3525
+ export declare const DEVICES_TABLE = "_voltro_devices";
3526
+
3527
+ export declare interface DeviceServiceOptions {
3528
+ readonly prefix?: string;
3529
+ /** Access token lifetime. Default 15 minutes — long enough that a device
3530
+ * polling every minute rotates rarely, short enough that a leaked token is
3531
+ * a bounded problem. */
3532
+ readonly accessTtlSeconds?: number;
3533
+ /** Refresh token lifetime — how long a device may stay silent and still
3534
+ * come back. Default 30 days. */
3535
+ readonly refreshTtlSeconds?: number;
3536
+ /** How long an admin has between pairing and the device's exchange. Default
3537
+ * 10 minutes. */
3538
+ readonly pairingTtlSeconds?: number;
3539
+ }
3540
+
3541
+ export declare interface DeviceServiceShape {
3542
+ /** Admin-side: create the device and its one-time pairing code. */
3543
+ readonly pair: (input: PairInput) => Promise<PairedDevice>;
3544
+ /** Device-side, once: the pairing code for the first token pair. Null when
3545
+ * the code is unknown, expired, already exchanged, or the device revoked —
3546
+ * one answer, so the route cannot be used to tell those apart. */
3547
+ readonly exchange: (pairingCode: string, now?: number) => Promise<DeviceTokens | null>;
3548
+ /** Device-side, repeatedly: the current refresh token for a new pair
3549
+ * (`generation + 1`). A refresh token that was already rotated out is a
3550
+ * REPLAY: the device is revoked with reason `refresh-replay` and null is
3551
+ * returned. Null also for unknown / expired / revoked. */
3552
+ readonly rotate: (refreshToken: string, now?: number) => Promise<DeviceTokens | null>;
3553
+ /** Hash lookup used by the auth strategy: the device's identity while its
3554
+ * access token is live. No write — see the module note. */
3555
+ readonly resolveAccess: (accessHash: string, now?: number) => Promise<ResolvedDevice | null>;
3556
+ /** Revoke (irreversible). `tenantId` is the CALLER's tenant and required for
3557
+ * the same reason as the api-key service's: ids leak, and an admin of one
3558
+ * tenant must not revoke another's device by id. */
3559
+ readonly revoke: (id: string, tenantId: string, now?: number) => Promise<boolean>;
3560
+ readonly list: (tenantId: string) => Promise<ReadonlyArray<PublicDevice>>;
3561
+ }
3562
+
3563
+ export declare interface DeviceStore {
3564
+ readonly insert: (row: DeviceRow) => Promise<void>;
3565
+ readonly findById: (id: string) => Promise<DeviceRow | null>;
3566
+ readonly findByPairingHash: (hash: string) => Promise<DeviceRow | null>;
3567
+ readonly findByAccessHash: (hash: string) => Promise<DeviceRow | null>;
3568
+ readonly findByRefreshHash: (hash: string) => Promise<DeviceRow | null>;
3569
+ readonly findByPreviousRefreshHash: (hash: string) => Promise<DeviceRow | null>;
3570
+ readonly listByTenant: (tenantId: string) => Promise<ReadonlyArray<DeviceRow>>;
3571
+ readonly patch: (id: string, fields: Partial<DeviceRow>) => Promise<void>;
3572
+ /**
3573
+ * A FENCED patch: applies `fields` only while `column` still equals
3574
+ * `expected`, and says whether it did. Exchange and rotation are claims on
3575
+ * a one-time secret — two exchanges of one pairing code, two rotations of
3576
+ * one refresh token — and the claim has to be decided by the row, not by a
3577
+ * read that both callers made a moment ago.
3578
+ */
3579
+ readonly claim: (id: string, column: 'pairingHash' | 'refreshHash', expected: string, fields: Partial<DeviceRow>) => Promise<boolean>;
3580
+ /** Plant the device's `actors` row (kind `serviceAccount`) — the audit
3581
+ * subject its writes point at. Required, like the api-key store's: an
3582
+ * omitted actor row is the foreign-key failure on the first audited
3583
+ * insert. Idempotent. */
3584
+ readonly ensureActor: (actor: DeviceActor) => Promise<void>;
3585
+ }
3586
+
3587
+ /** The three token shapes, derived from ONE configured prefix so a bearer
3588
+ * prefix collision check (`assertNoBearerPrefixCollision`) sees one claim:
3589
+ * `dev_` → access `dev_…`, refresh `devr_…`, pairing `devp_…`. Refresh and
3590
+ * pairing tokens are never presented as bearers; their prefixes exist so a
3591
+ * token pasted into the wrong field is refused by shape, not by luck. */
3592
+ export declare interface DeviceTokenPrefixes {
3593
+ readonly access: string;
3594
+ readonly refresh: string;
3595
+ readonly pairing: string;
3596
+ }
3597
+
3598
+ export declare const deviceTokenPrefixes: (prefix?: string) => DeviceTokenPrefixes;
3599
+
3600
+ /** What an exchange or a rotation hands the device. Shown once. */
3601
+ export declare interface DeviceTokens {
3602
+ readonly deviceId: string;
3603
+ readonly accessToken: string;
3604
+ readonly accessExpiresAt: number;
3605
+ readonly refreshToken: string;
3606
+ readonly refreshExpiresAt: number;
3607
+ readonly generation: number;
3608
+ }
3609
+
3477
3610
  export { DialectReplicationAdapter }
3478
3611
 
3479
3612
  /** Field-level diff for one change. Insert → every field `undefined→value`;
@@ -5213,6 +5346,15 @@ export declare const frameworkApiKeyStrategy: (service: ApiKeyServiceShape, opts
5213
5346
  prefix?: string;
5214
5347
  }) => AuthStrategy;
5215
5348
 
5349
+ /** The framework device auth strategy. A device is a machine caller WITH a
5350
+ * tenant — a `serviceAccount` subject whose id is the device id, so audit
5351
+ * columns point at the device's own `actors` row. `credentialExpiresAt` is
5352
+ * the access token's expiry: a standing subscription opened with it ends
5353
+ * when the token does, and the device's next rotation re-opens it. */
5354
+ export declare const frameworkDeviceStrategy: (service: DeviceServiceShape, opts?: {
5355
+ prefix?: string;
5356
+ }) => AuthStrategy;
5357
+
5216
5358
  /** Define a gauge (set with `setMetric`). */
5217
5359
  export declare const gauge: (name: string, description?: string) => Metric.Metric.Gauge<number>;
5218
5360
 
@@ -6156,6 +6298,8 @@ export declare const makeDataLoader: (deps: LoaderDeps) => DataLoader;
6156
6298
  */
6157
6299
  export declare const makeDefaultRefilter: (subject: Subject, onError?: (error: unknown) => void) => (() => Promise<RowFilterScope>) | undefined;
6158
6300
 
6301
+ export declare const makeDeviceService: (store: DeviceStore, opts?: DeviceServiceOptions) => DeviceServiceShape;
6302
+
6159
6303
  /** `__voltro.connections.disconnect` — forget the caller's credential. */
6160
6304
  export declare const makeDisconnectConnectionExecutor: (deps: ConnectionBuiltinDeps) => (input: {
6161
6305
  readonly connectionId: string;
@@ -6573,6 +6717,10 @@ export declare const memoryApiKeyStore: () => ApiKeyStore & {
6573
6717
  readonly actors: ReadonlyMap<string, MemoryActorRow>;
6574
6718
  };
6575
6719
 
6720
+ export declare const memoryDeviceStore: () => DeviceStore & {
6721
+ readonly actors: ReadonlyMap<string, DeviceActor>;
6722
+ };
6723
+
6576
6724
  /**
6577
6725
  * Adapter for in-memory testing. The "position" is a monotonically-
6578
6726
  * increasing integer tick the test controls. Primary writes bump the
@@ -6991,10 +7139,10 @@ export declare interface OAuth2ConnectionDefinition extends ConnectionDefinition
6991
7139
  readonly kind: 'oauth2';
6992
7140
  readonly authorizeUrl: string;
6993
7141
  readonly tokenUrl: string;
6994
- readonly clientId: string;
7142
+ readonly clientId: OAuthClientCredential;
6995
7143
  /** Runtime secret. Never logged, never sent to the browser, never bundled —
6996
7144
  * `*.connection.ts` is server-only and is NOT part of the rpcGroup codegen. */
6997
- readonly clientSecret: string;
7145
+ readonly clientSecret: OAuthClientCredential;
6998
7146
  readonly scopes?: ReadonlyArray<string>;
6999
7147
  /** Exact redirect URI registered with the provider. Defaults to
7000
7148
  * `<publicUrl>/_voltro/connections/<id>/callback`. */
@@ -7013,6 +7161,20 @@ export declare interface OAuth2ConnectionDefinition extends ConnectionDefinition
7013
7161
  readonly identify?: (tokens: ConnectionTokens) => Promise<ConnectionAccount>;
7014
7162
  }
7015
7163
 
7164
+ /**
7165
+ * A credential as the file states it: the string, or a thunk that produces it.
7166
+ *
7167
+ * The thunk exists because of WHEN a `*.connection.ts` is evaluated. Discovery
7168
+ * imports it before the env gate has run — `voltro doctor` runs no gate at all —
7169
+ * and `serverEnv.X` read at module top level throws there by design. A thunk
7170
+ * defers the read to the moment the connection is used (an authorize URL, a
7171
+ * token exchange), which is always after the gate. `oauthCredentialValue` is the
7172
+ * one reader.
7173
+ */
7174
+ export declare type OAuthClientCredential = string | (() => string);
7175
+
7176
+ export declare const oauthCredentialValue: (credential: OAuthClientCredential) => string;
7177
+
7016
7178
  /** One recorded `(procedure → table, op)` edge. */
7017
7179
  export declare interface ObservedEdge {
7018
7180
  readonly tag: string;
@@ -7336,6 +7498,25 @@ declare interface P2COptions {
7336
7498
  readonly random?: () => number;
7337
7499
  }
7338
7500
 
7501
+ export declare interface PairedDevice {
7502
+ readonly id: string;
7503
+ readonly name: string;
7504
+ readonly tenantId: string;
7505
+ readonly scopes: ReadonlyArray<string>;
7506
+ /** Shown ONCE. Exchanged for the first token pair, then gone. */
7507
+ readonly pairingCode: string;
7508
+ readonly pairingExpiresAt: number;
7509
+ }
7510
+
7511
+ export declare interface PairInput {
7512
+ readonly tenantId: string;
7513
+ readonly name: string;
7514
+ readonly scopes?: ReadonlyArray<string>;
7515
+ /** WHO PAIRED IT. Provenance. */
7516
+ readonly createdBy?: string | null;
7517
+ readonly metadata?: Readonly<Record<string, unknown>> | null;
7518
+ }
7519
+
7339
7520
  /**
7340
7521
  * Parse an interval string (`'30m'`, `'4h'`, `'7d'`) to ms, or `null`.
7341
7522
  * A local copy for the same reason `finops.ts` keeps one — this module must not
@@ -7602,6 +7783,22 @@ export declare interface PublicApiKey {
7602
7783
  readonly metadata?: Readonly<Record<string, unknown>> | null;
7603
7784
  }
7604
7785
 
7786
+ export declare interface PublicDevice {
7787
+ readonly id: string;
7788
+ readonly name: string;
7789
+ readonly tenantId: string;
7790
+ readonly scopes: ReadonlyArray<string>;
7791
+ readonly generation: number;
7792
+ readonly pairedAt: number | null;
7793
+ readonly lastSeenAt: number | null;
7794
+ readonly accessExpiresAt: number | null;
7795
+ readonly revokedAt: number | null;
7796
+ readonly revokedReason: string | null;
7797
+ readonly createdAt: number;
7798
+ readonly createdBy: string | null;
7799
+ readonly metadata?: Readonly<Record<string, unknown>> | null;
7800
+ }
7801
+
7605
7802
  /**
7606
7803
  * Validate, size-check and publish one event.
7607
7804
  *
@@ -8831,6 +9028,17 @@ export declare interface ResolvedConnection {
8831
9028
  readonly accountLabel: string | null;
8832
9029
  }
8833
9030
 
9031
+ /** What the auth strategy learns from a presented access token. */
9032
+ export declare interface ResolvedDevice {
9033
+ readonly id: string;
9034
+ readonly tenantId: string;
9035
+ readonly name: string;
9036
+ readonly scopes: ReadonlyArray<string>;
9037
+ readonly generation: number;
9038
+ readonly accessExpiresAt: number;
9039
+ readonly metadata?: Readonly<Record<string, unknown>>;
9040
+ }
9041
+
8834
9042
  /**
8835
9043
  * Walk a query descriptor's eager-load tree and return the FULL set
8836
9044
  * of tables whose changes could affect the resolved snapshot.
@@ -10547,6 +10755,20 @@ export declare class SelectBuilder {
10547
10755
  get descriptor(): QueryDescriptor;
10548
10756
  }
10549
10757
 
10758
+ /**
10759
+ * Select `tenantId` as the subject's active tenant — verified against
10760
+ * `memberships` through the one rule (`selectSubjectTenant`), then stored.
10761
+ * Returns the subject as it will resolve on the next request. Throws
10762
+ * `TenantSelectionRefused` when the target is not among the memberships or
10763
+ * the subject is of a type that has no tenant; nothing is written then.
10764
+ *
10765
+ * Three lines in an app's mutation:
10766
+ *
10767
+ * const memberships = await myMemberships(ctx.subject)
10768
+ * return selectTenant(ctx.store, ctx.subject, input.tenantId, memberships)
10769
+ */
10770
+ export declare const selectTenant: (store: DataStore, subject: Subject, tenantId: string, memberships: ReadonlyArray<Membership>) => Promise<Subject>;
10771
+
10550
10772
  /** A guard, flattened to something a row can hold. */
10551
10773
  export declare type SerializedGuard = {
10552
10774
  readonly kind: 'scope';
@@ -10714,6 +10936,8 @@ export declare const setTupleSource: (source: TupleSource | undefined) => void;
10714
10936
 
10715
10937
  export declare const sha256Hex: (input: string) => string;
10716
10938
 
10939
+ export declare const sha256HexOf: (input: string) => string;
10940
+
10717
10941
  export declare type ShapeClassification = {
10718
10942
  readonly maintainable: true;
10719
10943
  readonly shape: AggregateShape;
@@ -11360,6 +11584,8 @@ export declare interface TableRoutingStats {
11360
11584
 
11361
11585
  export { TableValidationFailed }
11362
11586
 
11587
+ export { TENANT_SELECTIONS_TABLE }
11588
+
11363
11589
  /** The standing compute-cost attribution for one tenant — the chargeback /
11364
11590
  * showback row + the "why is my bill high" breakdown. Maintained incrementally:
11365
11591
  * every `CostEvent` folds into `total`, `byUnit`, and `bySubscription` in O(1).
@@ -11380,6 +11606,14 @@ export { TenantRowNotFound }
11380
11606
 
11381
11607
  export { TenantScopeViolation }
11382
11608
 
11609
+ export { tenantSelectionId }
11610
+
11611
+ /** The framework's `TenantSelectionStore` over `_voltro_tenant_selections`.
11612
+ * Lazy on the store for the same reason the resolver is: dev builds the
11613
+ * store after the auth chain. A read before the store exists throws, and
11614
+ * the composer turns that into a fail-closed `Unauthenticated`. */
11615
+ export declare const tenantSelectionStore: (getStore: () => DataStore | undefined) => TenantSelectionStore;
11616
+
11383
11617
  /** The statuses a run never leaves — where a poller stops.
11384
11618
  *
11385
11619
  * `skipped` is terminal and is NOT a failure: it means the overlap policy
@@ -12311,6 +12545,17 @@ export declare interface WorkflowDefinitionLike {
12311
12545
  toLayer(execute: (payload: never, executionId: string) => Effect.Effect<unknown, unknown, unknown>): Layer.Layer<unknown, unknown, unknown>;
12312
12546
  }
12313
12547
 
12548
+ /** `__voltro.workflow.domainEvents` — the caller's domain events, newest first. */
12549
+ export declare const workflowDomainEventsExecutor: (input: {
12550
+ readonly name?: string;
12551
+ readonly limit?: number;
12552
+ }, ctx: WorkflowReadCtx) => Promise<ReadonlyArray<unknown>>;
12553
+
12554
+ /** `__voltro.workflow.event.deliveries` — the deliveries of one visible event. */
12555
+ export declare const workflowEventDeliveriesExecutor: (input: {
12556
+ readonly eventId: string;
12557
+ }, ctx: WorkflowReadCtx) => Promise<ReadonlyArray<unknown>>;
12558
+
12314
12559
  export declare interface WorkflowEventEmitOptions {
12315
12560
  readonly id?: string;
12316
12561
  readonly source?: string;
@@ -12494,6 +12739,20 @@ export declare interface WorkflowLayerOptions<Context> {
12494
12739
  * whose business logic said no.
12495
12740
  */
12496
12741
  readonly resolveAuthority?: (identity: unknown) => Promise<unknown>;
12742
+ /**
12743
+ * Told when an execution is refused BEFORE its body — the start context
12744
+ * could not be resolved, the identity was missing, the authority or row
12745
+ * filter resolver rejected. The engine already fails the attempt; what a
12746
+ * discarded start does NOT do is tell anyone, and a run refused here has no
12747
+ * row yet, so nothing else can. Measured: an acknowledged start whose
12748
+ * resident context was filed under another id died with a debug-level
12749
+ * "Fiber terminated" and nothing else. Hosts wire this to an error log.
12750
+ */
12751
+ readonly reportExecutionRefusal?: (refusal: {
12752
+ readonly workflowName: string;
12753
+ readonly executionId: string;
12754
+ readonly reason: string;
12755
+ }) => void;
12497
12756
  }
12498
12757
 
12499
12758
  /**
@@ -12547,6 +12806,14 @@ export declare class WorkflowRateLimitedError extends Error {
12547
12806
  constructor(workflowName: string, retryAfterMs: number);
12548
12807
  }
12549
12808
 
12809
+ /** The slice of AppContext these executors need. */
12810
+ export declare interface WorkflowReadCtx {
12811
+ readonly store: Pick<DataStore, 'query'>;
12812
+ readonly request?: {
12813
+ readonly subject?: Subject | null;
12814
+ } | undefined;
12815
+ }
12816
+
12550
12817
  /** Result of {@link WorkflowsAppContext.redrive} — whether the failed run's
12551
12818
  * durable journal was re-driven, how many failed step attempts were reset so
12552
12819
  * they re-execute, and a `reason` when it declined (no journal / still
@@ -12571,6 +12838,22 @@ export declare interface WorkflowRetryResult {
12571
12838
  readonly executionId: string;
12572
12839
  }
12573
12840
 
12841
+ /**
12842
+ * May `caller` read a row owned by `rowSubject`? Exported so the rule has one
12843
+ * spelling and a test can pin it without a store.
12844
+ */
12845
+ export declare const workflowRowVisibleTo: (caller: Subject | null, rowSubject: unknown) => boolean;
12846
+
12847
+ /** `__voltro.workflow.run.events` — the lifecycle events of one visible run. */
12848
+ export declare const workflowRunEventsExecutor: (input: {
12849
+ readonly runId: string;
12850
+ }, ctx: WorkflowReadCtx) => Promise<ReadonlyArray<unknown>>;
12851
+
12852
+ /** `__voltro.workflow.run` — one run by id or execution id. */
12853
+ export declare const workflowRunExecutor: (input: {
12854
+ readonly id: string;
12855
+ }, ctx: WorkflowReadCtx) => Promise<ReadonlyArray<unknown>>;
12856
+
12574
12857
  /** Filter passed to {@link WorkflowsAppContext.listRuns}. All fields are
12575
12858
  * optional; an empty filter returns the most-recent runs across every
12576
12859
  * workflow. `workflowName` and `tag` are aliases for the same column. */
@@ -12625,12 +12908,24 @@ export declare interface WorkflowRunRecord {
12625
12908
  * stored column exactly. */
12626
12909
  export declare type WorkflowRunRecordStatus = 'running' | 'succeeded' | 'failed' | 'cancelled' | 'suspended';
12627
12910
 
12911
+ /** `__voltro.workflow.runs` — the caller's runs, newest first. */
12912
+ export declare const workflowRunsExecutor: (input: {
12913
+ readonly tag?: string;
12914
+ readonly status?: string;
12915
+ readonly limit?: number;
12916
+ }, ctx: WorkflowReadCtx) => Promise<ReadonlyArray<unknown>>;
12917
+
12628
12918
  export declare type WorkflowRunSnapshot<Success = unknown> = Omit<WorkflowRunSnapshot_2, 'output'> & {
12629
12919
  readonly output?: Success;
12630
12920
  };
12631
12921
 
12632
12922
  export declare type WorkflowRunStatus = 'running' | 'succeeded' | 'failed' | 'cancelled' | 'suspended' | 'unknown';
12633
12923
 
12924
+ /** `__voltro.workflow.run.steps` — the steps of one visible run. */
12925
+ export declare const workflowRunStepsExecutor: (input: {
12926
+ readonly runId: string;
12927
+ }, ctx: WorkflowReadCtx) => Promise<ReadonlyArray<unknown>>;
12928
+
12634
12929
  /** Typed projection of a `_voltro_workflow_runs` row, returned by
12635
12930
  * {@link WorkflowsAppContext.listRuns}. Timestamps are `Date`s (the SDK
12636
12931
  * hydrates the stored values for app code; the wire/inspect path keeps
@@ -12670,6 +12965,20 @@ export declare interface WorkflowsAppContext {
12670
12965
  readControl(target: WorkflowControlTarget): Promise<WorkflowControlResult>;
12671
12966
  cancelQueued(target: WorkflowQueuedControlTarget, options?: WorkflowQueuedControlOptions): Promise<WorkflowQueuedCancellationResult>;
12672
12967
  readQueuedCancellation(target: WorkflowQueuedControlTarget, options?: Pick<WorkflowQueuedControlOptions, 'callerContext'>): Promise<WorkflowQueuedCancellationResult>;
12968
+ /** What a recorded start handle has become — queued, accepted under an
12969
+ * execution, or never to run — read once, without waiting. A queued handle
12970
+ * is resolved through its durable receipt, so the answer survives the
12971
+ * process that recorded the handle. */
12972
+ readStart(handle: WorkflowRunHandle, options?: Pick<WorkflowWaitOptions, 'callerContext'>): Promise<WorkflowStartObservation>;
12973
+ /**
12974
+ * Set on the facade a mutation TRANSACTION hands out: every start it records
12975
+ * is provisional until the transaction commits, so nothing about a start can
12976
+ * be observed through it — `readStart` refuses. A reconciler that would
12977
+ * otherwise read pending starts (the flow cadence tick) checks this and
12978
+ * leaves them to the next tick that runs outside a transaction, rather than
12979
+ * reporting the refusal as a failure of each row.
12980
+ */
12981
+ readonly startsDeferredUntilCommit?: true;
12673
12982
  signal(target: WorkflowSignalTarget, signalName: string, payload?: unknown): Promise<{
12674
12983
  readonly eventId: string;
12675
12984
  }>;
@@ -12740,6 +13049,27 @@ declare const WorkflowStartNotExecuted_base: Schema.TaggedErrorClass<WorkflowSta
12740
13049
  incumbentExecutionId: Schema.NullOr<typeof Schema.String>;
12741
13050
  }>;
12742
13051
 
13052
+ /**
13053
+ * What a start handle has become, read once and without waiting.
13054
+ *
13055
+ * `pending` — still queued (or submitted and not yet acknowledged). `accepted`
13056
+ * — the engine owns it under `executionId`; poll that. `not-executed` — it will
13057
+ * never run: the same reasons `WorkflowStartNotExecuted` carries, as a value
13058
+ * rather than a throw, because a reconciler asking "what happened to the start
13059
+ * I recorded" wants an answer, not an exception per row.
13060
+ */
13061
+ export declare type WorkflowStartObservation = {
13062
+ readonly kind: 'pending';
13063
+ } | {
13064
+ readonly kind: 'accepted';
13065
+ readonly executionId: string;
13066
+ } | {
13067
+ readonly kind: 'not-executed';
13068
+ readonly reason: WorkflowStartNotExecuted['reason'];
13069
+ readonly failureId: string | null;
13070
+ readonly incumbentExecutionId: string | null;
13071
+ };
13072
+
12743
13073
  export declare interface WorkflowStartOptions {
12744
13074
  /* Excluded from this release type: preparedExecutionId */
12745
13075
  readonly wait?: boolean;