@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,10 +5,12 @@ 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 {
|
|
9
|
-
|
|
8
|
+
import { WorkflowMessageStore } from "../state/workflow-messages.js";
|
|
9
|
+
import { EffectRequestConflictError, ManagedResourceConflictError, ManagedResourceNotFoundError, WorkflowRequestConflictError, } from "./errors.js";
|
|
10
|
+
export class SqliteResourceManagerStore {
|
|
10
11
|
filePath;
|
|
11
12
|
state;
|
|
13
|
+
workflowMessages;
|
|
12
14
|
ownsState;
|
|
13
15
|
projectId;
|
|
14
16
|
closed = false;
|
|
@@ -22,6 +24,7 @@ export class SqliteControllerStore {
|
|
|
22
24
|
checkLegacyState: filePath === workflowStatePath(),
|
|
23
25
|
});
|
|
24
26
|
this.filePath = this.state.filePath;
|
|
27
|
+
this.workflowMessages = new WorkflowMessageStore(this.state);
|
|
25
28
|
if (options.global === true) {
|
|
26
29
|
this.projectId = null;
|
|
27
30
|
}
|
|
@@ -40,27 +43,30 @@ export class SqliteControllerStore {
|
|
|
40
43
|
}
|
|
41
44
|
putResource(options) {
|
|
42
45
|
this.assertWritable();
|
|
43
|
-
validateName(options.
|
|
46
|
+
validateName(options.resourceManager, "resource manager");
|
|
44
47
|
validateKey(options.key, "resource key");
|
|
45
48
|
const now = epoch(validTimestamp(options.now));
|
|
46
49
|
const specHash = this.state.putJson(options.spec, now);
|
|
47
50
|
const initialStatus = {
|
|
48
51
|
observedGeneration: 0,
|
|
49
52
|
conditions: [],
|
|
50
|
-
|
|
53
|
+
resourceManagerStatus: options.initialStatus,
|
|
51
54
|
};
|
|
52
55
|
const statusHash = this.state.putJson(initialStatus, now);
|
|
53
56
|
return this.state.transaction(() => {
|
|
54
|
-
const existing = this.
|
|
57
|
+
const existing = this.resourceManagerRow({
|
|
58
|
+
resourceManager: options.resourceManager,
|
|
59
|
+
key: options.key,
|
|
60
|
+
});
|
|
55
61
|
if (existing === undefined) {
|
|
56
62
|
const uid = randomUUID();
|
|
57
|
-
const
|
|
58
|
-
const resourceId = resourceIdFor("controller", `${this.requireProjectId()}:${options.
|
|
63
|
+
const managedResourceId = `controller-resource-${randomUUID()}`;
|
|
64
|
+
const resourceId = resourceIdFor("controller", `${this.requireProjectId()}:${options.resourceManager}:${options.key}`);
|
|
59
65
|
this.state.connection
|
|
60
66
|
.prepare(`INSERT INTO resources(
|
|
61
67
|
resource_id, resource_type, aggregate_key, revision, created_at, updated_at
|
|
62
68
|
) VALUES (?, 'controller', ?, 1, ?, ?)`)
|
|
63
|
-
.run(resourceId,
|
|
69
|
+
.run(resourceId, managedResourceId, now, now);
|
|
64
70
|
this.state.connection
|
|
65
71
|
.prepare("INSERT INTO leases(resource_id, generation) VALUES (?, 0)")
|
|
66
72
|
.run(resourceId);
|
|
@@ -69,8 +75,8 @@ export class SqliteControllerStore {
|
|
|
69
75
|
controller_resource_id, resource_id, project_id, controller_name,
|
|
70
76
|
resource_key, uid, generation, spec_hash, status_hash, created_at, updated_at
|
|
71
77
|
) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`)
|
|
72
|
-
.run(
|
|
73
|
-
this.
|
|
78
|
+
.run(managedResourceId, resourceId, this.requireProjectId(), options.resourceManager, options.key, uid, specHash, statusHash, now, now);
|
|
79
|
+
this.enqueueResourceManagerRow(managedResourceId, now, now);
|
|
74
80
|
this.insertEvent(resourceId, 1, "resource.created", "control", null, { generation: 1 }, now);
|
|
75
81
|
}
|
|
76
82
|
else if (!existing.specHash.equals(specHash)) {
|
|
@@ -79,26 +85,26 @@ export class SqliteControllerStore {
|
|
|
79
85
|
.prepare(`UPDATE controller_resources
|
|
80
86
|
SET spec_hash = ?, generation = generation + 1, updated_at = ?
|
|
81
87
|
WHERE controller_resource_id = ?`)
|
|
82
|
-
.run(specHash, now, existing.
|
|
88
|
+
.run(specHash, now, existing.managedResourceId);
|
|
83
89
|
this.bumpResource(existing.resourceId, existing.resourceVersion, now);
|
|
84
|
-
this.
|
|
90
|
+
this.enqueueResourceManagerRow(existing.managedResourceId, now, now);
|
|
85
91
|
this.insertEvent(existing.resourceId, revision, "resource.spec_updated", "control", null, { generation: existing.generation + 1 }, now);
|
|
86
92
|
}
|
|
87
93
|
return this.requireResource({
|
|
88
|
-
|
|
94
|
+
resourceManager: options.resourceManager,
|
|
89
95
|
key: options.key,
|
|
90
96
|
});
|
|
91
97
|
});
|
|
92
98
|
}
|
|
93
99
|
getResource(ref) {
|
|
94
|
-
const row = this.
|
|
100
|
+
const row = this.resourceManagerRow(ref);
|
|
95
101
|
return row === undefined
|
|
96
102
|
? undefined
|
|
97
|
-
: this.
|
|
103
|
+
: this.mapManagedResource(row);
|
|
98
104
|
}
|
|
99
105
|
getResourceByUid(uid) {
|
|
100
|
-
const row = this.state.connection.prepare(
|
|
101
|
-
return
|
|
106
|
+
const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
|
|
107
|
+
return isResourceManagerRow(row) ? this.mapManagedResource(row) : undefined;
|
|
102
108
|
}
|
|
103
109
|
listResources(options = {}) {
|
|
104
110
|
const clauses = ["1 = 1"];
|
|
@@ -107,31 +113,31 @@ export class SqliteControllerStore {
|
|
|
107
113
|
clauses.push("c.project_id = ?");
|
|
108
114
|
params.push(this.projectId);
|
|
109
115
|
}
|
|
110
|
-
if (options.
|
|
116
|
+
if (options.resourceManager !== undefined) {
|
|
111
117
|
clauses.push("c.controller_name = ?");
|
|
112
|
-
params.push(options.
|
|
118
|
+
params.push(options.resourceManager);
|
|
113
119
|
}
|
|
114
120
|
const rows = this.state.connection
|
|
115
|
-
.prepare(
|
|
121
|
+
.prepare(resourceManagerSelect(`WHERE ${clauses.join(" AND ")} ORDER BY c.controller_name, c.resource_key`))
|
|
116
122
|
.all(...params);
|
|
117
123
|
return rows
|
|
118
|
-
.filter(
|
|
119
|
-
.map((row) => this.
|
|
124
|
+
.filter(isResourceManagerRow)
|
|
125
|
+
.map((row) => this.mapManagedResource(row));
|
|
120
126
|
}
|
|
121
127
|
updateStatus(options) {
|
|
122
128
|
const now = epoch(validTimestamp(options.now));
|
|
123
129
|
return this.state.transaction(() => {
|
|
124
|
-
const row = this.
|
|
125
|
-
this.
|
|
130
|
+
const row = this.requireResourceManagerRow(options.ref);
|
|
131
|
+
this.assertResourceManagerClaim(row, options.claim, now);
|
|
126
132
|
if (row.resourceVersion !== options.expectedResourceVersion) {
|
|
127
|
-
throw new
|
|
133
|
+
throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
|
|
128
134
|
}
|
|
129
135
|
const statusHash = this.state.putJson(options.status, now);
|
|
130
136
|
this.state.connection
|
|
131
137
|
.prepare("UPDATE controller_resources SET status_hash = ?, updated_at = ? WHERE controller_resource_id = ?")
|
|
132
|
-
.run(statusHash, now, row.
|
|
138
|
+
.run(statusHash, now, row.managedResourceId);
|
|
133
139
|
if (options.finalizers !== undefined)
|
|
134
|
-
this.replaceFinalizers(row.
|
|
140
|
+
this.replaceFinalizers(row.managedResourceId, options.finalizers);
|
|
135
141
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
136
142
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.status_updated", "controller", options.claim.ownerId, {}, now, options.claim.generation);
|
|
137
143
|
options.claim.resourceVersion = row.resourceVersion + 1;
|
|
@@ -141,13 +147,13 @@ export class SqliteControllerStore {
|
|
|
141
147
|
requestDeletion(ref, nowValue) {
|
|
142
148
|
const now = epoch(validTimestamp(nowValue));
|
|
143
149
|
return this.state.transaction(() => {
|
|
144
|
-
const row = this.
|
|
150
|
+
const row = this.requireResourceManagerRow(ref);
|
|
145
151
|
if (row.deletionRequestedAt === null) {
|
|
146
152
|
this.state.connection
|
|
147
153
|
.prepare("UPDATE controller_resources SET deletion_requested_at = ?, updated_at = ? WHERE controller_resource_id = ?")
|
|
148
|
-
.run(now, now, row.
|
|
154
|
+
.run(now, now, row.managedResourceId);
|
|
149
155
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
150
|
-
this.
|
|
156
|
+
this.enqueueResourceManagerRow(row.managedResourceId, now, now);
|
|
151
157
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.deletion_requested", "control", null, {}, now);
|
|
152
158
|
}
|
|
153
159
|
return this.requireResource(ref);
|
|
@@ -156,14 +162,14 @@ export class SqliteControllerStore {
|
|
|
156
162
|
updateFinalizers(options) {
|
|
157
163
|
const now = epoch(validTimestamp(options.now));
|
|
158
164
|
return this.state.transaction(() => {
|
|
159
|
-
const row = this.
|
|
165
|
+
const row = this.requireResourceManagerRow(options.ref);
|
|
160
166
|
if (row.resourceVersion !== options.expectedResourceVersion) {
|
|
161
|
-
throw new
|
|
167
|
+
throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
|
|
162
168
|
}
|
|
163
|
-
this.replaceFinalizers(row.
|
|
169
|
+
this.replaceFinalizers(row.managedResourceId, options.finalizers);
|
|
164
170
|
this.state.connection
|
|
165
171
|
.prepare("UPDATE controller_resources SET updated_at = ? WHERE controller_resource_id = ?")
|
|
166
|
-
.run(now, row.
|
|
172
|
+
.run(now, row.managedResourceId);
|
|
167
173
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
168
174
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.finalizers_updated", "control", null, {}, now);
|
|
169
175
|
return this.requireResource(options.ref);
|
|
@@ -171,37 +177,37 @@ export class SqliteControllerStore {
|
|
|
171
177
|
}
|
|
172
178
|
deleteResource(ref, expectedResourceVersion, claim) {
|
|
173
179
|
return this.state.transaction(() => {
|
|
174
|
-
const row = this.
|
|
175
|
-
this.
|
|
180
|
+
const row = this.requireResourceManagerRow(ref);
|
|
181
|
+
this.assertResourceManagerClaim(row, claim, Date.now());
|
|
176
182
|
if (row.resourceVersion !== expectedResourceVersion) {
|
|
177
|
-
throw new
|
|
183
|
+
throw new ManagedResourceConflictError(ref.resourceManager, ref.key);
|
|
178
184
|
}
|
|
179
|
-
if (row.deletionRequestedAt === null || this.finalizers(row.
|
|
185
|
+
if (row.deletionRequestedAt === null || this.finalizers(row.managedResourceId).length > 0)
|
|
180
186
|
return false;
|
|
181
187
|
this.state.connection
|
|
182
188
|
.prepare("DELETE FROM controller_resources WHERE controller_resource_id = ?")
|
|
183
|
-
.run(row.
|
|
189
|
+
.run(row.managedResourceId);
|
|
184
190
|
return true;
|
|
185
191
|
});
|
|
186
192
|
}
|
|
187
193
|
enqueue(ref, availableAt) {
|
|
188
|
-
const row = this.
|
|
194
|
+
const row = this.requireResourceManagerRow(ref);
|
|
189
195
|
const now = Date.now();
|
|
190
|
-
this.
|
|
196
|
+
this.enqueueResourceManagerRow(row.managedResourceId, epoch(validTimestamp(availableAt)), now);
|
|
191
197
|
}
|
|
192
198
|
claimNext(options) {
|
|
193
|
-
if (options.
|
|
199
|
+
if (options.resourceManagers.length === 0)
|
|
194
200
|
return undefined;
|
|
195
201
|
const now = epoch(validTimestamp(options.now));
|
|
196
|
-
const placeholders = options.
|
|
202
|
+
const placeholders = options.resourceManagers.map(() => "?").join(", ");
|
|
197
203
|
const exclusions = options.exclude ?? [];
|
|
198
204
|
const exclusionSql = exclusions
|
|
199
205
|
.map(() => "AND NOT (c.controller_name = ? AND c.resource_key = ?)")
|
|
200
206
|
.join("\n ");
|
|
201
|
-
const exclusionParams = exclusions.flatMap((ref) => [ref.
|
|
207
|
+
const exclusionParams = exclusions.flatMap((ref) => [ref.resourceManager, ref.key]);
|
|
202
208
|
return this.state.transaction(() => {
|
|
203
209
|
const row = this.state.connection
|
|
204
|
-
.prepare(`${
|
|
210
|
+
.prepare(`${resourceManagerSelect(`
|
|
205
211
|
JOIN controller_queue q ON q.controller_resource_id = c.controller_resource_id
|
|
206
212
|
JOIN leases l ON l.resource_id = c.resource_id
|
|
207
213
|
WHERE c.project_id = ?
|
|
@@ -211,8 +217,8 @@ export class SqliteControllerStore {
|
|
|
211
217
|
AND (l.owner_id IS NULL OR l.expires_at <= ?)
|
|
212
218
|
ORDER BY q.available_at, c.controller_name, c.resource_key
|
|
213
219
|
LIMIT 1`)}`)
|
|
214
|
-
.get(this.requireProjectId(), ...options.
|
|
215
|
-
if (!
|
|
220
|
+
.get(this.requireProjectId(), ...options.resourceManagers, ...exclusionParams, now, now);
|
|
221
|
+
if (!isResourceManagerRow(row))
|
|
216
222
|
return undefined;
|
|
217
223
|
const lease = this.requireLease(row.resourceId);
|
|
218
224
|
const token = randomBytes(32).toString("base64url");
|
|
@@ -231,14 +237,14 @@ export class SqliteControllerStore {
|
|
|
231
237
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
232
238
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "lease.claimed", "controller", options.ownerId, { expiresAt }, now, generation);
|
|
233
239
|
return {
|
|
234
|
-
|
|
240
|
+
resourceManager: row.resourceManagerName,
|
|
235
241
|
key: row.resourceKey,
|
|
236
242
|
ownerId: options.ownerId,
|
|
237
243
|
token,
|
|
238
244
|
generation,
|
|
239
|
-
queueVersion: this.queueVersion(row.
|
|
245
|
+
queueVersion: this.queueVersion(row.managedResourceId),
|
|
240
246
|
resourceVersion: row.resourceVersion + 1,
|
|
241
|
-
consecutiveErrors: this.queueErrors(row.
|
|
247
|
+
consecutiveErrors: this.queueErrors(row.managedResourceId),
|
|
242
248
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
243
249
|
};
|
|
244
250
|
});
|
|
@@ -246,7 +252,7 @@ export class SqliteControllerStore {
|
|
|
246
252
|
renewClaim(claim, leaseMs, nowValue) {
|
|
247
253
|
const now = epoch(validTimestamp(nowValue));
|
|
248
254
|
const expiresAt = now + leaseMs;
|
|
249
|
-
const row = this.
|
|
255
|
+
const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
|
|
250
256
|
if (row === undefined)
|
|
251
257
|
return false;
|
|
252
258
|
const result = this.state.connection
|
|
@@ -259,14 +265,14 @@ export class SqliteControllerStore {
|
|
|
259
265
|
return result.changes === 1;
|
|
260
266
|
}
|
|
261
267
|
settleClaim(claim, nowValue) {
|
|
262
|
-
return this.
|
|
268
|
+
return this.settleResourceManagerClaim(claim, undefined, nowValue);
|
|
263
269
|
}
|
|
264
270
|
requeueClaim(claim, options, nowValue) {
|
|
265
|
-
return this.
|
|
271
|
+
return this.settleResourceManagerClaim(claim, options, nowValue);
|
|
266
272
|
}
|
|
267
273
|
listQueue() {
|
|
268
274
|
const rows = this.state.connection
|
|
269
|
-
.prepare(`SELECT c.controller_name AS
|
|
275
|
+
.prepare(`SELECT c.controller_name AS resourceManager, c.resource_key AS resourceKey,
|
|
270
276
|
q.available_at AS availableAt, q.consecutive_errors AS consecutiveErrors,
|
|
271
277
|
l.expires_at AS claimExpiresAt
|
|
272
278
|
FROM controller_queue q
|
|
@@ -275,7 +281,7 @@ export class SqliteControllerStore {
|
|
|
275
281
|
WHERE c.project_id = ? ORDER BY q.available_at, c.controller_name, c.resource_key`)
|
|
276
282
|
.all(this.requireProjectId());
|
|
277
283
|
return rows.filter(isQueueListRow).map((row) => ({
|
|
278
|
-
|
|
284
|
+
resourceManager: row.resourceManager,
|
|
279
285
|
key: row.resourceKey,
|
|
280
286
|
availableAt: new Date(row.availableAt).toISOString(),
|
|
281
287
|
consecutiveErrors: row.consecutiveErrors,
|
|
@@ -287,8 +293,8 @@ export class SqliteControllerStore {
|
|
|
287
293
|
reserveEffect(options) {
|
|
288
294
|
const now = epoch(validTimestamp(options.now));
|
|
289
295
|
return this.state.transaction(() => {
|
|
290
|
-
const source = this.
|
|
291
|
-
this.
|
|
296
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
297
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
292
298
|
const effectId = effectIdFor(source.resourceId, options.key);
|
|
293
299
|
const existing = this.effectRow(options.resourceUid, options.key);
|
|
294
300
|
if (existing !== undefined) {
|
|
@@ -335,8 +341,8 @@ export class SqliteControllerStore {
|
|
|
335
341
|
updateEffect(options) {
|
|
336
342
|
const now = epoch(validTimestamp(options.now));
|
|
337
343
|
return this.state.transaction(() => {
|
|
338
|
-
const source = this.
|
|
339
|
-
this.
|
|
344
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
345
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
340
346
|
const row = this.requireEffectRow(options.resourceUid, options.key);
|
|
341
347
|
const effectResourceId = resourceIdFor("effect", row.effectId);
|
|
342
348
|
const revision = this.resourceRevision(effectResourceId);
|
|
@@ -360,8 +366,8 @@ export class SqliteControllerStore {
|
|
|
360
366
|
reserveWorkflow(options) {
|
|
361
367
|
const now = Date.now();
|
|
362
368
|
return this.state.transaction(() => {
|
|
363
|
-
const source = this.
|
|
364
|
-
this.
|
|
369
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
370
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
365
371
|
const existing = this.workflowRow(options.resourceUid, options.requestKey);
|
|
366
372
|
if (existing !== undefined) {
|
|
367
373
|
if (existing.workflowName !== options.workflow ||
|
|
@@ -376,7 +382,7 @@ export class SqliteControllerStore {
|
|
|
376
382
|
request_id, controller_resource_id, request_key, workflow_name,
|
|
377
383
|
input_fingerprint, status, attempt_count, created_at, updated_at
|
|
378
384
|
) VALUES (?, ?, ?, ?, ?, 'pending', 0, ?, ?)`)
|
|
379
|
-
.run(requestId, source.
|
|
385
|
+
.run(requestId, source.managedResourceId, options.requestKey, options.workflow, Buffer.from(options.inputFingerprint, "hex"), now, now);
|
|
380
386
|
return { record: this.mapWorkflow(this.requireWorkflowRow(requestId)), created: true };
|
|
381
387
|
});
|
|
382
388
|
}
|
|
@@ -392,8 +398,8 @@ export class SqliteControllerStore {
|
|
|
392
398
|
const now = Date.now();
|
|
393
399
|
return this.state.transaction(() => {
|
|
394
400
|
const row = this.requireWorkflowRow(requestId);
|
|
395
|
-
const source = this.
|
|
396
|
-
this.
|
|
401
|
+
const source = this.requireResourceManagerRowByUid(row.resourceUid);
|
|
402
|
+
this.assertResourceManagerClaim(source, claim, now);
|
|
397
403
|
const errorHash = update.error === undefined || update.error === null
|
|
398
404
|
? null
|
|
399
405
|
: this.state.putText(update.error, now);
|
|
@@ -419,7 +425,7 @@ export class SqliteControllerStore {
|
|
|
419
425
|
if (update.runId !== undefined && row.runId !== null && update.runId !== row.runId) {
|
|
420
426
|
throw new Error("Scheduler completion run ID does not match the reserved child run");
|
|
421
427
|
}
|
|
422
|
-
const source = this.
|
|
428
|
+
const source = this.requireResourceManagerRowByUid(row.resourceUid);
|
|
423
429
|
const now = Date.now();
|
|
424
430
|
return this.state.transaction(() => {
|
|
425
431
|
const errorHash = update.error === undefined || update.error === null
|
|
@@ -450,9 +456,12 @@ export class SqliteControllerStore {
|
|
|
450
456
|
recordEvent(options) {
|
|
451
457
|
const now = epoch(validTimestamp(options.now));
|
|
452
458
|
return this.state.transaction(() => {
|
|
453
|
-
const row = this.
|
|
459
|
+
const row = this.requireResourceManagerRow({
|
|
460
|
+
resourceManager: options.resourceManager,
|
|
461
|
+
key: options.key,
|
|
462
|
+
});
|
|
454
463
|
if (options.claim !== undefined)
|
|
455
|
-
this.
|
|
464
|
+
this.assertResourceManagerClaim(row, options.claim, now);
|
|
456
465
|
const revision = this.resourceRevision(row.resourceId) + 1;
|
|
457
466
|
this.bumpResource(row.resourceId, revision - 1, now);
|
|
458
467
|
const eventId = this.insertEvent(row.resourceId, revision, options.type, options.claim === undefined ? "control" : "controller", options.claim?.ownerId ?? null, options.payload ?? {}, now, options.claim?.generation);
|
|
@@ -463,11 +472,11 @@ export class SqliteControllerStore {
|
|
|
463
472
|
.get(eventId);
|
|
464
473
|
/* istanbul ignore if -- exact schema and internal query shape */
|
|
465
474
|
if (!isSequenceRow(event))
|
|
466
|
-
throw new Error("
|
|
475
|
+
throw new Error("ResourceManager event was not recorded");
|
|
467
476
|
return {
|
|
468
477
|
seq: event.seq,
|
|
469
478
|
recordedAt: new Date(now).toISOString(),
|
|
470
|
-
|
|
479
|
+
resourceManager: options.resourceManager,
|
|
471
480
|
key: options.key,
|
|
472
481
|
type: options.type,
|
|
473
482
|
payload: options.payload ?? {},
|
|
@@ -481,9 +490,9 @@ export class SqliteControllerStore {
|
|
|
481
490
|
clauses.push("c.project_id = ?");
|
|
482
491
|
params.push(this.projectId);
|
|
483
492
|
}
|
|
484
|
-
if (options.
|
|
493
|
+
if (options.resourceManager !== undefined) {
|
|
485
494
|
clauses.push("c.controller_name = ?");
|
|
486
|
-
params.push(options.
|
|
495
|
+
params.push(options.resourceManager);
|
|
487
496
|
}
|
|
488
497
|
if (options.key !== undefined) {
|
|
489
498
|
clauses.push("c.resource_key = ?");
|
|
@@ -492,7 +501,7 @@ export class SqliteControllerStore {
|
|
|
492
501
|
params.push(options.limit ?? 100);
|
|
493
502
|
const rows = this.state.connection
|
|
494
503
|
.prepare(`SELECT e.event_seq AS seq, e.recorded_at AS recordedAt,
|
|
495
|
-
c.controller_name AS
|
|
504
|
+
c.controller_name AS resourceManager, c.resource_key AS resourceKey,
|
|
496
505
|
e.event_type AS eventType, e.payload_hash AS payloadHash
|
|
497
506
|
FROM events e
|
|
498
507
|
JOIN resources r ON r.resource_id = e.resource_id
|
|
@@ -500,10 +509,10 @@ export class SqliteControllerStore {
|
|
|
500
509
|
WHERE ${clauses.join(" AND ")}
|
|
501
510
|
ORDER BY e.event_seq DESC LIMIT ?`)
|
|
502
511
|
.all(...params);
|
|
503
|
-
return rows.filter(
|
|
512
|
+
return rows.filter(isResourceManagerEventRow).map((row) => ({
|
|
504
513
|
seq: row.seq,
|
|
505
514
|
recordedAt: new Date(row.recordedAt).toISOString(),
|
|
506
|
-
|
|
515
|
+
resourceManager: row.resourceManager,
|
|
507
516
|
key: row.resourceKey,
|
|
508
517
|
type: row.eventType,
|
|
509
518
|
payload: row.payloadHash === null ? {} : this.state.readJson(row.payloadHash),
|
|
@@ -747,14 +756,14 @@ export class SqliteControllerStore {
|
|
|
747
756
|
const now = epoch(validTimestamp(options.now));
|
|
748
757
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now);
|
|
749
758
|
}
|
|
750
|
-
/** Schedule a
|
|
759
|
+
/** Schedule a runner that validates one pending interactive submission. */
|
|
751
760
|
claimWorkflowRunForInteractionValidation(options) {
|
|
752
761
|
const now = epoch(validTimestamp(options.now));
|
|
753
762
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now, {
|
|
754
763
|
allowPendingInteraction: true,
|
|
755
764
|
});
|
|
756
765
|
}
|
|
757
|
-
/** Take a short
|
|
766
|
+
/** Take a short server claim without scheduling a parked interactive run. */
|
|
758
767
|
claimWorkflowRunForControl(options) {
|
|
759
768
|
const now = epoch(validTimestamp(options.now));
|
|
760
769
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now, { allowPendingInteraction: true, preserveQueueStatus: true });
|
|
@@ -833,7 +842,7 @@ export class SqliteControllerStore {
|
|
|
833
842
|
"(q.affinity_runner_id IS NULL OR q.affinity_runner_id = ? OR q.status IN ('parked', 'starting', 'running'))",
|
|
834
843
|
"NOT (q.status = 'parked' AND (r.status = 'waiting' OR r.paused = 1))",
|
|
835
844
|
"NOT EXISTS (SELECT 1 FROM interactive_requests i WHERE i.run_id = r.run_id AND i.status = 'pending')",
|
|
836
|
-
"(q.error_code IS NULL OR q.error_code
|
|
845
|
+
"(q.error_code IS NULL OR q.error_code NOT IN ('workflowSourceChanged', 'runnerNoProgress'))",
|
|
837
846
|
];
|
|
838
847
|
const params = [now, now, options.runnerId, options.runnerId];
|
|
839
848
|
if (this.projectId !== null) {
|
|
@@ -907,6 +916,50 @@ export class SqliteControllerStore {
|
|
|
907
916
|
parkWorkflowRun(options) {
|
|
908
917
|
return this.releaseRunClaim(options.runId, options.claimToken, "parked", options.now);
|
|
909
918
|
}
|
|
919
|
+
parkWorkflowRunForRunnerNoProgress(options) {
|
|
920
|
+
const now = epoch(validTimestamp(options.now));
|
|
921
|
+
return this.state.transaction(() => {
|
|
922
|
+
if (!this.verifyWorkflowRunClaim({
|
|
923
|
+
runId: options.runId,
|
|
924
|
+
claimToken: options.claimToken,
|
|
925
|
+
now: new Date(now).toISOString(),
|
|
926
|
+
})) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
const row = this.requireWorkflowRunRow(options.runId);
|
|
930
|
+
const lease = this.requireLease(row.resourceId);
|
|
931
|
+
const detail = options.detail.slice(0, 8_192);
|
|
932
|
+
const errorHash = this.state.putText(detail, now);
|
|
933
|
+
const run = this.state.connection
|
|
934
|
+
.prepare(`UPDATE runs SET status_detail = ?, updated_at = ?, finished_at = NULL
|
|
935
|
+
WHERE run_id = ? AND status NOT IN ('completed', 'failed', 'timed_out', 'cancelled')`)
|
|
936
|
+
.run(detail, now, options.runId);
|
|
937
|
+
const queue = this.state.connection
|
|
938
|
+
.prepare(`UPDATE run_queue
|
|
939
|
+
SET status = 'parked', error_code = 'runnerNoProgress', error_hash = ?,
|
|
940
|
+
available_at = ?, updated_at = ?, finished_at = NULL
|
|
941
|
+
WHERE run_id = ? AND status IN ('queued', 'starting', 'running', 'parked')`)
|
|
942
|
+
.run(errorHash, now, now, options.runId);
|
|
943
|
+
if (run.changes !== 1 || queue.changes !== 1) {
|
|
944
|
+
throw new Error(`Workflow run ${options.runId} changed during runner recovery`);
|
|
945
|
+
}
|
|
946
|
+
const released = this.state.connection
|
|
947
|
+
.prepare(`UPDATE leases
|
|
948
|
+
SET owner_type = NULL, owner_id = NULL, token_hash = NULL,
|
|
949
|
+
acquired_at = NULL, heartbeat_at = NULL, expires_at = NULL
|
|
950
|
+
WHERE resource_id = ? AND token_hash = ? AND generation = ? AND expires_at > ?`)
|
|
951
|
+
.run(row.resourceId, tokenHash(options.claimToken), lease.generation, now);
|
|
952
|
+
/* istanbul ignore if -- the exact live claim is stable in this transaction */
|
|
953
|
+
if (released.changes !== 1) {
|
|
954
|
+
throw new Error(`Workflow run ${options.runId} claim changed during runner recovery`);
|
|
955
|
+
}
|
|
956
|
+
const revision = this.resourceRevision(row.resourceId);
|
|
957
|
+
this.bumpResource(row.resourceId, revision, now);
|
|
958
|
+
this.insertEvent(row.resourceId, revision + 1, "run.worker_no_progress", lease.ownerType ?? "system", lease.ownerId, { status: "parked", code: "runnerNoProgress" }, now, lease.generation || undefined);
|
|
959
|
+
recordViewerDeltas(this.state, options.runId, [{ targetType: "summary" }, { targetType: "replay" }], now);
|
|
960
|
+
return true;
|
|
961
|
+
});
|
|
962
|
+
}
|
|
910
963
|
pauseParkedWorkflowRun(options) {
|
|
911
964
|
const now = epoch(validTimestamp(options.now));
|
|
912
965
|
return this.state.transaction(() => {
|
|
@@ -1125,6 +1178,7 @@ export class SqliteControllerStore {
|
|
|
1125
1178
|
if (run.changes !== 1) {
|
|
1126
1179
|
throw new Error(`Workflow run ${options.runId} has inconsistent durable state`);
|
|
1127
1180
|
}
|
|
1181
|
+
this.settleWorkflowRunMessages(options.runId, now);
|
|
1128
1182
|
this.cancelWorkflowRunDependents(options.runId, controlId, errorHash, now);
|
|
1129
1183
|
const released = this.state.connection
|
|
1130
1184
|
.prepare(`UPDATE leases
|
|
@@ -1261,73 +1315,73 @@ export class SqliteControllerStore {
|
|
|
1261
1315
|
}
|
|
1262
1316
|
requireProjectId() {
|
|
1263
1317
|
if (this.projectId === null)
|
|
1264
|
-
throw new Error("
|
|
1318
|
+
throw new Error("ResourceManager project is not registered in the state database");
|
|
1265
1319
|
return this.projectId;
|
|
1266
1320
|
}
|
|
1267
|
-
|
|
1321
|
+
resourceManagerRow(ref) {
|
|
1268
1322
|
if (this.projectId === null)
|
|
1269
1323
|
return undefined;
|
|
1270
1324
|
const row = this.state.connection
|
|
1271
|
-
.prepare(
|
|
1272
|
-
.get(this.projectId, ref.
|
|
1273
|
-
return
|
|
1325
|
+
.prepare(resourceManagerSelect("WHERE c.project_id = ? AND c.controller_name = ? AND c.resource_key = ?"))
|
|
1326
|
+
.get(this.projectId, ref.resourceManager, ref.key);
|
|
1327
|
+
return isResourceManagerRow(row) ? row : undefined;
|
|
1274
1328
|
}
|
|
1275
|
-
|
|
1276
|
-
const row = this.
|
|
1329
|
+
requireResourceManagerRow(ref) {
|
|
1330
|
+
const row = this.resourceManagerRow(ref);
|
|
1277
1331
|
if (row === undefined)
|
|
1278
|
-
throw new
|
|
1332
|
+
throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
|
|
1279
1333
|
return row;
|
|
1280
1334
|
}
|
|
1281
|
-
|
|
1282
|
-
const row = this.state.connection.prepare(
|
|
1335
|
+
requireResourceManagerRowByUid(uid) {
|
|
1336
|
+
const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
|
|
1283
1337
|
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
1284
|
-
if (!
|
|
1285
|
-
throw new Error(`
|
|
1338
|
+
if (!isResourceManagerRow(row))
|
|
1339
|
+
throw new Error(`Managed resource not found: ${uid}`);
|
|
1286
1340
|
return row;
|
|
1287
1341
|
}
|
|
1288
1342
|
requireResource(ref) {
|
|
1289
1343
|
const resource = this.getResource(ref);
|
|
1290
1344
|
if (resource === undefined)
|
|
1291
|
-
throw new
|
|
1345
|
+
throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
|
|
1292
1346
|
return resource;
|
|
1293
1347
|
}
|
|
1294
1348
|
/* istanbul ignore next -- pure projection covered by integration tests */
|
|
1295
|
-
|
|
1349
|
+
mapManagedResource(row) {
|
|
1296
1350
|
const spec = this.state.readJson(row.specHash);
|
|
1297
1351
|
const status = this.state.readJson(row.statusHash);
|
|
1298
1352
|
return {
|
|
1299
1353
|
metadata: {
|
|
1300
1354
|
uid: row.uid,
|
|
1301
|
-
|
|
1355
|
+
resourceManager: row.resourceManagerName,
|
|
1302
1356
|
key: row.resourceKey,
|
|
1303
1357
|
resourceVersion: row.resourceVersion,
|
|
1304
1358
|
generation: row.generation,
|
|
1305
1359
|
...(row.deletionRequestedAt === null
|
|
1306
1360
|
? {}
|
|
1307
1361
|
: { deletionTimestamp: new Date(row.deletionRequestedAt).toISOString() }),
|
|
1308
|
-
finalizers: this.finalizers(row.
|
|
1362
|
+
finalizers: this.finalizers(row.managedResourceId),
|
|
1309
1363
|
},
|
|
1310
1364
|
spec,
|
|
1311
1365
|
status,
|
|
1312
1366
|
};
|
|
1313
1367
|
}
|
|
1314
|
-
finalizers(
|
|
1368
|
+
finalizers(managedResourceId) {
|
|
1315
1369
|
const rows = this.state.connection
|
|
1316
1370
|
.prepare("SELECT finalizer FROM controller_finalizers WHERE controller_resource_id = ? ORDER BY position")
|
|
1317
|
-
.all(
|
|
1371
|
+
.all(managedResourceId);
|
|
1318
1372
|
return rows.flatMap((row) => (isFinalizerRow(row) ? [row.finalizer] : []));
|
|
1319
1373
|
}
|
|
1320
|
-
replaceFinalizers(
|
|
1374
|
+
replaceFinalizers(managedResourceId, finalizers) {
|
|
1321
1375
|
const unique = [...new Set(finalizers)];
|
|
1322
1376
|
if (unique.length !== finalizers.length)
|
|
1323
|
-
throw new Error("
|
|
1377
|
+
throw new Error("ResourceManager finalizers must be unique");
|
|
1324
1378
|
this.state.connection
|
|
1325
1379
|
.prepare("DELETE FROM controller_finalizers WHERE controller_resource_id = ?")
|
|
1326
|
-
.run(
|
|
1380
|
+
.run(managedResourceId);
|
|
1327
1381
|
const insert = this.state.connection.prepare("INSERT INTO controller_finalizers(controller_resource_id, finalizer, position) VALUES (?, ?, ?)");
|
|
1328
|
-
unique.forEach((finalizer, index) => insert.run(
|
|
1382
|
+
unique.forEach((finalizer, index) => insert.run(managedResourceId, finalizer, index));
|
|
1329
1383
|
}
|
|
1330
|
-
|
|
1384
|
+
enqueueResourceManagerRow(managedResourceId, availableAt, now) {
|
|
1331
1385
|
this.state.connection
|
|
1332
1386
|
.prepare(`INSERT INTO controller_queue(
|
|
1333
1387
|
controller_resource_id, available_at, queue_version,
|
|
@@ -1337,42 +1391,45 @@ export class SqliteControllerStore {
|
|
|
1337
1391
|
available_at = MIN(controller_queue.available_at, excluded.available_at),
|
|
1338
1392
|
queue_version = controller_queue.queue_version + 1,
|
|
1339
1393
|
updated_at = excluded.updated_at`)
|
|
1340
|
-
.run(
|
|
1394
|
+
.run(managedResourceId, availableAt, now, now);
|
|
1341
1395
|
}
|
|
1342
|
-
queueErrors(
|
|
1396
|
+
queueErrors(managedResourceId) {
|
|
1343
1397
|
const row = this.state.connection
|
|
1344
1398
|
.prepare("SELECT consecutive_errors AS consecutiveErrors FROM controller_queue WHERE controller_resource_id = ?")
|
|
1345
|
-
.get(
|
|
1399
|
+
.get(managedResourceId);
|
|
1346
1400
|
return isErrorCountRow(row) ? row.consecutiveErrors : 0;
|
|
1347
1401
|
}
|
|
1348
|
-
queueVersion(
|
|
1402
|
+
queueVersion(managedResourceId) {
|
|
1349
1403
|
const row = this.state.connection
|
|
1350
1404
|
.prepare("SELECT queue_version AS queueVersion FROM controller_queue WHERE controller_resource_id = ?")
|
|
1351
|
-
.get(
|
|
1405
|
+
.get(managedResourceId);
|
|
1352
1406
|
/* istanbul ignore if -- exact schema and internal query shape */
|
|
1353
1407
|
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
1354
1408
|
if (!isQueueVersionRow(row))
|
|
1355
|
-
throw new Error("
|
|
1409
|
+
throw new Error("ResourceManager queue item is missing");
|
|
1356
1410
|
return row.queueVersion;
|
|
1357
1411
|
}
|
|
1358
|
-
|
|
1412
|
+
settleResourceManagerClaim(claim, requeue, nowValue) {
|
|
1359
1413
|
const now = epoch(validTimestamp(nowValue));
|
|
1360
1414
|
return this.state.transaction(() => {
|
|
1361
|
-
const row = this.
|
|
1415
|
+
const row = this.resourceManagerRow({
|
|
1416
|
+
resourceManager: claim.resourceManager,
|
|
1417
|
+
key: claim.key,
|
|
1418
|
+
});
|
|
1362
1419
|
if (row === undefined)
|
|
1363
1420
|
return false;
|
|
1364
1421
|
try {
|
|
1365
|
-
this.
|
|
1422
|
+
this.assertResourceManagerClaim(row, claim, now, false);
|
|
1366
1423
|
}
|
|
1367
1424
|
catch {
|
|
1368
1425
|
return false;
|
|
1369
1426
|
}
|
|
1370
|
-
const currentQueueVersion = this.queueVersion(row.
|
|
1427
|
+
const currentQueueVersion = this.queueVersion(row.managedResourceId);
|
|
1371
1428
|
if (requeue === undefined) {
|
|
1372
1429
|
if (currentQueueVersion === claim.queueVersion) {
|
|
1373
1430
|
this.state.connection
|
|
1374
1431
|
.prepare("DELETE FROM controller_queue WHERE controller_resource_id = ?")
|
|
1375
|
-
.run(row.
|
|
1432
|
+
.run(row.managedResourceId);
|
|
1376
1433
|
}
|
|
1377
1434
|
}
|
|
1378
1435
|
else {
|
|
@@ -1382,15 +1439,15 @@ export class SqliteControllerStore {
|
|
|
1382
1439
|
SET available_at = ?, queue_version = queue_version + 1,
|
|
1383
1440
|
consecutive_errors = consecutive_errors + 1,
|
|
1384
1441
|
last_error_hash = ?, updated_at = ? WHERE controller_resource_id = ?`)
|
|
1385
|
-
.run(epoch(requeue.availableAt), errorHash, now, row.
|
|
1442
|
+
.run(epoch(requeue.availableAt), errorHash, now, row.managedResourceId);
|
|
1386
1443
|
}
|
|
1387
1444
|
this.releaseLease(row.resourceId, claim, now);
|
|
1388
1445
|
return true;
|
|
1389
1446
|
});
|
|
1390
1447
|
}
|
|
1391
|
-
|
|
1392
|
-
if (row.
|
|
1393
|
-
throw new Error("
|
|
1448
|
+
assertResourceManagerClaim(row, claim, now, requireUnexpired = true) {
|
|
1449
|
+
if (row.resourceManagerName !== claim.resourceManager || row.resourceKey !== claim.key)
|
|
1450
|
+
throw new Error("ResourceManager claim targets another resource");
|
|
1394
1451
|
const lease = this.requireLease(row.resourceId);
|
|
1395
1452
|
if (lease.ownerType !== "controller" ||
|
|
1396
1453
|
lease.ownerId !== claim.ownerId ||
|
|
@@ -1398,7 +1455,7 @@ export class SqliteControllerStore {
|
|
|
1398
1455
|
lease.tokenHash === null ||
|
|
1399
1456
|
!lease.tokenHash.equals(tokenHash(claim.token)) ||
|
|
1400
1457
|
(requireUnexpired && (lease.expiresAt === null || lease.expiresAt <= now))) {
|
|
1401
|
-
throw new Error("
|
|
1458
|
+
throw new Error("ResourceManager claim is stale");
|
|
1402
1459
|
}
|
|
1403
1460
|
}
|
|
1404
1461
|
requireLease(resourceId) {
|
|
@@ -1414,7 +1471,7 @@ export class SqliteControllerStore {
|
|
|
1414
1471
|
return row;
|
|
1415
1472
|
}
|
|
1416
1473
|
releaseLease(resourceId, claim, now) {
|
|
1417
|
-
const row = this.
|
|
1474
|
+
const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
|
|
1418
1475
|
if (row === undefined)
|
|
1419
1476
|
return;
|
|
1420
1477
|
const revision = this.resourceRevision(resourceId);
|
|
@@ -1489,7 +1546,7 @@ export class SqliteControllerStore {
|
|
|
1489
1546
|
resourceUid: row.resourceUid,
|
|
1490
1547
|
generation: row.generation,
|
|
1491
1548
|
kind: row.kind,
|
|
1492
|
-
state:
|
|
1549
|
+
state: resourceManagerEffectState(row.state),
|
|
1493
1550
|
requestFingerprint: row.requestFingerprint,
|
|
1494
1551
|
startedAt: new Date(row.startedAt).toISOString(),
|
|
1495
1552
|
...(row.completedAt === null ? {} : { completedAt: new Date(row.completedAt).toISOString() }),
|
|
@@ -1769,6 +1826,7 @@ export class SqliteControllerStore {
|
|
|
1769
1826
|
updated_at = ?, finished_at = ?
|
|
1770
1827
|
WHERE run_id = ?`)
|
|
1771
1828
|
.run(status, errorHash, now, now, runId);
|
|
1829
|
+
this.settleWorkflowRunMessages(runId, now);
|
|
1772
1830
|
if (status === "cancelled") {
|
|
1773
1831
|
this.cancelWorkflowRunDependents(runId, lease.ownerId ?? "workflow-control", errorHash, now);
|
|
1774
1832
|
}
|
|
@@ -1778,6 +1836,10 @@ export class SqliteControllerStore {
|
|
|
1778
1836
|
return true;
|
|
1779
1837
|
});
|
|
1780
1838
|
}
|
|
1839
|
+
settleWorkflowRunMessages(runId, now) {
|
|
1840
|
+
this.workflowMessages.settleOpenTurnsForRun(runId, "lost", now);
|
|
1841
|
+
this.workflowMessages.cancelPendingForRun(runId, now);
|
|
1842
|
+
}
|
|
1781
1843
|
cancelWorkflowRunDependents(runId, actorId, errorHash, now) {
|
|
1782
1844
|
this.state.connection
|
|
1783
1845
|
.prepare(`UPDATE node_attempts
|
|
@@ -1840,13 +1902,13 @@ export class SqliteControllerStore {
|
|
|
1840
1902
|
}
|
|
1841
1903
|
assertWritable() {
|
|
1842
1904
|
if (this.state.mode === "read-only")
|
|
1843
|
-
throw new Error("
|
|
1905
|
+
throw new Error("ResourceManager store is read-only");
|
|
1844
1906
|
}
|
|
1845
1907
|
}
|
|
1846
|
-
function
|
|
1847
|
-
return `SELECT c.controller_resource_id AS
|
|
1908
|
+
function resourceManagerSelect(clause) {
|
|
1909
|
+
return `SELECT c.controller_resource_id AS managedResourceId,
|
|
1848
1910
|
c.resource_id AS resourceId, r.revision AS resourceVersion,
|
|
1849
|
-
c.controller_name AS
|
|
1911
|
+
c.controller_name AS resourceManagerName, c.resource_key AS resourceKey,
|
|
1850
1912
|
c.uid, c.generation, c.spec_hash AS specHash, c.status_hash AS statusHash,
|
|
1851
1913
|
c.deletion_requested_at AS deletionRequestedAt
|
|
1852
1914
|
FROM controller_resources c JOIN resources r ON r.resource_id = c.resource_id ${clause}`;
|
|
@@ -1912,7 +1974,7 @@ function workflowRunViewSelect(clause) {
|
|
|
1912
1974
|
function isRecord(value) {
|
|
1913
1975
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1914
1976
|
}
|
|
1915
|
-
function
|
|
1977
|
+
function isResourceManagerRow(value) {
|
|
1916
1978
|
return isRecord(value);
|
|
1917
1979
|
}
|
|
1918
1980
|
function isLeaseRow(value) {
|
|
@@ -2007,7 +2069,7 @@ function isWorkflowRow(value) {
|
|
|
2007
2069
|
function isQueueListRow(value) {
|
|
2008
2070
|
return isRecord(value);
|
|
2009
2071
|
}
|
|
2010
|
-
function
|
|
2072
|
+
function isResourceManagerEventRow(value) {
|
|
2011
2073
|
return isRecord(value);
|
|
2012
2074
|
}
|
|
2013
2075
|
function isRunEventRow(value) {
|
|
@@ -2151,7 +2213,7 @@ function rowToSource(row) {
|
|
|
2151
2213
|
function effectStatus(value) {
|
|
2152
2214
|
return value === "indeterminate" ? "ambiguous" : value;
|
|
2153
2215
|
}
|
|
2154
|
-
function
|
|
2216
|
+
function resourceManagerEffectState(value) {
|
|
2155
2217
|
return value === "ambiguous" ? "indeterminate" : value;
|
|
2156
2218
|
}
|
|
2157
2219
|
function validTimestamp(value) {
|