@deksden-com/dd-flow-cli 0.1.0 → 0.2.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/README.md CHANGED
@@ -41,6 +41,17 @@ The flow contract defines mechanical protocol stages, allowed transitions, the `
41
41
 
42
42
  ## Basic Commands
43
43
 
44
+ Inspect runtime and canonical Memory Bank discovery:
45
+
46
+ ```bash
47
+ dd-flow status --project-root "$PWD" --json
48
+ dd-flow canon resolve --json
49
+ dd-flow canon register --root "$DD_MEMORYBANK" --json
50
+ dd-flow canon status --json
51
+ ```
52
+
53
+ `canon resolve` checks explicit `--root`, then `DD_MEMORYBANK`, then the globally registered value in `DD_FLOW_HOME`, then known nearby `dd-memorybank` checkouts from the current working directory. If two different valid canonical roots are discovered, it fails closed with structured blockers and bootstrap hints. Use `DD_MEMORYBANK` for the local canonical `dd-memorybank` checkout.
54
+
44
55
  Register a project:
45
56
 
46
57
  ```bash
@@ -0,0 +1,11 @@
1
+ {
2
+ "cli_package": "@deksden-com/dd-flow-cli",
3
+ "cli_version": "0.2.0",
4
+ "cli_commit": "8351d94bdeaca22d2fff503fd2b08071c86205cd",
5
+ "built_at": "2026-06-15T14:46:55.550Z",
6
+ "built_with_canon": {
7
+ "version": "2.4.0",
8
+ "commit": "cf1f6d08e8d4775bf14334c6010b479d1c6fd73f",
9
+ "flow_contract": "dd-flow-canonical-2026-05"
10
+ }
11
+ }
package/dist/cli/help.js CHANGED
@@ -9,6 +9,8 @@ Usage:
9
9
  dd-flow <command> --help
10
10
 
11
11
  Core commands:
12
+ status Inspect dd-flow runtime, project and canon roots.
13
+ canon register/status/resolve Register and resolve the canonical Memory Bank.
12
14
  project register/status Register a project and inspect runtime state.
13
15
  project resolve/archive Resolve typed ids and archive stale project roots.
14
16
  project migrate-ids Migrate old project ids.
@@ -17,6 +19,8 @@ Core commands:
17
19
  run start/status/list Track concrete flow executions and stage artifacts.
18
20
  plan set/status/item Attach and update protocol plans.
19
21
  lane workspace/lock/status Manage shared workspace lanes and leases.
22
+ merge status/one-shot Inspect or claim one merge job from the current session.
23
+ merge-worker status/start/stop Manage long-lived project merge workers.
20
24
  merge-queue status/next Claim, wait for, complete, or fail merge jobs.
21
25
  session register/status/stop Register flow sessions and stop workers.
22
26
  dashboard render/open/refresh Render markdown project/global dashboards.
@@ -28,6 +32,9 @@ Core commands:
28
32
 
29
33
  Examples:
30
34
  dd-flow project register --root "$PWD"
35
+ dd-flow status --project-root "$PWD" --json
36
+ dd-flow canon register --root "$DD_MEMORYBANK" --json
37
+ dd-flow canon resolve --json
31
38
  dd-flow project status --root "$PWD" --json
32
39
  dd-flow project resolve PRJ-001 --json
33
40
  dd-flow project archive PRJ-001 --reason "old missing worktree root" --json
@@ -35,10 +42,44 @@ Examples:
35
42
  dd-flow run start --project-root "$PWD" --flow-kind coding --subject-type protocol --subject-id PRT-001-demo --slug demo --json
36
43
  dd-flow lane workspace set --project-root "$PWD" --lane merge --path "$PWD" --branch main --json
37
44
  dd-flow lane lock acquire --project-root "$PWD" --lane merge --worker-id worker-1 --path "$PWD" --ttl 300 --reason "merge worker" --json
45
+ dd-flow merge status --project-root "$PWD" --json
46
+ dd-flow merge-worker start --project-root "$PWD" --worker-id merge-worker --path "$PWD" --json
38
47
 
39
48
  Default output is human-readable. Use --json for hooks, prompts, scripts and other agent automation; JSON mode never mixes human progress text into stdout.
