ultra-builder-pro-cli 0.23.0 → 0.24.1

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.
Files changed (95) hide show
  1. package/.ultra-template/docs/research/README.md +4 -4
  2. package/.ultra-template/tasks/tasks.json +4 -2
  3. package/CHANGELOG.md +72 -0
  4. package/README.md +55 -39
  5. package/adapters/_shared/codex-assets.cjs +121 -65
  6. package/adapters/_shared/interaction-contract.cjs +7 -1
  7. package/adapters/_shared/provenance.cjs +7 -2
  8. package/adapters/_shared/runtime-assets.cjs +3 -2
  9. package/adapters/_shared/runtime-native.cjs +132 -0
  10. package/adapters/claude.js +60 -30
  11. package/adapters/codex.js +178 -81
  12. package/adapters/grok.js +372 -0
  13. package/adapters/kimi.js +7 -3
  14. package/adapters/opencode.js +210 -32
  15. package/agents/code-reviewer.md +4 -2
  16. package/agents/debugger.md +4 -1
  17. package/agents/review-code.md +7 -3
  18. package/agents/review-comments.md +7 -4
  19. package/agents/review-coordinator.md +11 -7
  20. package/agents/review-design.md +8 -4
  21. package/agents/review-errors.md +8 -4
  22. package/agents/review-spec.md +8 -5
  23. package/agents/review-tests.md +7 -4
  24. package/agents/tdd-runner.md +5 -2
  25. package/bin/install.js +9 -4
  26. package/bin/orchestrator.js +4 -13
  27. package/docs/AGENT-CONTEXT.md +143 -229
  28. package/docs/ARCHITECTURE.md +205 -494
  29. package/docs/ARTIFACT-AUTHORITY.md +35 -28
  30. package/docs/LEGACY-CLI-CROSSWALK.md +1 -1
  31. package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
  32. package/docs/ROADMAP.md +85 -110
  33. package/docs/RUNTIME-COMPAT-MATRIX.md +139 -175
  34. package/docs/SKILL-AUTHORING.md +3 -3
  35. package/docs/STATE-DB-ACCESS-POLICY.md +13 -8
  36. package/docs/WORKFLOW-LIFECYCLE.md +189 -448
  37. package/hooks/README.md +11 -8
  38. package/hooks/active_task_context.py +16 -16
  39. package/hooks/adapters/grok.py +134 -0
  40. package/hooks/{context_spine.py → context_envelope.py} +26 -22
  41. package/hooks/health_check.py +29 -34
  42. package/hooks/pre_stop_check.py +22 -12
  43. package/hooks/subagent_tracker.py +1 -1
  44. package/hooks/workflow_checkpoint.py +14 -13
  45. package/hooks/workflow_context.py +11 -12
  46. package/hooks/workflow_resume.py +14 -13
  47. package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
  48. package/mcp-server/lib/baseline-workflow.cjs +108 -82
  49. package/mcp-server/lib/canonical-json.cjs +19 -0
  50. package/mcp-server/lib/change-authority.cjs +26 -0
  51. package/mcp-server/lib/change-workflow.cjs +630 -45
  52. package/mcp-server/lib/context-envelope.cjs +508 -0
  53. package/mcp-server/lib/decision-records.cjs +337 -0
  54. package/mcp-server/lib/doctor.cjs +91 -33
  55. package/mcp-server/lib/init-project.cjs +78 -39
  56. package/mcp-server/lib/managed-file-write.cjs +116 -0
  57. package/mcp-server/lib/plan-checkpoint.cjs +177 -0
  58. package/mcp-server/lib/project-context.cjs +178 -0
  59. package/mcp-server/lib/stage-checkpoints.cjs +309 -0
  60. package/mcp-server/lib/state-db.cjs +155 -24
  61. package/mcp-server/lib/state-ops.cjs +21 -3
  62. package/mcp-server/lib/task-ledger.cjs +522 -13
  63. package/mcp-server/lib/ultra-facade.cjs +757 -442
  64. package/mcp-server/lib/worker-packet.cjs +358 -0
  65. package/mcp-server/server.cjs +200 -1103
  66. package/orchestrator/daemon.cjs +27 -9
  67. package/orchestrator/parallel-orchestrator.cjs +111 -76
  68. package/orchestrator/session-runner.cjs +135 -34
  69. package/package.json +10 -3
  70. package/skills/ultra-change/SKILL.md +10 -8
  71. package/skills/ultra-deliver/SKILL.md +17 -19
  72. package/skills/ultra-dev/SKILL.md +20 -11
  73. package/skills/ultra-doctor/SKILL.md +4 -4
  74. package/skills/ultra-init/SKILL.md +6 -5
  75. package/skills/ultra-init/references/brownfield-adoption.md +20 -14
  76. package/skills/ultra-plan/SKILL.md +13 -9
  77. package/skills/ultra-research/SKILL.md +18 -9
  78. package/skills/ultra-review/SKILL.md +14 -9
  79. package/skills/ultra-review/references/unified-schema.md +2 -0
  80. package/skills/ultra-review/scripts/review_wait.py +8 -0
  81. package/skills/ultra-status/SKILL.md +2 -2
  82. package/skills/ultra-test/SKILL.md +10 -6
  83. package/skills/ultra-think/SKILL.md +8 -7
  84. package/skills/ultra-think/references/interaction-boundary.md +56 -0
  85. package/spec/cli-protocol.md +80 -199
  86. package/spec/mcp-tools.yaml +43 -2806
  87. package/spec/schemas/state-db.sql +107 -4
  88. package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
  89. package/ultra-tools/commands/status.cjs +369 -243
  90. package/mcp-server/lib/context-spine.cjs +0 -1208
  91. package/mcp-server/lib/decision-dialogue.cjs +0 -1128
  92. package/mcp-server/lib/project-breadcrumb.cjs +0 -173
  93. package/mcp-server/lib/spec-learning.cjs +0 -284
  94. package/mcp-server/lib/workflow-state.cjs +0 -3129
  95. package/skills/ultra-think/references/decision-dialogue.md +0 -155
@@ -1,13 +1,23 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('node:fs');
3
4
  const path = require('node:path');
4
5
 
5
- const ops = require('./state-ops.cjs');
6
+ const artifactRegistry = require('./artifact-registry.cjs');
6
7
  const baselines = require('./baseline-workflow.cjs');
7
8
  const changes = require('./change-workflow.cjs');
8
- const workflows = require('./workflow-state.cjs');
9
+ const contextEnvelope = require('./context-envelope.cjs');
10
+ const decisionRecords = require('./decision-records.cjs');
9
11
  const doctor = require('./doctor.cjs');
12
+ const gitBootstrap = require('./git-bootstrap.cjs');
13
+ const { initProject } = require('./init-project.cjs');
14
+ const ops = require('./state-ops.cjs');
15
+ const planCheckpoint = require('./plan-checkpoint.cjs');
16
+ const { readStableProjectFile } = require('./safe-project-file.cjs');
17
+ const stageCheckpoints = require('./stage-checkpoints.cjs');
10
18
  const taskLedger = require('./task-ledger.cjs');
19
+ const workerPacket = require('./worker-packet.cjs');
20
+ const sessionRunner = require('../../orchestrator/session-runner.cjs');
11
21
 
