@nylorun/runtime 0.22.0-beta → 0.23.0-beta

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +17 -3
  3. package/dist/adapters/execution/restate.d.ts +15 -3
  4. package/dist/adapters/execution/restate.js +77 -83
  5. package/dist/adapters/streams/s2.js +1 -13
  6. package/dist/core/flow-host.d.ts +1 -19
  7. package/dist/core/flow-host.js +8 -37
  8. package/dist/execution/types.d.ts +10 -8
  9. package/dist/execution/types.js +5 -7
  10. package/dist/host/execution.d.ts +8 -6
  11. package/dist/host/main.js +10 -16
  12. package/dist/host/stack-config.d.ts +6 -0
  13. package/dist/host/stack-config.js +6 -0
  14. package/dist/infra/execution.d.ts +18 -1
  15. package/dist/infra/execution.js +28 -4
  16. package/dist/infra/index.d.ts +1 -1
  17. package/dist/management-openapi.json +1 -1
  18. package/dist/openapi.json +1 -1
  19. package/dist/retry.d.ts +21 -0
  20. package/dist/retry.js +34 -0
  21. package/dist/sandbox/join.js +6 -7
  22. package/dist/sandbox/pods/reconcile.js +3 -3
  23. package/dist/store/postgres/control.js +8 -1
  24. package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
  25. package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
  26. package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
  27. package/dist/store/postgres/schema.d.ts +263 -2
  28. package/dist/store/postgres/schema.js +34 -3
  29. package/dist/store/postgres/store.js +165 -52
  30. package/dist/store/types.d.ts +96 -39
  31. package/dist/streams/relay/core.js +1 -13
  32. package/dist/tenant/advance.js +6 -7
  33. package/dist/tenant/commands.js +1 -2
  34. package/dist/tenant/context.d.ts +19 -11
  35. package/dist/tenant/control.d.ts +8 -1
  36. package/dist/tenant/control.js +2 -0
  37. package/dist/tenant/runtime.js +23 -3
  38. package/dist/tenant/sandboxes.d.ts +2 -1
  39. package/dist/tenant/sandboxes.js +23 -19
  40. package/dist/tenant/scheduler.d.ts +7 -0
  41. package/dist/tenant/scheduler.js +38 -7
  42. package/dist/tenant/session-streams.d.ts +0 -1
  43. package/dist/tenant/session-streams.js +1 -13
  44. package/dist/tenant/streams.js +14 -29
  45. package/dist/tenant/sweep.d.ts +39 -12
  46. package/dist/tenant/sweep.js +157 -58
  47. package/dist/version.d.ts +1 -1
  48. package/dist/version.js +1 -1
  49. package/package.json +2 -1