40
49
 
41
50
  project_root is the stable repository identity. workspace_path is the concrete checkout where an agent is about to work.`
51
+ ],
52
+ [
53
+ "status",
54
+ `dd-flow status - inspect runtime roots and active discovery state
55
+
56
+ Usage:
57
+ dd-flow status [--project-root <root>] --json
58
+ dd-flow status [--root <root>] --json
59
+
60
+ status reports DD_FLOW_HOME, the requested or current project root, registered project identity when available, and canonical Memory Bank discovery. It does not require the project to be registered and does not mutate state. In JSON mode canon discovery is embedded as a structured assessment so agents can detect missing or ambiguous canon roots without parsing stderr.
61
+
62
+ Examples:
63
+ dd-flow status --project-root "$PWD" --json
64
+ dd-flow status --json`
65
+ ],
66
+ [
67
+ "canon",
68
+ `dd-flow canon - register and resolve the canonical Memory Bank checkout
69
+
70
+ Usage:
71
+ dd-flow canon register --root <canonical-dd-memorybank-root> --json
72
+ dd-flow canon status [--root <canonical-dd-memorybank-root>] --json
73
+ dd-flow canon resolve [--root <canonical-dd-memorybank-root>] --json
74
+
75
+ Discovery order is explicit --root, then DD_MEMORYBANK, then the globally registered runtime value in DD_FLOW_HOME, then known nearby dd-memorybank checkouts from the current working directory. Explicit --root is the only source considered for that command. If multiple different valid canonical checkouts are found, resolve fails closed with blockers so the caller chooses intentionally.
76
+
77
+ A valid canonical root contains dd-flow/README.md, mbb/index.md, protocol/index.md, and canonical-only entrypoints such as dd-flow/mb-init.md, dd-flow/mb-upgrade.md, dd-flow/mb-upgrade-review.md, and dd-flow/mb-distill.md.
78
+
79
+ Examples:
80
+ dd-flow canon register --root "$DD_MEMORYBANK" --json
81
+ dd-flow canon resolve --json
82
+ dd-flow canon status --json`
42
83
  ],
43
84
  [
44
85
  "run",
@@ -132,6 +173,27 @@ Examples:
132
173
  dd-flow lane lock acquire --project-root "$PWD" --lane merge --worker-id worker-1 --path "$PWD" --ttl 300 --reason "merge worker" --json
133
174
  dd-flow lane lock heartbeat --project-root "$PWD" --lane merge --worker-id worker-1 --path "$PWD" --lease-token "$TOKEN" --json
134
175
  dd-flow lane lock release --project-root "$PWD" --lane merge --worker-id worker-1 --path "$PWD" --lease-token "$TOKEN" --reason done --json`
176
+ ],
177
+ [
178
+ "merge",
179
+ `dd-flow merge - current-session merge entrypoint helpers
180
+
181
+ Usage:
182
+ dd-flow merge status --project-root <root> --json
183
+ dd-flow merge one-shot --project-root <root> --worker-id <id> [--path <workspace>] --json
184
+
185
+ merge status reports active merge worker, claimed jobs, active merge lane lock and queue state. merge one-shot is status-only when a long-lived worker, claimed job or active lock exists; otherwise it acquires the merge lane, claims at most one ready job, and returns that job for the shared merge job prompt. If no job is ready, it releases the lock and returns job: null.`
186
+ ],
187
+ [
188
+ "merge-worker",
189
+ `dd-flow merge-worker - manage long-lived project merge workers
190
+
191
+ Usage:
192
+ dd-flow merge-worker status --project-root <root> --json
193
+ dd-flow merge-worker start --project-root <root> --worker-id <id> [--path <workspace>] [--branch <branch>] --json
194
+ dd-flow merge-worker stop --project-root <root> [--worker-id <id>] --reason <text> --json
195
+
196
+ start is idempotent: if a worker, claimed job, or active merge lock already exists it returns status instead of creating a duplicate worker. stop is no-op/status when no worker exists, refuses ambiguous multiple workers unless --worker-id is provided, stops idle workers immediately, and marks busy workers as stop-after-current while preserving the claimed job and lock.`
135
197
  ],
