@deksden-com/dd-flow-cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +10 -3
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +24 -11
- package/dist/cli/run-cli.js +117 -28
- package/dist/domain/flow-contract.js +15 -4
- package/dist/domain/session-coverage.js +88 -0
- package/dist/runtime/context.js +8 -2
- package/dist/schemas/code-stage-report.schema.json +7 -2
- package/dist/schemas/engine-manifest.schema.json +22 -0
- package/dist/schemas/flow-contract.schema.json +15 -13
- package/dist/schemas/flow-run.schema.json +1 -0
- package/dist/schemas/mb-upgrade-migration-report.schema.json +3 -1
- package/dist/schemas/merge-stage-report-legacy-0.4.2.schema.json +24 -0
- package/dist/schemas/run-engine-binding.schema.json +37 -0
- package/dist/schemas/stage-prompt.schema.json +9 -5
- package/dist/schemas/stage-start-response.schema.json +6 -5
- package/dist/services/canon.js +15 -1
- package/dist/services/cleanup.js +77 -0
- package/dist/services/cli-operation-classifier.js +52 -8
- package/dist/services/compatibility-preflight.js +1 -1
- package/dist/services/dashboard.js +2 -2
- package/dist/services/engines.js +408 -30
- package/dist/services/hooks.js +28 -15
- package/dist/services/lanes.js +0 -4
- package/dist/services/merge-queue.js +48 -0
- package/dist/services/merge-worker.js +3 -4
- package/dist/services/migrations.js +307 -44
- package/dist/services/plan-runtime.js +4 -4
- package/dist/services/plans.js +5 -3
- package/dist/services/protocols.js +23 -2
- package/dist/services/run-engine-bindings.js +157 -0
- package/dist/services/run-projection.js +18 -4
- package/dist/services/runs.js +54 -7
- package/dist/services/schema-validation.js +96 -0
- package/dist/services/sessions.js +33 -73
- package/dist/services/stage-lifecycle.js +298 -45
- package/dist/services/status.js +8 -3
- package/dist/storage/database.js +32 -11
- package/package.json +1 -1
package/dist/runtime/context.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { getDatabase } from "../storage/database.js";
|
|
2
2
|
import { resolveDdFlowHome } from "../storage/paths.js";
|
|
3
|
-
export function
|
|
3
|
+
export function createRouterContext(env) {
|
|
4
|
+
return {
|
|
5
|
+
ddFlowHome: resolveDdFlowHome(env),
|
|
6
|
+
now: () => new Date().toISOString()
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function createContext(env, mode = "initialize") {
|
|
4
10
|
const ddFlowHome = resolveDdFlowHome(env);
|
|
5
11
|
return {
|
|
6
12
|
ddFlowHome,
|
|
7
|
-
db: getDatabase(ddFlowHome),
|
|
13
|
+
db: getDatabase(ddFlowHome, mode),
|
|
8
14
|
env,
|
|
9
15
|
now: () => new Date().toISOString()
|
|
10
16
|
};
|
|
@@ -124,10 +124,15 @@
|
|
|
124
124
|
"run": {
|
|
125
125
|
"type": "object",
|
|
126
126
|
"additionalProperties": true,
|
|
127
|
-
"required": ["run_id"
|
|
127
|
+
"required": ["run_id"],
|
|
128
|
+
"anyOf": [
|
|
129
|
+
{"required": ["run_state"]},
|
|
130
|
+
{"required": ["run_index"]}
|
|
131
|
+
],
|
|
128
132
|
"properties": {
|
|
129
133
|
"run_id": { "type": "string", "pattern": "^RUN-[0-9]{3}-[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
130
|
-
"run_state": { "type": "string", "minLength": 1 }
|
|
134
|
+
"run_state": { "type": "string", "minLength": 1 },
|
|
135
|
+
"run_index": { "type": "string", "minLength": 1 }
|
|
131
136
|
}
|
|
132
137
|
},
|
|
133
138
|
"stage": {
|
|
@@ -44,6 +44,28 @@
|
|
|
44
44
|
"items": { "type": "string", "minLength": 1 }
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
|
+
"schema_registry": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["schema_id", "entries"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"schema_id": { "const": "dd-flow/engine-schema-registry@1" },
|
|
53
|
+
"entries": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"additionalProperties": false,
|
|
58
|
+
"required": ["name", "id", "path", "checksum"],
|
|
59
|
+
"properties": {
|
|
60
|
+
"name": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
|
|
61
|
+
"id": { "type": "string", "minLength": 1 },
|
|
62
|
+
"path": { "type": "string", "pattern": "^dist/schemas/[a-z0-9][a-z0-9-]*\\.schema\\.json$" },
|
|
63
|
+
"checksum": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
47
69
|
"integrity": {
|
|
48
70
|
"type": "object",
|
|
49
71
|
"additionalProperties": false,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "dd-flow/flow-contract@
|
|
3
|
+
"$id": "dd-flow/flow-contract@6",
|
|
4
4
|
"title": "dd-flow canonical runtime contract",
|
|
5
|
-
"description": "Structural contract for the SPC-004
|
|
5
|
+
"description": "Structural contract for the SPC-004/005/006 runtime, PLAN and deterministic stage-context cutover. Dynamic command and cross-repository checks belong to companion projects.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
@@ -25,21 +25,21 @@
|
|
|
25
25
|
"policy"
|
|
26
26
|
],
|
|
27
27
|
"properties": {
|
|
28
|
-
"schema_id": {"const": "dd-flow/flow-contract@
|
|
28
|
+
"schema_id": {"const": "dd-flow/flow-contract@6"},
|
|
29
29
|
"id": {"const": "dd-flow-canonical-2026-08"},
|
|
30
30
|
"contract_id": {"type": "string", "pattern": "^dd-flow-canonical-[0-9]{4}-[0-9]{2}$"},
|
|
31
|
-
"version": {"const":
|
|
31
|
+
"version": {"const": 6},
|
|
32
32
|
"status": {"const": "active"},
|
|
33
33
|
"source": {"const": ".memory-bank/dd-flow/common/runtime-contract.md"},
|
|
34
34
|
"specifications": {
|
|
35
35
|
"type": "array",
|
|
36
|
-
"minItems":
|
|
36
|
+
"minItems": 3,
|
|
37
37
|
"items": {
|
|
38
38
|
"type": "object",
|
|
39
39
|
"additionalProperties": false,
|
|
40
40
|
"required": ["id", "version", "sha256"],
|
|
41
41
|
"properties": {
|
|
42
|
-
"id": {"enum": ["SPC-004", "SPC-005"]},
|
|
42
|
+
"id": {"enum": ["SPC-004", "SPC-005", "SPC-006"]},
|
|
43
43
|
"version": {"type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"},
|
|
44
44
|
"sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}
|
|
45
45
|
}
|
|
@@ -73,21 +73,23 @@
|
|
|
73
73
|
"stageLifecycle": {
|
|
74
74
|
"type": "object",
|
|
75
75
|
"additionalProperties": false,
|
|
76
|
-
"required": ["actions", "start_command", "finish_command", "prompt_sections", "current_write_alias", "archive_pattern"],
|
|
76
|
+
"required": ["actions", "start_command", "bootstrap_command", "finish_command", "prompt_sections", "current_write_alias", "archive_pattern"],
|
|
77
77
|
"properties": {
|
|
78
78
|
"actions": {
|
|
79
79
|
"const": ["stage_start", "stage_finish"]
|
|
80
80
|
},
|
|
81
81
|
"start_command": {"const": "dd-flow stage start <RUN> --stage <stage> --json"},
|
|
82
|
-
"
|
|
82
|
+
"bootstrap_command": {"const": "dd-flow stage start --bootstrap --stage specify --project-root <root> --subject <label> --intake-file <path> --json"},
|
|
83
|
+
"finish_command": {"const": "dd-flow stage finish <RUN> --stage <stage> --outcome <outcome> --json"},
|
|
83
84
|
"prompt_sections": {
|
|
84
85
|
"const": [
|
|
85
86
|
"stage_identity",
|
|
86
|
-
"
|
|
87
|
-
"
|
|
87
|
+
"authoritative_runtime_facts",
|
|
88
|
+
"preflight",
|
|
89
|
+
"task_intake",
|
|
88
90
|
"applicable_instructions",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
+
"required_context",
|
|
92
|
+
"work_contract",
|
|
91
93
|
"completion_contract"
|
|
92
94
|
]
|
|
93
95
|
},
|
|
@@ -112,7 +114,7 @@
|
|
|
112
114
|
"required": ["finish_outputs", "model_input", "mechanical_facts"],
|
|
113
115
|
"properties": {
|
|
114
116
|
"finish_outputs": {"const": ["stage-report.json", "stage-report.md", "stage-report.html", "summary.md"]},
|
|
115
|
-
"model_input": {"const": "semantic-only"},
|
|
117
|
+
"model_input": {"const": "semantic-only; outcome flag"},
|
|
116
118
|
"mechanical_facts": {"const": "cli-owned"}
|
|
117
119
|
}
|
|
118
120
|
},
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
"expected": {"type": "array", "items": {"type": "string"}},
|
|
124
124
|
"observed": {"type": "array", "items": {"type": "string"}},
|
|
125
125
|
"missing": {"type": "array", "items": {"type": "string"}},
|
|
126
|
+
"diagnostics": {"type": "array", "items": {"type": "string"}},
|
|
126
127
|
"reason": {"type": "string"}
|
|
127
128
|
}
|
|
128
129
|
},
|
|
@@ -70,12 +70,14 @@
|
|
|
70
70
|
},
|
|
71
71
|
"backup": {
|
|
72
72
|
"type": "object",
|
|
73
|
-
"required": ["status", "path", "created_at", "rollback_route"],
|
|
73
|
+
"required": ["status", "path", "created_at", "rollback_route", "size_bytes", "sha256"],
|
|
74
74
|
"additionalProperties": true,
|
|
75
75
|
"properties": {
|
|
76
76
|
"status": { "enum": ["present", "planned", "missing"] },
|
|
77
77
|
"path": { "type": ["string", "null"] },
|
|
78
78
|
"created_at": { "type": ["string", "null"] },
|
|
79
|
+
"size_bytes": { "type": ["integer", "null"], "minimum": 1 },
|
|
80
|
+
"sha256": { "type": ["string", "null"], "pattern": "^[a-f0-9]{64}$" },
|
|
79
81
|
"rollback_route": { "type": "string", "minLength": 1 }
|
|
80
82
|
}
|
|
81
83
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "dd-flow/merge-stage-report@2/engine-0.4.2",
|
|
4
|
+
"title": "Merge stage report emitted by dd-flow engine 0.4.2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": ["schema_id", "run", "stage", "project", "subject", "overall", "breadcrumbs", "implemented_goals", "acceptance_scenarios", "changed_files", "checks", "review", "review_gates", "defs"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_id": { "const": "dd-flow/merge-stage-report@2" },
|
|
10
|
+
"run": { "type": "object", "required": ["run_id", "run_index"], "properties": { "run_id": { "type": "string", "pattern": "^RUN-[0-9]{3}-[a-z0-9]+(?:-[a-z0-9]+)*$" }, "run_index": { "type": "string", "minLength": 1 } } },
|
|
11
|
+
"stage": { "type": "object", "required": ["name", "dir", "status"] },
|
|
12
|
+
"project": { "type": "object", "required": ["id", "title"] },
|
|
13
|
+
"subject": { "type": "object", "required": ["id", "title"] },
|
|
14
|
+
"overall": { "type": "object", "required": ["verdict", "summary", "next_action"], "properties": { "verdict": { "enum": ["accepted", "blocked", "failed"] }, "summary": { "type": "string", "minLength": 1 }, "next_action": { "type": "string", "minLength": 1 } } },
|
|
15
|
+
"breadcrumbs": { "type": "array" },
|
|
16
|
+
"implemented_goals": { "type": "array" },
|
|
17
|
+
"acceptance_scenarios": { "type": "array" },
|
|
18
|
+
"changed_files": { "type": "array" },
|
|
19
|
+
"checks": { "type": "array" },
|
|
20
|
+
"review": { "type": "object" },
|
|
21
|
+
"review_gates": { "type": "array" },
|
|
22
|
+
"defs": { "type": "array" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "dd-flow/run-engine-binding@1",
|
|
4
|
+
"title": "Immutable RUN engine binding",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_id", "run_id", "project_root", "bound_at", "source", "reason", "engine", "probe"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_id": {"const": "dd-flow/run-engine-binding@1"},
|
|
10
|
+
"run_id": {"type": "string", "pattern": "^RUN-[0-9]+-[a-z0-9][a-z0-9-]*$"},
|
|
11
|
+
"project_root": {"type": "string", "minLength": 1},
|
|
12
|
+
"bound_at": {"type": "string", "format": "date-time"},
|
|
13
|
+
"source": {"enum": ["run_creation", "legacy_recovery"]},
|
|
14
|
+
"reason": {"type": "string", "minLength": 1},
|
|
15
|
+
"engine": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"required": ["package_name", "package_version", "engine_version", "integrity_checksum", "snapshot_root"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"package_name": {"type": "string", "minLength": 1},
|
|
21
|
+
"package_version": {"type": "string", "minLength": 1},
|
|
22
|
+
"engine_version": {"type": "string", "minLength": 1},
|
|
23
|
+
"integrity_checksum": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
|
24
|
+
"snapshot_root": {"type": "string", "minLength": 1}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"probe": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"required": ["status", "command"],
|
|
31
|
+
"properties": {
|
|
32
|
+
"status": {"enum": ["not_required", "passed"]},
|
|
33
|
+
"command": {"type": ["string", "null"]}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "dd-flow/stage-prompt@
|
|
3
|
+
"$id": "dd-flow/stage-prompt@2",
|
|
4
4
|
"title": "Generated stage prompt",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
@@ -16,16 +16,20 @@
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
],
|
|
19
|
-
"required": ["schema_id", "run_id", "stage", "generated_at", "prompt_path", "sections", "aliases", "write_boundary", "source_fragments"],
|
|
19
|
+
"required": ["schema_id", "run_id", "stage", "generated_at", "prompt_path", "sections", "aliases", "write_boundary", "source_fragments", "authoritative_facts", "preflight", "required_context", "worker_prompt_markdown"],
|
|
20
20
|
"properties": {
|
|
21
|
-
"schema_id": {"const": "dd-flow/stage-prompt@
|
|
21
|
+
"schema_id": {"const": "dd-flow/stage-prompt@2"},
|
|
22
22
|
"run_id": {"type": "string", "minLength": 1},
|
|
23
23
|
"stage": {"type": "string", "minLength": 1},
|
|
24
24
|
"generated_at": {"type": "string", "format": "date-time"},
|
|
25
25
|
"prompt_path": {"type": "string", "minLength": 1},
|
|
26
|
-
"sections": {"
|
|
26
|
+
"sections": {"const": ["stage_identity", "authoritative_runtime_facts", "preflight", "task_intake", "applicable_instructions", "required_context", "work_contract", "completion_contract"]},
|
|
27
27
|
"aliases": {"type": "object", "required": ["project", "workspace", "run", "stage", "protocol", "intake"], "additionalProperties": {"type": "string"}},
|
|
28
28
|
"write_boundary": {"type": "object", "required": ["current", "archive", "archive_writable"], "properties": {"current": {"const": "@stage"}, "archive": {"pattern": "try-[0-9]{3}"}, "archive_writable": {"const": false}}, "additionalProperties": false},
|
|
29
|
-
"source_fragments": {"type": "array", "items": {"type": "string", "minLength": 1}}
|
|
29
|
+
"source_fragments": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
|
30
|
+
"authoritative_facts": {"type": "object", "required": ["git", "runtime"], "additionalProperties": true},
|
|
31
|
+
"preflight": {"type": "object", "required": ["compatibility", "permissions", "session_binding"], "additionalProperties": true},
|
|
32
|
+
"required_context": {"type": "array", "items": {"type": "object", "additionalProperties": false, "required": ["path", "reason", "stop_condition"], "properties": {"path": {"type": "string", "minLength": 1}, "reason": {"type": "string", "minLength": 1}, "stop_condition": {"type": "string", "minLength": 1}}}},
|
|
33
|
+
"worker_prompt_markdown": {"type": "string", "minLength": 1}
|
|
30
34
|
}
|
|
31
35
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "dd-flow/stage-start-response@
|
|
3
|
+
"$id": "dd-flow/stage-start-response@2",
|
|
4
4
|
"title": "Stage start response",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"then": {"required": ["plan_ref", "aspect_map_ref"]}
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
|
-
"required": ["schema_id", "run_id", "stage", "attempt_number", "stage_root", "prompt_path", "aliases", "next_command"],
|
|
13
|
+
"required": ["schema_id", "run_id", "stage", "attempt_number", "stage_root", "prompt_path", "aliases", "next_command", "worker_prompt_markdown"],
|
|
14
14
|
"properties": {
|
|
15
|
-
"schema_id": {"const": "dd-flow/stage-start-response@
|
|
15
|
+
"schema_id": {"const": "dd-flow/stage-start-response@2"},
|
|
16
16
|
"run_id": {"type": "string", "minLength": 1},
|
|
17
17
|
"stage": {"type": "string", "minLength": 1},
|
|
18
18
|
"attempt_number": {"type": "integer", "minimum": 1},
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"resolved_context": {"type": "object", "additionalProperties": true},
|
|
24
24
|
"plan_ref": {"type": "string", "pattern": "^\\.memory-bank/protocol/[^/]+/plan\\.json$"},
|
|
25
25
|
"aspect_map_ref": {"type": "string", "minLength": 1},
|
|
26
|
-
"next_command": {"
|
|
27
|
-
"permission_probe": {"type": "object", "additionalProperties": true}
|
|
26
|
+
"next_command": {"type": "string", "pattern": "^dd-flow stage finish "},
|
|
27
|
+
"permission_probe": {"type": "object", "additionalProperties": true},
|
|
28
|
+
"worker_prompt_markdown": {"type": "string", "minLength": 1}
|
|
28
29
|
}
|
|
29
30
|
}
|
package/dist/services/canon.js
CHANGED
|
@@ -36,7 +36,21 @@ export function getCanonStatus(context, input = {}) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
export function resolveCanonRoot(context, input = {}) {
|
|
39
|
-
|
|
39
|
+
return resolveCanonCandidates(canonCandidates(context, input));
|
|
40
|
+
}
|
|
41
|
+
/** Read only: resolve an explicit/environment/nearby canon without opening DD_FLOW_HOME. */
|
|
42
|
+
export function resolveCanonRootBeforeContext(env, input = {}) {
|
|
43
|
+
const candidates = [];
|
|
44
|
+
if (input.explicitRoot)
|
|
45
|
+
candidates.push(validateCanonCandidate("explicit", input.explicitRoot));
|
|
46
|
+
else if (env.DD_MEMORYBANK)
|
|
47
|
+
candidates.push(validateCanonCandidate("env", env.DD_MEMORYBANK));
|
|
48
|
+
else
|
|
49
|
+
for (const known of knownCanonRoots())
|
|
50
|
+
candidates.push(validateCanonCandidate("known", known));
|
|
51
|
+
return resolveCanonCandidates(uniqueBy(candidates, (candidate) => `${candidate.source}:${candidate.root}`));
|
|
52
|
+
}
|
|
53
|
+
function resolveCanonCandidates(candidates) {
|
|
40
54
|
const validCandidates = candidates.filter((candidate) => candidate.valid && candidate.realpath);
|
|
41
55
|
const uniqueValid = uniqueBy(validCandidates, (candidate) => candidate.realpath ?? candidate.root);
|
|
42
56
|
const blockers = candidates.flatMap((candidate) => candidate.blockers);
|
package/dist/services/cleanup.js
CHANGED
|
@@ -12,6 +12,7 @@ const actionKinds = new Set([
|
|
|
12
12
|
"repair_protocol_state",
|
|
13
13
|
"cancel_queue_job",
|
|
14
14
|
"close_flow_session",
|
|
15
|
+
"discard_stale_run",
|
|
15
16
|
"expire_lane_lock",
|
|
16
17
|
"expire_lane_waiter",
|
|
17
18
|
"close_worktree_record"
|
|
@@ -86,6 +87,36 @@ export function cleanupScan(context, input) {
|
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
}
|
|
90
|
+
const staleRuns = context.db.all(`SELECT id, runtime_path, run_dir, workspace_root FROM flow_runs
|
|
91
|
+
WHERE project_id = ? AND status = 'running'
|
|
92
|
+
ORDER BY updated_at ASC, id ASC`, [project.id]);
|
|
93
|
+
for (const run of staleRuns) {
|
|
94
|
+
const missingAuthority = !fs.existsSync(run.runtime_path);
|
|
95
|
+
const missingRunHome = !fs.existsSync(run.run_dir);
|
|
96
|
+
const missingWorkspace = !fs.existsSync(run.workspace_root);
|
|
97
|
+
if (!missingAuthority)
|
|
98
|
+
continue;
|
|
99
|
+
const details = {
|
|
100
|
+
code: "stale_run_missing_authority",
|
|
101
|
+
severity: "warning",
|
|
102
|
+
run_id: run.id,
|
|
103
|
+
runtime_path: run.runtime_path,
|
|
104
|
+
missing_run_home: missingRunHome,
|
|
105
|
+
missing_workspace: missingWorkspace
|
|
106
|
+
};
|
|
107
|
+
if (missingRunHome && missingWorkspace) {
|
|
108
|
+
pushFinding(findings, actions, details.code, details.severity, {
|
|
109
|
+
run_id: run.id,
|
|
110
|
+
runtime_path: run.runtime_path,
|
|
111
|
+
missing_run_home: missingRunHome,
|
|
112
|
+
missing_workspace: missingWorkspace,
|
|
113
|
+
action: { kind: "discard_stale_run", project_id: project.id, run_id: run.id }
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
findings.push({ ...details, action: null, reason: "manual_reconciliation_required" });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
89
120
|
const now = context.now();
|
|
90
121
|
const expiredLocks = context.db.all(`SELECT id, lane, worker_id, expires_at FROM lane_locks
|
|
91
122
|
WHERE project_id = ? AND status = 'active' AND expires_at <= ?
|
|
@@ -289,6 +320,41 @@ function applyAction(context, action, reason, force, now) {
|
|
|
289
320
|
});
|
|
290
321
|
return { kind: action.kind, changed: true, session_id: action.session_id };
|
|
291
322
|
}
|
|
323
|
+
if (action.kind === "discard_stale_run" && action.run_id) {
|
|
324
|
+
const run = context.db.get("SELECT status, runtime_path, run_dir, workspace_root, index_json FROM flow_runs WHERE project_id = ? AND id = ?", [action.project_id, action.run_id]);
|
|
325
|
+
if (!run) {
|
|
326
|
+
return { kind: action.kind, changed: false, skipped: true, reason: "run_not_found", run_id: action.run_id };
|
|
327
|
+
}
|
|
328
|
+
if (run.status === "discarded") {
|
|
329
|
+
const index = discardedRunIndex(run.index_json, reason, now);
|
|
330
|
+
context.db.run("UPDATE flow_runs SET verdict = 'discarded', next_action = ?, index_json = ?, updated_at = ?, completed_at = COALESCE(completed_at, ?) WHERE project_id = ? AND id = ?", [reason, JSON.stringify(index), now, now, action.project_id, action.run_id]);
|
|
331
|
+
if (fs.existsSync(run.runtime_path))
|
|
332
|
+
fs.writeFileSync(run.runtime_path, `${JSON.stringify(index, null, 2)}\n`);
|
|
333
|
+
return { kind: action.kind, changed: true, run_id: action.run_id };
|
|
334
|
+
}
|
|
335
|
+
if (run.status !== "running") {
|
|
336
|
+
return { kind: action.kind, changed: false, skipped: true, reason: `already_${run.status}`, run_id: action.run_id };
|
|
337
|
+
}
|
|
338
|
+
if (fs.existsSync(run.runtime_path) || fs.existsSync(run.run_dir) || fs.existsSync(run.workspace_root)) {
|
|
339
|
+
return { kind: action.kind, changed: false, skipped: true, reason: "runtime_or_workspace_reappeared", run_id: action.run_id };
|
|
340
|
+
}
|
|
341
|
+
const index = discardedRunIndex(run.index_json, reason, now);
|
|
342
|
+
context.db.run(`UPDATE flow_runs SET status = 'discarded', verdict = 'discarded', next_action = ?, index_json = ?, updated_at = ?, completed_at = ?
|
|
343
|
+
WHERE project_id = ? AND id = ? AND status = 'running'`, [reason, JSON.stringify(index), now, now, action.project_id, action.run_id]);
|
|
344
|
+
fs.mkdirSync(run.run_dir, { recursive: true });
|
|
345
|
+
fs.writeFileSync(run.runtime_path, `${JSON.stringify(index, null, 2)}\n`);
|
|
346
|
+
context.db.run(`UPDATE flow_sessions SET status = 'stopped', stop_reason = ?, updated_at = ?, stopped_at = ?
|
|
347
|
+
WHERE project_id = ? AND run_id = ? AND status IN ('pending', 'active', 'waiting_user', 'blocked', 'stopping')`, [reason, now, now, action.project_id, action.run_id]);
|
|
348
|
+
context.db.run("UPDATE flow_session_segments SET ended_at = ? WHERE project_id = ? AND run_id = ? AND ended_at IS NULL", [now, action.project_id, action.run_id]);
|
|
349
|
+
appendAudit(context, {
|
|
350
|
+
projectId: action.project_id,
|
|
351
|
+
eventType: "flow_run.discarded",
|
|
352
|
+
reason,
|
|
353
|
+
forced: force,
|
|
354
|
+
payload: { run_id: action.run_id, source: "cleanup.apply", authority_missing: true }
|
|
355
|
+
});
|
|
356
|
+
return { kind: action.kind, changed: true, run_id: action.run_id };
|
|
357
|
+
}
|
|
292
358
|
if (action.kind === "expire_lane_lock" && typeof action.lock_id === "number") {
|
|
293
359
|
const result = context.db.run("UPDATE lane_locks SET status = 'expired', reason = ?, updated_at = ? WHERE project_id = ? AND id = ? AND status = 'active'", [reason, now, action.project_id, action.lock_id]);
|
|
294
360
|
return {
|
|
@@ -330,6 +396,17 @@ function applyAction(context, action, reason, force, now) {
|
|
|
330
396
|
}
|
|
331
397
|
return { kind: action.kind, changed: false, skipped: true, reason: "invalid_action" };
|
|
332
398
|
}
|
|
399
|
+
function discardedRunIndex(indexJson, reason, now) {
|
|
400
|
+
const index = JSON.parse(indexJson);
|
|
401
|
+
index.schema_id = "dd-flow/flow-run@2";
|
|
402
|
+
index.status = "discarded";
|
|
403
|
+
index.verdict = "discarded";
|
|
404
|
+
index.next_action = reason;
|
|
405
|
+
index.updated_at = now;
|
|
406
|
+
index.completed_at ??= now;
|
|
407
|
+
index.finished_at ??= now;
|
|
408
|
+
return index;
|
|
409
|
+
}
|
|
333
410
|
function dirtyGit(worktreePath) {
|
|
334
411
|
if (!fs.existsSync(worktreePath)) {
|
|
335
412
|
return false;
|
|
@@ -34,21 +34,63 @@ export function isReadOnlyCliOperation(args, env = process.env) {
|
|
|
34
34
|
export function isMbUpgradeCliOperation(args, env = process.env) {
|
|
35
35
|
return classifyCliOperation(args, env).mode === "mb_upgrade";
|
|
36
36
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
37
|
+
export function requiresExplicitUpgradeAuthorization(args) {
|
|
38
|
+
const positional = positionalArgs(args);
|
|
39
|
+
const [family, command] = positional;
|
|
40
|
+
if (family === "migration" && command === "apply")
|
|
39
41
|
return true;
|
|
40
|
-
|
|
42
|
+
if (family === "run" && command === "start" && optionValue(args, "flow-kind") === "mb-upgrade")
|
|
43
|
+
return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
function isMbUpgradeMode(args, env) {
|
|
47
|
+
const marked = env.DD_FLOW_COMPATIBILITY_MODE === "mb-upgrade" || env.DD_FLOW_MB_UPGRADE_MODE === "1";
|
|
41
48
|
for (let index = 0; index < args.length; index += 1) {
|
|
42
49
|
const arg = args[index];
|
|
43
50
|
if (arg === "--compatibility-mode" && args[index + 1] === "mb-upgrade")
|
|
44
|
-
return
|
|
51
|
+
return isUpgradeAllowlisted(args);
|
|
45
52
|
if (arg === "--mb-upgrade-mode")
|
|
46
|
-
return
|
|
47
|
-
if (arg?.startsWith("--compatibility-mode="))
|
|
48
|
-
return
|
|
53
|
+
return isUpgradeAllowlisted(args);
|
|
54
|
+
if (arg?.startsWith("--compatibility-mode=") && arg.slice("--compatibility-mode=".length) === "mb-upgrade") {
|
|
55
|
+
return isUpgradeAllowlisted(args);
|
|
56
|
+
}
|
|
49
57
|
}
|
|
58
|
+
return marked && isUpgradeAllowlisted(args);
|
|
59
|
+
}
|
|
60
|
+
function isUpgradeAllowlisted(args) {
|
|
61
|
+
const positional = positionalArgs(args);
|
|
62
|
+
const [family, command] = positional;
|
|
63
|
+
if (family === "status")
|
|
64
|
+
return true;
|
|
65
|
+
if (family === "engine" && command === "resolve")
|
|
66
|
+
return true;
|
|
67
|
+
if (family === "migration" && ["impact", "plan", "report", "verify", "apply"].includes(command ?? ""))
|
|
68
|
+
return true;
|
|
69
|
+
if (family === "memory" && command === "permissions" && positional[2] === "preflight")
|
|
70
|
+
return true;
|
|
71
|
+
if (family === "run" && command === "start")
|
|
72
|
+
return optionValue(args, "flow-kind") === "mb-upgrade";
|
|
73
|
+
if (family === "stage" && ["start", "finish"].includes(command ?? ""))
|
|
74
|
+
return hasRunBinding(args);
|
|
75
|
+
if (family === "session" && ["status", "register", "stop", "usage"].includes(command ?? ""))
|
|
76
|
+
return hasRunBinding(args);
|
|
77
|
+
if (family === "dashboard" && ["data", "render", "refresh", "open"].includes(command ?? ""))
|
|
78
|
+
return true;
|
|
50
79
|
return false;
|
|
51
80
|
}
|
|
81
|
+
function hasRunBinding(args) {
|
|
82
|
+
return Boolean(optionValue(args, "run") || positionalArgs(args).some((value) => value.startsWith("RUN-")));
|
|
83
|
+
}
|
|
84
|
+
function optionValue(args, name) {
|
|
85
|
+
for (let index = args.length - 1; index >= 0; index -= 1) {
|
|
86
|
+
const arg = args[index];
|
|
87
|
+
if (arg === `--${name}`)
|
|
88
|
+
return args[index + 1] ?? null;
|
|
89
|
+
if (arg?.startsWith(`--${name}=`))
|
|
90
|
+
return arg.slice(name.length + 3);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
52
94
|
function isRouterNative(args) {
|
|
53
95
|
const first = args[0];
|
|
54
96
|
if (!first || first === "--version")
|
|
@@ -57,7 +99,9 @@ function isRouterNative(args) {
|
|
|
57
99
|
return true;
|
|
58
100
|
if (args.includes("--help") || args.includes("-h"))
|
|
59
101
|
return true;
|
|
60
|
-
|
|
102
|
+
if (["engine", "version", "schema"].includes(first))
|
|
103
|
+
return true;
|
|
104
|
+
return first === "codex" && args[1] === "hook" && args[2] === "handle";
|
|
61
105
|
}
|
|
62
106
|
function isReadOnlyDiagnostic(family, command, action) {
|
|
63
107
|
if (!family)
|
|
@@ -11,7 +11,7 @@ export function preflightCliCompatibility(context, args, env = process.env, reso
|
|
|
11
11
|
if (!projectRoot) {
|
|
12
12
|
return { ok: true, classification, compatibility: null };
|
|
13
13
|
}
|
|
14
|
-
const selection = selectEngine(context, { projectRoot });
|
|
14
|
+
const selection = selectEngine(context, { projectRoot, env }, { allowCurrentInProcess: true }, classification);
|
|
15
15
|
const compatibility = compatibilityReport(selection, classification);
|
|
16
16
|
if (classification.mode === "read_only_diagnostics" || classification.mode === "mb_upgrade") {
|
|
17
17
|
return { ok: true, classification, compatibility };
|
|
@@ -893,7 +893,7 @@ function planSummaryForProtocol(context, projectId, protocolId) {
|
|
|
893
893
|
return { plan_id: "none", total: 0, done: 0, blocked: 0 };
|
|
894
894
|
}
|
|
895
895
|
try {
|
|
896
|
-
const summary = planSummary(context, { projectId, protocolId, planPath: protocol.plan_path });
|
|
896
|
+
const summary = planSummary(context, { projectId, protocolId, planPath: protocol.plan_path }, { bind: false });
|
|
897
897
|
return { plan_id: summary.plan_id, total: summary.total, done: summary.done, blocked: summary.blocked };
|
|
898
898
|
}
|
|
899
899
|
catch {
|
|
@@ -909,7 +909,7 @@ function activePlanItems(context, projectId, protocols) {
|
|
|
909
909
|
}
|
|
910
910
|
let plan;
|
|
911
911
|
try {
|
|
912
|
-
plan = planWithProgress(context, { projectId, protocolId: protocol.id, planPath: record.plan_path }).plan;
|
|
912
|
+
plan = planWithProgress(context, { projectId, protocolId: protocol.id, planPath: record.plan_path }, { bind: false }).plan;
|
|
913
913
|
}
|
|
914
914
|
catch {
|
|
915
915
|
continue;
|