@@ -39,6 +39,12 @@ export interface StackEndpoints {
39
39
  restateIngressUrl?: string;
40
40
  restateAdminUrl?: string;
41
41
  workerUrl?: string;
42
+ /**
43
+ * The Worker deployment's version, from `NYLORUN_WORKER_VERSION` (default: the Runtime's
44
+ * version): the Worker registers `<NYLORUN_WORKER_URL>/nylorun/<version>` with Restate
45
+ * (`infra/execution.ts`, `workerDeployment`).
46
+ */
47
+ workerVersion?: string;
42
48
  s2Endpoint?: string;
43
49
  s2Token?: string;
44
50
  workspaceStoreUrl?: string;
@@ -281,6 +281,12 @@ export function parseStackConfig(env, argv) {
281
281
  const workerUrl = parseUrl(env, "NYLORUN_WORKER_URL", http);
282
282
  if (workerUrl)
283
283
  endpoints.workerUrl = workerUrl;
284
+ const workerVersion = read(env, "NYLORUN_WORKER_VERSION");
285
+ if (workerVersion !== undefined) {
286
+ if (!/^[A-Za-z0-9._-]{1,64}$/.test(workerVersion))
287
+ throw new StackConfigError("NYLORUN_WORKER_VERSION must be 1 to 64 letters, digits, `.`, `_` or `-` (it is a URL path segment)");
288
+ endpoints.workerVersion = workerVersion;
289
+ }
284
290
  const s2Endpoint = parseUrl(env, "NYLORUN_S2_ENDPOINT", http);
285
291
  if (s2Endpoint)
286
292
  endpoints.s2Endpoint = s2Endpoint;
@@ -29,7 +29,24 @@ export declare function executionKind(config: Pick<StackConfig, "endpoints">): "
29
29
  export declare function validateExecutionConfig(config: Pick<StackConfig, "endpoints"> & {
30
30
  services?: RuntimeServices;
31
31
  }): void;
32
- export declare function createExecution(config: Pick<StackConfig, "endpoints"> & {
32
+ /**
33
+ * The deployment a Worker registers with Restate (§14.6): `<NYLORUN_WORKER_URL>/nylorun/<version>`,
34
+ * the version being `NYLORUN_WORKER_VERSION` or the Runtime's. Restate sends new invocations to
35
+ * the latest deployment of a service and keeps each running invocation on the deployment it
36
+ * started on, so during a rolling upgrade the Workers of one version share that version's
37
+ * deployment (behind one load-balanced `NYLORUN_WORKER_URL`) and the old one finishes what it
38
+ * started; the endpoint answers on any path, so an older deployment's URL still reaches it.
39
+ * `force` replaces a deployment already registered at the URL with different code: only
40
+ * outside a container (a development Host, whose code changes under one version). Without
41
+ * it Restate keeps the deployment it already has at the URL, and a Worker whose services that
42
+ * deployment lacks refuses to start: a build whose code changed under the same version sets
43
+ * its own `NYLORUN_WORKER_VERSION`.
44
+ */
45
+ export declare function workerDeployment(config: Pick<StackConfig, "endpoints" | "listen">): {
46
+ url: string;
47
+ force: boolean;
48
+ } | undefined;
49
+ export declare function createExecution(config: Pick<StackConfig, "endpoints" | "listen"> & {
33
50
  services?: RuntimeServices;
34
51
  }, options?: CreateExecutionOptions): DurableExecution;
35
52
  /**
@@ -8,8 +8,8 @@
8
8
  * - wakes, timers and sweep arming go through the ingress, so every process can
9
9
  * call them;
10
10
  * - a Worker (a process running the loop service) serves the Restate endpoint on
11
- * `0.0.0.0:9080` and registers `NYLORUN_WORKER_URL` with the admin API when
12
- * `infra/workers.ts` starts it;
11
+ * `0.0.0.0:9080` and registers it with the admin API when `infra/workers.ts` starts it,
12
+ * as a versioned deployment (`workerDeployment`);
13
13
  * - with `NYLORUN_RESTATE_IDENTITY_KEY` the endpoint accepts only requests
14
14
  * signed by the Restate server holding the matching private key.
15
15
  *
@@ -17,6 +17,7 @@
17
17
  * only unit tests and a local development Host use.
18
18
  */
19
19
  import { createRestateExecution } from "../adapters/execution/restate.js";
20
+ import { RUNTIME_VERSION } from "../version.js";
20
21
  import { MemoryExecution } from "../execution/memory.js";
21
22
  import { DEFAULT_SERVICES, describeServices, } from "../host/stack-config.js";
22
23
  /** Where a Worker serves the Restate endpoint (architecture §14.3). */
@@ -46,16 +47,39 @@ export function validateExecutionConfig(config) {
46
47
  if (services.has("loop") && !workerUrl)
47
48
  throw new Error(`NYLORUN_WORKER_URL is required for --service ${describeServices(services)}: the Worker endpoint URL Restate calls`);
48
49
  }
50
+ /**
51
+ * The deployment a Worker registers with Restate (§14.6): `<NYLORUN_WORKER_URL>/nylorun/<version>`,
52
+ * the version being `NYLORUN_WORKER_VERSION` or the Runtime's. Restate sends new invocations to
53
+ * the latest deployment of a service and keeps each running invocation on the deployment it
54
+ * started on, so during a rolling upgrade the Workers of one version share that version's
55
+ * deployment (behind one load-balanced `NYLORUN_WORKER_URL`) and the old one finishes what it
56
+ * started; the endpoint answers on any path, so an older deployment's URL still reaches it.
57
+ * `force` replaces a deployment already registered at the URL with different code: only
58
+ * outside a container (a development Host, whose code changes under one version). Without
59
+ * it Restate keeps the deployment it already has at the URL, and a Worker whose services that
60
+ * deployment lacks refuses to start: a build whose code changed under the same version sets
61
+ * its own `NYLORUN_WORKER_VERSION`.
62
+ */
63
+ export function workerDeployment(config) {
64
+ const { workerUrl, workerVersion } = config.endpoints;
65
+ if (!workerUrl)
66
+ return undefined;
67
+ const version = encodeURIComponent(workerVersion ?? RUNTIME_VERSION);
68
+ return { url: `${workerUrl.replace(/\/+$/, "")}/nylorun/${version}`, force: !config.listen };
69
+ }
49
70
  export function createExecution(config, options = {}) {
50
71
  validateExecutionConfig(config);
51
- const { restateIngressUrl, restateAdminUrl, workerUrl, restateIdentityKeys } = config.endpoints;
72
+ const { restateIngressUrl, restateAdminUrl, restateIdentityKeys } = config.endpoints;
52
73
  if (!restateIngressUrl || !restateAdminUrl)
53
74
  return new MemoryExecution();
75
+ const deployment = workerDeployment(config);
54
76
  return createRestateExecution({
55
77
  ingressUrl: restateIngressUrl,
56
78
  adminUrl: restateAdminUrl,
57
79
  workerListen: options.workerListen ?? { ...WORKER_LISTEN },
58
- ...(workerUrl ? { workerAdvertisedUrl: workerUrl } : {}),
80
+ ...(deployment
81
+ ? { workerAdvertisedUrl: deployment.url, forceRegistration: deployment.force }
82
+ : {}),
59
83
  ...(restateIdentityKeys?.length ? { identityKeys: [...restateIdentityKeys] } : {}),
60
84
  ...(options.servicePrefix ? { servicePrefix: options.servicePrefix } : {}),
61
85
  ...(options.logger ? { logger: options.logger } : {}),
@@ -36,7 +36,7 @@ export interface Infra {
36
36
  * Builds the configured clients. Throws naming the variable when endpoints are
37
37
  * incomplete (e.g. a Restate ingress without an admin URL).
38
38
  */
39
- export declare function createInfra(config: Pick<StackConfig, "services" | "endpoints">, options?: {
39
+ export declare function createInfra(config: Pick<StackConfig, "services" | "endpoints" | "listen">, options?: {
40
40
  logger?: Logger;
41
41
  execution?: CreateExecutionOptions;
42
42
  streams?: CreateStreamsOptions;
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Management API",
5
- "version": "0.22.0-beta",
5
+ "version": "0.23.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 4,
8
8
  "max": 10,
package/dist/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.2.0",
3
3
  "info": {
4
4
  "title": "Nylorun Runtime API",
5
- "version": "0.22.0-beta",
5
+ "version": "0.23.0-beta",
6
6
  "x-nylorun-protocol": {
7
7
  "min": 4,
8
8
  "max": 10,
@@ -0,0 +1,21 @@
1
+ /** Waits `ms`, or until `signal` aborts; resolves either way and never rejects. */
2
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
3
+ export interface RetryOptions {
4
+ /** The first wait after a failure. Doubled after each, up to `maxMs`. */
5
+ minMs: number;
6
+ maxMs: number;
7
+ /** Attempts, the first included, before giving up. Default: no limit. */
8
+ attempts?: number;
9
+ /** Gives up when the next wait would end later than this long after the first attempt. */
10
+ deadlineMs?: number;
11
+ /** Gives up when it aborts, after the attempt or wait under way. */
12
+ signal?: AbortSignal;
13
+ /** Called with each failure that is retried, and how many attempts have failed so far. */
14
+ onError?: (error: unknown, failures: number) => void;
15
+ }
16
+ /**
17
+ * Runs `attempt` until it resolves, waiting between failures with exponential backoff
18
+ * (`minMs`, doubling to `maxMs`). Rejects with the last failure once `attempts`, `deadlineMs`
19
+ * or `signal` ends the retries.
20
+ */
21
+ export declare function retry<T>(attempt: () => Promise<T>, options: RetryOptions): Promise<T>;
package/dist/retry.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Waiting and retrying, shared by the Runtime's background loops and its calls to other
3
+ * services. Nothing here logs or decides what is worth retrying: callers do.
4
+ */
5
+ import { setTimeout as delay } from "node:timers/promises";
6
+ /** Waits `ms`, or until `signal` aborts; resolves either way and never rejects. */
7
+ export function sleep(ms, signal) {
8
+ return delay(ms, undefined, signal ? { signal } : undefined).catch(() => undefined);
9
+ }
10
+ /**
11
+ * Runs `attempt` until it resolves, waiting between failures with exponential backoff
12
+ * (`minMs`, doubling to `maxMs`). Rejects with the last failure once `attempts`, `deadlineMs`
13
+ * or `signal` ends the retries.
14
+ */
15
+ export async function retry(attempt, options) {
16
+ const deadline = options.deadlineMs === undefined ? Infinity : Date.now() + options.deadlineMs;
17
+ let wait = options.minMs;
18
+ for (let failures = 1;; failures += 1) {
19
+ try {
20
+ return await attempt();
21
+ }
22
+ catch (error) {
23
+ if (failures >= (options.attempts ?? Infinity) ||
24
+ Date.now() + wait > deadline ||
25
+ options.signal?.aborted)
26
+ throw error;
27
+ options.onError?.(error, failures);
28
+ await sleep(wait, options.signal);
29
+ if (options.signal?.aborted)
30
+ throw error;
31
+ wait = Math.min(wait * 2, options.maxMs);
32
+ }
33
+ }
34
+ }
@@ -134,6 +134,9 @@ export function hostAuthority(ctx) {
134
134
  const reason = "The sandbox's volume was replaced: its files are gone";
135
135
  await t.updateSandboxPod(sandboxId, { observed: "lost", reason, hostEpoch: epoch }, now);
136
136
  await t.sandboxEvent(sandboxId, "sandbox.lost", { reason });
137
+ // Its Sandbox is deleted; only a reset brings the sandbox back.
138
+ await t.signal({ type: "host.revoked", sandboxId, epoch });
139
+ await t.signalSandbox(sandboxId, { kind: "reconcile" });
137
140
  return { lost: epoch };
138
141
  }
139
142
  await t.updateSandboxPod(sandboxId, {
@@ -148,16 +151,12 @@ export function hostAuthority(ctx) {
148
151
  else
149
152
  await t.sandboxEvent(sandboxId, "sandbox.running", { hostEpoch: epoch });
150
153
  }
154
+ // The previous host's connection (an older epoch) serves nothing more, on any process.
155
+ await t.signal({ type: "host.revoked", sandboxId, epoch });
151
156
  return epoch;
152
157
  });
153
- if (typeof joined === "object") {
154
- ctx.harness.revokeHost(sandboxId, joined.lost);
155
- // Its Sandbox is deleted; only a reset brings the sandbox back.
156
- await ctx.sandboxSignal(sandboxId, { kind: "reconcile" });
158
+ if (typeof joined === "object")
157
159
  return refuse("the volume was replaced: the sandbox is lost");
158
- }
159
- // The previous host's connection (an older epoch) serves nothing more.
160
- ctx.harness.revokeHost(sandboxId, joined);
161
160
  ctx.config.logger.info("sandbox host joined", { sandboxId, epoch: joined });
162
161
  return answerOf(await mintHostTokens(ctx, { sandboxId, epoch: joined, podUid }), await ctx.keys.egressCa());
163
162
  },
@@ -135,7 +135,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
135
135
  await t.updateSandboxPod(sandboxId, finalPatch, new Date().toISOString());
136
136
  for (const item of decision.events)
137
137
  await t.sandboxEvent(sandboxId, item.type, item.payload);
138
- return { kind: "written", epoch: epochMoved ? current.pod.hostEpoch + 1 : undefined };
138
+ if (epochMoved)
139
+ await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
140
+ return { kind: "written" };
139
141
  });
140
142
  if (written.kind === "changed")
141
143
  return { retryAfterMs: 0 };
@@ -143,8 +145,6 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
143
145
  await ctx.sandbox.removeSandbox(sandboxId).catch(() => undefined);
144
146
  return {};
145
147
  }
146
- if (written.kind === "written" && written.epoch !== undefined)
147
- ctx.harness.revokeHost(sandboxId, written.epoch);
148
148
  return {
149
149
  ...(decision.retryAfterMs === undefined ? {} : { retryAfterMs: decision.retryAfterMs }),
150
150
  ...(decision.arm.length > 0 ? { arm: decision.arm } : {}),
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The control bus on Postgres (blueprint D21, D48): how one process tells the others with the
3
3
  * Tenant open to act now. `session.cancel` aborts the advance of a cancelled turn;
4
- * `sessions.reset` moves the session streams to the Tenant's new basin generation. Nothing
4
+ * `sessions.reset` moves the session streams to the Tenant's new basin generation;
5
+ * `host.revoked` closes a pod sandbox's host connections of an older epoch. Nothing
5
6
  * internal goes through S2, which only serves API listeners.
6
7
  *
7
8
  * - **Write.** `writeSignal` inserts a `control_signals` row and calls `pg_notify` on
@@ -36,6 +37,8 @@ const SIGNAL = {
36
37
  sessionId: controlSignals.sessionId,
37
38
  turnId: controlSignals.turnId,
38
39
  generation: controlSignals.generation,
40
+ sandboxId: controlSignals.sandboxId,
41
+ epoch: controlSignals.epoch,
39
42
  };
40
43
  /** Puts `signal` on the bus in the transaction `db` runs: a row, notified at commit. */
41
44
  export async function writeSignal(db, signal) {
@@ -46,6 +49,8 @@ export async function writeSignal(db, signal) {
46
49
  sessionId: signal.type === "session.cancel" ? signal.sessionId : null,
47
50
  turnId: signal.type === "session.cancel" ? (signal.turnId ?? null) : null,
48
51
  generation: signal.type === "sessions.reset" ? signal.generation : null,
52
+ sandboxId: signal.type === "host.revoked" ? signal.sandboxId : null,
53
+ epoch: signal.type === "host.revoked" ? signal.epoch : null,
49
54
  })
50
55
  .returning({ id: controlSignals.id });
51
56
  // The payload is only the id: a follower reads the rows back either way.
@@ -183,5 +188,7 @@ function signalOf(row) {
183
188
  };
184
189
  if (row.kind === "sessions.reset" && row.generation !== null)
185
190
  return { type: "sessions.reset", generation: row.generation };
191
+ if (row.kind === "host.revoked" && row.sandboxId !== null && row.epoch !== null)
192
+ return { type: "host.revoked", sandboxId: row.sandboxId, epoch: row.epoch };
186
193
  return undefined;
187
194
  }
@@ -0,0 +1,3 @@
1
+ ALTER TABLE "nylorun"."wakes" ADD COLUMN "attempts" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "nylorun"."wakes" ADD COLUMN "retry_at" timestamp with time zone;--> statement-breakpoint
3
+ ALTER TABLE "nylorun"."wakes" ADD COLUMN "parked_at" timestamp with time zone;
@@ -0,0 +1,18 @@
1
+ CREATE TABLE "nylorun"."sandbox_signals" (
2
+ "id" text COLLATE "C" PRIMARY KEY NOT NULL,
3
+ "sandbox_id" text COLLATE "C" NOT NULL,
4
+ "kind" text NOT NULL,
5
+ "timer" text,
6
+ "at" bigint,
7
+ "created_at" timestamp with time zone DEFAULT clock_timestamp() NOT NULL,
8
+ "attempts" integer DEFAULT 0 NOT NULL,
9
+ "retry_at" timestamp with time zone,
10
+ "parked_at" timestamp with time zone,
11
+ CONSTRAINT "sandbox_signals_kind_check" CHECK ("nylorun"."sandbox_signals"."kind" IN ('reconcile', 'arm'))
12
+ );
13
+ --> statement-breakpoint
14
+ ALTER TABLE "nylorun"."control_signals" DROP CONSTRAINT "control_signals_kind_check";--> statement-breakpoint
15
+ ALTER TABLE "nylorun"."control_signals" ADD COLUMN "sandbox_id" text COLLATE "C";--> statement-breakpoint
16
+ ALTER TABLE "nylorun"."control_signals" ADD COLUMN "epoch" integer;--> statement-breakpoint
17
+ CREATE INDEX "sandbox_signals_created_at" ON "nylorun"."sandbox_signals" USING btree ("created_at");--> statement-breakpoint
18
+ ALTER TABLE "nylorun"."control_signals" ADD CONSTRAINT "control_signals_kind_check" CHECK ("nylorun"."control_signals"."kind" IN ('session.cancel', 'sessions.reset', 'host.revoked'));
@@ -127,6 +127,20 @@
127
127
  "when": 1791455573196,
128
128
  "tag": "0017_wake_outbox",
129
129
  "breakpoints": true
130
+ },
131
+ {
132
+ "idx": 18,
133
+ "version": "7",
134
+ "when": 1791529391438,
135
+ "tag": "0018_wake_outbox_retries",
136
+ "breakpoints": true
137
+ },
138
+ {
139
+ "idx": 19,
140
+ "version": "7",
141
+ "when": 1791530515498,
142
+ "tag": "0019_sandbox_signal_outbox",
143
+ "breakpoints": true
130
144
  }
131
145
  ]
132
146
  }
@@ -3225,7 +3225,8 @@ export declare const definitionFileUses: import("drizzle-orm/pg-core").PgTableWi
3225
3225
  * written in the transaction that commits what they announce and notified on
3226
3226
  * `nylorun_control` at commit (`control.ts`). A row outlives its notification so a process
3227
3227
  * whose listener was down catches up; the Tenant sweep deletes old ones. `created_at` is the
3228
- * insert's own clock (`clock_timestamp()`), not its transaction's start.
3228
+ * insert's own clock (`clock_timestamp()`), not its transaction's start. `sandbox_id` and
3229
+ * `epoch` are a `host.revoked` signal's.
3229
3230
  */
3230
3231
  export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithColumns<{
3231
3232
  name: "control_signals";
@@ -3337,6 +3338,42 @@ export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithCo
3337
3338
  identity: undefined;
3338
3339
  generated: undefined;
3339
3340
  }, {}, {}>;
3341
+ sandboxId: import("drizzle-orm/pg-core").PgColumn<{
3342
+ name: "sandboxId";
3343
+ tableName: "control_signals";
3344
+ dataType: "custom";
3345
+ columnType: "PgCustomColumn";
3346
+ data: string;
3347
+ driverParam: string;
3348
+ notNull: false;
3349
+ hasDefault: false;
3350
+ isPrimaryKey: false;
3351
+ isAutoincrement: false;
3352
+ hasRuntimeDefault: false;
3353
+ enumValues: undefined;
3354
+ baseColumn: never;
3355
+ identity: undefined;
3356
+ generated: undefined;
3357
+ }, {}, {
3358
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
3359
+ }>;
3360
+ epoch: import("drizzle-orm/pg-core").PgColumn<{
3361
+ name: "epoch";
3362
+ tableName: "control_signals";
3363
+ dataType: "number";
3364
+ columnType: "PgInteger";
3365
+ data: number;
3366
+ driverParam: string | number;
3367
+ notNull: false;
3368
+ hasDefault: false;
3369
+ isPrimaryKey: false;
3370
+ isAutoincrement: false;
3371
+ hasRuntimeDefault: false;
3372
+ enumValues: undefined;
3373
+ baseColumn: never;
3374
+ identity: undefined;
3375
+ generated: undefined;
3376
+ }, {}, {}>;
3340
3377
  };
3341
3378
  dialect: 'pg';
3342
3379
  }>;
@@ -3345,7 +3382,9 @@ export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithCo
3345
3382
  * (`Tx.wake`), written in that transaction, so a commit never loses its wake. The row is
3346
3383
  * deleted once Durable Session Execution has accepted the wake; until then the Tenant sweep
3347
3384
  * delivers it again, with the same idempotency key (`dedupe_key`, or `wake:<id>` without
3348
- * one). `created_at` is the insert's own clock (`clock_timestamp()`).
3385
+ * one). `created_at` is the insert's own clock (`clock_timestamp()`). `attempts` counts the
3386
+ * sweep's failed deliveries, `retry_at` is when it tries the row again, and a row
3387
+ * `parked_at` is never tried again: it stays for an operator (`Tx.failOutbox`).
3349
3388
  */
3350
3389
  export declare const wakes: import("drizzle-orm/pg-core").PgTableWithColumns<{
3351
3390
  name: "wakes";
@@ -3440,6 +3479,228 @@ export declare const wakes: import("drizzle-orm/pg-core").PgTableWithColumns<{
3440
3479
  identity: undefined;
3441
3480
  generated: undefined;
3442
3481
  }, {}, {}>;
3482
+ attempts: import("drizzle-orm/pg-core").PgColumn<{
3483
+ name: "attempts";
3484
+ tableName: "wakes";
3485
+ dataType: "number";
3486
+ columnType: "PgInteger";
3487
+ data: number;
3488
+ driverParam: string | number;
3489
+ notNull: true;
3490
+ hasDefault: true;
3491
+ isPrimaryKey: false;
3492
+ isAutoincrement: false;
3493
+ hasRuntimeDefault: false;
3494
+ enumValues: undefined;
3495
+ baseColumn: never;
3496
+ identity: undefined;
3497
+ generated: undefined;
3498
+ }, {}, {}>;
3499
+ retryAt: import("drizzle-orm/pg-core").PgColumn<{
3500
+ name: "retryAt";
3501
+ tableName: "wakes";
3502
+ dataType: "date";
3503
+ columnType: "PgTimestamp";
3504
+ data: Date;
3505
+ driverParam: string;
3506
+ notNull: false;
3507
+ hasDefault: false;
3508
+ isPrimaryKey: false;
3509
+ isAutoincrement: false;
3510
+ hasRuntimeDefault: false;
3511
+ enumValues: undefined;
3512
+ baseColumn: never;
3513
+ identity: undefined;
3514
+ generated: undefined;
3515
+ }, {}, {}>;
3516
+ parkedAt: import("drizzle-orm/pg-core").PgColumn<{
3517
+ name: "parkedAt";
3518
+ tableName: "wakes";
3519
+ dataType: "date";
3520
+ columnType: "PgTimestamp";
3521
+ data: Date;
3522
+ driverParam: string;
3523
+ notNull: false;
3524
+ hasDefault: false;
3525
+ isPrimaryKey: false;
3526
+ isAutoincrement: false;
3527
+ hasRuntimeDefault: false;
3528
+ enumValues: undefined;
3529
+ baseColumn: never;
3530
+ identity: undefined;
3531
+ generated: undefined;
3532
+ }, {}, {}>;
3533
+ };
3534
+ dialect: 'pg';
3535
+ }>;
3536
+ /**
3537
+ * The sandbox signal outbox (F7.2): one row per pod sandbox reconcile or timer a committed
3538
+ * transaction asked for (`Tx.signalSandbox`), kept and delivered as the wake outbox's rows are
3539
+ * (`attempts`, `retry_at`, `parked_at`). A reconcile is sent with the idempotency key
3540
+ * `signal:<id>`; a timer (`kind` `arm`) is its `timer` at `at` (ms since the epoch). Not in
3541
+ * `wakes`, so a Runtime that delivers only wakes never reads one.
3542
+ */
3543
+ export declare const sandboxSignals: import("drizzle-orm/pg-core").PgTableWithColumns<{
3544
+ name: "sandbox_signals";
3545
+ schema: "nylorun";
3546
+ columns: {
3547
+ id: import("drizzle-orm/pg-core").PgColumn<{
3548
+ name: "id";
3549
+ tableName: "sandbox_signals";
3550
+ dataType: "custom";
3551
+ columnType: "PgCustomColumn";
3552
+ data: string;
3553
+ driverParam: string;
3554
+ notNull: true;
3555
+ hasDefault: false;
3556
+ isPrimaryKey: true;
3557
+ isAutoincrement: false;
3558
+ hasRuntimeDefault: false;
3559
+ enumValues: undefined;
3560
+ baseColumn: never;
3561
+ identity: undefined;
3562
+ generated: undefined;
3563
+ }, {}, {
3564
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
3565
+ }>;
3566
+ sandboxId: import("drizzle-orm/pg-core").PgColumn<{
3567
+ name: "sandboxId";
3568
+ tableName: "sandbox_signals";
3569
+ dataType: "custom";
3570
+ columnType: "PgCustomColumn";
3571
+ data: string;
3572
+ driverParam: string;
3573
+ notNull: true;
3574
+ hasDefault: false;
3575
+ isPrimaryKey: false;
3576
+ isAutoincrement: false;
3577
+ hasRuntimeDefault: false;
3578
+ enumValues: undefined;
3579
+ baseColumn: never;
3580
+ identity: undefined;
3581
+ generated: undefined;
3582
+ }, {}, {
3583
+ pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
3584
+ }>;
3585
+ kind: import("drizzle-orm/pg-core").PgColumn<{
3586
+ name: "kind";
3587
+ tableName: "sandbox_signals";
3588
+ dataType: "string";
3589
+ columnType: "PgText";
3590
+ data: string;
3591
+ driverParam: string;
3592
+ notNull: true;
3593
+ hasDefault: false;
3594
+ isPrimaryKey: false;
3595
+ isAutoincrement: false;
3596
+ hasRuntimeDefault: false;
3597
+ enumValues: [string, ...string[]];
3598
+ baseColumn: never;
3599
+ identity: undefined;
3600
+ generated: undefined;
3601
+ }, {}, {}>;
3602
+ timer: import("drizzle-orm/pg-core").PgColumn<{
3603
+ name: "timer";
3604
+ tableName: "sandbox_signals";
3605
+ dataType: "string";
3606
+ columnType: "PgText";
3607
+ data: string;
3608
+ driverParam: string;
3609
+ notNull: false;
3610
+ hasDefault: false;
3611
+ isPrimaryKey: false;
3612
+ isAutoincrement: false;
3613
+ hasRuntimeDefault: false;
3614
+ enumValues: [string, ...string[]];
3615
+ baseColumn: never;
3616
+ identity: undefined;
3617
+ generated: undefined;
3618
+ }, {}, {}>;
3619
+ at: import("drizzle-orm/pg-core").PgColumn<{
3620
+ name: "at";
3621
+ tableName: "sandbox_signals";
3622
+ dataType: "number";
3623
+ columnType: "PgBigInt53";
3624
+ data: number;
3625
+ driverParam: string | number;
3626
+ notNull: false;
3627
+ hasDefault: false;
3628
+ isPrimaryKey: false;
3629
+ isAutoincrement: false;
3630
+ hasRuntimeDefault: false;
3631
+ enumValues: undefined;
3632
+ baseColumn: never;
3633
+ identity: undefined;
3634
+ generated: undefined;
3635
+ }, {}, {}>;
3636
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
3637
+ name: "createdAt";
3638
+ tableName: "sandbox_signals";
3639
+ dataType: "date";
3640
+ columnType: "PgTimestamp";
3641
+ data: Date;
3642
+ driverParam: string;
3643
+ notNull: true;
3644
+ hasDefault: true;
3645
+ isPrimaryKey: false;
3646
+ isAutoincrement: false;
3647
+ hasRuntimeDefault: false;
3648
+ enumValues: undefined;
3649
+ baseColumn: never;
3650
+ identity: undefined;
3651
+ generated: undefined;
3652
+ }, {}, {}>;
3653
+ attempts: import("drizzle-orm/pg-core").PgColumn<{
3654
+ name: "attempts";
3655
+ tableName: "sandbox_signals";
3656
+ dataType: "number";
3657
+ columnType: "PgInteger";
3658
+ data: number;
3659
+ driverParam: string | number;
3660
+ notNull: true;
3661
+ hasDefault: true;
3662
+ isPrimaryKey: false;
3663
+ isAutoincrement: false;
3664
+ hasRuntimeDefault: false;
3665
+ enumValues: undefined;
3666
+ baseColumn: never;
3667
+ identity: undefined;
3668
+ generated: undefined;
3669
+ }, {}, {}>;
3670
+ retryAt: import("drizzle-orm/pg-core").PgColumn<{
3671
+ name: "retryAt";
3672
+ tableName: "sandbox_signals";
3673
+ dataType: "date";
3674
+ columnType: "PgTimestamp";
3675
+ data: Date;
3676
+ driverParam: string;
3677
+ notNull: false;
3678
+ hasDefault: false;
3679
+ isPrimaryKey: false;
3680
+ isAutoincrement: false;
3681
+ hasRuntimeDefault: false;
3682
+ enumValues: undefined;
3683
+ baseColumn: never;
3684
+ identity: undefined;
3685
+ generated: undefined;
3686
+ }, {}, {}>;
3687
+ parkedAt: import("drizzle-orm/pg-core").PgColumn<{
3688
+ name: "parkedAt";
3689
+ tableName: "sandbox_signals";
3690
+ dataType: "date";
3691
+ columnType: "PgTimestamp";
3692
+ data: Date;
3693
+ driverParam: string;
3694
+ notNull: false;
3695
+ hasDefault: false;
3696
+ isPrimaryKey: false;
3697
+ isAutoincrement: false;
3698
+ hasRuntimeDefault: false;
3699
+ enumValues: undefined;
3700
+ baseColumn: never;
3701
+ identity: undefined;
3702
+ generated: undefined;
3703
+ }, {}, {}>;
3443
3704
  };
3444
3705
  dialect: 'pg';
3445
3706
  }>;