136
198
  [
137
199
  "merge-queue",
@@ -233,7 +295,7 @@ Dashboards are rendered views of dd-flow state. State-changing commands refresh
233
295
  Usage:
234
296
  dd-flow schema validate --schema <name> --file <json-file> [--project-root <root>] [--schema-dir <dir>] --json
235
297
 
236
- Schema names resolve to <name>.schema.json. Lookup order is --schema-dir, then <project-root>/.memory-bank/dd-flow/schemas/, then bundled CLI schemas. If --project-root is omitted, the current working directory is used. JSON mode writes valid results to stdout and structured validation/usage errors to stderr.`
298
+ Schema names resolve to <name>.schema.json. Lookup order is --schema-dir, then <project-root>/.memory-bank/dd-flow/schemas/, then <project-root>/dd-flow/schemas/ for canonical checkouts, then bundled CLI schemas. If --project-root is omitted, the current working directory is used. JSON mode writes valid results to stdout and structured validation/usage errors to stderr.`
237
299
  ],
238
300
  [
239
301
  "schema validate",
@@ -6,11 +6,14 @@ import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, r
6
6
  import { cancelProtocol, getProtocolStatus, readyForMerge, registerProtocol, requireProtocol, transitionProtocol } from "../services/protocols.js";
7
7
  import { blockPlanItem, completePlanItem, getPlanStatus, setProtocolPlan, skipPlanItem, startPlanItem } from "../services/plans.js";
8
8
  import { cancelMergeQueueJob, claimNextMergeJob, completeMergeJob, failMergeJob, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
9
+ import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
9
10
  import { cleanupApply, cleanupScan } from "../services/cleanup.js";
10
11
  import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
11
12
  import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
12
13
  import { acquireLaneLock, checkLaneWorkspace, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitForLaneLock } from "../services/lanes.js";
13
14
  import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
15
+ import { getCanonStatus, registerCanonRoot, resolveCanonRoot } from "../services/canon.js";
16
+ import { getRuntimeStatus } from "../services/status.js";
14
17
  import { autoRefreshDashboards, getCmuxStatus, openDashboard, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
15
18
  import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker } from "../services/sessions.js";
16
19
  import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunStatus, listFlowRuns, startFlowRun } from "../services/runs.js";
@@ -91,6 +94,8 @@ function progressForCommand(args) {
91
94
  ];
92
95
  }
93
96
  if ((family === "cleanup" && command === "apply") ||
97
+ (family === "merge" && command === "one-shot") ||
98
+ family === "merge-worker" ||
94
99
  (family === "protocol" && command === "cancel") ||
95
100
  (family === "project" && command === "archive") ||
96
101
  family === "run") {
@@ -144,6 +149,9 @@ function renderHumanResult(args, result) {
144
149
  lines.push(`errors: ${errors.length}`);
145
150
  return `${lines.join("\n")}\n`;
146
151
  }
152
+ if (family === "status") {
153
+ return renderStatusHuman(record);
154
+ }
147
155
  const lines = [`dd-flow ${family ?? ""}${command ? ` ${command}` : ""}: ${ok}`.trim()];
148
156
  const project = record.project && typeof record.project === "object" ? record.project : null;
149
157
  const protocol = record.protocol && typeof record.protocol === "object" ? record.protocol : null;
@@ -170,9 +178,47 @@ function renderHumanResult(args, result) {
170
178
  }
171
179
  return `${lines.join("\n")}\n`;
172
180
  }
