@osolmaz/pi-workflows 0.16.2 → 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 +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +32 -32
- 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 +1 -1
- package/dist/extension/workflow-message-coordinator.js +6 -6
- 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 +44 -44
- package/dist/{controllers → resource-managers}/sqlite.js +139 -130
- 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} +39 -38
- package/dist/{host/runner.js → server/server.js} +408 -374
- 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 +13 -13
- 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/{host/worker-entry.d.ts → server/workflow-runner-entry.d.ts} +3 -3
- package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +68 -35
- 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 +1 -1
- package/dist/state/workflow-messages.js +1 -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/engine.js +14 -15
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/store.d.ts +12 -11
- package/dist/workflows/store.js +24 -18
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +2 -2
- 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 +6 -6
- 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 +35 -33
- package/docs/WORKFLOW_COMPOSITION.md +1 -1
- package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +125 -119
- 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 +72 -66
- 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 +1 -1
- package/src/extension/index.ts +44 -34
- 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 +6 -6
- 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 +198 -186
- 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} +526 -461
- package/src/{host → server}/state.ts +78 -67
- package/src/{host → server}/view.ts +15 -15
- package/src/server/workflow-runner-content.ts +119 -0
- package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +98 -57
- 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 +1 -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/engine.ts +14 -15
- package/src/workflows/store.ts +31 -28
- package/src/workflows/types.ts +2 -2
- 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.js.map +0 -1
- package/dist/host/worker-protocol.d.ts +0 -46
- 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 -176
- 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
|
@@ -6,8 +6,8 @@ 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
8
|
import { WorkflowMessageStore } from "../state/workflow-messages.js";
|
|
9
|
-
import { EffectRequestConflictError,
|
|
10
|
-
export class
|
|
9
|
+
import { EffectRequestConflictError, ManagedResourceConflictError, ManagedResourceNotFoundError, WorkflowRequestConflictError, } from "./errors.js";
|
|
10
|
+
export class SqliteResourceManagerStore {
|
|
11
11
|
filePath;
|
|
12
12
|
state;
|
|
13
13
|
workflowMessages;
|
|
@@ -43,27 +43,30 @@ export class SqliteControllerStore {
|
|
|
43
43
|
}
|
|
44
44
|
putResource(options) {
|
|
45
45
|
this.assertWritable();
|
|
46
|
-
validateName(options.
|
|
46
|
+
validateName(options.resourceManager, "resource manager");
|
|
47
47
|
validateKey(options.key, "resource key");
|
|
48
48
|
const now = epoch(validTimestamp(options.now));
|
|
49
49
|
const specHash = this.state.putJson(options.spec, now);
|
|
50
50
|
const initialStatus = {
|
|
51
51
|
observedGeneration: 0,
|
|
52
52
|
conditions: [],
|
|
53
|
-
|
|
53
|
+
resourceManagerStatus: options.initialStatus,
|
|
54
54
|
};
|
|
55
55
|
const statusHash = this.state.putJson(initialStatus, now);
|
|
56
56
|
return this.state.transaction(() => {
|
|
57
|
-
const existing = this.
|
|
57
|
+
const existing = this.resourceManagerRow({
|
|
58
|
+
resourceManager: options.resourceManager,
|
|
59
|
+
key: options.key,
|
|
60
|
+
});
|
|
58
61
|
if (existing === undefined) {
|
|
59
62
|
const uid = randomUUID();
|
|
60
|
-
const
|
|
61
|
-
const resourceId = resourceIdFor("controller", `${this.requireProjectId()}:${options.
|
|
63
|
+
const managedResourceId = `controller-resource-${randomUUID()}`;
|
|
64
|
+
const resourceId = resourceIdFor("controller", `${this.requireProjectId()}:${options.resourceManager}:${options.key}`);
|
|
62
65
|
this.state.connection
|
|
63
66
|
.prepare(`INSERT INTO resources(
|
|
64
67
|
resource_id, resource_type, aggregate_key, revision, created_at, updated_at
|
|
65
68
|
) VALUES (?, 'controller', ?, 1, ?, ?)`)
|
|
66
|
-
.run(resourceId,
|
|
69
|
+
.run(resourceId, managedResourceId, now, now);
|
|
67
70
|
this.state.connection
|
|
68
71
|
.prepare("INSERT INTO leases(resource_id, generation) VALUES (?, 0)")
|
|
69
72
|
.run(resourceId);
|
|
@@ -72,8 +75,8 @@ export class SqliteControllerStore {
|
|
|
72
75
|
controller_resource_id, resource_id, project_id, controller_name,
|
|
73
76
|
resource_key, uid, generation, spec_hash, status_hash, created_at, updated_at
|
|
74
77
|
) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`)
|
|
75
|
-
.run(
|
|
76
|
-
this.
|
|
78
|
+
.run(managedResourceId, resourceId, this.requireProjectId(), options.resourceManager, options.key, uid, specHash, statusHash, now, now);
|
|
79
|
+
this.enqueueResourceManagerRow(managedResourceId, now, now);
|
|
77
80
|
this.insertEvent(resourceId, 1, "resource.created", "control", null, { generation: 1 }, now);
|
|
78
81
|
}
|
|
79
82
|
else if (!existing.specHash.equals(specHash)) {
|
|
@@ -82,26 +85,26 @@ export class SqliteControllerStore {
|
|
|
82
85
|
.prepare(`UPDATE controller_resources
|
|
83
86
|
SET spec_hash = ?, generation = generation + 1, updated_at = ?
|
|
84
87
|
WHERE controller_resource_id = ?`)
|
|
85
|
-
.run(specHash, now, existing.
|
|
88
|
+
.run(specHash, now, existing.managedResourceId);
|
|
86
89
|
this.bumpResource(existing.resourceId, existing.resourceVersion, now);
|
|
87
|
-
this.
|
|
90
|
+
this.enqueueResourceManagerRow(existing.managedResourceId, now, now);
|
|
88
91
|
this.insertEvent(existing.resourceId, revision, "resource.spec_updated", "control", null, { generation: existing.generation + 1 }, now);
|
|
89
92
|
}
|
|
90
93
|
return this.requireResource({
|
|
91
|
-
|
|
94
|
+
resourceManager: options.resourceManager,
|
|
92
95
|
key: options.key,
|
|
93
96
|
});
|
|
94
97
|
});
|
|
95
98
|
}
|
|
96
99
|
getResource(ref) {
|
|
97
|
-
const row = this.
|
|
100
|
+
const row = this.resourceManagerRow(ref);
|
|
98
101
|
return row === undefined
|
|
99
102
|
? undefined
|
|
100
|
-
: this.
|
|
103
|
+
: this.mapManagedResource(row);
|
|
101
104
|
}
|
|
102
105
|
getResourceByUid(uid) {
|
|
103
|
-
const row = this.state.connection.prepare(
|
|
104
|
-
return
|
|
106
|
+
const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
|
|
107
|
+
return isResourceManagerRow(row) ? this.mapManagedResource(row) : undefined;
|
|
105
108
|
}
|
|
106
109
|
listResources(options = {}) {
|
|
107
110
|
const clauses = ["1 = 1"];
|
|
@@ -110,31 +113,31 @@ export class SqliteControllerStore {
|
|
|
110
113
|
clauses.push("c.project_id = ?");
|
|
111
114
|
params.push(this.projectId);
|
|
112
115
|
}
|
|
113
|
-
if (options.
|
|
116
|
+
if (options.resourceManager !== undefined) {
|
|
114
117
|
clauses.push("c.controller_name = ?");
|
|
115
|
-
params.push(options.
|
|
118
|
+
params.push(options.resourceManager);
|
|
116
119
|
}
|
|
117
120
|
const rows = this.state.connection
|
|
118
|
-
.prepare(
|
|
121
|
+
.prepare(resourceManagerSelect(`WHERE ${clauses.join(" AND ")} ORDER BY c.controller_name, c.resource_key`))
|
|
119
122
|
.all(...params);
|
|
120
123
|
return rows
|
|
121
|
-
.filter(
|
|
122
|
-
.map((row) => this.
|
|
124
|
+
.filter(isResourceManagerRow)
|
|
125
|
+
.map((row) => this.mapManagedResource(row));
|
|
123
126
|
}
|
|
124
127
|
updateStatus(options) {
|
|
125
128
|
const now = epoch(validTimestamp(options.now));
|
|
126
129
|
return this.state.transaction(() => {
|
|
127
|
-
const row = this.
|
|
128
|
-
this.
|
|
130
|
+
const row = this.requireResourceManagerRow(options.ref);
|
|
131
|
+
this.assertResourceManagerClaim(row, options.claim, now);
|
|
129
132
|
if (row.resourceVersion !== options.expectedResourceVersion) {
|
|
130
|
-
throw new
|
|
133
|
+
throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
|
|
131
134
|
}
|
|
132
135
|
const statusHash = this.state.putJson(options.status, now);
|
|
133
136
|
this.state.connection
|
|
134
137
|
.prepare("UPDATE controller_resources SET status_hash = ?, updated_at = ? WHERE controller_resource_id = ?")
|
|
135
|
-
.run(statusHash, now, row.
|
|
138
|
+
.run(statusHash, now, row.managedResourceId);
|
|
136
139
|
if (options.finalizers !== undefined)
|
|
137
|
-
this.replaceFinalizers(row.
|
|
140
|
+
this.replaceFinalizers(row.managedResourceId, options.finalizers);
|
|
138
141
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
139
142
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.status_updated", "controller", options.claim.ownerId, {}, now, options.claim.generation);
|
|
140
143
|
options.claim.resourceVersion = row.resourceVersion + 1;
|
|
@@ -144,13 +147,13 @@ export class SqliteControllerStore {
|
|
|
144
147
|
requestDeletion(ref, nowValue) {
|
|
145
148
|
const now = epoch(validTimestamp(nowValue));
|
|
146
149
|
return this.state.transaction(() => {
|
|
147
|
-
const row = this.
|
|
150
|
+
const row = this.requireResourceManagerRow(ref);
|
|
148
151
|
if (row.deletionRequestedAt === null) {
|
|
149
152
|
this.state.connection
|
|
150
153
|
.prepare("UPDATE controller_resources SET deletion_requested_at = ?, updated_at = ? WHERE controller_resource_id = ?")
|
|
151
|
-
.run(now, now, row.
|
|
154
|
+
.run(now, now, row.managedResourceId);
|
|
152
155
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
153
|
-
this.
|
|
156
|
+
this.enqueueResourceManagerRow(row.managedResourceId, now, now);
|
|
154
157
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.deletion_requested", "control", null, {}, now);
|
|
155
158
|
}
|
|
156
159
|
return this.requireResource(ref);
|
|
@@ -159,14 +162,14 @@ export class SqliteControllerStore {
|
|
|
159
162
|
updateFinalizers(options) {
|
|
160
163
|
const now = epoch(validTimestamp(options.now));
|
|
161
164
|
return this.state.transaction(() => {
|
|
162
|
-
const row = this.
|
|
165
|
+
const row = this.requireResourceManagerRow(options.ref);
|
|
163
166
|
if (row.resourceVersion !== options.expectedResourceVersion) {
|
|
164
|
-
throw new
|
|
167
|
+
throw new ManagedResourceConflictError(options.ref.resourceManager, options.ref.key);
|
|
165
168
|
}
|
|
166
|
-
this.replaceFinalizers(row.
|
|
169
|
+
this.replaceFinalizers(row.managedResourceId, options.finalizers);
|
|
167
170
|
this.state.connection
|
|
168
171
|
.prepare("UPDATE controller_resources SET updated_at = ? WHERE controller_resource_id = ?")
|
|
169
|
-
.run(now, row.
|
|
172
|
+
.run(now, row.managedResourceId);
|
|
170
173
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
171
174
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "resource.finalizers_updated", "control", null, {}, now);
|
|
172
175
|
return this.requireResource(options.ref);
|
|
@@ -174,37 +177,37 @@ export class SqliteControllerStore {
|
|
|
174
177
|
}
|
|
175
178
|
deleteResource(ref, expectedResourceVersion, claim) {
|
|
176
179
|
return this.state.transaction(() => {
|
|
177
|
-
const row = this.
|
|
178
|
-
this.
|
|
180
|
+
const row = this.requireResourceManagerRow(ref);
|
|
181
|
+
this.assertResourceManagerClaim(row, claim, Date.now());
|
|
179
182
|
if (row.resourceVersion !== expectedResourceVersion) {
|
|
180
|
-
throw new
|
|
183
|
+
throw new ManagedResourceConflictError(ref.resourceManager, ref.key);
|
|
181
184
|
}
|
|
182
|
-
if (row.deletionRequestedAt === null || this.finalizers(row.
|
|
185
|
+
if (row.deletionRequestedAt === null || this.finalizers(row.managedResourceId).length > 0)
|
|
183
186
|
return false;
|
|
184
187
|
this.state.connection
|
|
185
188
|
.prepare("DELETE FROM controller_resources WHERE controller_resource_id = ?")
|
|
186
|
-
.run(row.
|
|
189
|
+
.run(row.managedResourceId);
|
|
187
190
|
return true;
|
|
188
191
|
});
|
|
189
192
|
}
|
|
190
193
|
enqueue(ref, availableAt) {
|
|
191
|
-
const row = this.
|
|
194
|
+
const row = this.requireResourceManagerRow(ref);
|
|
192
195
|
const now = Date.now();
|
|
193
|
-
this.
|
|
196
|
+
this.enqueueResourceManagerRow(row.managedResourceId, epoch(validTimestamp(availableAt)), now);
|
|
194
197
|
}
|
|
195
198
|
claimNext(options) {
|
|
196
|
-
if (options.
|
|
199
|
+
if (options.resourceManagers.length === 0)
|
|
197
200
|
return undefined;
|
|
198
201
|
const now = epoch(validTimestamp(options.now));
|
|
199
|
-
const placeholders = options.
|
|
202
|
+
const placeholders = options.resourceManagers.map(() => "?").join(", ");
|
|
200
203
|
const exclusions = options.exclude ?? [];
|
|
201
204
|
const exclusionSql = exclusions
|
|
202
205
|
.map(() => "AND NOT (c.controller_name = ? AND c.resource_key = ?)")
|
|
203
206
|
.join("\n ");
|
|
204
|
-
const exclusionParams = exclusions.flatMap((ref) => [ref.
|
|
207
|
+
const exclusionParams = exclusions.flatMap((ref) => [ref.resourceManager, ref.key]);
|
|
205
208
|
return this.state.transaction(() => {
|
|
206
209
|
const row = this.state.connection
|
|
207
|
-
.prepare(`${
|
|
210
|
+
.prepare(`${resourceManagerSelect(`
|
|
208
211
|
JOIN controller_queue q ON q.controller_resource_id = c.controller_resource_id
|
|
209
212
|
JOIN leases l ON l.resource_id = c.resource_id
|
|
210
213
|
WHERE c.project_id = ?
|
|
@@ -214,8 +217,8 @@ export class SqliteControllerStore {
|
|
|
214
217
|
AND (l.owner_id IS NULL OR l.expires_at <= ?)
|
|
215
218
|
ORDER BY q.available_at, c.controller_name, c.resource_key
|
|
216
219
|
LIMIT 1`)}`)
|
|
217
|
-
.get(this.requireProjectId(), ...options.
|
|
218
|
-
if (!
|
|
220
|
+
.get(this.requireProjectId(), ...options.resourceManagers, ...exclusionParams, now, now);
|
|
221
|
+
if (!isResourceManagerRow(row))
|
|
219
222
|
return undefined;
|
|
220
223
|
const lease = this.requireLease(row.resourceId);
|
|
221
224
|
const token = randomBytes(32).toString("base64url");
|
|
@@ -234,14 +237,14 @@ export class SqliteControllerStore {
|
|
|
234
237
|
this.bumpResource(row.resourceId, row.resourceVersion, now);
|
|
235
238
|
this.insertEvent(row.resourceId, row.resourceVersion + 1, "lease.claimed", "controller", options.ownerId, { expiresAt }, now, generation);
|
|
236
239
|
return {
|
|
237
|
-
|
|
240
|
+
resourceManager: row.resourceManagerName,
|
|
238
241
|
key: row.resourceKey,
|
|
239
242
|
ownerId: options.ownerId,
|
|
240
243
|
token,
|
|
241
244
|
generation,
|
|
242
|
-
queueVersion: this.queueVersion(row.
|
|
245
|
+
queueVersion: this.queueVersion(row.managedResourceId),
|
|
243
246
|
resourceVersion: row.resourceVersion + 1,
|
|
244
|
-
consecutiveErrors: this.queueErrors(row.
|
|
247
|
+
consecutiveErrors: this.queueErrors(row.managedResourceId),
|
|
245
248
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
246
249
|
};
|
|
247
250
|
});
|
|
@@ -249,7 +252,7 @@ export class SqliteControllerStore {
|
|
|
249
252
|
renewClaim(claim, leaseMs, nowValue) {
|
|
250
253
|
const now = epoch(validTimestamp(nowValue));
|
|
251
254
|
const expiresAt = now + leaseMs;
|
|
252
|
-
const row = this.
|
|
255
|
+
const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
|
|
253
256
|
if (row === undefined)
|
|
254
257
|
return false;
|
|
255
258
|
const result = this.state.connection
|
|
@@ -262,14 +265,14 @@ export class SqliteControllerStore {
|
|
|
262
265
|
return result.changes === 1;
|
|
263
266
|
}
|
|
264
267
|
settleClaim(claim, nowValue) {
|
|
265
|
-
return this.
|
|
268
|
+
return this.settleResourceManagerClaim(claim, undefined, nowValue);
|
|
266
269
|
}
|
|
267
270
|
requeueClaim(claim, options, nowValue) {
|
|
268
|
-
return this.
|
|
271
|
+
return this.settleResourceManagerClaim(claim, options, nowValue);
|
|
269
272
|
}
|
|
270
273
|
listQueue() {
|
|
271
274
|
const rows = this.state.connection
|
|
272
|
-
.prepare(`SELECT c.controller_name AS
|
|
275
|
+
.prepare(`SELECT c.controller_name AS resourceManager, c.resource_key AS resourceKey,
|
|
273
276
|
q.available_at AS availableAt, q.consecutive_errors AS consecutiveErrors,
|
|
274
277
|
l.expires_at AS claimExpiresAt
|
|
275
278
|
FROM controller_queue q
|
|
@@ -278,7 +281,7 @@ export class SqliteControllerStore {
|
|
|
278
281
|
WHERE c.project_id = ? ORDER BY q.available_at, c.controller_name, c.resource_key`)
|
|
279
282
|
.all(this.requireProjectId());
|
|
280
283
|
return rows.filter(isQueueListRow).map((row) => ({
|
|
281
|
-
|
|
284
|
+
resourceManager: row.resourceManager,
|
|
282
285
|
key: row.resourceKey,
|
|
283
286
|
availableAt: new Date(row.availableAt).toISOString(),
|
|
284
287
|
consecutiveErrors: row.consecutiveErrors,
|
|
@@ -290,8 +293,8 @@ export class SqliteControllerStore {
|
|
|
290
293
|
reserveEffect(options) {
|
|
291
294
|
const now = epoch(validTimestamp(options.now));
|
|
292
295
|
return this.state.transaction(() => {
|
|
293
|
-
const source = this.
|
|
294
|
-
this.
|
|
296
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
297
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
295
298
|
const effectId = effectIdFor(source.resourceId, options.key);
|
|
296
299
|
const existing = this.effectRow(options.resourceUid, options.key);
|
|
297
300
|
if (existing !== undefined) {
|
|
@@ -338,8 +341,8 @@ export class SqliteControllerStore {
|
|
|
338
341
|
updateEffect(options) {
|
|
339
342
|
const now = epoch(validTimestamp(options.now));
|
|
340
343
|
return this.state.transaction(() => {
|
|
341
|
-
const source = this.
|
|
342
|
-
this.
|
|
344
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
345
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
343
346
|
const row = this.requireEffectRow(options.resourceUid, options.key);
|
|
344
347
|
const effectResourceId = resourceIdFor("effect", row.effectId);
|
|
345
348
|
const revision = this.resourceRevision(effectResourceId);
|
|
@@ -363,8 +366,8 @@ export class SqliteControllerStore {
|
|
|
363
366
|
reserveWorkflow(options) {
|
|
364
367
|
const now = Date.now();
|
|
365
368
|
return this.state.transaction(() => {
|
|
366
|
-
const source = this.
|
|
367
|
-
this.
|
|
369
|
+
const source = this.requireResourceManagerRowByUid(options.resourceUid);
|
|
370
|
+
this.assertResourceManagerClaim(source, options.claim, now);
|
|
368
371
|
const existing = this.workflowRow(options.resourceUid, options.requestKey);
|
|
369
372
|
if (existing !== undefined) {
|
|
370
373
|
if (existing.workflowName !== options.workflow ||
|
|
@@ -379,7 +382,7 @@ export class SqliteControllerStore {
|
|
|
379
382
|
request_id, controller_resource_id, request_key, workflow_name,
|
|
380
383
|
input_fingerprint, status, attempt_count, created_at, updated_at
|
|
381
384
|
) VALUES (?, ?, ?, ?, ?, 'pending', 0, ?, ?)`)
|
|
382
|
-
.run(requestId, source.
|
|
385
|
+
.run(requestId, source.managedResourceId, options.requestKey, options.workflow, Buffer.from(options.inputFingerprint, "hex"), now, now);
|
|
383
386
|
return { record: this.mapWorkflow(this.requireWorkflowRow(requestId)), created: true };
|
|
384
387
|
});
|
|
385
388
|
}
|
|
@@ -395,8 +398,8 @@ export class SqliteControllerStore {
|
|
|
395
398
|
const now = Date.now();
|
|
396
399
|
return this.state.transaction(() => {
|
|
397
400
|
const row = this.requireWorkflowRow(requestId);
|
|
398
|
-
const source = this.
|
|
399
|
-
this.
|
|
401
|
+
const source = this.requireResourceManagerRowByUid(row.resourceUid);
|
|
402
|
+
this.assertResourceManagerClaim(source, claim, now);
|
|
400
403
|
const errorHash = update.error === undefined || update.error === null
|
|
401
404
|
? null
|
|
402
405
|
: this.state.putText(update.error, now);
|
|
@@ -422,7 +425,7 @@ export class SqliteControllerStore {
|
|
|
422
425
|
if (update.runId !== undefined && row.runId !== null && update.runId !== row.runId) {
|
|
423
426
|
throw new Error("Scheduler completion run ID does not match the reserved child run");
|
|
424
427
|
}
|
|
425
|
-
const source = this.
|
|
428
|
+
const source = this.requireResourceManagerRowByUid(row.resourceUid);
|
|
426
429
|
const now = Date.now();
|
|
427
430
|
return this.state.transaction(() => {
|
|
428
431
|
const errorHash = update.error === undefined || update.error === null
|
|
@@ -453,9 +456,12 @@ export class SqliteControllerStore {
|
|
|
453
456
|
recordEvent(options) {
|
|
454
457
|
const now = epoch(validTimestamp(options.now));
|
|
455
458
|
return this.state.transaction(() => {
|
|
456
|
-
const row = this.
|
|
459
|
+
const row = this.requireResourceManagerRow({
|
|
460
|
+
resourceManager: options.resourceManager,
|
|
461
|
+
key: options.key,
|
|
462
|
+
});
|
|
457
463
|
if (options.claim !== undefined)
|
|
458
|
-
this.
|
|
464
|
+
this.assertResourceManagerClaim(row, options.claim, now);
|
|
459
465
|
const revision = this.resourceRevision(row.resourceId) + 1;
|
|
460
466
|
this.bumpResource(row.resourceId, revision - 1, now);
|
|
461
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);
|
|
@@ -466,11 +472,11 @@ export class SqliteControllerStore {
|
|
|
466
472
|
.get(eventId);
|
|
467
473
|
/* istanbul ignore if -- exact schema and internal query shape */
|
|
468
474
|
if (!isSequenceRow(event))
|
|
469
|
-
throw new Error("
|
|
475
|
+
throw new Error("ResourceManager event was not recorded");
|
|
470
476
|
return {
|
|
471
477
|
seq: event.seq,
|
|
472
478
|
recordedAt: new Date(now).toISOString(),
|
|
473
|
-
|
|
479
|
+
resourceManager: options.resourceManager,
|
|
474
480
|
key: options.key,
|
|
475
481
|
type: options.type,
|
|
476
482
|
payload: options.payload ?? {},
|
|
@@ -484,9 +490,9 @@ export class SqliteControllerStore {
|
|
|
484
490
|
clauses.push("c.project_id = ?");
|
|
485
491
|
params.push(this.projectId);
|
|
486
492
|
}
|
|
487
|
-
if (options.
|
|
493
|
+
if (options.resourceManager !== undefined) {
|
|
488
494
|
clauses.push("c.controller_name = ?");
|
|
489
|
-
params.push(options.
|
|
495
|
+
params.push(options.resourceManager);
|
|
490
496
|
}
|
|
491
497
|
if (options.key !== undefined) {
|
|
492
498
|
clauses.push("c.resource_key = ?");
|
|
@@ -495,7 +501,7 @@ export class SqliteControllerStore {
|
|
|
495
501
|
params.push(options.limit ?? 100);
|
|
496
502
|
const rows = this.state.connection
|
|
497
503
|
.prepare(`SELECT e.event_seq AS seq, e.recorded_at AS recordedAt,
|
|
498
|
-
c.controller_name AS
|
|
504
|
+
c.controller_name AS resourceManager, c.resource_key AS resourceKey,
|
|
499
505
|
e.event_type AS eventType, e.payload_hash AS payloadHash
|
|
500
506
|
FROM events e
|
|
501
507
|
JOIN resources r ON r.resource_id = e.resource_id
|
|
@@ -503,10 +509,10 @@ export class SqliteControllerStore {
|
|
|
503
509
|
WHERE ${clauses.join(" AND ")}
|
|
504
510
|
ORDER BY e.event_seq DESC LIMIT ?`)
|
|
505
511
|
.all(...params);
|
|
506
|
-
return rows.filter(
|
|
512
|
+
return rows.filter(isResourceManagerEventRow).map((row) => ({
|
|
507
513
|
seq: row.seq,
|
|
508
514
|
recordedAt: new Date(row.recordedAt).toISOString(),
|
|
509
|
-
|
|
515
|
+
resourceManager: row.resourceManager,
|
|
510
516
|
key: row.resourceKey,
|
|
511
517
|
type: row.eventType,
|
|
512
518
|
payload: row.payloadHash === null ? {} : this.state.readJson(row.payloadHash),
|
|
@@ -750,14 +756,14 @@ export class SqliteControllerStore {
|
|
|
750
756
|
const now = epoch(validTimestamp(options.now));
|
|
751
757
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now);
|
|
752
758
|
}
|
|
753
|
-
/** Schedule a
|
|
759
|
+
/** Schedule a runner that validates one pending interactive submission. */
|
|
754
760
|
claimWorkflowRunForInteractionValidation(options) {
|
|
755
761
|
const now = epoch(validTimestamp(options.now));
|
|
756
762
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now, {
|
|
757
763
|
allowPendingInteraction: true,
|
|
758
764
|
});
|
|
759
765
|
}
|
|
760
|
-
/** Take a short
|
|
766
|
+
/** Take a short server claim without scheduling a parked interactive run. */
|
|
761
767
|
claimWorkflowRunForControl(options) {
|
|
762
768
|
const now = epoch(validTimestamp(options.now));
|
|
763
769
|
return this.claimRun(options.runId, options.runnerId, options.claimToken, options.leaseMs, now, { allowPendingInteraction: true, preserveQueueStatus: true });
|
|
@@ -836,7 +842,7 @@ export class SqliteControllerStore {
|
|
|
836
842
|
"(q.affinity_runner_id IS NULL OR q.affinity_runner_id = ? OR q.status IN ('parked', 'starting', 'running'))",
|
|
837
843
|
"NOT (q.status = 'parked' AND (r.status = 'waiting' OR r.paused = 1))",
|
|
838
844
|
"NOT EXISTS (SELECT 1 FROM interactive_requests i WHERE i.run_id = r.run_id AND i.status = 'pending')",
|
|
839
|
-
"(q.error_code IS NULL OR q.error_code NOT IN ('workflowSourceChanged', '
|
|
845
|
+
"(q.error_code IS NULL OR q.error_code NOT IN ('workflowSourceChanged', 'runnerNoProgress'))",
|
|
840
846
|
];
|
|
841
847
|
const params = [now, now, options.runnerId, options.runnerId];
|
|
842
848
|
if (this.projectId !== null) {
|
|
@@ -910,7 +916,7 @@ export class SqliteControllerStore {
|
|
|
910
916
|
parkWorkflowRun(options) {
|
|
911
917
|
return this.releaseRunClaim(options.runId, options.claimToken, "parked", options.now);
|
|
912
918
|
}
|
|
913
|
-
|
|
919
|
+
parkWorkflowRunForRunnerNoProgress(options) {
|
|
914
920
|
const now = epoch(validTimestamp(options.now));
|
|
915
921
|
return this.state.transaction(() => {
|
|
916
922
|
if (!this.verifyWorkflowRunClaim({
|
|
@@ -930,12 +936,12 @@ export class SqliteControllerStore {
|
|
|
930
936
|
.run(detail, now, options.runId);
|
|
931
937
|
const queue = this.state.connection
|
|
932
938
|
.prepare(`UPDATE run_queue
|
|
933
|
-
SET status = 'parked', error_code = '
|
|
939
|
+
SET status = 'parked', error_code = 'runnerNoProgress', error_hash = ?,
|
|
934
940
|
available_at = ?, updated_at = ?, finished_at = NULL
|
|
935
941
|
WHERE run_id = ? AND status IN ('queued', 'starting', 'running', 'parked')`)
|
|
936
942
|
.run(errorHash, now, now, options.runId);
|
|
937
943
|
if (run.changes !== 1 || queue.changes !== 1) {
|
|
938
|
-
throw new Error(`Workflow run ${options.runId} changed during
|
|
944
|
+
throw new Error(`Workflow run ${options.runId} changed during runner recovery`);
|
|
939
945
|
}
|
|
940
946
|
const released = this.state.connection
|
|
941
947
|
.prepare(`UPDATE leases
|
|
@@ -945,11 +951,11 @@ export class SqliteControllerStore {
|
|
|
945
951
|
.run(row.resourceId, tokenHash(options.claimToken), lease.generation, now);
|
|
946
952
|
/* istanbul ignore if -- the exact live claim is stable in this transaction */
|
|
947
953
|
if (released.changes !== 1) {
|
|
948
|
-
throw new Error(`Workflow run ${options.runId} claim changed during
|
|
954
|
+
throw new Error(`Workflow run ${options.runId} claim changed during runner recovery`);
|
|
949
955
|
}
|
|
950
956
|
const revision = this.resourceRevision(row.resourceId);
|
|
951
957
|
this.bumpResource(row.resourceId, revision, now);
|
|
952
|
-
this.insertEvent(row.resourceId, revision + 1, "run.worker_no_progress", lease.ownerType ?? "system", lease.ownerId, { status: "parked", code: "
|
|
958
|
+
this.insertEvent(row.resourceId, revision + 1, "run.worker_no_progress", lease.ownerType ?? "system", lease.ownerId, { status: "parked", code: "runnerNoProgress" }, now, lease.generation || undefined);
|
|
953
959
|
recordViewerDeltas(this.state, options.runId, [{ targetType: "summary" }, { targetType: "replay" }], now);
|
|
954
960
|
return true;
|
|
955
961
|
});
|
|
@@ -1309,73 +1315,73 @@ export class SqliteControllerStore {
|
|
|
1309
1315
|
}
|
|
1310
1316
|
requireProjectId() {
|
|
1311
1317
|
if (this.projectId === null)
|
|
1312
|
-
throw new Error("
|
|
1318
|
+
throw new Error("ResourceManager project is not registered in the state database");
|
|
1313
1319
|
return this.projectId;
|
|
1314
1320
|
}
|
|
1315
|
-
|
|
1321
|
+
resourceManagerRow(ref) {
|
|
1316
1322
|
if (this.projectId === null)
|
|
1317
1323
|
return undefined;
|
|
1318
1324
|
const row = this.state.connection
|
|
1319
|
-
.prepare(
|
|
1320
|
-
.get(this.projectId, ref.
|
|
1321
|
-
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;
|
|
1322
1328
|
}
|
|
1323
|
-
|
|
1324
|
-
const row = this.
|
|
1329
|
+
requireResourceManagerRow(ref) {
|
|
1330
|
+
const row = this.resourceManagerRow(ref);
|
|
1325
1331
|
if (row === undefined)
|
|
1326
|
-
throw new
|
|
1332
|
+
throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
|
|
1327
1333
|
return row;
|
|
1328
1334
|
}
|
|
1329
|
-
|
|
1330
|
-
const row = this.state.connection.prepare(
|
|
1335
|
+
requireResourceManagerRowByUid(uid) {
|
|
1336
|
+
const row = this.state.connection.prepare(resourceManagerSelect("WHERE c.uid = ?")).get(uid);
|
|
1331
1337
|
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
1332
|
-
if (!
|
|
1333
|
-
throw new Error(`
|
|
1338
|
+
if (!isResourceManagerRow(row))
|
|
1339
|
+
throw new Error(`Managed resource not found: ${uid}`);
|
|
1334
1340
|
return row;
|
|
1335
1341
|
}
|
|
1336
1342
|
requireResource(ref) {
|
|
1337
1343
|
const resource = this.getResource(ref);
|
|
1338
1344
|
if (resource === undefined)
|
|
1339
|
-
throw new
|
|
1345
|
+
throw new ManagedResourceNotFoundError(ref.resourceManager, ref.key);
|
|
1340
1346
|
return resource;
|
|
1341
1347
|
}
|
|
1342
1348
|
/* istanbul ignore next -- pure projection covered by integration tests */
|
|
1343
|
-
|
|
1349
|
+
mapManagedResource(row) {
|
|
1344
1350
|
const spec = this.state.readJson(row.specHash);
|
|
1345
1351
|
const status = this.state.readJson(row.statusHash);
|
|
1346
1352
|
return {
|
|
1347
1353
|
metadata: {
|
|
1348
1354
|
uid: row.uid,
|
|
1349
|
-
|
|
1355
|
+
resourceManager: row.resourceManagerName,
|
|
1350
1356
|
key: row.resourceKey,
|
|
1351
1357
|
resourceVersion: row.resourceVersion,
|
|
1352
1358
|
generation: row.generation,
|
|
1353
1359
|
...(row.deletionRequestedAt === null
|
|
1354
1360
|
? {}
|
|
1355
1361
|
: { deletionTimestamp: new Date(row.deletionRequestedAt).toISOString() }),
|
|
1356
|
-
finalizers: this.finalizers(row.
|
|
1362
|
+
finalizers: this.finalizers(row.managedResourceId),
|
|
1357
1363
|
},
|
|
1358
1364
|
spec,
|
|
1359
1365
|
status,
|
|
1360
1366
|
};
|
|
1361
1367
|
}
|
|
1362
|
-
finalizers(
|
|
1368
|
+
finalizers(managedResourceId) {
|
|
1363
1369
|
const rows = this.state.connection
|
|
1364
1370
|
.prepare("SELECT finalizer FROM controller_finalizers WHERE controller_resource_id = ? ORDER BY position")
|
|
1365
|
-
.all(
|
|
1371
|
+
.all(managedResourceId);
|
|
1366
1372
|
return rows.flatMap((row) => (isFinalizerRow(row) ? [row.finalizer] : []));
|
|
1367
1373
|
}
|
|
1368
|
-
replaceFinalizers(
|
|
1374
|
+
replaceFinalizers(managedResourceId, finalizers) {
|
|
1369
1375
|
const unique = [...new Set(finalizers)];
|
|
1370
1376
|
if (unique.length !== finalizers.length)
|
|
1371
|
-
throw new Error("
|
|
1377
|
+
throw new Error("ResourceManager finalizers must be unique");
|
|
1372
1378
|
this.state.connection
|
|
1373
1379
|
.prepare("DELETE FROM controller_finalizers WHERE controller_resource_id = ?")
|
|
1374
|
-
.run(
|
|
1380
|
+
.run(managedResourceId);
|
|
1375
1381
|
const insert = this.state.connection.prepare("INSERT INTO controller_finalizers(controller_resource_id, finalizer, position) VALUES (?, ?, ?)");
|
|
1376
|
-
unique.forEach((finalizer, index) => insert.run(
|
|
1382
|
+
unique.forEach((finalizer, index) => insert.run(managedResourceId, finalizer, index));
|
|
1377
1383
|
}
|
|
1378
|
-
|
|
1384
|
+
enqueueResourceManagerRow(managedResourceId, availableAt, now) {
|
|
1379
1385
|
this.state.connection
|
|
1380
1386
|
.prepare(`INSERT INTO controller_queue(
|
|
1381
1387
|
controller_resource_id, available_at, queue_version,
|
|
@@ -1385,42 +1391,45 @@ export class SqliteControllerStore {
|
|
|
1385
1391
|
available_at = MIN(controller_queue.available_at, excluded.available_at),
|
|
1386
1392
|
queue_version = controller_queue.queue_version + 1,
|
|
1387
1393
|
updated_at = excluded.updated_at`)
|
|
1388
|
-
.run(
|
|
1394
|
+
.run(managedResourceId, availableAt, now, now);
|
|
1389
1395
|
}
|
|
1390
|
-
queueErrors(
|
|
1396
|
+
queueErrors(managedResourceId) {
|
|
1391
1397
|
const row = this.state.connection
|
|
1392
1398
|
.prepare("SELECT consecutive_errors AS consecutiveErrors FROM controller_queue WHERE controller_resource_id = ?")
|
|
1393
|
-
.get(
|
|
1399
|
+
.get(managedResourceId);
|
|
1394
1400
|
return isErrorCountRow(row) ? row.consecutiveErrors : 0;
|
|
1395
1401
|
}
|
|
1396
|
-
queueVersion(
|
|
1402
|
+
queueVersion(managedResourceId) {
|
|
1397
1403
|
const row = this.state.connection
|
|
1398
1404
|
.prepare("SELECT queue_version AS queueVersion FROM controller_queue WHERE controller_resource_id = ?")
|
|
1399
|
-
.get(
|
|
1405
|
+
.get(managedResourceId);
|
|
1400
1406
|
/* istanbul ignore if -- exact schema and internal query shape */
|
|
1401
1407
|
/* istanbul ignore if -- impossible after exact schema and transaction checks */
|
|
1402
1408
|
if (!isQueueVersionRow(row))
|
|
1403
|
-
throw new Error("
|
|
1409
|
+
throw new Error("ResourceManager queue item is missing");
|
|
1404
1410
|
return row.queueVersion;
|
|
1405
1411
|
}
|
|
1406
|
-
|
|
1412
|
+
settleResourceManagerClaim(claim, requeue, nowValue) {
|
|
1407
1413
|
const now = epoch(validTimestamp(nowValue));
|
|
1408
1414
|
return this.state.transaction(() => {
|
|
1409
|
-
const row = this.
|
|
1415
|
+
const row = this.resourceManagerRow({
|
|
1416
|
+
resourceManager: claim.resourceManager,
|
|
1417
|
+
key: claim.key,
|
|
1418
|
+
});
|
|
1410
1419
|
if (row === undefined)
|
|
1411
1420
|
return false;
|
|
1412
1421
|
try {
|
|
1413
|
-
this.
|
|
1422
|
+
this.assertResourceManagerClaim(row, claim, now, false);
|
|
1414
1423
|
}
|
|
1415
1424
|
catch {
|
|
1416
1425
|
return false;
|
|
1417
1426
|
}
|
|
1418
|
-
const currentQueueVersion = this.queueVersion(row.
|
|
1427
|
+
const currentQueueVersion = this.queueVersion(row.managedResourceId);
|
|
1419
1428
|
if (requeue === undefined) {
|
|
1420
1429
|
if (currentQueueVersion === claim.queueVersion) {
|
|
1421
1430
|
this.state.connection
|
|
1422
1431
|
.prepare("DELETE FROM controller_queue WHERE controller_resource_id = ?")
|
|
1423
|
-
.run(row.
|
|
1432
|
+
.run(row.managedResourceId);
|
|
1424
1433
|
}
|
|
1425
1434
|
}
|
|
1426
1435
|
else {
|
|
@@ -1430,15 +1439,15 @@ export class SqliteControllerStore {
|
|
|
1430
1439
|
SET available_at = ?, queue_version = queue_version + 1,
|
|
1431
1440
|
consecutive_errors = consecutive_errors + 1,
|
|
1432
1441
|
last_error_hash = ?, updated_at = ? WHERE controller_resource_id = ?`)
|
|
1433
|
-
.run(epoch(requeue.availableAt), errorHash, now, row.
|
|
1442
|
+
.run(epoch(requeue.availableAt), errorHash, now, row.managedResourceId);
|
|
1434
1443
|
}
|
|
1435
1444
|
this.releaseLease(row.resourceId, claim, now);
|
|
1436
1445
|
return true;
|
|
1437
1446
|
});
|
|
1438
1447
|
}
|
|
1439
|
-
|
|
1440
|
-
if (row.
|
|
1441
|
-
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");
|
|
1442
1451
|
const lease = this.requireLease(row.resourceId);
|
|
1443
1452
|
if (lease.ownerType !== "controller" ||
|
|
1444
1453
|
lease.ownerId !== claim.ownerId ||
|
|
@@ -1446,7 +1455,7 @@ export class SqliteControllerStore {
|
|
|
1446
1455
|
lease.tokenHash === null ||
|
|
1447
1456
|
!lease.tokenHash.equals(tokenHash(claim.token)) ||
|
|
1448
1457
|
(requireUnexpired && (lease.expiresAt === null || lease.expiresAt <= now))) {
|
|
1449
|
-
throw new Error("
|
|
1458
|
+
throw new Error("ResourceManager claim is stale");
|
|
1450
1459
|
}
|
|
1451
1460
|
}
|
|
1452
1461
|
requireLease(resourceId) {
|
|
@@ -1462,7 +1471,7 @@ export class SqliteControllerStore {
|
|
|
1462
1471
|
return row;
|
|
1463
1472
|
}
|
|
1464
1473
|
releaseLease(resourceId, claim, now) {
|
|
1465
|
-
const row = this.
|
|
1474
|
+
const row = this.resourceManagerRow({ resourceManager: claim.resourceManager, key: claim.key });
|
|
1466
1475
|
if (row === undefined)
|
|
1467
1476
|
return;
|
|
1468
1477
|
const revision = this.resourceRevision(resourceId);
|
|
@@ -1537,7 +1546,7 @@ export class SqliteControllerStore {
|
|
|
1537
1546
|
resourceUid: row.resourceUid,
|
|
1538
1547
|
generation: row.generation,
|
|
1539
1548
|
kind: row.kind,
|
|
1540
|
-
state:
|
|
1549
|
+
state: resourceManagerEffectState(row.state),
|
|
1541
1550
|
requestFingerprint: row.requestFingerprint,
|
|
1542
1551
|
startedAt: new Date(row.startedAt).toISOString(),
|
|
1543
1552
|
...(row.completedAt === null ? {} : { completedAt: new Date(row.completedAt).toISOString() }),
|
|
@@ -1893,13 +1902,13 @@ export class SqliteControllerStore {
|
|
|
1893
1902
|
}
|
|
1894
1903
|
assertWritable() {
|
|
1895
1904
|
if (this.state.mode === "read-only")
|
|
1896
|
-
throw new Error("
|
|
1905
|
+
throw new Error("ResourceManager store is read-only");
|
|
1897
1906
|
}
|
|
1898
1907
|
}
|
|
1899
|
-
function
|
|
1900
|
-
return `SELECT c.controller_resource_id AS
|
|
1908
|
+
function resourceManagerSelect(clause) {
|
|
1909
|
+
return `SELECT c.controller_resource_id AS managedResourceId,
|
|
1901
1910
|
c.resource_id AS resourceId, r.revision AS resourceVersion,
|
|
1902
|
-
c.controller_name AS
|
|
1911
|
+
c.controller_name AS resourceManagerName, c.resource_key AS resourceKey,
|
|
1903
1912
|
c.uid, c.generation, c.spec_hash AS specHash, c.status_hash AS statusHash,
|
|
1904
1913
|
c.deletion_requested_at AS deletionRequestedAt
|
|
1905
1914
|
FROM controller_resources c JOIN resources r ON r.resource_id = c.resource_id ${clause}`;
|
|
@@ -1965,7 +1974,7 @@ function workflowRunViewSelect(clause) {
|
|
|
1965
1974
|
function isRecord(value) {
|
|
1966
1975
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1967
1976
|
}
|
|
1968
|
-
function
|
|
1977
|
+
function isResourceManagerRow(value) {
|
|
1969
1978
|
return isRecord(value);
|
|
1970
1979
|
}
|
|
1971
1980
|
function isLeaseRow(value) {
|
|
@@ -2060,7 +2069,7 @@ function isWorkflowRow(value) {
|
|
|
2060
2069
|
function isQueueListRow(value) {
|
|
2061
2070
|
return isRecord(value);
|
|
2062
2071
|
}
|
|
2063
|
-
function
|
|
2072
|
+
function isResourceManagerEventRow(value) {
|
|
2064
2073
|
return isRecord(value);
|
|
2065
2074
|
}
|
|
2066
2075
|
function isRunEventRow(value) {
|
|
@@ -2204,7 +2213,7 @@ function rowToSource(row) {
|
|
|
2204
2213
|
function effectStatus(value) {
|
|
2205
2214
|
return value === "indeterminate" ? "ambiguous" : value;
|
|
2206
2215
|
}
|
|
2207
|
-
function
|
|
2216
|
+
function resourceManagerEffectState(value) {
|
|
2208
2217
|
return value === "ambiguous" ? "indeterminate" : value;
|
|
2209
2218
|
}
|
|
2210
2219
|
function validTimestamp(value) {
|