12
22
  const PUBLIC_TOOLS = Object.freeze([
13
23
  'ultra.context',
@@ -19,82 +29,84 @@ const PUBLIC_TOOLS = Object.freeze([
19
29
  'ultra.doctor',
20
30
  ]);
21
31
 
22
- const RECORD_OPERATIONS = new Set([
23
- 'task.init_project',
24
- 'task.create',
25
- 'task.update',
26
- 'task.delete',
27
- 'task.append_event',
28
- 'baseline.start',
29
- 'baseline.record',
30
- 'baseline.converge',
31
- 'change.create',
32
- 'change.update',
33
- 'change.delta',
34
- 'change.documentation_reconcile',
35
- 'change.context',
36
- 'change.learning_propose',
37
- 'change.learning_resolve',
38
- 'decision.thread_start',
39
- 'decision.open',
40
- 'decision.resolve',
41
- 'decision.delegate',
42
- 'decision.defer',
43
- 'decision.supersede',
44
- 'decision.complete',
45
- 'decision.checkpoint',
46
- 'artifact.record',
32
+ const RECORD_KINDS = Object.freeze([
33
+ 'baseline',
34
+ 'change_contract',
35
+ 'decision',
36
+ 'task_contract',
37
+ 'task_outcome',
38
+ 'artifact',
39
+ 'event',
47
40
  ]);
48
41
 
49
- const STAGE_CONTEXT = Object.freeze({
50
- plan: { role: 'plan', gate: 'planning' },
51
- dev: { role: 'implement', gate: 'implementation' },
52
- test: { role: 'check', gate: 'verification' },
53
- review: { role: 'review', gate: 'review' },
54
- deliver: { role: 'check', gate: 'convergence' },
55
- });
56
-
57
42
  const HARD_ERROR_CODES = new Set([
58
- 'STATE_CORRUPT',
59
- 'STATE_DB_ERROR',
60
- 'STATE_DB_MISSING',
61
- 'SCHEMA_VERSION_MISMATCH',
62
- 'PATH_AUTHORITY_VIOLATION',
63
43
  'ARCHIVE_PATH_UNSAFE',
44
+ 'ARCHIVE_RECOVERY_REQUIRED',
64
45
  'ARCHIVE_RUNTIME_UNAVAILABLE',
65
- 'PLAN_RECOVERY_REQUIRED',
66
46
  'BACKUP_FAILED',
47
+ 'CHECKPOINT_DIGEST_MISMATCH',
48
+ 'CHECKPOINT_EVIDENCE_AUTHORITY_MISSING',
49
+ 'CHECKPOINT_EVIDENCE_DIGEST_MISMATCH',
50
+ 'CHECKPOINT_SCOPE_MISMATCH',
51
+ 'CHECKPOINT_SCOPE_NOT_FOUND',
52
+ 'CONTEXT_ENVELOPE_LIMIT_EXCEEDED',
53
+ 'CONTEXT_ENVELOPE_DIGEST_MISMATCH',
54
+ 'CONTEXT_ENVELOPE_FILE_DRIFT',
55
+ 'DECISION_DIGEST_MISMATCH',
56
+ 'DECISION_FILE_DRIFT',
57
+ 'DECISION_ID_CONFLICT',
58
+ 'DECISION_SCOPE_CONFLICT',
67
59
  'OUTPUT_SCHEMA_DRIFT',
60
+ 'PATH_AUTHORITY_VIOLATION',
61
+ 'PLAN_RECOVERY_REQUIRED',
62
+ 'RUNTIME_ABI_MISMATCH',
63
+ 'RUNTIME_NATIVE_MISSING',
64
+ 'SCHEMA_VERSION_MISMATCH',
65
+ 'SESSION_STATUS_CONFLICT',
66
+ 'STATE_CORRUPT',
67
+ 'STATE_DB_ERROR',
68
+ 'STATE_DB_MISSING',
69
+ 'TASK_LEDGER_BASELINE_CONFLICT',
70
+ 'TASK_LEDGER_CHANGE_CONFLICT',
71
+ 'TASK_LEDGER_HISTORY_CONFLICT',
72
+ 'TASK_LEDGER_TASK_CONFLICT',
73
+ 'WORKER_PACKET_DIGEST_MISMATCH',
74
+ 'WORKER_PACKET_FILE_DRIFT',
68
75
  ]);
69
76
 
70
- function parseJson(value, fallback = null) {
71
- try { return value == null ? fallback : JSON.parse(value); }
72
- catch { return fallback; }
73
- }
74
-
75
77
  function errorCode(error) {
76
- return String(error?.code || error?.name || 'CHECKPOINT_REJECTED');
78
+ return String(error?.code || error?.name || 'NEEDS_ATTENTION');
77
79
  }
78
80
 
79
81
  function isHardError(error) {
80
82
  const code = errorCode(error);
81
83
  return HARD_ERROR_CODES.has(code)
82
84
  || code.startsWith('PATH_')
85
+ || code.startsWith('SQLITE_')
83
86
  || code.startsWith('STATE_CORRUPT')
84
- || code.startsWith('SQLITE_');
87
+ || code.startsWith('TASK_LEDGER_') && code.endsWith('_CONFLICT')
88
+ || code.startsWith('SESSION_CLOSE_') && code.endsWith('_CONFLICT')
89
+ || code.startsWith('WORKTREE_') && (
90
+ code.endsWith('_UNSAFE') || code.endsWith('_CONFLICT')
91
+ );
92
+ }
93
+
94
+ function parseJson(value, fallback = null) {
95
+ try { return value == null ? fallback : JSON.parse(value); }
96
+ catch { return fallback; }
85
97
  }
86
98
 
87
- function priorFacadeResult(db, idempotencyKey, operation = null) {
88
- if (!idempotencyKey) return null;
99
+ function priorResult(db, idempotencyKey, operation) {
100
+ if (!db || !idempotencyKey) return null;
89
101
  const rows = db.prepare(
90
102
  `SELECT payload_json FROM events
91
- WHERE type = 'ultra_facade_call'
103
+ WHERE type = 'ultra_kernel_call'
92
104
  ORDER BY id DESC LIMIT 2000`,
93
105
  ).all();
94
106
  for (const row of rows) {
95
107
  const payload = parseJson(row.payload_json, {});
96
108
  if (payload.idempotency_key === idempotencyKey
97
- && (operation === null || payload.operation === operation)
109
+ && payload.operation === operation
98
110
  && payload.accepted === true) {
99
111
  return payload.result;
100
112
  }
@@ -102,15 +114,16 @@ function priorFacadeResult(db, idempotencyKey, operation = null) {
102
114
  return null;
103
115
  }
104
116
 
105
- function rememberFacadeResult(db, {
117
+ function rememberResult(db, {
106
118
  idempotencyKey,
107
119
  operation,
108
120
  result,
109
121
  changeId = null,
110
122
  taskId = null,
111
123
  }) {
124
+ if (!db || !idempotencyKey) return;
112
125
  ops.appendEvent(db, {
113
- type: 'ultra_facade_call',
126
+ type: 'ultra_kernel_call',
114
127
  change_id: changeId,
115
128
  task_id: taskId,
116
129
  payload: {
@@ -122,197 +135,298 @@ function rememberFacadeResult(db, {
122
135
  });
123
136
  }
124
137
 
125
- function currentBaselineId(db) {
126
- return db.prepare(
127
- `SELECT id FROM baselines
128
- WHERE status <> 'superseded'
129
- ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
130
- ).get()?.id || null;
138
+ function diagnostic(error, severity = 'needs_attention') {
139
+ return {
140
+ code: errorCode(error),
141
+ severity,
142
+ message: error.message,
143
+ ...(error.details === undefined ? {} : { details: error.details }),
144
+ };
145
+ }
146
+
147
+ function selectedScope(db, input = {}) {
148
+ const scope = input.scope || {};
149
+ const task = scope.task_id ? ops.readTask(db, scope.task_id) : null;
150
+ if (scope.task_id && !task) {
151
+ throw Object.assign(
152
+ new Error(`checkpoint task scope does not exist: ${scope.task_id}`),
153
+ {
154
+ code: 'CHECKPOINT_SCOPE_NOT_FOUND',
155
+ details: { scope_type: 'task', scope_id: scope.task_id },
156
+ },
157
+ );
158
+ }
159
+ const changeId = scope.change_id || task?.change_id || null;
160
+ const change = changeId ? changes.readChange(db, changeId) : null;
161
+ if (changeId && !change) {
162
+ throw Object.assign(
163
+ new Error(`checkpoint Change scope does not exist: ${changeId}`),
164
+ {
165
+ code: 'CHECKPOINT_SCOPE_NOT_FOUND',
166
+ details: { scope_type: 'change', scope_id: changeId },
167
+ },
168
+ );
169
+ }
170
+ if (task && scope.change_id && task.change_id !== scope.change_id) {
171
+ throw Object.assign(
172
+ new Error(
173
+ `checkpoint task ${task.id} belongs to Change ${task.change_id || '(none)'}, not ${scope.change_id}`,
174
+ ),
175
+ {
176
+ code: 'CHECKPOINT_SCOPE_MISMATCH',
177
+ details: {
178
+ task_id: task.id,
179
+ task_change_id: task.change_id || null,
180
+ requested_change_id: scope.change_id,
181
+ },
182
+ },
183
+ );
184
+ }
185
+ if (task) {
186
+ return {
187
+ ...(changeId ? { change_id: changeId } : {}),
188
+ task_id: task.id,
189
+ };
190
+ }
191
+ if (change) return { change_id: change.id };
192
+ const baseline = baselines.readBaseline(db);
193
+ return baseline ? { baseline_id: baseline.id } : { project_id: 'project' };
194
+ }
195
+
196
+ function readContext(db, input = {}, {
197
+ rootDir = process.cwd(),
198
+ runtime = 'unknown',
199
+ } = {}) {
200
+ return contextEnvelope.buildEnvelope(db, input, { rootDir, runtime });
131
201
  }
132
202
 
133
- function currentWorkflow(db, stage, scope = {}, rootDir) {
134
- return workflows.listWorkflows(db, {
135
- kind: stage,
136
- change_id: scope.change_id || null,
137
- task_id: scope.task_id || null,
138
- limit: 100,
139
- }, { rootDir }).find((run) => ['active', 'blocked', 'ready'].includes(run.status)) || null;
203
+ function initializeProject(data = {}) {
204
+ const targetDir = path.resolve(data.target_dir || process.cwd());
205
+ const alreadyInitialized = fs.existsSync(path.join(targetDir, '.ultra'));
206
+ return initProject({
207
+ ...data,
208
+ target_dir: targetDir,
209
+ resume: alreadyInitialized || data.resume === true,
210
+ });
140
211
  }
141
212
 
142
- function ensureWorkflow(db, stage, scope = {}, payload = {}, rootDir) {
143
- const existing = currentWorkflow(db, stage, scope, rootDir);
144
- if (existing) return existing;
145
- const input = {
146
- kind: stage,
147
- subject: typeof payload.subject === 'string' && payload.subject.trim()
148
- ? payload.subject.trim()
149
- : `Record the ${stage} stage for ${scope.task_id || scope.change_id || 'this project'}.`,
150
- baseline_id: currentBaselineId(db),
151
- change_id: scope.change_id || undefined,
152
- task_id: scope.task_id || undefined,
153
- metadata: { public_facade: true },
154
- };
155
- if (stage === 'research') {
156
- input.mode = payload.mode || 'custom';
157
- if (payload.coverage !== undefined) input.coverage = payload.coverage;
158
- if (payload.selected_steps !== undefined) input.selected_steps = payload.selected_steps;
213
+ function baselineRecord(db, action, data, rootDir) {
214
+ if (action === 'initialize') return initializeProject(data);
215
+ if (action === 'start') {
216
+ return { baseline: baselines.startBaseline(db, data, { rootDir }) };
217
+ }
218
+ if (action === 'observe' || action === 'revise') {
219
+ return { baseline: baselines.recordBaseline(db, data, { rootDir }) };
159
220
  }
160
- return workflows.startWorkflow(db, input, { rootDir });
221
+ if (action === 'accept') {
222
+ const result = baselines.convergeBaseline(db, data, { rootDir });
223
+ return {
224
+ ...result,
225
+ diagnostics: [
226
+ ...(result.blockers || []).map((code) => ({
227
+ code,
228
+ severity: 'needs_attention',
229
+ })),
230
+ ...(result.warnings || []).map((code) => ({
231
+ code,
232
+ severity: 'warning',
233
+ })),
234
+ ],
235
+ };
236
+ }
237
+ throw Object.assign(new Error(`unsupported baseline action: ${action}`), {
238
+ code: 'VALIDATION_ERROR',
239
+ });
161
240
  }
162
241
 
163
- function contextOutput(compiled, rootDir) {
164
- return {
165
- path: path.relative(rootDir, compiled.context_manifest_path).split(path.sep).join('/'),
166
- kind: 'context-manifest',
167
- };
242
+ function changeContractRecord(db, action, data, rootDir) {
243
+ if (action === 'open') {
244
+ return changes.createChange(db, data, { rootDir, kernelMode: true });
245
+ }
246
+ if (action === 'revise') {
247
+ return {
248
+ change: changes.updateChange(
249
+ db,
250
+ data.id,
251
+ data.patch || {},
252
+ { rootDir, kernelMode: true },
253
+ ),
254
+ };
255
+ }
256
+ if (action === 'cancel') {
257
+ return {
258
+ change: changes.updateChange(
259
+ db,
260
+ data.id,
261
+ { status: 'cancelled' },
262
+ { rootDir, kernelMode: true },
263
+ ),
264
+ };
265
+ }
266
+ throw Object.assign(new Error(`unsupported change_contract action: ${action}`), {
267
+ code: 'VALIDATION_ERROR',
268
+ });
168
269
  }
169
270
 
170
- function compileStageContext(db, stage, scope, input, rootDir) {
171
- const expectation = STAGE_CONTEXT[stage];
172
- if (!expectation || !scope.change_id) return null;
173
- return changes.compileContext(db, {
174
- id: scope.change_id,
175
- task_id: scope.task_id,
176
- role: expectation.role,
177
- gate: expectation.gate,
178
- ...(Array.isArray(input.context_refs) ? { context_refs: input.context_refs } : {}),
179
- ...(input.budget && typeof input.budget === 'object' ? { budget: input.budget } : {}),
180
- }, { rootDir });
271
+ function taskContractRecord(db, action, data) {
272
+ if (action === 'define') return { task: ops.createTask(db, data) };
273
+ if (action === 'revise') return { task: ops.patchTask(db, data.id, data.patch || {}) };
274
+ if (action === 'remove') return ops.deleteTask(db, data.id, { force: data.force === true });
275
+ throw Object.assign(new Error(`unsupported task_contract action: ${action}`), {
276
+ code: 'VALIDATION_ERROR',
277
+ });
181
278
  }
182
279
 
183
- function readContext(db, input = {}, { rootDir = process.cwd() } = {}) {
184
- const stage = input.stage || 'project';
185
- const scope = input.scope || {};
186
- const health = doctor.inspectSystem(db, { rootDir });
187
- const baseline = baselines.inspectBaseline(db, { rootDir });
188
- const project = {
189
- health: health.status,
190
- baseline,
191
- changes: changes.listChanges(db, { limit: input.detail === 'full' ? 500 : 20 }),
192
- tasks: ops.listTasks(db, { status: 'any', limit: input.detail === 'full' ? 1000 : 100 }),
193
- workflows: workflows.listWorkflows(
280
+ function taskOutcomeRecord(db, action, data, rootDir) {
281
+ const status = {
282
+ start: 'in_progress',
283
+ complete: 'completed',
284
+ block: 'blocked',
285
+ reopen: 'in_progress',
286
+ }[action];
287
+ if (!status) {
288
+ throw Object.assign(new Error(`unsupported task_outcome action: ${action}`), {
289
+ code: 'VALIDATION_ERROR',
290
+ });
291
+ }
292
+ if (action === 'complete') {
293
+ const packet = db.prepare(
294
+ `SELECT id, packet_digest, packet_path, output_path
295
+ FROM worker_packets
296
+ WHERE scope_type = 'task' AND scope_id = ? AND status = 'assigned'
297
+ ORDER BY created_at DESC, rowid DESC LIMIT 1`,
298
+ ).get(data.id);
299
+ if (!packet) {
300
+ throw Object.assign(
301
+ new Error(`task ${data.id} has no assigned Worker Packet`),
302
+ { code: 'WORKER_PACKET_REQUIRED' },
303
+ );
304
+ }
305
+ const packetDocument = workerPacket.readWorkerPacket(
194
306
  db,
195
- { limit: input.detail === 'full' ? 500 : 50 },
307
+ packet.id,
196
308
  { rootDir },
197
- ),
198
- team_checkpoint: taskLedger.inspectTaskLedger(db, { rootDir }),
199
- };
200
- const expectation = STAGE_CONTEXT[stage];
201
- const snapshot = expectation && scope.change_id
202
- ? db.prepare(
203
- `SELECT * FROM context_snapshots
204
- WHERE change_id = ? AND (? IS NULL OR task_id = ?)
205
- AND role = ? AND gate = ?
206
- ORDER BY created_at DESC, rowid DESC LIMIT 1`,
207
- ).get(
208
- scope.change_id,
209
- scope.task_id || null,
210
- scope.task_id || null,
211
- expectation.role,
212
- expectation.gate,
213
- )
214
- : null;
215
- const context = snapshot
216
- ? {
217
- snapshot_id: snapshot.id,
218
- manifest_path: snapshot.manifest_path,
219
- manifest_hash: snapshot.manifest_hash,
220
- readiness: snapshot.readiness,
221
- blockers: parseJson(snapshot.blockers_json, []),
222
- spine: parseJson(snapshot.context_json, {}),
309
+ );
310
+ if (data.packet_digest !== packet.packet_digest) {
311
+ throw Object.assign(
312
+ new Error('task outcome does not reference the exact assigned Worker Packet'),
313
+ { code: 'WORKER_PACKET_DIGEST_MISMATCH' },
314
+ );
223
315
  }
224
- : null;
225
- return {
226
- status: context?.readiness
227
- || (health.status === 'healthy' ? 'ready' : 'needs_attention'),
228
- warnings: baseline.warnings || [],
229
- blockers: [...new Set([
230
- ...(baseline.blockers || []),
231
- ...(context?.blockers || []),
232
- ])],
233
- project,
234
- workflow: expectation ? currentWorkflow(db, stage, scope, rootDir) : null,
235
- context,
236
- };
316
+ const output = readStableProjectFile(rootDir, packet.output_path, { encoding: 'utf8' });
317
+ let result;
318
+ try {
319
+ result = JSON.parse(output.text);
320
+ } catch (cause) {
321
+ throw Object.assign(
322
+ new Error(`worker output is not valid JSON: ${packet.output_path}`),
323
+ { code: 'WORKER_OUTPUT_INVALID', cause },
324
+ );
325
+ }
326
+ workerPacket.verifyWorkerResult(packetDocument, result);
327
+ artifactRegistry.recordArtifact(db, {
328
+ id: `artifact-worker-outcome-${packet.id}`,
329
+ owner_type: 'task',
330
+ owner_id: data.id,
331
+ task_id: data.id,
332
+ change_id: ops.readTask(db, data.id)?.change_id || null,
333
+ kind: 'task_outcome',
334
+ path: packet.output_path,
335
+ content_digest: output.digest,
336
+ source_refs: [{
337
+ type: 'external',
338
+ id: `worker-packet:${packet.id}`,
339
+ relation: 'produced_from_packet',
340
+ }],
341
+ consumer_refs: [{
342
+ type: 'external',
343
+ id: 'ultra-test',
344
+ relation: 'consumed_by',
345
+ }],
346
+ provenance: {
347
+ writer: 'worker-result',
348
+ packet_digest: packet.packet_digest,
349
+ },
350
+ metadata: {
351
+ worker_packet_id: packet.id,
352
+ packet_path: packet.packet_path,
353
+ },
354
+ }, { rootDir });
355
+ }
356
+ if (data.patch && Object.keys(data.patch).length > 0) {
357
+ ops.patchTask(db, data.id, data.patch);
358
+ }
359
+ return { task: ops.updateTaskStatus(db, data.id, status) };
360
+ }
361
+
362
+ function applyRecord(db, entry, { rootDir }) {
363
+ const { kind, action, data = {} } = entry;
364
+ if (!RECORD_KINDS.includes(kind)) {
365
+ throw Object.assign(new Error(`unsupported record kind: ${kind}`), {
366
+ code: 'VALIDATION_ERROR',
367
+ });
368
+ }
369
+ if (kind === 'baseline') return baselineRecord(db, action, data, rootDir);
370
+ if (kind === 'change_contract') return changeContractRecord(db, action, data, rootDir);
371
+ if (kind === 'decision' && action === 'accept') {
372
+ return { decision: decisionRecords.acceptDecision(db, data, { rootDir }) };
373
+ }
374
+ if (kind === 'task_contract') return taskContractRecord(db, action, data);
375
+ if (kind === 'task_outcome') return taskOutcomeRecord(db, action, data, rootDir);
376
+ if (kind === 'artifact' && action === 'bind') {
377
+ return artifactRegistry.recordArtifact(db, data, { rootDir });
378
+ }
379
+ if (kind === 'event' && action === 'append') {
380
+ return ops.appendEvent(db, data);
381
+ }
382
+ throw Object.assign(
383
+ new Error(`unsupported ${kind} action: ${action}`),
384
+ { code: 'VALIDATION_ERROR' },
385
+ );
237
386
  }
238
387
 
239
388
  async function record(db, input = {}, {
240
389
  rootDir = process.cwd(),
241
- callLegacy,
242
390
  } = {}) {
243
391
  const results = [];
244
392
  for (const entry of input.entries || []) {
245
- const cached = db
246
- ? priorFacadeResult(db, entry.idempotency_key, entry.operation)
247
- : null;
393
+ const operation = `${entry.kind}:${entry.action}`;
394
+ const cached = priorResult(db, entry.idempotency_key, operation);
248
395
  if (cached) {
249
- results.push({ operation: entry.operation, accepted: true, idempotent: true, result: cached });
250
- continue;
251
- }
252
- if (entry.operation === 'workflow.abandon') {
253
- try {
254
- const result = workflows.abandonWorkflow(db, entry.data, { rootDir });
255
- rememberFacadeResult(db, {
256
- idempotencyKey: entry.idempotency_key,
257
- operation: entry.operation,
258
- result,
259
- changeId: result.change_id,
260
- taskId: result.task_id,
261
- });
262
- results.push({ operation: entry.operation, accepted: true, idempotent: false, result });
263
- } catch (error) {
264
- if (isHardError(error)) throw error;
265
- results.push({
266
- operation: entry.operation,
267
- accepted: false,
268
- mutable: true,
269
- blockers: [errorCode(error)],
270
- diagnostic: { code: errorCode(error), message: error.message },
271
- });
272
- }
396
+ results.push({
397
+ kind: entry.kind,
398
+ action: entry.action,
399
+ accepted: true,
400
+ idempotent: true,
401
+ result: cached,
402
+ });
273
403
  continue;
274
404
  }
275
- if (entry.operation === 'event.append') {
276
- const event = ops.appendEvent(db, entry.data);
277
- rememberFacadeResult(db, {
405
+ try {
406
+ const result = applyRecord(db, entry, { rootDir });
407
+ rememberResult(db, {
278
408
  idempotencyKey: entry.idempotency_key,
279
- operation: entry.operation,
280
- result: event,
281
- changeId: entry.data.change_id || null,
282
- taskId: entry.data.task_id || null,
409
+ operation,
410
+ result,
411
+ changeId: entry.data?.change_id || entry.data?.id || null,
412
+ taskId: entry.kind.startsWith('task_') ? entry.data?.id || null : null,
283
413
  });
284
- results.push({ operation: entry.operation, accepted: true, idempotent: false, result: event });
285
- continue;
286
- }
287
- if (!RECORD_OPERATIONS.has(entry.operation)) {
288
414
  results.push({
289
- operation: entry.operation,
290
- accepted: false,
291
- mutable: true,
292
- blockers: ['ULTRA_RECORD_OPERATION_UNSUPPORTED'],
415
+ kind: entry.kind,
416
+ action: entry.action,
417
+ accepted: true,
418
+ idempotent: false,
419
+ result,
420
+ diagnostics: result?.diagnostics || [],
293
421
  });
294
- continue;
295
- }
296
- try {
297
- const result = await callLegacy(entry.operation, entry.data);
298
- if (db) {
299
- rememberFacadeResult(db, {
300
- idempotencyKey: entry.idempotency_key,
301
- operation: entry.operation,
302
- result,
303
- changeId: entry.data.change_id || entry.data.id || null,
304
- taskId: entry.data.task_id || null,
305
- });
306
- }
307
- results.push({ operation: entry.operation, accepted: true, idempotent: false, result });
308
422
  } catch (error) {
309
423
  if (isHardError(error)) throw error;
310
424
  results.push({
311
- operation: entry.operation,
425
+ kind: entry.kind,
426
+ action: entry.action,
312
427
  accepted: false,
313
428
  mutable: true,
314
- blockers: [errorCode(error)],
315
- diagnostic: { code: errorCode(error), message: error.message, details: error.details },
429
+ diagnostics: [diagnostic(error)],
316
430
  });
317
431
  }
318
432
  }
@@ -323,273 +437,480 @@ async function record(db, input = {}, {
323
437
  };
324
438
  }
325
439
 
326
- function addStepOutput(steps, stepId, output) {
327
- const current = steps.find((step) => step.step_id === stepId);
328
- if (current) current.outputs = [...(current.outputs || []), output];
329
- else steps.push({ step_id: stepId, outputs: [output] });
330
- }
331
-
332
- function checkpointOutputStep(stage, output) {
333
- if (output.step_id) return output.step_id;
334
- if (stage === 'test') return 'write-report';
335
- if (stage === 'deliver') return 'verify-delivery';
336
- if (stage === 'review') {
337
- if (output.role === 'spec_review') return 'review-specification';
338
- if (output.role === 'engineering_review') return 'review-engineering';
339
- return 'coordinate-findings';
340
- }
341
- return null;
440
+ function checkpointDiagnostics(input, context) {
441
+ const supplied = Array.isArray(input.payload?.diagnostics)
442
+ ? input.payload.diagnostics
443
+ : [];
444
+ const derived = [
445
+ ...(context.envelope?.diagnostics?.warnings || []).map((item) => ({
446
+ ...item,
447
+ severity: 'warning',
448
+ })),
449
+ ...(context.envelope?.diagnostics?.needs_attention || []).map((item) => ({
450
+ ...item,
451
+ severity: 'warning',
452
+ })),
453
+ ...(context.envelope?.diagnostics?.hard_conflicts || []).map((item) => ({
454
+ ...item,
455
+ severity: 'hard_conflict',
456
+ })),
457
+ ];
458
+ return [...supplied, ...derived]
459
+ .filter((item) => item && typeof item === 'object' && item.code)
460
+ .map((item) => ({
461
+ code: String(item.code),
462
+ severity: item.severity || 'needs_attention',
463
+ ...(item.message ? { message: String(item.message) } : {}),
464
+ ...(item.details === undefined ? {} : { details: item.details }),
465
+ }));
342
466
  }
343
467
 
344
- async function checkpoint(db, input = {}, {
345
- rootDir = process.cwd(),
346
- callLegacy,
347
- } = {}) {
348
- const cached = priorFacadeResult(db, input.idempotency_key, `checkpoint:${input.stage}`);
349
- if (cached) return { ...cached, idempotent: true };
350
- const stage = input.stage;
351
- const scope = input.scope || {};
468
+ function stageAdvisoryDiagnostics(db, input, scope) {
352
469
  const payload = input.payload || {};
353
- let run = null;
354
- try {
355
- if (stage === 'plan') {
356
- const taskCount = ops.listTasks(db, { change_id: scope.change_id }).length;
357
- if (taskCount === 0) {
358
- return {
359
- accepted: false,
360
- mutable: true,
361
- blockers: ['WORKFLOW_TASKS_REQUIRED'],
362
- workflow: null,
363
- };
364
- }
365
- }
366
- run = ensureWorkflow(db, stage, scope, payload, rootDir);
367
- const steps = Array.isArray(payload.steps)
368
- ? payload.steps.map((step) => ({ ...step }))
369
- : [];
370
- const compiled = STAGE_CONTEXT[stage]
371
- ? compileStageContext(db, stage, scope, payload, rootDir)
470
+ const evidence = Array.isArray(payload.evidence) ? payload.evidence : [];
471
+ const warning = (code, message) => ({ code, severity: 'warning', message });
472
+ if (input.stage === 'research') {
473
+ return evidence.length > 0
474
+ ? []
475
+ : [warning(
476
+ 'RESEARCH_EVIDENCE_MISSING',
477
+ 'No research evidence was declared; the caller remains responsible for semantic sufficiency.',
478
+ )];
479
+ }
480
+ if (input.stage === 'dev') {
481
+ const task = scope.task_id ? ops.readTask(db, scope.task_id) : null;
482
+ const outcome = task
483
+ ? db.prepare(
484
+ `SELECT id FROM artifacts
485
+ WHERE task_id = ? AND kind = 'task_outcome' AND status = 'current'
486
+ ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
487
+ ).get(task.id)
372
488
  : null;
373
- if (compiled) {
374
- addStepOutput(
375
- steps,
376
- stage === 'deliver' ? 'verify-candidate' : 'compile-context',
377
- contextOutput(compiled, rootDir),
378
- );
489
+ const diagnostics = [];
490
+ if (!task) diagnostics.push(warning('TASK_NOT_FOUND', 'No task scope was found for this Dev checkpoint.'));
491
+ else if (task.status !== 'completed') {
492
+ diagnostics.push(warning('TASK_OUTCOME_INCOMPLETE', `Task ${task.id} is not completed.`));
379
493
  }
380
- let stageResult = null;
381
- if (stage === 'plan') {
382
- stageResult = await callLegacy('plan.export', { change_id: scope.change_id });
383
- addStepOutput(steps, 'verify-plan', {
384
- path: path.relative(rootDir, stageResult.plan_path).split(path.sep).join('/'),
385
- kind: 'execution-plan',
386
- });
494
+ if (task && !outcome) {
495
+ diagnostics.push(warning(
496
+ 'WORKER_OUTCOME_MISSING',
497
+ `Task ${task.id} has no current Worker Packet outcome artifact.`,
498
+ ));
387
499
  }
388
- for (const output of Array.isArray(payload.outputs) ? payload.outputs : []) {
389
- const stepId = checkpointOutputStep(stage, output);
390
- if (!stepId) continue;
391
- const { step_id: _stepId, role: _role, ...normalized } = output;
392
- addStepOutput(steps, stepId, normalized);
500
+ return diagnostics;
501
+ }
502
+ if (input.stage === 'test') {
503
+ const result = String(payload.result || '').toLowerCase();
504
+ const diagnostics = [];
505
+ if (!result) {
506
+ diagnostics.push(warning('TEST_RESULT_MISSING', 'No test result was declared.'));
507
+ } else if (!['pass', 'known_red'].includes(result)) {
508
+ diagnostics.push(warning(
509
+ 'TEST_RESULT_RECORDED',
510
+ `The caller recorded test result ${result}; MCP does not reinterpret it.`,
511
+ ));
393
512
  }
394
- const prepared = workflows.prepareWorkflowCheckpoint(db, {
395
- id: run.id,
396
- steps,
397
- evidence: payload.evidence,
398
- }, { rootDir });
399
- if (!prepared.ready) {
400
- return {
401
- accepted: false,
402
- mutable: true,
403
- blockers: prepared.blockers,
404
- workflow: prepared.workflow,
405
- };
513
+ if (evidence.length === 0) {
514
+ diagnostics.push(warning('TEST_EVIDENCE_MISSING', 'No test evidence was declared.'));
406
515
  }
407
- const completed = workflows.completeWorkflow(db, {
408
- id: run.id,
409
- ...(payload.approval ? { approval: payload.approval } : {}),
410
- }, { rootDir });
411
- if (stage === 'plan') {
412
- stageResult = {
413
- ...stageResult,
414
- team_checkpoint: taskLedger.publishTaskLedger(db, {
415
- rootDir,
416
- reason: 'plan_accepted',
417
- }),
418
- };
516
+ return diagnostics;
517
+ }
518
+ if (input.stage === 'review') {
519
+ const verdict = String(payload.verdict || '').toLowerCase();
520
+ const diagnostics = [];
521
+ if (!verdict) {
522
+ diagnostics.push(warning('REVIEW_VERDICT_MISSING', 'No review verdict was declared.'));
523
+ } else if (!['approve', 'pass'].includes(verdict)) {
524
+ diagnostics.push(warning(
525
+ 'REVIEW_VERDICT_RECORDED',
526
+ `The caller recorded review verdict ${verdict}; MCP does not reinterpret it.`,
527
+ ));
419
528
  }
420
- const result = {
421
- accepted: true,
422
- mutable: false,
423
- blockers: [],
424
- workflow: completed,
425
- result: stageResult,
426
- idempotent: false,
427
- };
428
- rememberFacadeResult(db, {
429
- idempotencyKey: input.idempotency_key,
430
- operation: `checkpoint:${stage}`,
431
- result,
432
- changeId: scope.change_id || null,
433
- taskId: scope.task_id || null,
434
- });
435
- ops.appendEvent(db, {
436
- type: 'ultra_checkpoint_accepted',
437
- change_id: scope.change_id || null,
438
- task_id: scope.task_id || null,
439
- payload: { stage, workflow_id: completed.id, idempotency_key: input.idempotency_key },
440
- });
441
- return result;
442
- } catch (error) {
443
- if (isHardError(error)) throw error;
444
- if (run?.id) {
445
- workflows.reopenWorkflowDraft(db, {
446
- id: run.id,
447
- blocker: errorCode(error),
448
- }, { rootDir });
529
+ if (evidence.length === 0) {
530
+ diagnostics.push(warning('REVIEW_EVIDENCE_MISSING', 'No review evidence was declared.'));
449
531
  }
450
- ops.appendEvent(db, {
451
- type: 'ultra_checkpoint_rejected',
452
- change_id: scope.change_id || null,
453
- task_id: scope.task_id || null,
454
- payload: {
532
+ return diagnostics;
533
+ }
534
+ if (input.stage === 'deliver') {
535
+ const diagnostics = [];
536
+ for (const stage of ['test', 'review']) {
537
+ if (!stageCheckpoints.currentCheckpoint(
538
+ db,
455
539
  stage,
456
- workflow_id: run?.id || null,
457
- code: errorCode(error),
458
- idempotency_key: input.idempotency_key,
459
- },
460
- });
540
+ { change_id: scope.change_id },
541
+ { includeDraft: false },
542
+ )) {
543
+ diagnostics.push(warning(
544
+ `${stage.toUpperCase()}_CHECKPOINT_MISSING`,
545
+ `No accepted ${stage} checkpoint was found; the caller owns the delivery decision.`,
546
+ ));
547
+ }
548
+ }
549
+ if (String(payload.summary || '').trim().length < 3) {
550
+ diagnostics.push(warning('DELIVERY_SUMMARY_MISSING', 'No durable delivery summary was declared.'));
551
+ }
552
+ if (evidence.length === 0) {
553
+ diagnostics.push(warning('DELIVERY_EVIDENCE_MISSING', 'No delivery evidence was declared.'));
554
+ }
555
+ return diagnostics;
556
+ }
557
+ return [];
558
+ }
559
+
560
+ function normalizeCheckpointEvidence(db, input, rootDir) {
561
+ const evidence = Array.isArray(input.payload?.evidence) ? input.payload.evidence : [];
562
+ return evidence.map((item, index) => {
563
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
564
+ const error = new Error(`checkpoint evidence[${index}] must be an object`);
565
+ error.code = 'VALIDATION_ERROR';
566
+ throw error;
567
+ }
568
+ const normalized = { ...item };
569
+ let relative = typeof item.ref === 'string' ? item.ref.trim() : '';
570
+ let artifact = null;
571
+ if (item.artifact_id) {
572
+ try {
573
+ artifact = artifactRegistry.getArtifact(db, { id: String(item.artifact_id) });
574
+ } catch (error) {
575
+ if (error?.code !== 'ARTIFACT_NOT_FOUND') throw error;
576
+ }
577
+ if (!artifact || artifact.status !== 'current') {
578
+ const error = new Error(`checkpoint evidence artifact is not current: ${item.artifact_id}`);
579
+ error.code = 'CHECKPOINT_EVIDENCE_AUTHORITY_MISSING';
580
+ throw error;
581
+ }
582
+ if (relative && artifact.path !== relative) {
583
+ const error = new Error(`checkpoint evidence path does not match artifact ${item.artifact_id}`);
584
+ error.code = 'CHECKPOINT_EVIDENCE_AUTHORITY_MISSING';
585
+ throw error;
586
+ }
587
+ relative = artifact.path;
588
+ normalized.artifact_id = artifact.id;
589
+ if (item.digest && String(item.digest) !== artifact.digest) {
590
+ const error = new Error(
591
+ `checkpoint evidence digest contradicts artifact ${item.artifact_id}`,
592
+ );
593
+ error.code = 'CHECKPOINT_EVIDENCE_DIGEST_MISMATCH';
594
+ error.details = {
595
+ path: relative,
596
+ expected: artifact.digest,
597
+ actual: String(item.digest),
598
+ };
599
+ throw error;
600
+ }
601
+ }
602
+ if (!item.digest && !item.artifact_id) return normalized;
603
+ if (!relative) {
604
+ const error = new Error(`checkpoint evidence[${index}] requires ref when digest is supplied`);
605
+ error.code = 'VALIDATION_ERROR';
606
+ throw error;
607
+ }
608
+ const read = readStableProjectFile(rootDir, relative);
609
+ const expected = item.artifact_id
610
+ ? artifact.digest
611
+ : String(item.digest);
612
+ if (read.digest !== expected) {
613
+ const error = new Error(`checkpoint evidence digest mismatch: ${relative}`);
614
+ error.code = 'CHECKPOINT_EVIDENCE_DIGEST_MISMATCH';
615
+ error.details = { path: relative, expected, actual: read.digest };
616
+ throw error;
617
+ }
618
+ normalized.ref = relative;
619
+ normalized.digest = read.digest;
620
+ return normalized;
621
+ });
622
+ }
623
+
624
+ async function checkpoint(db, input = {}, {
625
+ rootDir = process.cwd(),
626
+ runtime = 'unknown',
627
+ markPlanRecoveryRequired = null,
628
+ } = {}) {
629
+ const scope = selectedScope(db, input);
630
+ const context = contextEnvelope.persistEnvelope(db, {
631
+ stage: input.stage,
632
+ scope,
633
+ }, { rootDir, runtime });
634
+ const view = contextEnvelope.buildEnvelope(db, {
635
+ stage: input.stage,
636
+ scope,
637
+ detail: 'summary',
638
+ }, { rootDir, runtime });
639
+ const evidence = normalizeCheckpointEvidence(db, input, rootDir);
640
+ const diagnostics = [
641
+ ...checkpointDiagnostics(input, view),
642
+ ...stageAdvisoryDiagnostics(db, input, scope),
643
+ ...evidence
644
+ .filter((item) => !item.digest && !item.artifact_id)
645
+ .map((item, index) => ({
646
+ code: 'CHECKPOINT_EVIDENCE_UNBOUND',
647
+ severity: 'warning',
648
+ message: `Evidence declaration ${item.ref || item.kind || index} has no current digest or managed artifact binding.`,
649
+ })),
650
+ ];
651
+ let result = null;
652
+
653
+ if (input.stage === 'plan'
654
+ && !diagnostics.some((item) => item.severity === 'hard_conflict')) {
655
+ try {
656
+ result = planCheckpoint.publishPlan(db, {
657
+ change_id: scope.change_id,
658
+ context,
659
+ }, { rootDir, markRecoveryRequired: markPlanRecoveryRequired });
660
+ } catch (error) {
661
+ if (isHardError(error)) throw error;
662
+ diagnostics.push(diagnostic(error, 'hard_conflict'));
663
+ }
664
+ }
665
+
666
+ const draft = stageCheckpoints.saveDraft(db, {
667
+ stage: input.stage,
668
+ scope: scope.task_id
669
+ ? { task_id: scope.task_id }
670
+ : scope.change_id
671
+ ? { change_id: scope.change_id }
672
+ : scope.baseline_id
673
+ ? { baseline_id: scope.baseline_id }
674
+ : { project_id: 'project' },
675
+ payload: input.stage === 'plan' && result
676
+ ? { ...(input.payload || {}), plan: result }
677
+ : (input.payload || {}),
678
+ evidence,
679
+ diagnostics,
680
+ context_envelope_id: context.id,
681
+ idempotency_key: `${input.idempotency_key}:draft`,
682
+ });
683
+ if (diagnostics.some((item) => item.severity === 'hard_conflict')) {
461
684
  return {
462
685
  accepted: false,
463
686
  mutable: true,
464
- blockers: [errorCode(error)],
465
- workflow: run?.id ? workflows.readWorkflow(db, run.id, { rootDir }) : null,
466
- diagnostic: { code: errorCode(error), message: error.message, details: error.details },
687
+ diagnostics,
688
+ blockers: diagnostics.map((item) => item.code),
689
+ checkpoint: draft,
690
+ context,
691
+ result,
692
+ };
693
+ }
694
+ const accepted = stageCheckpoints.acceptDraft(db, {
695
+ id: draft.id,
696
+ idempotency_key: `${input.idempotency_key}:accept`,
697
+ });
698
+ if (input.stage === 'plan') {
699
+ result = {
700
+ ...result,
701
+ team_checkpoint: taskLedger.publishTaskLedger(db, {
702
+ rootDir,
703
+ reason: 'plan_checkpoint_accepted',
704
+ }),
467
705
  };
468
706
  }
707
+ return {
708
+ accepted: true,
709
+ mutable: false,
710
+ diagnostics,
711
+ blockers: [],
712
+ checkpoint: accepted,
713
+ context,
714
+ result,
715
+ };
469
716
  }
470
717
 
471
718
  function sync(db, input = {}, { rootDir = process.cwd() } = {}) {
472
- if (input.action === 'inspect') return taskLedger.inspectTaskLedger(db, { rootDir });
719
+ gitBootstrap.ensureExistingProjectStorageBoundary(rootDir);
720
+ if (input.action === 'inspect') {
721
+ try {
722
+ return taskLedger.inspectTaskLedger(db, { rootDir });
723
+ } catch (error) {
724
+ return {
725
+ status: 'migration_required',
726
+ migration: {
727
+ required: true,
728
+ code: errorCode(error),
729
+ action: 'ultra.sync migrate',
730
+ },
731
+ };
732
+ }
733
+ }
473
734
  if (input.action === 'import') return taskLedger.importTaskLedger(db, { rootDir });
474
- const cached = priorFacadeResult(db, input.idempotency_key, 'sync:publish');
475
- if (cached) return { ...cached, idempotent: true };
476
- const result = taskLedger.publishTaskLedger(db, {
477
- rootDir,
478
- reason: input.reason || 'manual_checkpoint',
479
- });
480
- if (input.idempotency_key) {
481
- rememberFacadeResult(db, {
735
+ if (input.action === 'migrate') {
736
+ const published = taskLedger.publishTaskLedger(db, {
737
+ rootDir,
738
+ reason: 'legacy_authority_migrated',
739
+ });
740
+ return {
741
+ migrated: Boolean(published.migrated_legacy_projection),
742
+ ...published,
743
+ };
744
+ }
745
+ if (input.action === 'publish') {
746
+ const cached = priorResult(db, input.idempotency_key, 'sync:publish');
747
+ if (cached) return { ...cached, idempotent: true };
748
+ const result = taskLedger.publishTaskLedger(db, {
749
+ rootDir,
750
+ reason: input.reason || 'manual_checkpoint',
751
+ });
752
+ rememberResult(db, {
482
753
  idempotencyKey: input.idempotency_key,
483
754
  operation: 'sync:publish',
484
755
  result,
485
756
  });
757
+ return result;
486
758
  }
487
- return result;
759
+ throw Object.assign(new Error(`unsupported sync action: ${input.action}`), {
760
+ code: 'VALIDATION_ERROR',
761
+ });
488
762
  }
489
763
 
490
- async function session(db, input = {}, { callLegacy } = {}) {
491
- const scope = input.scope || {};
492
- const payload = input.payload || {};
493
- const operation = {
494
- admission: 'session.admission_check',
495
- acquire: 'session.spawn',
496
- get: 'session.get',
497
- list: 'session.list',
498
- heartbeat: 'session.heartbeat',
499
- release: 'session.close',
500
- }[input.action];
501
- const args = {
502
- ...payload,
503
- ...(scope.task_id ? { task_id: scope.task_id } : {}),
504
- ...(scope.sid ? { sid: scope.sid } : {}),
764
+ function sessionDiagnostic(error) {
765
+ if (isHardError(error)) throw error;
766
+ return {
767
+ accepted: false,
768
+ mutable: true,
769
+ can_acquire: false,
770
+ diagnostics: [diagnostic(error)],
505
771
  };
506
- if (input.action === 'release' && args.status === undefined) args.status = 'completed';
507
- return callLegacy(operation, args);
508
772
  }
509
773
 
510
- async function archive(db, input = {}, {
774
+ async function session(db, input = {}, {
511
775
  rootDir = process.cwd(),
512
- callLegacy,
776
+ runtime = 'unknown',
777
+ sessionId = null,
513
778
  } = {}) {
514
- const cached = priorFacadeResult(db, input.idempotency_key, 'archive');
515
- if (cached) return { ...cached, idempotent: true };
779
+ const scope = input.scope || {};
516
780
  const payload = input.payload || {};
517
- let run = null;
518
- try {
519
- run = ensureWorkflow(
520
- db,
521
- 'deliver',
522
- { change_id: input.change_id },
523
- payload,
524
- rootDir,
525
- );
526
- const steps = Array.isArray(payload.steps)
527
- ? payload.steps.map((step) => ({ ...step }))
528
- : [];
529
- const compiled = compileStageContext(
530
- db,
531
- 'deliver',
532
- { change_id: input.change_id },
533
- payload,
534
- rootDir,
535
- );
536
- if (compiled) {
537
- addStepOutput(steps, 'verify-candidate', contextOutput(compiled, rootDir));
538
- }
539
- for (const output of Array.isArray(payload.outputs) ? payload.outputs : []) {
540
- const stepId = checkpointOutputStep('deliver', output);
541
- if (!stepId) continue;
542
- const { step_id: _stepId, role: _role, ...normalized } = output;
543
- addStepOutput(steps, stepId, normalized);
544
- }
545
- const prepared = workflows.prepareWorkflowCheckpoint(db, {
546
- id: run.id,
547
- steps,
548
- evidence: payload.evidence,
549
- }, { rootDir });
550
- if (!prepared.ready) {
551
- return {
552
- accepted: false,
553
- mutable: true,
554
- blockers: prepared.blockers,
555
- workflow: prepared.workflow,
556
- };
781
+ if (input.action === 'get') {
782
+ return { session: ops.readSession(db, scope.sid) };
783
+ }
784
+ if (input.action === 'list') {
785
+ const sessions = ops.listActiveSessions(db, { task_id: scope.task_id });
786
+ return { sessions, count: sessions.length };
787
+ }
788
+ if (input.action === 'heartbeat') return ops.heartbeatSession(db, scope.sid);
789
+ if (input.action === 'release') {
790
+ if (sessionId) {
791
+ return sessionDiagnostic(Object.assign(
792
+ new Error('a worker session is settled by its parent host'),
793
+ { code: 'WORKER_SESSION_PARENT_OWNED' },
794
+ ));
557
795
  }
558
- const convergence = await callLegacy('change.converge', { id: input.change_id });
559
- if (convergence.ready !== true) {
560
- workflows.reopenWorkflowDraft(db, {
561
- id: run.id,
562
- blocker: 'CHANGE_NOT_READY',
563
- }, { rootDir });
796
+ return sessionRunner.closeSession(
797
+ { db, repoRoot: rootDir, sid: scope.sid },
798
+ {
799
+ status: payload.status || 'completed',
800
+ remove_worktree: payload.remove_worktree === true,
801
+ },
802
+ );
803
+ }
804
+ const taskId = scope.task_id;
805
+ if (input.action === 'admission') {
806
+ try {
807
+ const task = ops.readTask(db, taskId);
808
+ if (!task) throw Object.assign(new Error(`task ${taskId} not found`), { code: 'TASK_NOT_FOUND' });
809
+ const attention = ops.taskContractBlockers(task).map((code) => ({
810
+ code,
811
+ severity: 'needs_attention',
812
+ }));
813
+ const lease = ops.admissionCheck(db, taskId);
564
814
  return {
565
- accepted: false,
815
+ accepted: attention.length === 0 && lease.can_spawn,
566
816
  mutable: true,
567
- blockers: convergence.blockers || ['CHANGE_NOT_READY'],
568
- convergence,
817
+ can_acquire: attention.length === 0 && lease.can_spawn,
818
+ diagnostics: [
819
+ ...attention,
820
+ ...(lease.can_spawn ? [] : [{
821
+ code: 'ACTIVE_SESSION_LEASE_CONFLICT',
822
+ severity: 'hard_conflict',
823
+ details: lease.conflict,
824
+ }]),
825
+ ],
826
+ lease,
569
827
  };
828
+ } catch (error) {
829
+ return sessionDiagnostic(error);
570
830
  }
571
- const {
572
- steps: _steps,
573
- outputs: _outputs,
574
- evidence: _evidence,
575
- context_refs: _contextRefs,
576
- budget: _budget,
577
- ...archivePayload
578
- } = payload;
579
- const result = await callLegacy('change.archive', {
580
- id: input.change_id,
581
- ...archivePayload,
831
+ }
832
+ if (input.action !== 'acquire') {
833
+ throw Object.assign(new Error(`unsupported session action: ${input.action}`), {
834
+ code: 'VALIDATION_ERROR',
835
+ });
836
+ }
837
+ let packet = null;
838
+ try {
839
+ packet = workerPacket.createWorkerPacket(db, {
840
+ role: payload.role || 'implement',
841
+ task_id: taskId,
842
+ runtime: payload.runtime || runtime,
843
+ output_path: payload.output_path
844
+ || `.ultra/changes/active/${ops.readTask(db, taskId)?.change_id}/delivery/${taskId}-outcome.json`,
845
+ output_schema: payload.output_schema,
846
+ evidence_refs: payload.evidence_refs,
847
+ diff_range: payload.diff_range,
848
+ changed_files: payload.changed_files,
849
+ }, { rootDir });
850
+ const handle = sessionRunner.spawnSession({
851
+ db,
852
+ repoRoot: rootDir,
853
+ task_id: taskId,
854
+ runtime: payload.runtime || runtime,
855
+ takeover: payload.takeover === true,
856
+ worktree_base: payload.worktree_base,
857
+ kernel_mode: true,
858
+ mark_task_started: true,
859
+ packet_digest: packet.packet_digest,
582
860
  });
583
- const completed = workflows.completeWorkflow(db, { id: run.id }, { rootDir });
861
+ workerPacket.markWorkerPacketAssigned(db, packet.id);
862
+ return {
863
+ accepted: true,
864
+ sid: handle.sid,
865
+ worktree_path: handle.worktree_path,
866
+ artifact_dir: handle.artifact_dir,
867
+ lease_expires_at: handle.lease_expires_at,
868
+ packet,
869
+ };
870
+ } catch (error) {
871
+ if (packet?.id) {
872
+ workerPacket.abandonWorkerPacket(db, packet.id, error.code || error.message);
873
+ }
874
+ return sessionDiagnostic(error);
875
+ }
876
+ }
877
+
878
+ async function archive(db, input = {}, {
879
+ rootDir = process.cwd(),
880
+ } = {}) {
881
+ const cached = priorResult(db, input.idempotency_key, 'archive');
882
+ if (cached) return { ...cached, idempotent: true };
883
+ const change = changes.readChange(db, input.change_id);
884
+ if (!change) {
885
+ return {
886
+ accepted: false,
887
+ mutable: true,
888
+ blockers: ['CHANGE_NOT_FOUND'],
889
+ diagnostics: [{
890
+ code: 'CHANGE_NOT_FOUND',
891
+ severity: 'needs_attention',
892
+ message: `change ${input.change_id} not found`,
893
+ }],
894
+ };
895
+ }
896
+ try {
897
+ const result = changes.archiveChange(
898
+ db,
899
+ { id: input.change_id, ...(input.payload || {}) },
900
+ { rootDir, kernelMode: true },
901
+ );
584
902
  const output = {
585
903
  accepted: true,
586
904
  mutable: false,
587
905
  blockers: [],
588
- workflow: completed,
906
+ diagnostics: [],
589
907
  result,
590
- idempotent: false,
908
+ team_checkpoint: taskLedger.publishTaskLedger(db, {
909
+ rootDir,
910
+ reason: 'change_archived',
911
+ }),
591
912
  };
592
- rememberFacadeResult(db, {
913
+ rememberResult(db, {
593
914
  idempotencyKey: input.idempotency_key,
594
915
  operation: 'archive',
595
916
  result: output,
@@ -598,17 +919,11 @@ async function archive(db, input = {}, {
598
919
  return output;
599
920
  } catch (error) {
600
921
  if (isHardError(error)) throw error;
601
- if (run?.id) {
602
- workflows.reopenWorkflowDraft(db, {
603
- id: run.id,
604
- blocker: errorCode(error),
605
- }, { rootDir });
606
- }
607
922
  return {
608
923
  accepted: false,
609
924
  mutable: true,
610
925
  blockers: [errorCode(error)],
611
- diagnostic: { code: errorCode(error), message: error.message, details: error.details },
926
+ diagnostics: [diagnostic(error)],
612
927
  };
613
928
  }
614
929
  }
@@ -634,7 +949,7 @@ async function dispatch(name, input, db, context = {}) {
634
949
 
635
950
  module.exports = {
636
951
  PUBLIC_TOOLS,
637
- RECORD_OPERATIONS,
952
+ RECORD_KINDS,
638
953
  dispatch,
639
954
  readContext,
640
955
  record,