181
+ function renderStatusHuman(record) {
182
+ const lines = [`dd-flow status: ${record.ok === false ? "failed" : "ok"}`];
183
+ const cli = recordObject(record.cli);
184
+ const cliBuild = recordObject(cli?.build);
185
+ const builtWithCanon = recordObject(cliBuild?.built_with_canon);
186
+ const canon = recordObject(record.canon);
187
+ const resolvedCanon = recordObject(canon?.resolved);
188
+ const project = recordObject(record.project);
189
+ const memoryBank = recordObject(project?.memory_bank);
190
+ const flowPack = recordObject(project?.flow_pack);
191
+ const drift = recordObject(project?.drift);
192
+ lines.push(`CLI package: ${stringValue(cli?.package_name) ?? "unknown"} ${stringValue(cli?.version) ?? "unknown"}`);
193
+ lines.push(`CLI tested with canon: ${stringValue(builtWithCanon?.version) ?? "unknown"} @ ${shortSha(stringValue(builtWithCanon?.commit))}`);
194
+ lines.push(`Active local canon: ${stringValue(resolvedCanon?.root) ?? "unresolved"} ${stringValue(resolvedCanon?.version) ?? "unknown"} @ ${shortSha(stringValue(resolvedCanon?.commit))}`);
195
+ lines.push(`Project: ${stringValue(project?.root) ?? "unknown"}${project?.registered === true ? " (registered)" : " (unregistered)"}`);
196
+ lines.push(`Project Memory Bank: ${stringValue(memoryBank?.version) ?? stringValue(memoryBank?.status) ?? "unknown"}`);
197
+ lines.push(`Project flow pack: ${stringValue(flowPack?.pack_version) ?? stringValue(flowPack?.status) ?? "unknown"} @ ${shortSha(stringValue(flowPack?.source_commit))}`);
198
+ lines.push(`Drift: ${stringValue(drift?.overall) ?? "unknown"}`);
199
+ if (drift?.next_action)
200
+ lines.push(`Next action: ${String(drift.next_action)}`);
201
+ return `${lines.join("\n")}\n`;
202
+ }
203
+ function recordObject(value) {
204
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
205
+ }
206
+ function stringValue(value) {
207
+ return typeof value === "string" && value.length > 0 ? value : null;
208
+ }
209
+ function shortSha(value) {
210
+ return value ? value.slice(0, 12) : "unknown";
211
+ }
173
212
  async function dispatch(args, context, io) {
174
213
  const [family, command, ...rest] = args;
175
214
  const parsed = parseArgs(rest);
215
+ if (family === "status") {
216
+ const rootParsed = parseArgs([command ?? "", ...rest]);
217
+ return getRuntimeStatus(context, { projectRoot: optionalOption(rootParsed, "project-root") ?? optionalOption(rootParsed, "root") });
218
+ }
219
+ if (family === "canon") {
220
+ return dispatchCanon(context, command, parsed);
221
+ }
176
222
  if (family === "project" && command === "register") {
177
223
  return registerProject(context, { root: requiredOption(parsed, "root") });
178
224
  }
@@ -252,6 +298,12 @@ async function dispatch(args, context, io) {
252
298
  if (family === "merge-queue") {
253
299
  return dispatchMergeQueue(context, command, parsed);
254
300
  }
301
+ if (family === "merge") {
302
+ return dispatchMerge(context, command, parsed);
303
+ }
304
+ if (family === "merge-worker") {
305
+ return dispatchMergeWorker(context, command, parsed);
306
+ }
255
307
  if (family === "cleanup") {
256
308
  return dispatchCleanup(context, command, parsed);
257
309
  }
@@ -287,6 +339,18 @@ async function dispatch(args, context, io) {
287
339
  }
288
340
  throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
289
341
  }
342
+ function dispatchCanon(context, command, parsed) {
343
+ if (command === "register") {
344
+ return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
345
+ }
346
+ if (command === "status") {
347
+ return getCanonStatus(context, { root: optionalOption(parsed, "root") });
348
+ }
349
+ if (command === "resolve") {
350
+ return resolveCanonRoot(context, { explicitRoot: optionalOption(parsed, "root") });
351
+ }
352
+ throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
353
+ }
290
354
  function dispatchMergeQueue(context, command, parsed) {
291
355
  if (hasOption(parsed, "session-id")) {
292
356
  throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
@@ -346,6 +410,40 @@ function dispatchMergeQueue(context, command, parsed) {
346
410
  }
347
411
  throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
348
412
  }
413
+ function dispatchMerge(context, command, parsed) {
414
+ if (command === "status") {
415
+ return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root") });
416
+ }
417
+ if (command === "one-shot") {
418
+ return oneShotMergeClaim(context, {
419
+ projectRoot: requiredOption(parsed, "project-root"),
420
+ workerId: requiredWorkerId(parsed),
421
+ workspacePath: workspacePathOption(parsed)
422
+ });
423
+ }
424
+ throw new AppError("usage", `Unknown merge command: ${command ?? "<empty>"}`, 2);
425
+ }
426
+ function dispatchMergeWorker(context, command, parsed) {
427
+ if (command === "status") {
428
+ return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root") });
429
+ }
430
+ if (command === "start") {
431
+ return startMergeWorker(context, {
432
+ projectRoot: requiredOption(parsed, "project-root"),
433
+ workerId: requiredWorkerId(parsed),
434
+ workspacePath: workspacePathOption(parsed),
435
+ branch: optionalOption(parsed, "branch")
436
+ });
437
+ }
438
+ if (command === "stop") {
439
+ return stopProjectMergeWorker(context, {
440
+ projectRoot: requiredOption(parsed, "project-root"),
441
+ workerId: optionalOption(parsed, "worker-id"),
442
+ reason: requiredOption(parsed, "reason")
443
+ });
444
+ }
445
+ throw new AppError("usage", `Unknown merge-worker command: ${command ?? "<empty>"}`, 2);
446
+ }
349
447
  function dispatchMemory(command, parsed) {
350
448
  const subcommand = requiredPosition(parsed, 0, "memory subcommand");
351
449
  if (command === "permissions" && subcommand === "preflight") {
@@ -882,6 +980,12 @@ function projectRootForMutation(context, args, result) {
882
980
  if (family === "merge-queue") {
883
981
  return projectRootForMergeQueueMutation(context, command, parsed);
884
982
  }
983
+ if (family === "merge" && command === "one-shot") {
984
+ return requiredOption(parsed, "project-root");
985
+ }
986
+ if (family === "merge-worker" && ["start", "stop"].includes(command ?? "")) {
987
+ return requiredOption(parsed, "project-root");
988
+ }
885
989
  if (family === "cleanup" && command === "apply") {
886
990
  return requiredOption(parsed, "project-root");
887
991
  }
@@ -0,0 +1,112 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "dd-flow/archived-flow-manifest@1",
4
+ "title": "Archived flow manifest",
5
+ "description": "Manifest for dd-flow files archived from an active project flow pack during Memory Bank upgrade.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema_id",
10
+ "archive_id",
11
+ "created_at",
12
+ "target_project_root",
13
+ "source_flow_root",
14
+ "archive_root",
15
+ "items"
16
+ ],
17
+ "properties": {
18
+ "schema_id": {
19
+ "const": "dd-flow/archived-flow-manifest@1"
20
+ },
21
+ "archive_id": {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "created_at": {
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "target_project_root": {
30
+ "type": "string",
31
+ "minLength": 1
32
+ },
33
+ "source_flow_root": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ },
37
+ "archive_root": {
38
+ "type": "string",
39
+ "minLength": 1
40
+ },
41
+ "items": {
42
+ "type": "array",
43
+ "minItems": 1,
44
+ "items": {
45
+ "$ref": "#/definitions/item"
46
+ }
47
+ },
48
+ "notes": {
49
+ "type": "string"
50
+ }
51
+ },
52
+ "definitions": {
53
+ "item": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": [
57
+ "original_path",
58
+ "archive_path",
59
+ "classification",
60
+ "reason",
61
+ "manual_review_required",
62
+ "replacement"
63
+ ],
64
+ "properties": {
65
+ "original_path": {
66
+ "type": "string",
67
+ "minLength": 1,
68
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*//)[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$"
69
+ },
70
+ "archive_path": {
71
+ "type": "string",
72
+ "minLength": 1,
73
+ "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*//)[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$"
74
+ },
75
+ "classification": {
76
+ "type": "string",
77
+ "enum": ["canonical_only_obsolete", "old_full_canonical_copy", "project_customization", "unknown_extra", "generated_or_runtime"]
78
+ },
79
+ "reason": {
80
+ "type": "string",
81
+ "minLength": 1
82
+ },
83
+ "manual_review_required": {
84
+ "type": "boolean"
85
+ },
86
+ "replacement": {
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "required": ["kind", "hint"],
90
+ "properties": {
91
+ "kind": {
92
+ "type": "string",
93
+ "enum": ["canonical_only_flow", "project_flow_pack_file", "removed", "needs_decision"]
94
+ },
95
+ "hint": {
96
+ "type": "string",
97
+ "minLength": 1
98
+ }
99
+ }
100
+ },
101
+ "source_commit": {
102
+ "type": "string",
103
+ "minLength": 7
104
+ },
105
+ "source_hash": {
106
+ "type": "string",
107
+ "minLength": 1
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
@@ -11,7 +11,12 @@
11
11
  "project",
12
12
  "subject",
13
13
  "overall",
14
+ "invocation_mode",
14
15
  "breadcrumbs",
16
+ "worker",
17
+ "queue_context",
18
+ "lane_lock",
19
+ "integration",
15
20
  "merge_job",
16
21
  "git",
17
22
  "checks",
@@ -29,6 +34,61 @@
29
34
  "type": "array",
30
35
  "items": { "$ref": "#/definitions/breadcrumb" }
31
36
  },
37
+ "invocation_mode": {
38
+ "type": "string",
39
+ "enum": ["one_shot", "long_lived_worker"]
40
+ },
41
+ "worker": {
42
+ "type": "object",
43
+ "additionalProperties": true,
44
+ "required": ["id", "session_kind", "status_before", "status_after"],
45
+ "properties": {
46
+ "id": { "type": "string", "minLength": 1 },
47
+ "session_kind": { "type": "string", "enum": ["merge_job", "merge_worker"] },
48
+ "status_before": { "type": "string", "minLength": 1 },
49
+ "status_after": { "type": "string", "minLength": 1 },
50
+ "stop_flag": { "type": "string" }
51
+ }
52
+ },
53
+ "queue_context": {
54
+ "type": "object",
55
+ "additionalProperties": true,
56
+ "required": ["before", "claimed_job", "after", "final_reason"],
57
+ "properties": {
58
+ "before": { "type": "string", "minLength": 1 },
59
+ "claimed_job": { "type": "string", "minLength": 1 },
60
+ "position_before": { "type": ["number", "null"] },
61
+ "pending_after": { "type": ["number", "null"] },
62
+ "after": { "type": "string", "minLength": 1 },
63
+ "final_reason": { "type": "string", "minLength": 1 }
64
+ }
65
+ },
66
+ "lane_lock": {
67
+ "type": "object",
68
+ "additionalProperties": true,
69
+ "required": ["owner", "status_before", "status_after"],
70
+ "properties": {
71
+ "owner": { "type": "string", "minLength": 1 },
72
+ "lease_token": { "type": "string" },
73
+ "status_before": { "type": "string", "minLength": 1 },
74
+ "status_after": { "type": "string", "minLength": 1 },
75
+ "acquired_at": { "type": "string" },
76
+ "released_at": { "type": "string" }
77
+ }
78
+ },
79
+ "integration": {
80
+ "type": "object",
81
+ "additionalProperties": true,
82
+ "required": ["source_branch", "target_branch", "result"],
83
+ "properties": {
84
+ "source_branch": { "type": "string", "minLength": 1 },
85
+ "source_commit": { "type": "string" },
86
+ "target_branch": { "type": "string", "minLength": 1 },
87
+ "result": { "type": "string", "enum": ["merged", "failed", "requeued", "blocked"] },
88
+ "result_commit": { "type": "string" },
89
+ "conflicts": { "type": "string" }
90
+ }
91
+ },
32
92
  "merge_job": {
33
93
  "type": "object",
34
94
  "additionalProperties": true,
@@ -96,7 +156,7 @@
96
156
  "additionalProperties": true,
97
157
  "required": ["verdict", "summary", "next_action"],
98
158
  "properties": {
99
- "verdict": { "type": "string", "minLength": 1 },
159
+ "verdict": { "type": "string", "enum": ["merged", "failed", "requeued", "blocked"] },
100
160
  "summary": { "type": "string", "minLength": 1 },
101
161
  "next_action": { "type": "string", "minLength": 1 }
102
162
  }