@osolmaz/pi-workflows 0.16.1 → 0.16.3
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/README.md +41 -41
- package/dist/builtins/monitor.workflow.d.ts +1 -1
- package/dist/builtins/monitor.workflow.js +8 -8
- package/dist/builtins/pi-agent-group.js +3 -3
- package/dist/builtins/pi-agent-group.js.map +1 -1
- package/dist/channels/adapter-entry.js +14 -14
- package/dist/channels/adapter-entry.js.map +1 -1
- package/dist/client/client.d.ts +6 -6
- package/dist/client/client.js +27 -27
- package/dist/client/client.js.map +1 -1
- package/dist/client/protocol.d.ts +4 -1
- package/dist/client/protocol.js +17 -11
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/resolver.d.ts +4 -4
- package/dist/client/view.d.ts +7 -1
- package/dist/client/view.js +1 -0
- package/dist/client/view.js.map +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +66 -47
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +1 -1
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/remote-recorder-store.d.ts +1 -1
- package/dist/extension/remote-recorder-store.js +2 -2
- package/dist/extension/remote-recorder-store.js.map +1 -1
- package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
- package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
- package/dist/extension/resource-manager-command.js.map +1 -0
- package/dist/extension/session-delivery.d.ts +1 -1
- package/dist/extension/session-delivery.js +2 -2
- package/dist/extension/session-delivery.js.map +1 -1
- package/dist/extension/session-view.d.ts +1 -1
- package/dist/extension/session-view.js +1 -1
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +4 -3
- package/dist/extension/workflow-message-coordinator.js +81 -29
- package/dist/extension/workflow-message-coordinator.js.map +1 -1
- package/dist/resource-managers/conditions.d.ts +6 -0
- package/dist/{controllers → resource-managers}/conditions.js +3 -3
- package/dist/resource-managers/conditions.js.map +1 -0
- package/dist/resource-managers/definition.d.ts +6 -0
- package/dist/resource-managers/definition.js +45 -0
- package/dist/resource-managers/definition.js.map +1 -0
- package/dist/resource-managers/effects.d.ts +15 -0
- package/dist/{controllers → resource-managers}/effects.js +2 -2
- package/dist/resource-managers/effects.js.map +1 -0
- package/dist/resource-managers/errors.d.ts +12 -0
- package/dist/resource-managers/errors.js +25 -0
- package/dist/resource-managers/errors.js.map +1 -0
- package/dist/resource-managers/index.d.ts +12 -0
- package/dist/resource-managers/index.js +12 -0
- package/dist/resource-managers/index.js.map +1 -0
- package/dist/resource-managers/json.js.map +1 -0
- package/dist/resource-managers/loader.d.ts +23 -0
- package/dist/resource-managers/loader.js +75 -0
- package/dist/resource-managers/loader.js.map +1 -0
- package/dist/resource-managers/results.d.ts +5 -0
- package/dist/resource-managers/results.js.map +1 -0
- package/dist/resource-managers/runtime.d.ts +59 -0
- package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
- package/dist/resource-managers/runtime.js.map +1 -0
- package/dist/{controllers → resource-managers}/sqlite.d.ts +51 -43
- package/dist/{controllers → resource-managers}/sqlite.js +187 -125
- package/dist/resource-managers/sqlite.js.map +1 -0
- package/dist/{controllers → resource-managers}/store.d.ts +33 -33
- package/dist/{controllers → resource-managers}/store.js.map +1 -1
- package/dist/{controllers → resource-managers}/types.d.ts +41 -41
- package/dist/{controllers → resource-managers}/types.js.map +1 -1
- package/dist/resource-managers/workflows.d.ts +34 -0
- package/dist/{controllers → resource-managers}/workflows.js +15 -15
- package/dist/resource-managers/workflows.js.map +1 -0
- package/dist/server/channel-effects.js.map +1 -0
- package/dist/{host → server}/channel-supervisor.d.ts +6 -6
- package/dist/{host → server}/channel-supervisor.js +3 -3
- package/dist/server/channel-supervisor.js.map +1 -0
- package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
- package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
- package/dist/server/child-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/processes.d.ts +3 -3
- package/dist/{host → server}/processes.js +3 -3
- package/dist/server/processes.js.map +1 -0
- package/dist/{host → server}/resolver-entry.d.ts +6 -6
- package/dist/{host → server}/resolver-entry.js +14 -14
- package/dist/server/resolver-entry.js.map +1 -0
- package/dist/server/resource-runner-entry.d.ts +2 -0
- package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
- package/dist/server/resource-runner-entry.js.map +1 -0
- package/dist/server/resource-runner-protocol.d.ts +36 -0
- package/dist/server/resource-runner-protocol.js +49 -0
- package/dist/server/resource-runner-protocol.js.map +1 -0
- package/dist/server/resource-runner-supervisor.d.ts +21 -0
- package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
- package/dist/server/resource-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/rpc-bridge.d.ts +3 -3
- package/dist/{host → server}/rpc-bridge.js +3 -3
- package/dist/server/rpc-bridge.js.map +1 -0
- package/dist/{host → server}/rpc-executor.d.ts +1 -1
- package/dist/{host → server}/rpc-executor.js +3 -3
- package/dist/server/rpc-executor.js.map +1 -0
- package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
- package/dist/server/server-entry.js.map +1 -0
- package/dist/{host/runner.d.ts → server/server.d.ts} +47 -39
- package/dist/{host/runner.js → server/server.js} +624 -535
- package/dist/server/server.js.map +1 -0
- package/dist/{host → server}/state.d.ts +25 -25
- package/dist/{host → server}/state.js +49 -49
- package/dist/server/state.js.map +1 -0
- package/dist/{host → server}/view.d.ts +7 -7
- package/dist/{host → server}/view.js +19 -15
- package/dist/server/view.js.map +1 -0
- package/dist/server/workflow-runner-content.d.ts +6 -0
- package/dist/server/workflow-runner-content.js +96 -0
- package/dist/server/workflow-runner-content.js.map +1 -0
- package/dist/server/workflow-runner-entry.d.ts +13 -0
- package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +89 -57
- package/dist/server/workflow-runner-entry.js.map +1 -0
- package/dist/server/workflow-runner-protocol.d.ts +69 -0
- package/dist/server/workflow-runner-protocol.js +176 -0
- package/dist/server/workflow-runner-protocol.js.map +1 -0
- package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
- package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
- package/dist/server/workflow-runner-store.js.map +1 -0
- package/dist/server/workflow-runner-supervisor.d.ts +22 -0
- package/dist/server/workflow-runner-supervisor.js +56 -0
- package/dist/server/workflow-runner-supervisor.js.map +1 -0
- package/dist/state/prune.d.ts +1 -1
- package/dist/state/prune.js +17 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +2 -2
- package/dist/state/workflow-messages.d.ts +3 -1
- package/dist/state/workflow-messages.js +32 -1
- package/dist/state/workflow-messages.js.map +1 -1
- package/dist/viewer/backup.js +1 -1
- package/dist/viewer/backup.js.map +1 -1
- package/dist/viewer/cli.d.ts +2 -2
- package/dist/viewer/cli.js +46 -32
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/tui.d.ts +1 -1
- package/dist/viewer/tui.js +1 -1
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/command-batch.js +2 -2
- package/dist/workflows/composition.js +0 -4
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.js +0 -8
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.js +18 -20
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +27 -11
- package/dist/workflows/store.js +66 -18
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +3 -5
- package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
- package/docs/2026-08-25-workflow-follow-ups.md +7 -7
- package/docs/2026-08-25-workflow-settings.md +3 -3
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +5 -5
- package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
- package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
- package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
- package/docs/2026-09-04-workflow-run-state-plan.md +363 -0
- package/docs/DEFERRED_TURNS.md +7 -7
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +11 -11
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
- package/docs/MONITOR.md +1 -1
- package/docs/RESOURCE_MANAGERS.md +221 -0
- package/docs/SQLITE_STATE.md +40 -34
- package/docs/WORKFLOW_COMPOSITION.md +1 -1
- package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +131 -116
- package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
- package/docs/WORKFLOW_UPDATES.md +16 -16
- package/docs/development.md +15 -14
- package/docs/live-replay-protocol.md +20 -20
- package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
- package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
- package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
- package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
- package/docs/tui-viewer.md +9 -9
- package/docs/workflows.md +78 -63
- package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
- package/herdr-plugin.toml +1 -1
- package/package.json +5 -5
- package/protocol/client.v1.schema.json +7 -7
- package/protocol/fixtures/client-v1.json +1 -1
- package/src/builtins/monitor.workflow.ts +9 -9
- package/src/builtins/pi-agent-group.ts +3 -3
- package/src/channels/adapter-entry.ts +14 -14
- package/src/client/client.ts +33 -32
- package/src/client/protocol.ts +16 -11
- package/src/client/resolver.ts +4 -4
- package/src/client/view.ts +9 -1
- package/src/extension/index.ts +81 -53
- package/src/extension/recorder.ts +1 -1
- package/src/extension/remote-recorder-store.ts +2 -2
- package/src/extension/resource-manager-command.ts +45 -0
- package/src/extension/session-delivery.ts +2 -2
- package/src/extension/session-view.ts +1 -1
- package/src/extension/workflow-message-coordinator.ts +94 -30
- package/src/{controllers → resource-managers}/conditions.ts +23 -23
- package/src/resource-managers/definition.ts +71 -0
- package/src/{controllers → resource-managers}/effects.ts +9 -9
- package/src/resource-managers/errors.ts +27 -0
- package/src/resource-managers/index.ts +88 -0
- package/src/resource-managers/loader.ts +111 -0
- package/src/{controllers → resource-managers}/results.ts +4 -4
- package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
- package/src/{controllers → resource-managers}/sqlite.ts +275 -181
- package/src/{controllers → resource-managers}/store.ts +46 -38
- package/src/{controllers → resource-managers}/types.ts +50 -41
- package/src/{controllers → resource-managers}/workflows.ts +43 -41
- package/src/{host → server}/channel-supervisor.ts +8 -8
- package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
- package/src/{host → server}/processes.ts +3 -3
- package/src/{host → server}/resolver-entry.ts +28 -25
- package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
- package/src/server/resource-runner-protocol.ts +103 -0
- package/src/server/resource-runner-supervisor.ts +76 -0
- package/src/{host → server}/rpc-bridge.ts +3 -3
- package/src/{host → server}/rpc-executor.ts +4 -4
- package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
- package/src/{host/runner.ts → server/server.ts} +786 -672
- package/src/{host → server}/state.ts +78 -67
- package/src/{host → server}/view.ts +21 -17
- package/src/server/workflow-runner-content.ts +119 -0
- package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +131 -82
- package/src/server/workflow-runner-protocol.ts +241 -0
- package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
- package/src/server/workflow-runner-supervisor.ts +79 -0
- package/src/state/prune.ts +27 -9
- package/src/state/schema.ts +2 -2
- package/src/state/workflow-messages.ts +52 -1
- package/src/viewer/backup.ts +1 -1
- package/src/viewer/cli.ts +50 -36
- package/src/viewer/tui.ts +1 -1
- package/src/workflows/command-batch.ts +2 -2
- package/src/workflows/composition.ts +0 -5
- package/src/workflows/definition.ts +0 -8
- package/src/workflows/engine.ts +18 -21
- package/src/workflows/schema.ts +0 -6
- package/src/workflows/store.ts +95 -28
- package/src/workflows/types.ts +3 -5
- package/dist/controllers/conditions.d.ts +0 -6
- package/dist/controllers/conditions.js.map +0 -1
- package/dist/controllers/definition.d.ts +0 -6
- package/dist/controllers/definition.js +0 -45
- package/dist/controllers/definition.js.map +0 -1
- package/dist/controllers/effects.d.ts +0 -15
- package/dist/controllers/effects.js.map +0 -1
- package/dist/controllers/errors.d.ts +0 -12
- package/dist/controllers/errors.js +0 -25
- package/dist/controllers/errors.js.map +0 -1
- package/dist/controllers/index.d.ts +0 -12
- package/dist/controllers/index.js +0 -12
- package/dist/controllers/index.js.map +0 -1
- package/dist/controllers/json.js.map +0 -1
- package/dist/controllers/loader.d.ts +0 -23
- package/dist/controllers/loader.js +0 -74
- package/dist/controllers/loader.js.map +0 -1
- package/dist/controllers/manager.d.ts +0 -59
- package/dist/controllers/manager.js.map +0 -1
- package/dist/controllers/results.d.ts +0 -5
- package/dist/controllers/results.js.map +0 -1
- package/dist/controllers/sqlite.js.map +0 -1
- package/dist/controllers/workflows.d.ts +0 -34
- package/dist/controllers/workflows.js.map +0 -1
- package/dist/extension/controller-command.js.map +0 -1
- package/dist/host/channel-effects.js.map +0 -1
- package/dist/host/channel-supervisor.js.map +0 -1
- package/dist/host/child-worker-supervisor.js.map +0 -1
- package/dist/host/controller-worker-entry.d.ts +0 -2
- package/dist/host/controller-worker-entry.js.map +0 -1
- package/dist/host/controller-worker-protocol.d.ts +0 -36
- package/dist/host/controller-worker-protocol.js +0 -49
- package/dist/host/controller-worker-protocol.js.map +0 -1
- package/dist/host/controller-worker-supervisor.d.ts +0 -21
- package/dist/host/controller-worker-supervisor.js +0 -54
- package/dist/host/controller-worker-supervisor.js.map +0 -1
- package/dist/host/host-entry.js.map +0 -1
- package/dist/host/processes.js.map +0 -1
- package/dist/host/resolver-entry.js.map +0 -1
- package/dist/host/rpc-bridge.js.map +0 -1
- package/dist/host/rpc-executor.js.map +0 -1
- package/dist/host/runner.js.map +0 -1
- package/dist/host/state.js.map +0 -1
- package/dist/host/view.js.map +0 -1
- package/dist/host/worker-entry.d.ts +0 -10
- package/dist/host/worker-entry.js.map +0 -1
- package/dist/host/worker-protocol.d.ts +0 -31
- package/dist/host/worker-protocol.js +0 -122
- package/dist/host/worker-protocol.js.map +0 -1
- package/dist/host/worker-store.js.map +0 -1
- package/dist/host/worker-supervisor.d.ts +0 -22
- package/dist/host/worker-supervisor.js.map +0 -1
- package/docs/CONTROLLERS.md +0 -217
- package/src/controllers/definition.ts +0 -65
- package/src/controllers/errors.ts +0 -27
- package/src/controllers/index.ts +0 -88
- package/src/controllers/loader.ts +0 -104
- package/src/extension/controller-command.ts +0 -45
- package/src/host/controller-worker-protocol.ts +0 -104
- package/src/host/controller-worker-supervisor.ts +0 -79
- package/src/host/worker-protocol.ts +0 -165
- package/src/host/worker-supervisor.ts +0 -74
- /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
- /package/dist/{controllers → resource-managers}/json.js +0 -0
- /package/dist/{controllers → resource-managers}/results.js +0 -0
- /package/dist/{controllers → resource-managers}/store.js +0 -0
- /package/dist/{controllers → resource-managers}/types.js +0 -0
- /package/dist/{host → server}/channel-effects.d.ts +0 -0
- /package/dist/{host → server}/channel-effects.js +0 -0
- /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
- /package/src/{controllers → resource-managers}/json.ts +0 -0
- /package/src/{host → server}/channel-effects.ts +0 -0
|
@@ -5,14 +5,15 @@ import { StateDatabase, workflowStatePath } from "../state/database.js";
|
|
|
5
5
|
import { canonicalJson } from "../state/json.js";
|
|
6
6
|
import { resourceIdFor, tokenHash } from "../state/mutation.js";
|
|
7
7
|
import { initializeViewerRun, recordViewerDeltas } from "../state/viewer.js";
|
|
8
|
+
import { WorkflowMessageStore } from "../state/workflow-messages.js";
|
|
8
9
|
import {
|
|
9
10
|
EffectRequestConflictError,
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
ManagedResourceConflictError,
|
|
12
|
+
ManagedResourceNotFoundError,
|
|
12
13
|
WorkflowRequestConflictError,
|
|
13
14
|
} from "./errors.js";
|
|
14
15
|
import type {
|
|
15
|
-
|
|
16
|
+
ResourceManagerStore,
|
|
16
17
|
EffectReservation,
|
|
17
18
|
QueueItem,
|
|
18
19
|
QueueRequeueOptions,
|
|
@@ -21,11 +22,11 @@ import type {
|
|
|
21
22
|
} from "./store.js";
|
|
22
23
|
import type {
|
|
23
24
|
ChildWorkflowRecord,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
ResourceManagerEvent,
|
|
26
|
+
ResourceManagerQueueClaim,
|
|
27
|
+
ManagedResource,
|
|
28
|
+
ManagedResourceRef,
|
|
29
|
+
ManagedResourceStatus,
|
|
29
30
|
EffectRecord,
|
|
30
31
|
JsonObject,
|
|
31
32
|
} from "./types.js";
|
|
@@ -138,11 +139,11 @@ export type RunEventRecord = {
|
|
|
138
139
|
payload: JsonObject;
|
|
139
140
|
};
|
|
140
141
|
|
|
141
|
-
type
|
|
142
|
-
|
|
142
|
+
type ResourceManagerRow = {
|
|
143
|
+
managedResourceId: string;
|
|
143
144
|
resourceId: string;
|
|
144
145
|
resourceVersion: number;
|
|
145
|
-
|
|
146
|
+
resourceManagerName: string;
|
|
146
147
|
resourceKey: string;
|
|
147
148
|
uid: string;
|
|
148
149
|
generation: number;
|
|
@@ -267,9 +268,10 @@ type WorkflowRow = {
|
|
|
267
268
|
errorHash: Buffer | null;
|
|
268
269
|
};
|
|
269
270
|
|
|
270
|
-
export class
|
|
271
|
+
export class SqliteResourceManagerStore implements ResourceManagerStore {
|
|
271
272
|
readonly filePath: string;
|
|
272
273
|
readonly state: StateDatabase;
|
|
274
|
+
private readonly workflowMessages: WorkflowMessageStore;
|
|
273
275
|
private readonly ownsState: boolean;
|
|
274
276
|
private readonly projectId: string | null;
|
|
275
277
|
private closed = false;
|
|
@@ -280,7 +282,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
280
282
|
readOnly?: boolean;
|
|
281
283
|
projectPath?: string;
|
|
282
284
|
state?: StateDatabase;
|
|
283
|
-
/**
|
|
285
|
+
/** Server-only global view. Project-scoped mutations still require projectPath. */
|
|
284
286
|
global?: boolean;
|
|
285
287
|
} = {},
|
|
286
288
|
) {
|
|
@@ -293,6 +295,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
293
295
|
checkLegacyState: filePath === workflowStatePath(),
|
|
294
296
|
});
|
|
295
297
|
this.filePath = this.state.filePath;
|
|
298
|
+
this.workflowMessages = new WorkflowMessageStore(this.state);
|
|
296
299
|
if (options.global === true) {
|
|
297
300
|
this.projectId = null;
|
|
298
301
|
} else {
|
|
@@ -309,31 +312,34 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
putResource<TSpec, TStatus>(options: {
|
|
312
|
-
|
|
315
|
+
resourceManager: string;
|
|
313
316
|
key: string;
|
|
314
317
|
spec: TSpec;
|
|
315
318
|
initialStatus: TStatus;
|
|
316
319
|
now?: string;
|
|
317
|
-
}):
|
|
320
|
+
}): ManagedResource<TSpec, TStatus> {
|
|
318
321
|
this.assertWritable();
|
|
319
|
-
validateName(options.
|
|
322
|
+
validateName(options.resourceManager, "resource manager");
|
|
320
323
|
validateKey(options.key, "resource key");
|
|
321
324
|
const now = epoch(validTimestamp(options.now));
|
|
322
325
|
const specHash = this.state.putJson(options.spec, now);
|
|
323
|
-
const initialStatus:
|
|
326
|
+
const initialStatus: ManagedResourceStatus<TStatus> = {
|
|
324
327
|
observedGeneration: 0,
|
|
325
328
|
conditions: [],
|
|
326
|
-
|
|
329
|
+
resourceManagerStatus: options.initialStatus,
|
|
327
330
|
};
|
|
328
331
|
const statusHash = this.state.putJson(initialStatus, now);
|
|
329
332
|
return this.state.transaction(() => {
|
|
330
|
-
const existing = this.
|
|
333
|
+
const existing = this.resourceManagerRow({
|
|
334
|
+
resourceManager: options.resourceManager,
|
|
335
|
+
key: options.key,
|
|
336
|
+
});
|
|
331
337
|
if (existing === undefined) {
|
|
332
338
|
const uid = randomUUID();
|
|
333
|
-
const
|
|
339
|
+
const managedResourceId = `controller-resource-${randomUUID()}`;
|
|
334
340
|
const resourceId = resourceIdFor(
|
|
335
341
|
"controller",
|
|
336
|
-
`${this.requireProjectId()}:${options.
|
|
342
|
+
`${this.requireProjectId()}:${options.resourceManager}:${options.key}`,
|
|
337
343
|
);
|
|
338
344
|
this.state.connection
|
|
339
345
|
.prepare(
|
|
@@ -341,7 +347,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
341
347
|
resource_id, resource_type, aggregate_key, revision, created_at, updated_at
|
|
342
348
|
) VALUES (?, 'controller', ?, 1, ?, ?)`,
|
|
343
349
|
)
|
|
344
|
-
.run(resourceId,
|
|
350
|
+
.run(resourceId, managedResourceId, now, now);
|
|
345
351
|
this.state.connection
|
|
346
352
|
.prepare("INSERT INTO leases(resource_id, generation) VALUES (?, 0)")
|
|
347
353
|
.run(resourceId);
|
|
@@ -353,10 +359,10 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
353
359
|
) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`,
|
|
354
360
|
)
|
|
355
361
|
.run(
|
|
356
|
-
|
|
362
|
+
managedResourceId,
|
|
357
363
|
resourceId,
|
|
358
364
|
this.requireProjectId(),
|
|
359
|
-
options.
|
|
365
|
+
options.resourceManager,
|
|
360
366
|
options.key,
|
|
361
367
|
uid,
|
|
362
368
|
specHash,
|
|
@@ -364,7 +370,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
364
370
|
now,
|
|
365
371
|
now,
|
|
366
372
|
);
|
|
367
|
-
this.
|
|
373
|
+
this.enqueueResourceManagerRow(managedResourceId, now, now);
|
|
368
374
|
this.insertEvent(
|
|
369
375
|
resourceId,
|
|
370
376
|
1,
|
|
@@ -382,9 +388,9 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
382
388
|
SET spec_hash = ?, generation = generation + 1, updated_at = ?
|
|
383
389
|
WHERE controller_resource_id = ?`,
|
|
384
390
|
)
|
|
385
|
-
.run(specHash, now, existing.
|
|
391
|
+
.run(specHash, now, existing.managedResourceId);
|
|
386
392
|
this.bumpResource(existing.resourceId, existing.resourceVersion, now);
|
|
387
|
-
this.
|
|
393
|
+
this.enqueueResourceManagerRow(existing.managedResourceId, now, now);
|
|
388
394
|
this.insertEvent(
|
|
389
395
|
existing.resourceId,
|
|
390
396
|
revision,
|
|
@@ -396,74 +402,74 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
396
402
|
);
|
|
397
403
|
}
|
|
398
404
|
return this.requireResource({
|
|
399
|
-
|
|
405
|
+
resourceManager: options.resourceManager,
|
|
400
406
|
key: options.key,
|
|
401
|
-
}) as
|
|
407
|
+
}) as ManagedResource<TSpec, TStatus>;
|
|
402
408
|
});
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
getResource<TSpec = unknown, TStatus = unknown>(
|
|
406
|
-
ref:
|
|
407
|
-
):
|
|
408
|
-
const row = this.
|
|
412
|
+
ref: ManagedResourceRef,
|
|
413
|
+
): ManagedResource<TSpec, TStatus> | undefined {
|
|
414
|
+
const row = this.resourceManagerRow(ref);
|
|
409
415
|
return row === undefined
|
|
410
416
|
? undefined
|
|
411
|
-
: (this.
|
|
417
|
+
: (this.mapManagedResource(row) as ManagedResource<TSpec, TStatus>);
|
|
412
418
|
}
|
|
413
419
|
|
|
414
|
-
getResourceByUid(uid: string):
|
|
415
|
-
const row = this.state.connection.prepare(
|
|
416
|
-
return
|
|
420
|
+
getResourceByUid(uid: string): ManagedResource | undefined {
|
|
421
|
+
const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
|
|
422
|
+
return isResourceManagerRow(row) ? this.mapManagedResource(row) : undefined;
|
|
417
423
|
}
|
|
418
424
|
|
|
419
425
|
listResources<TSpec = unknown, TStatus = unknown>(
|
|
420
|
-
options: {
|
|
421
|
-
):
|
|
426
|
+
options: { resourceManager?: string } = {},
|
|
427
|
+
): ManagedResource<TSpec, TStatus>[] {
|
|
422
428
|
const clauses = ["1 = 1"];
|
|
423
429
|
const params: unknown[] = [];
|
|
424
430
|
if (this.projectId !== null) {
|
|
425
431
|
clauses.push("c.project_id = ?");
|
|
426
432
|
params.push(this.projectId);
|
|
427
433
|
}
|
|
428
|
-
if (options.
|
|
434
|
+
if (options.resourceManager !== undefined) {
|
|
429
435
|
clauses.push("c.controller_name = ?");
|
|
430
|
-
params.push(options.
|
|
436
|
+
params.push(options.resourceManager);
|
|
431
437
|
}
|
|
432
438
|
const rows = this.state.connection
|
|
433
439
|
.prepare(
|
|
434
|
-
|
|
440
|
+
resourceManagerSelect(
|
|
435
441
|
`WHERE ${clauses.join(" AND ")} ORDER BY c.controller_name, c.resource_key`,
|
|
436
442
|
),
|
|
437
443
|
)
|
|
438
444
|
.all(...params);
|
|
439
445
|
return rows
|
|
440
|
-
.filter(
|
|
441
|
-
.map((row) => this.
|
|
446
|
+
.filter(isResourceManagerRow)
|
|
447
|
+
.map((row) => this.mapManagedResource(row) as ManagedResource<TSpec, TStatus>);
|
|
442
448
|
}
|
|
443
449
|
|
|
444
450
|
updateStatus<TStatus>(options: {
|
|
445
|
-
ref:
|
|
451
|
+
ref: ManagedResourceRef;
|
|
446
452
|
expectedResourceVersion: number;
|
|
447
|
-
claim:
|
|
448
|
-
status:
|
|
453
|
+
claim: ResourceManagerQueueClaim;
|
|
454
|
+
status: ManagedResourceStatus<TStatus>;
|
|
449
455
|
finalizers?: string[];
|
|
450
456
|
now?: string;
|
|
451
|
-
}):
|
|
457
|
+
}): ManagedResource<unknown, TStatus> {
|
|
452
458
|
const now = epoch(validTimestamp(options.now));
|
|
453
459
|
return this.state.transaction(() => {
|
|
454
|
-
const row = this.
|
|
455
|
-
this.
|
|
460
|
+
const row = this.requireResourceManagerRow(options.ref);
|
|
461
|
+
this.assertResourceManagerClaim(row, options.claim, now);
|
|
456
462
|
if (row.resourceVersion !== options.expectedResourceVersion) {
|
|
457
|
-
throw new
|
|
463
|
+
throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
|
|
458
464
|
}
|
|
459
465
|
const statusHash = this.state.putJson(options.status, now);
|
|
460
466
|
this.state.connection
|
|
461
467
|
.prepare(
|
|
462
468
|
"UPDATE controller_resources SET status_hash = ?, updated_at = ? WHERE controller_resource_id = ?",
|
|
463
469
|
)
|
|
464
|
-
.run(statusHash, now, row.
|
|
470
|
+
.run(statusHash, now, row.managedResourceId);
|
|
465
471
|
if (options.finalizers !== undefined)
|
|
466
|
-
this.replaceFinalizers(row.
|
|
472
|
+
this.replaceFinalizers(row.managedResourceId, options.finalizers);
|
|
467
473
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
468
474
|
this.insertEvent(
|
|
469
475
|
row.resourceId,
|
|
@@ -476,22 +482,22 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
476
482
|
options.claim.generation,
|
|
477
483
|
);
|
|
478
484
|
options.claim.resourceVersion = row.resourceVersion + 1;
|
|
479
|
-
return this.requireResource(options.ref) as
|
|
485
|
+
return this.requireResource(options.ref) as ManagedResource<unknown, TStatus>;
|
|
480
486
|
});
|
|
481
487
|
}
|
|
482
488
|
|
|
483
|
-
requestDeletion(ref:
|
|
489
|
+
requestDeletion(ref: ManagedResourceRef, nowValue?: string): ManagedResource {
|
|
484
490
|
const now = epoch(validTimestamp(nowValue));
|
|
485
491
|
return this.state.transaction(() => {
|
|
486
|
-
const row = this.
|
|
492
|
+
const row = this.requireResourceManagerRow(ref);
|
|
487
493
|
if (row.deletionRequestedAt === null) {
|
|
488
494
|
this.state.connection
|
|
489
495
|
.prepare(
|
|
490
496
|
"UPDATE controller_resources SET deletion_requested_at = ?, updated_at = ? WHERE controller_resource_id = ?",
|
|
491
497
|
)
|
|
492
|
-
.run(now, now, row.
|
|
498
|
+
.run(now, now, row.managedResourceId);
|
|
493
499
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
494
|
-
this.
|
|
500
|
+
this.enqueueResourceManagerRow(row.managedResourceId, now, now);
|
|
495
501
|
this.insertEvent(
|
|
496
502
|
row.resourceId,
|
|
497
503
|
row.resourceVersion + 1,
|
|
@@ -507,21 +513,21 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
507
513
|
}
|
|
508
514
|
|
|
509
515
|
updateFinalizers(options: {
|
|
510
|
-
ref:
|
|
516
|
+
ref: ManagedResourceRef;
|
|
511
517
|
expectedResourceVersion: number;
|
|
512
518
|
finalizers: string[];
|
|
513
519
|
now?: string;
|
|
514
|
-
}):
|
|
520
|
+
}): ManagedResource {
|
|
515
521
|
const now = epoch(validTimestamp(options.now));
|
|
516
522
|
return this.state.transaction(() => {
|
|
517
|
-
const row = this.
|
|
523
|
+
const row = this.requireResourceManagerRow(options.ref);
|
|
518
524
|
if (row.resourceVersion !== options.expectedResourceVersion) {
|
|
519
|
-
throw new
|
|
525
|
+
throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
|
|
520
526
|
}
|
|
521
|
-
this.replaceFinalizers(row.
|
|
527
|
+
this.replaceFinalizers(row.managedResourceId, options.finalizers);
|
|
522
528
|
this.state.connection
|
|
523
529
|
.prepare("UPDATE controller_resources SET updated_at = ? WHERE controller_resource_id = ?")
|
|
524
|
-
.run(now, row.
|
|
530
|
+
.run(now, row.managedResourceId);
|
|
525
531
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
526
532
|
this.insertEvent(
|
|
527
533
|
row.resourceId,
|
|
@@ -537,50 +543,50 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
537
543
|
}
|
|
538
544
|
|
|
539
545
|
deleteResource(
|
|
540
|
-
ref:
|
|
546
|
+
ref: ManagedResourceRef,
|
|
541
547
|
expectedResourceVersion: number,
|
|
542
|
-
claim:
|
|
548
|
+
claim: ResourceManagerQueueClaim,
|
|
543
549
|
): boolean {
|
|
544
550
|
return this.state.transaction(() => {
|
|
545
|
-
const row = this.
|
|
546
|
-
this.
|
|
551
|
+
const row = this.requireResourceManagerRow(ref);
|
|
552
|
+
this.assertResourceManagerClaim(row, claim, Date.now());
|
|
547
553
|
if (row.resourceVersion !== expectedResourceVersion) {
|
|
548
|
-
throw new
|
|
554
|
+
throw new ManagedResourceConflictError(ref.resourceManager, ref.key);
|
|
549
555
|
}
|
|
550
|
-
if (row.deletionRequestedAt === null || this.finalizers(row.
|
|
556
|
+
if (row.deletionRequestedAt === null || this.finalizers(row.managedResourceId).length > 0)
|
|
551
557
|
return false;
|
|
552
558
|
this.state.connection
|
|
553
559
|
.prepare("DELETE FROM controller_resources WHERE controller_resource_id = ?")
|
|
554
|
-
.run(row.
|
|
560
|
+
.run(row.managedResourceId);
|
|
555
561
|
return true;
|
|
556
562
|
});
|
|
557
563
|
}
|
|
558
564
|
|
|
559
|
-
enqueue(ref:
|
|
560
|
-
const row = this.
|
|
565
|
+
enqueue(ref: ManagedResourceRef, availableAt?: string): void {
|
|
566
|
+
const row = this.requireResourceManagerRow(ref);
|
|
561
567
|
const now = Date.now();
|
|
562
|
-
this.
|
|
568
|
+
this.enqueueResourceManagerRow(row.managedResourceId, epoch(validTimestamp(availableAt)), now);
|
|
563
569
|
}
|
|
564
570
|
|
|
565
571
|
claimNext(options: {
|
|
566
|
-
|
|
572
|
+
resourceManagers: string[];
|
|
567
573
|
ownerId: string;
|
|
568
574
|
leaseMs: number;
|
|
569
575
|
now?: string;
|
|
570
|
-
exclude?:
|
|
571
|
-
}):
|
|
572
|
-
if (options.
|
|
576
|
+
exclude?: ManagedResourceRef[];
|
|
577
|
+
}): ResourceManagerQueueClaim | undefined {
|
|
578
|
+
if (options.resourceManagers.length === 0) return undefined;
|
|
573
579
|
const now = epoch(validTimestamp(options.now));
|
|
574
|
-
const placeholders = options.
|
|
580
|
+
const placeholders = options.resourceManagers.map(() => "?").join(", ");
|
|
575
581
|
const exclusions = options.exclude ?? [];
|
|
576
582
|
const exclusionSql = exclusions
|
|
577
583
|
.map(() => "AND NOT (c.controller_name = ? AND c.resource_key = ?)")
|
|
578
584
|
.join("\n ");
|
|
579
|
-
const exclusionParams = exclusions.flatMap((ref) => [ref.
|
|
585
|
+
const exclusionParams = exclusions.flatMap((ref) => [ref.resourceManager, ref.key]);
|
|
580
586
|
return this.state.transaction(() => {
|
|
581
587
|
const row = this.state.connection
|
|
582
588
|
.prepare(
|
|
583
|
-
`${
|
|
589
|
+
`${resourceManagerSelect(`
|
|
584
590
|
JOIN controller_queue q ON q.controller_resource_id = c.controller_resource_id
|
|
585
591
|
JOIN leases l ON l.resource_id = c.resource_id
|
|
586
592
|
WHERE c.project_id = ?
|
|
@@ -591,8 +597,8 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
591
597
|
ORDER BY q.available_at, c.controller_name, c.resource_key
|
|
592
598
|
LIMIT 1`)}`,
|
|
593
599
|
)
|
|
594
|
-
.get(this.requireProjectId(), ...options.
|
|
595
|
-
if (!
|
|
600
|
+
.get(this.requireProjectId(), ...options.resourceManagers, ...exclusionParams, now, now);
|
|
601
|
+
if (!isResourceManagerRow(row)) return undefined;
|
|
596
602
|
const lease = this.requireLease(row.resourceId);
|
|
597
603
|
const token = randomBytes(32).toString("base64url");
|
|
598
604
|
const generation = lease.generation + 1;
|
|
@@ -630,23 +636,23 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
630
636
|
generation,
|
|
631
637
|
);
|
|
632
638
|
return {
|
|
633
|
-
|
|
639
|
+
resourceManager: row.resourceManagerName,
|
|
634
640
|
key: row.resourceKey,
|
|
635
641
|
ownerId: options.ownerId,
|
|
636
642
|
token,
|
|
637
643
|
generation,
|
|
638
|
-
queueVersion: this.queueVersion(row.
|
|
644
|
+
queueVersion: this.queueVersion(row.managedResourceId),
|
|
639
645
|
resourceVersion: row.resourceVersion + 1,
|
|
640
|
-
consecutiveErrors: this.queueErrors(row.
|
|
646
|
+
consecutiveErrors: this.queueErrors(row.managedResourceId),
|
|
641
647
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
642
648
|
};
|
|
643
649
|
});
|
|
644
650
|
}
|
|
645
651
|
|
|
646
|
-
renewClaim(claim:
|
|
652
|
+
renewClaim(claim: ResourceManagerQueueClaim, leaseMs: number, nowValue?: string): boolean {
|
|
647
653
|
const now = epoch(validTimestamp(nowValue));
|
|
648
654
|
const expiresAt = now + leaseMs;
|
|
649
|
-
const row = this.
|
|
655
|
+
const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
|
|
650
656
|
if (row === undefined) return false;
|
|
651
657
|
const result = this.state.connection
|
|
652
658
|
.prepare(
|
|
@@ -667,22 +673,22 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
667
673
|
return result.changes === 1;
|
|
668
674
|
}
|
|
669
675
|
|
|
670
|
-
settleClaim(claim:
|
|
671
|
-
return this.
|
|
676
|
+
settleClaim(claim: ResourceManagerQueueClaim, nowValue?: string): boolean {
|
|
677
|
+
return this.settleResourceManagerClaim(claim, undefined, nowValue);
|
|
672
678
|
}
|
|
673
679
|
|
|
674
680
|
requeueClaim(
|
|
675
|
-
claim:
|
|
681
|
+
claim: ResourceManagerQueueClaim,
|
|
676
682
|
options: QueueRequeueOptions,
|
|
677
683
|
nowValue?: string,
|
|
678
684
|
): boolean {
|
|
679
|
-
return this.
|
|
685
|
+
return this.settleResourceManagerClaim(claim, options, nowValue);
|
|
680
686
|
}
|
|
681
687
|
|
|
682
688
|
listQueue(): QueueItem[] {
|
|
683
689
|
const rows = this.state.connection
|
|
684
690
|
.prepare(
|
|
685
|
-
`SELECT c.controller_name AS
|
|
691
|
+
`SELECT c.controller_name AS resourceManager, c.resource_key AS resourceKey,
|
|
686
692
|
q.available_at AS availableAt, q.consecutive_errors AS consecutiveErrors,
|
|
687
693
|
l.expires_at AS claimExpiresAt
|
|
688
694
|
FROM controller_queue q
|
|
@@ -692,7 +698,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
692
698
|
)
|
|
693
699
|
.all(this.requireProjectId());
|
|
694
700
|
return rows.filter(isQueueListRow).map((row) => ({
|
|
695
|
-
|
|
701
|
+
resourceManager: row.resourceManager,
|
|
696
702
|
key: row.resourceKey,
|
|
697
703
|
availableAt: new Date(row.availableAt).toISOString(),
|
|
698
704
|
consecutiveErrors: row.consecutiveErrors,
|
|
@@ -705,7 +711,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
705
711
|
reserveEffect(options: {
|
|
706
712
|
key: string;
|
|
707
713
|
resourceUid: string;
|
|
708
|
-
claim:
|
|
714
|
+
claim: ResourceManagerQueueClaim;
|
|
709
715
|
generation: number;
|
|
710
716
|
kind: string;
|
|
711
717
|
requestFingerprint: string;
|
|
@@ -713,8 +719,8 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
713
719
|
}): EffectReservation {
|
|
714
720
|
const now = epoch(validTimestamp(options.now));
|
|
715
721
|
return this.state.transaction(() => {
|
|
716
|
-
const source = this.
|
|
717
|
-
this.
|
|
722
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
723
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
718
724
|
const effectId = effectIdFor(source.resourceId, options.key);
|
|
719
725
|
const existing = this.effectRow(options.resourceUid, options.key);
|
|
720
726
|
if (existing !== undefined) {
|
|
@@ -791,7 +797,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
791
797
|
updateEffect(options: {
|
|
792
798
|
resourceUid: string;
|
|
793
799
|
key: string;
|
|
794
|
-
claim:
|
|
800
|
+
claim: ResourceManagerQueueClaim;
|
|
795
801
|
state: EffectRecord["state"];
|
|
796
802
|
externalRef?: string;
|
|
797
803
|
error?: string;
|
|
@@ -799,8 +805,8 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
799
805
|
}): EffectRecord {
|
|
800
806
|
const now = epoch(validTimestamp(options.now));
|
|
801
807
|
return this.state.transaction(() => {
|
|
802
|
-
const source = this.
|
|
803
|
-
this.
|
|
808
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
809
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
804
810
|
const row = this.requireEffectRow(options.resourceUid, options.key);
|
|
805
811
|
const effectResourceId = resourceIdFor("effect", row.effectId);
|
|
806
812
|
const revision = this.resourceRevision(effectResourceId);
|
|
@@ -843,15 +849,15 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
843
849
|
|
|
844
850
|
reserveWorkflow(options: {
|
|
845
851
|
resourceUid: string;
|
|
846
|
-
claim:
|
|
852
|
+
claim: ResourceManagerQueueClaim;
|
|
847
853
|
requestKey: string;
|
|
848
854
|
workflow: string;
|
|
849
855
|
inputFingerprint: string;
|
|
850
856
|
}): WorkflowReservation {
|
|
851
857
|
const now = Date.now();
|
|
852
858
|
return this.state.transaction(() => {
|
|
853
|
-
const source = this.
|
|
854
|
-
this.
|
|
859
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
860
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
855
861
|
const existing = this.workflowRow(options.resourceUid, options.requestKey);
|
|
856
862
|
if (existing !== undefined) {
|
|
857
863
|
if (
|
|
@@ -872,7 +878,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
872
878
|
)
|
|
873
879
|
.run(
|
|
874
880
|
requestId,
|
|
875
|
-
source.
|
|
881
|
+
source.managedResourceId,
|
|
876
882
|
options.requestKey,
|
|
877
883
|
options.workflow,
|
|
878
884
|
Buffer.from(options.inputFingerprint, "hex"),
|
|
@@ -896,13 +902,13 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
896
902
|
updateWorkflow(
|
|
897
903
|
requestId: string,
|
|
898
904
|
update: WorkflowRecordUpdate,
|
|
899
|
-
claim:
|
|
905
|
+
claim: ResourceManagerQueueClaim,
|
|
900
906
|
): ChildWorkflowRecord {
|
|
901
907
|
const now = Date.now();
|
|
902
908
|
return this.state.transaction(() => {
|
|
903
909
|
const row = this.requireWorkflowRow(requestId);
|
|
904
|
-
const source = this.
|
|
905
|
-
this.
|
|
910
|
+
const source = this.requireResourceManagerRowByUid(row.resourceUid);
|
|
911
|
+
this.assertResourceManagerClaim(source, claim, now);
|
|
906
912
|
const errorHash =
|
|
907
913
|
update.error === undefined || update.error === null
|
|
908
914
|
? null
|
|
@@ -942,7 +948,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
942
948
|
if (update.runId !== undefined && row.runId !== null && update.runId !== row.runId) {
|
|
943
949
|
throw new Error("Scheduler completion run ID does not match the reserved child run");
|
|
944
950
|
}
|
|
945
|
-
const source = this.
|
|
951
|
+
const source = this.requireResourceManagerRowByUid(row.resourceUid);
|
|
946
952
|
const now = Date.now();
|
|
947
953
|
return this.state.transaction(() => {
|
|
948
954
|
const errorHash =
|
|
@@ -993,17 +999,20 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
993
999
|
}
|
|
994
1000
|
|
|
995
1001
|
recordEvent(options: {
|
|
996
|
-
|
|
1002
|
+
resourceManager: string;
|
|
997
1003
|
key: string;
|
|
998
|
-
claim?:
|
|
1004
|
+
claim?: ResourceManagerQueueClaim;
|
|
999
1005
|
type: string;
|
|
1000
1006
|
payload?: JsonObject;
|
|
1001
1007
|
now?: string;
|
|
1002
|
-
}):
|
|
1008
|
+
}): ResourceManagerEvent {
|
|
1003
1009
|
const now = epoch(validTimestamp(options.now));
|
|
1004
1010
|
return this.state.transaction(() => {
|
|
1005
|
-
const row = this.
|
|
1006
|
-
|
|
1011
|
+
const row = this.requireResourceManagerRow({
|
|
1012
|
+
resourceManager: options.resourceManager,
|
|
1013
|
+
key: options.key,
|
|
1014
|
+
});
|
|
1015
|
+
if (options.claim !== undefined) this.assertResourceManagerClaim(row, options.claim, now);
|
|
1007
1016
|
const revision = this.resourceRevision(row.resourceId) + 1;
|
|
1008
1017
|
this.bumpResource(row.resourceId, revision - 1, now);
|
|
1009
1018
|
const eventId = this.insertEvent(
|
|
@@ -1021,11 +1030,11 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1021
1030
|
.prepare("SELECT event_seq AS seq FROM events WHERE event_id = ?")
|
|
1022
1031
|
.get(eventId);
|
|
1023
1032
|
/* istanbul ignore if -- exact schema and internal query shape */
|
|
1024
|
-
if (!isSequenceRow(event)) throw new Error("
|
|
1033
|
+
if (!isSequenceRow(event)) throw new Error("ResourceManager event was not recorded");
|
|
1025
1034
|
return {
|
|
1026
1035
|
seq: event.seq,
|
|
1027
1036
|
recordedAt: new Date(now).toISOString(),
|
|
1028
|
-
|
|
1037
|
+
resourceManager: options.resourceManager,
|
|
1029
1038
|
key: options.key,
|
|
1030
1039
|
type: options.type,
|
|
1031
1040
|
payload: options.payload ?? {},
|
|
@@ -1034,17 +1043,17 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1034
1043
|
}
|
|
1035
1044
|
|
|
1036
1045
|
listEvents(
|
|
1037
|
-
options: {
|
|
1038
|
-
):
|
|
1046
|
+
options: { resourceManager?: string; key?: string; limit?: number } = {},
|
|
1047
|
+
): ResourceManagerEvent[] {
|
|
1039
1048
|
const clauses = ["r.resource_type = 'controller'"];
|
|
1040
1049
|
const params: unknown[] = [];
|
|
1041
1050
|
if (this.projectId !== null) {
|
|
1042
1051
|
clauses.push("c.project_id = ?");
|
|
1043
1052
|
params.push(this.projectId);
|
|
1044
1053
|
}
|
|
1045
|
-
if (options.
|
|
1054
|
+
if (options.resourceManager !== undefined) {
|
|
1046
1055
|
clauses.push("c.controller_name = ?");
|
|
1047
|
-
params.push(options.
|
|
1056
|
+
params.push(options.resourceManager);
|
|
1048
1057
|
}
|
|
1049
1058
|
if (options.key !== undefined) {
|
|
1050
1059
|
clauses.push("c.resource_key = ?");
|
|
@@ -1054,7 +1063,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1054
1063
|
const rows = this.state.connection
|
|
1055
1064
|
.prepare(
|
|
1056
1065
|
`SELECT e.event_seq AS seq, e.recorded_at AS recordedAt,
|
|
1057
|
-
c.controller_name AS
|
|
1066
|
+
c.controller_name AS resourceManager, c.resource_key AS resourceKey,
|
|
1058
1067
|
e.event_type AS eventType, e.payload_hash AS payloadHash
|
|
1059
1068
|
FROM events e
|
|
1060
1069
|
JOIN resources r ON r.resource_id = e.resource_id
|
|
@@ -1063,10 +1072,10 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1063
1072
|
ORDER BY e.event_seq DESC LIMIT ?`,
|
|
1064
1073
|
)
|
|
1065
1074
|
.all(...params);
|
|
1066
|
-
return rows.filter(
|
|
1075
|
+
return rows.filter(isResourceManagerEventRow).map((row) => ({
|
|
1067
1076
|
seq: row.seq,
|
|
1068
1077
|
recordedAt: new Date(row.recordedAt).toISOString(),
|
|
1069
|
-
|
|
1078
|
+
resourceManager: row.resourceManager,
|
|
1070
1079
|
key: row.resourceKey,
|
|
1071
1080
|
type: row.eventType,
|
|
1072
1081
|
payload: row.payloadHash === null ? {} : (this.state.readJson(row.payloadHash) as JsonObject),
|
|
@@ -1403,7 +1412,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1403
1412
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now);
|
|
1404
1413
|
}
|
|
1405
1414
|
|
|
1406
|
-
/** Schedule a
|
|
1415
|
+
/** Schedule a runner that validates one pending interactive submission. */
|
|
1407
1416
|
claimWorkflowRunForInteractionValidation(options: {
|
|
1408
1417
|
runId: string;
|
|
1409
1418
|
runnerId: string;
|
|
@@ -1424,7 +1433,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1424
1433
|
);
|
|
1425
1434
|
}
|
|
1426
1435
|
|
|
1427
|
-
/** Take a short
|
|
1436
|
+
/** Take a short server claim without scheduling a parked interactive run. */
|
|
1428
1437
|
claimWorkflowRunForControl(options: {
|
|
1429
1438
|
runId: string;
|
|
1430
1439
|
runnerId: string;
|
|
@@ -1556,7 +1565,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1556
1565
|
"(q.affinity_runner_id IS NULL OR q.affinity_runner_id = ? OR q.status IN ('parked', 'starting', 'running'))",
|
|
1557
1566
|
"NOT (q.status = 'parked' AND (r.status = 'waiting' OR r.paused = 1))",
|
|
1558
1567
|
"NOT EXISTS (SELECT 1 FROM interactive_requests i WHERE i.run_id = r.run_id AND i.status = 'pending')",
|
|
1559
|
-
"(q.error_code IS NULL OR q.error_code
|
|
1568
|
+
"(q.error_code IS NULL OR q.error_code NOT IN ('workflowSourceChanged', 'runnerNoProgress'))",
|
|
1560
1569
|
];
|
|
1561
1570
|
const params: unknown[] = [now, now, options.runnerId, options.runnerId];
|
|
1562
1571
|
if (this.projectId !== null) {
|
|
@@ -1669,6 +1678,78 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
1669
1678
|
return this.releaseRunClaim(options.runId, options.claimToken, "parked", options.now);
|
|
1670
1679
|
}
|
|
1671
1680
|
|
|
1681
|
+
parkWorkflowRunForRunnerNoProgress(options: {
|
|
1682
|
+
runId: string;
|
|
1683
|
+
claimToken: string;
|
|
1684
|
+
detail: string;
|
|
1685
|
+
now?: string;
|
|
1686
|
+
}): boolean {
|
|
1687
|
+
const now = epoch(validTimestamp(options.now));
|
|
1688
|
+
return this.state.transaction(() => {
|
|
1689
|
+
if (
|
|
1690
|
+
!this.verifyWorkflowRunClaim({
|
|
1691
|
+
runId: options.runId,
|
|
1692
|
+
claimToken: options.claimToken,
|
|
1693
|
+
now: new Date(now).toISOString(),
|
|
1694
|
+
})
|
|
1695
|
+
) {
|
|
1696
|
+
return false;
|
|
1697
|
+
}
|
|
1698
|
+
const row = this.requireWorkflowRunRow(options.runId);
|
|
1699
|
+
const lease = this.requireLease(row.resourceId);
|
|
1700
|
+
const detail = options.detail.slice(0, 8_192);
|
|
1701
|
+
const errorHash = this.state.putText(detail, now);
|
|
1702
|
+
const run = this.state.connection
|
|
1703
|
+
.prepare(
|
|
1704
|
+
`UPDATE runs SET status_detail = ?, updated_at = ?, finished_at = NULL
|
|
1705
|
+
WHERE run_id = ? AND status NOT IN ('completed', 'failed', 'timed_out', 'cancelled')`,
|
|
1706
|
+
)
|
|
1707
|
+
.run(detail, now, options.runId);
|
|
1708
|
+
const queue = this.state.connection
|
|
1709
|
+
.prepare(
|
|
1710
|
+
`UPDATE run_queue
|
|
1711
|
+
SET status = 'parked', error_code = 'runnerNoProgress', error_hash = ?,
|
|
1712
|
+
available_at = ?, updated_at = ?, finished_at = NULL
|
|
1713
|
+
WHERE run_id = ? AND status IN ('queued', 'starting', 'running', 'parked')`,
|
|
1714
|
+
)
|
|
1715
|
+
.run(errorHash, now, now, options.runId);
|
|
1716
|
+
if (run.changes !== 1 || queue.changes !== 1) {
|
|
1717
|
+
throw new Error(`Workflow run ${options.runId} changed during runner recovery`);
|
|
1718
|
+
}
|
|
1719
|
+
const released = this.state.connection
|
|
1720
|
+
.prepare(
|
|
1721
|
+
`UPDATE leases
|
|
1722
|
+
SET owner_type = NULL, owner_id = NULL, token_hash = NULL,
|
|
1723
|
+
acquired_at = NULL, heartbeat_at = NULL, expires_at = NULL
|
|
1724
|
+
WHERE resource_id = ? AND token_hash = ? AND generation = ? AND expires_at > ?`,
|
|
1725
|
+
)
|
|
1726
|
+
.run(row.resourceId, tokenHash(options.claimToken), lease.generation, now);
|
|
1727
|
+
/* istanbul ignore if -- the exact live claim is stable in this transaction */
|
|
1728
|
+
if (released.changes !== 1) {
|
|
1729
|
+
throw new Error(`Workflow run ${options.runId} claim changed during runner recovery`);
|
|
1730
|
+
}
|
|
1731
|
+
const revision = this.resourceRevision(row.resourceId);
|
|
1732
|
+
this.bumpResource(row.resourceId, revision, now);
|
|
1733
|
+
this.insertEvent(
|
|
1734
|
+
row.resourceId,
|
|
1735
|
+
revision + 1,
|
|
1736
|
+
"run.worker_no_progress",
|
|
1737
|
+
lease.ownerType ?? "system",
|
|
1738
|
+
lease.ownerId,
|
|
1739
|
+
{ status: "parked", code: "runnerNoProgress" },
|
|
1740
|
+
now,
|
|
1741
|
+
lease.generation || undefined,
|
|
1742
|
+
);
|
|
1743
|
+
recordViewerDeltas(
|
|
1744
|
+
this.state,
|
|
1745
|
+
options.runId,
|
|
1746
|
+
[{ targetType: "summary" }, { targetType: "replay" }],
|
|
1747
|
+
now,
|
|
1748
|
+
);
|
|
1749
|
+
return true;
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1672
1753
|
pauseParkedWorkflowRun(options: { runId: string; now?: string }): boolean {
|
|
1673
1754
|
const now = epoch(validTimestamp(options.now));
|
|
1674
1755
|
return this.state.transaction(() => {
|
|
@@ -2015,6 +2096,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2015
2096
|
if (run.changes !== 1) {
|
|
2016
2097
|
throw new Error(`Workflow run ${options.runId} has inconsistent durable state`);
|
|
2017
2098
|
}
|
|
2099
|
+
this.settleWorkflowRunMessages(options.runId, now);
|
|
2018
2100
|
this.cancelWorkflowRunDependents(options.runId, controlId, errorHash, now);
|
|
2019
2101
|
|
|
2020
2102
|
const released = this.state.connection
|
|
@@ -2216,84 +2298,87 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2216
2298
|
|
|
2217
2299
|
private requireProjectId(): string {
|
|
2218
2300
|
if (this.projectId === null)
|
|
2219
|
-
throw new Error("
|
|
2301
|
+
throw new Error("ResourceManager project is not registered in the state database");
|
|
2220
2302
|
return this.projectId;
|
|
2221
2303
|
}
|
|
2222
2304
|
|
|
2223
|
-
private
|
|
2305
|
+
private resourceManagerRow(ref: ManagedResourceRef): ResourceManagerRow | undefined {
|
|
2224
2306
|
if (this.projectId === null) return undefined;
|
|
2225
2307
|
const row = this.state.connection
|
|
2226
2308
|
.prepare(
|
|
2227
|
-
|
|
2309
|
+
resourceManagerSelect(
|
|
2310
|
+
"WHERE c.project_id = ? AND c.controller_name = ? AND c.resource_key = ?",
|
|
2311
|
+
),
|
|
2228
2312
|
)
|
|
2229
|
-
.get(this.projectId, ref.
|
|
2230
|
-
return
|
|
2313
|
+
.get(this.projectId, ref.resourceManager, ref.key);
|
|
2314
|
+
return isResourceManagerRow(row) ? row : undefined;
|
|
2231
2315
|
}
|
|
2232
2316
|
|
|
2233
|
-
private
|
|
2234
|
-
const row = this.
|
|
2235
|
-
if (row === undefined) throw new
|
|
2317
|
+
private requireResourceManagerRow(ref: ManagedResourceRef): ResourceManagerRow {
|
|
2318
|
+
const row = this.resourceManagerRow(ref);
|
|
2319
|
+
if (row === undefined) throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
|
|
2236
2320
|
return row;
|
|
2237
2321
|
}
|
|
2238
2322
|
|
|
2239
|
-
private
|
|
2240
|
-
const row = this.state.connection.prepare(
|
|
2323
|
+
private requireResourceManagerRowByUid(uid: string): ResourceManagerRow {
|
|
2324
|
+
const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
|
|
2241
2325
|
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
2242
|
-
if (!
|
|
2326
|
+
if (!isResourceManagerRow(row)) throw new Error(`Managed resource not found: ${uid}`);
|
|
2243
2327
|
return row;
|
|
2244
2328
|
}
|
|
2245
2329
|
|
|
2246
|
-
private requireResource(ref:
|
|
2330
|
+
private requireResource(ref: ManagedResourceRef): ManagedResource {
|
|
2247
2331
|
const resource = this.getResource(ref);
|
|
2248
|
-
if (resource === undefined)
|
|
2332
|
+
if (resource === undefined)
|
|
2333
|
+
throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
|
|
2249
2334
|
return resource;
|
|
2250
2335
|
}
|
|
2251
2336
|
|
|
2252
2337
|
/* istanbul ignore next -- pure projection covered by integration tests */
|
|
2253
|
-
private
|
|
2338
|
+
private mapManagedResource(row: ResourceManagerRow): ManagedResource {
|
|
2254
2339
|
const spec = this.state.readJson(row.specHash);
|
|
2255
|
-
const status = this.state.readJson(row.statusHash) as
|
|
2340
|
+
const status = this.state.readJson(row.statusHash) as ManagedResourceStatus<unknown>;
|
|
2256
2341
|
return {
|
|
2257
2342
|
metadata: {
|
|
2258
2343
|
uid: row.uid,
|
|
2259
|
-
|
|
2344
|
+
resourceManager: row.resourceManagerName,
|
|
2260
2345
|
key: row.resourceKey,
|
|
2261
2346
|
resourceVersion: row.resourceVersion,
|
|
2262
2347
|
generation: row.generation,
|
|
2263
2348
|
...(row.deletionRequestedAt === null
|
|
2264
2349
|
? {}
|
|
2265
2350
|
: { deletionTimestamp: new Date(row.deletionRequestedAt).toISOString() }),
|
|
2266
|
-
finalizers: this.finalizers(row.
|
|
2351
|
+
finalizers: this.finalizers(row.managedResourceId),
|
|
2267
2352
|
},
|
|
2268
2353
|
spec,
|
|
2269
2354
|
status,
|
|
2270
2355
|
};
|
|
2271
2356
|
}
|
|
2272
2357
|
|
|
2273
|
-
private finalizers(
|
|
2358
|
+
private finalizers(managedResourceId: string): string[] {
|
|
2274
2359
|
const rows = this.state.connection
|
|
2275
2360
|
.prepare(
|
|
2276
2361
|
"SELECT finalizer FROM controller_finalizers WHERE controller_resource_id = ? ORDER BY position",
|
|
2277
2362
|
)
|
|
2278
|
-
.all(
|
|
2363
|
+
.all(managedResourceId);
|
|
2279
2364
|
return rows.flatMap((row) => (isFinalizerRow(row) ? [row.finalizer] : []));
|
|
2280
2365
|
}
|
|
2281
2366
|
|
|
2282
|
-
private replaceFinalizers(
|
|
2367
|
+
private replaceFinalizers(managedResourceId: string, finalizers: string[]): void {
|
|
2283
2368
|
const unique = [...new Set(finalizers)];
|
|
2284
2369
|
if (unique.length !== finalizers.length)
|
|
2285
|
-
throw new Error("
|
|
2370
|
+
throw new Error("ResourceManager finalizers must be unique");
|
|
2286
2371
|
this.state.connection
|
|
2287
2372
|
.prepare("DELETE FROM controller_finalizers WHERE controller_resource_id = ?")
|
|
2288
|
-
.run(
|
|
2373
|
+
.run(managedResourceId);
|
|
2289
2374
|
const insert = this.state.connection.prepare(
|
|
2290
2375
|
"INSERT INTO controller_finalizers(controller_resource_id, finalizer, position) VALUES (?, ?, ?)",
|
|
2291
2376
|
);
|
|
2292
|
-
unique.forEach((finalizer, index) => insert.run(
|
|
2377
|
+
unique.forEach((finalizer, index) => insert.run(managedResourceId, finalizer, index));
|
|
2293
2378
|
}
|
|
2294
2379
|
|
|
2295
|
-
private
|
|
2296
|
-
|
|
2380
|
+
private enqueueResourceManagerRow(
|
|
2381
|
+
managedResourceId: string,
|
|
2297
2382
|
availableAt: number,
|
|
2298
2383
|
now: number,
|
|
2299
2384
|
): void {
|
|
@@ -2308,50 +2393,53 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2308
2393
|
queue_version = controller_queue.queue_version + 1,
|
|
2309
2394
|
updated_at = excluded.updated_at`,
|
|
2310
2395
|
)
|
|
2311
|
-
.run(
|
|
2396
|
+
.run(managedResourceId, availableAt, now, now);
|
|
2312
2397
|
}
|
|
2313
2398
|
|
|
2314
|
-
private queueErrors(
|
|
2399
|
+
private queueErrors(managedResourceId: string): number {
|
|
2315
2400
|
const row = this.state.connection
|
|
2316
2401
|
.prepare(
|
|
2317
2402
|
"SELECT consecutive_errors AS consecutiveErrors FROM controller_queue WHERE controller_resource_id = ?",
|
|
2318
2403
|
)
|
|
2319
|
-
.get(
|
|
2404
|
+
.get(managedResourceId);
|
|
2320
2405
|
return isErrorCountRow(row) ? row.consecutiveErrors : 0;
|
|
2321
2406
|
}
|
|
2322
2407
|
|
|
2323
|
-
private queueVersion(
|
|
2408
|
+
private queueVersion(managedResourceId: string): number {
|
|
2324
2409
|
const row = this.state.connection
|
|
2325
2410
|
.prepare(
|
|
2326
2411
|
"SELECT queue_version AS queueVersion FROM controller_queue WHERE controller_resource_id = ?",
|
|
2327
2412
|
)
|
|
2328
|
-
.get(
|
|
2413
|
+
.get(managedResourceId);
|
|
2329
2414
|
/* istanbul ignore if -- exact schema and internal query shape */
|
|
2330
2415
|
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
2331
|
-
if (!isQueueVersionRow(row)) throw new Error("
|
|
2416
|
+
if (!isQueueVersionRow(row)) throw new Error("ResourceManager queue item is missing");
|
|
2332
2417
|
return row.queueVersion;
|
|
2333
2418
|
}
|
|
2334
2419
|
|
|
2335
|
-
private
|
|
2336
|
-
claim:
|
|
2420
|
+
private settleResourceManagerClaim(
|
|
2421
|
+
claim: ResourceManagerQueueClaim,
|
|
2337
2422
|
requeue: QueueRequeueOptions | undefined,
|
|
2338
2423
|
nowValue?: string,
|
|
2339
2424
|
): boolean {
|
|
2340
2425
|
const now = epoch(validTimestamp(nowValue));
|
|
2341
2426
|
return this.state.transaction(() => {
|
|
2342
|
-
const row = this.
|
|
2427
|
+
const row = this.resourceManagerRow({
|
|
2428
|
+
resourceManager: claim.resourceManager,
|
|
2429
|
+
key: claim.key,
|
|
2430
|
+
});
|
|
2343
2431
|
if (row === undefined) return false;
|
|
2344
2432
|
try {
|
|
2345
|
-
this.
|
|
2433
|
+
this.assertResourceManagerClaim(row, claim, now, false);
|
|
2346
2434
|
} catch {
|
|
2347
2435
|
return false;
|
|
2348
2436
|
}
|
|
2349
|
-
const currentQueueVersion = this.queueVersion(row.
|
|
2437
|
+
const currentQueueVersion = this.queueVersion(row.managedResourceId);
|
|
2350
2438
|
if (requeue === undefined) {
|
|
2351
2439
|
if (currentQueueVersion === claim.queueVersion) {
|
|
2352
2440
|
this.state.connection
|
|
2353
2441
|
.prepare("DELETE FROM controller_queue WHERE controller_resource_id = ?")
|
|
2354
|
-
.run(row.
|
|
2442
|
+
.run(row.managedResourceId);
|
|
2355
2443
|
}
|
|
2356
2444
|
} else {
|
|
2357
2445
|
const errorHash =
|
|
@@ -2363,21 +2451,21 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2363
2451
|
consecutive_errors = consecutive_errors + 1,
|
|
2364
2452
|
last_error_hash = ?, updated_at = ? WHERE controller_resource_id = ?`,
|
|
2365
2453
|
)
|
|
2366
|
-
.run(epoch(requeue.availableAt), errorHash, now, row.
|
|
2454
|
+
.run(epoch(requeue.availableAt), errorHash, now, row.managedResourceId);
|
|
2367
2455
|
}
|
|
2368
2456
|
this.releaseLease(row.resourceId, claim, now);
|
|
2369
2457
|
return true;
|
|
2370
2458
|
});
|
|
2371
2459
|
}
|
|
2372
2460
|
|
|
2373
|
-
private
|
|
2374
|
-
row:
|
|
2375
|
-
claim:
|
|
2461
|
+
private assertResourceManagerClaim(
|
|
2462
|
+
row: ResourceManagerRow,
|
|
2463
|
+
claim: ResourceManagerQueueClaim,
|
|
2376
2464
|
now: number,
|
|
2377
2465
|
requireUnexpired = true,
|
|
2378
2466
|
): void {
|
|
2379
|
-
if (row.
|
|
2380
|
-
throw new Error("
|
|
2467
|
+
if (row.resourceManagerName !== claim.resourceManager || row.resourceKey !== claim.key)
|
|
2468
|
+
throw new Error("ResourceManager claim targets another resource");
|
|
2381
2469
|
const lease = this.requireLease(row.resourceId);
|
|
2382
2470
|
if (
|
|
2383
2471
|
lease.ownerType !== "controller" ||
|
|
@@ -2387,7 +2475,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2387
2475
|
!lease.tokenHash.equals(tokenHash(claim.token)) ||
|
|
2388
2476
|
(requireUnexpired && (lease.expiresAt === null || lease.expiresAt <= now))
|
|
2389
2477
|
) {
|
|
2390
|
-
throw new Error("
|
|
2478
|
+
throw new Error("ResourceManager claim is stale");
|
|
2391
2479
|
}
|
|
2392
2480
|
}
|
|
2393
2481
|
|
|
@@ -2405,8 +2493,8 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2405
2493
|
return row;
|
|
2406
2494
|
}
|
|
2407
2495
|
|
|
2408
|
-
private releaseLease(resourceId: string, claim:
|
|
2409
|
-
const row = this.
|
|
2496
|
+
private releaseLease(resourceId: string, claim: ResourceManagerQueueClaim, now: number): void {
|
|
2497
|
+
const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
|
|
2410
2498
|
if (row === undefined) return;
|
|
2411
2499
|
const revision = this.resourceRevision(resourceId);
|
|
2412
2500
|
const result = this.state.connection
|
|
@@ -2524,7 +2612,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2524
2612
|
resourceUid: row.resourceUid,
|
|
2525
2613
|
generation: row.generation,
|
|
2526
2614
|
kind: row.kind,
|
|
2527
|
-
state:
|
|
2615
|
+
state: resourceManagerEffectState(row.state),
|
|
2528
2616
|
requestFingerprint: row.requestFingerprint,
|
|
2529
2617
|
startedAt: new Date(row.startedAt).toISOString(),
|
|
2530
2618
|
...(row.completedAt === null ? {} : { completedAt: new Date(row.completedAt).toISOString() }),
|
|
@@ -2924,6 +3012,7 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2924
3012
|
WHERE run_id = ?`,
|
|
2925
3013
|
)
|
|
2926
3014
|
.run(status, errorHash, now, now, runId);
|
|
3015
|
+
this.settleWorkflowRunMessages(runId, now);
|
|
2927
3016
|
if (status === "cancelled") {
|
|
2928
3017
|
this.cancelWorkflowRunDependents(
|
|
2929
3018
|
runId,
|
|
@@ -2953,6 +3042,11 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
2953
3042
|
});
|
|
2954
3043
|
}
|
|
2955
3044
|
|
|
3045
|
+
private settleWorkflowRunMessages(runId: string, now: number): void {
|
|
3046
|
+
this.workflowMessages.settleOpenTurnsForRun(runId, "lost", now);
|
|
3047
|
+
this.workflowMessages.cancelPendingForRun(runId, now);
|
|
3048
|
+
}
|
|
3049
|
+
|
|
2956
3050
|
private cancelWorkflowRunDependents(
|
|
2957
3051
|
runId: string,
|
|
2958
3052
|
actorId: string,
|
|
@@ -3047,14 +3141,14 @@ export class SqliteControllerStore implements ControllerStore {
|
|
|
3047
3141
|
}
|
|
3048
3142
|
|
|
3049
3143
|
private assertWritable(): void {
|
|
3050
|
-
if (this.state.mode === "read-only") throw new Error("
|
|
3144
|
+
if (this.state.mode === "read-only") throw new Error("ResourceManager store is read-only");
|
|
3051
3145
|
}
|
|
3052
3146
|
}
|
|
3053
3147
|
|
|
3054
|
-
function
|
|
3055
|
-
return `SELECT c.controller_resource_id AS
|
|
3148
|
+
function resourceManagerSelect(clause: string): string {
|
|
3149
|
+
return `SELECT c.controller_resource_id AS managedResourceId,
|
|
3056
3150
|
c.resource_id AS resourceId, r.revision AS resourceVersion,
|
|
3057
|
-
c.controller_name AS
|
|
3151
|
+
c.controller_name AS resourceManagerName, c.resource_key AS resourceKey,
|
|
3058
3152
|
c.uid, c.generation, c.spec_hash AS specHash, c.status_hash AS statusHash,
|
|
3059
3153
|
c.deletion_requested_at AS deletionRequestedAt
|
|
3060
3154
|
FROM controller_resources c JOIN resources r ON r.resource_id = c.resource_id ${clause}`;
|
|
@@ -3123,16 +3217,16 @@ function workflowRunViewSelect(clause: string): string {
|
|
|
3123
3217
|
}
|
|
3124
3218
|
|
|
3125
3219
|
type QueueListRow = {
|
|
3126
|
-
|
|
3220
|
+
resourceManager: string;
|
|
3127
3221
|
resourceKey: string;
|
|
3128
3222
|
availableAt: number;
|
|
3129
3223
|
consecutiveErrors: number;
|
|
3130
3224
|
claimExpiresAt: number | null;
|
|
3131
3225
|
};
|
|
3132
|
-
type
|
|
3226
|
+
type ResourceManagerEventRow = {
|
|
3133
3227
|
seq: number;
|
|
3134
3228
|
recordedAt: number;
|
|
3135
|
-
|
|
3229
|
+
resourceManager: string;
|
|
3136
3230
|
resourceKey: string;
|
|
3137
3231
|
eventType: string;
|
|
3138
3232
|
payloadHash: Buffer | null;
|
|
@@ -3149,7 +3243,7 @@ type RunEventRow = {
|
|
|
3149
3243
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
3150
3244
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3151
3245
|
}
|
|
3152
|
-
function
|
|
3246
|
+
function isResourceManagerRow(value: unknown): value is ResourceManagerRow {
|
|
3153
3247
|
return isRecord(value);
|
|
3154
3248
|
}
|
|
3155
3249
|
function isLeaseRow(value: unknown): value is LeaseRow {
|
|
@@ -3256,7 +3350,7 @@ function isWorkflowRow(value: unknown): value is WorkflowRow {
|
|
|
3256
3350
|
function isQueueListRow(value: unknown): value is QueueListRow {
|
|
3257
3351
|
return isRecord(value);
|
|
3258
3352
|
}
|
|
3259
|
-
function
|
|
3353
|
+
function isResourceManagerEventRow(value: unknown): value is ResourceManagerEventRow {
|
|
3260
3354
|
return isRecord(value);
|
|
3261
3355
|
}
|
|
3262
3356
|
function isRunEventRow(value: unknown): value is RunEventRow {
|
|
@@ -3436,7 +3530,7 @@ function rowToSource(row: RunSourceIdentityRow): QueuedSource["root"] {
|
|
|
3436
3530
|
function effectStatus(value: EffectRecord["state"]): string {
|
|
3437
3531
|
return value === "indeterminate" ? "ambiguous" : value;
|
|
3438
3532
|
}
|
|
3439
|
-
function
|
|
3533
|
+
function resourceManagerEffectState(value: string): EffectRecord["state"] {
|
|
3440
3534
|
return value === "ambiguous" ? "indeterminate" : (value as EffectRecord["state"]);
|
|
3441
3535
|
}
|
|
3442
3536
|
function validTimestamp(value?: string): string {
|