ultra-builder-pro-cli 0.23.0 → 0.24.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.
Files changed (91) hide show
  1. package/.ultra-template/docs/research/README.md +4 -4
  2. package/CHANGELOG.md +45 -0
  3. package/README.md +21 -18
  4. package/adapters/_shared/codex-assets.cjs +121 -65
  5. package/adapters/_shared/interaction-contract.cjs +7 -1
  6. package/adapters/_shared/provenance.cjs +7 -2
  7. package/adapters/_shared/runtime-assets.cjs +3 -2
  8. package/adapters/_shared/runtime-native.cjs +132 -0
  9. package/adapters/claude.js +60 -30
  10. package/adapters/codex.js +178 -81
  11. package/adapters/grok.js +372 -0
  12. package/adapters/kimi.js +7 -3
  13. package/adapters/opencode.js +210 -32
  14. package/agents/code-reviewer.md +4 -2
  15. package/agents/debugger.md +4 -1
  16. package/agents/review-code.md +7 -3
  17. package/agents/review-comments.md +7 -4
  18. package/agents/review-coordinator.md +11 -7
  19. package/agents/review-design.md +8 -4
  20. package/agents/review-errors.md +8 -4
  21. package/agents/review-spec.md +8 -5
  22. package/agents/review-tests.md +7 -4
  23. package/agents/tdd-runner.md +5 -2
  24. package/bin/install.js +9 -4
  25. package/bin/orchestrator.js +4 -13
  26. package/docs/AGENT-CONTEXT.md +141 -229
  27. package/docs/ARCHITECTURE.md +203 -494
  28. package/docs/ARTIFACT-AUTHORITY.md +26 -22
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +12 -9
  30. package/docs/ROADMAP.md +85 -110
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +139 -175
  32. package/docs/SKILL-AUTHORING.md +3 -3
  33. package/docs/STATE-DB-ACCESS-POLICY.md +13 -8
  34. package/docs/WORKFLOW-LIFECYCLE.md +174 -448
  35. package/hooks/README.md +11 -8
  36. package/hooks/active_task_context.py +16 -16
  37. package/hooks/adapters/grok.py +134 -0
  38. package/hooks/{context_spine.py → context_envelope.py} +26 -22
  39. package/hooks/health_check.py +19 -29
  40. package/hooks/pre_stop_check.py +22 -12
  41. package/hooks/subagent_tracker.py +1 -1
  42. package/hooks/workflow_checkpoint.py +14 -13
  43. package/hooks/workflow_context.py +11 -12
  44. package/hooks/workflow_resume.py +14 -13
  45. package/mcp-server/{breadcrumb.cjs → hook-context.cjs} +7 -9
  46. package/mcp-server/lib/baseline-workflow.cjs +36 -38
  47. package/mcp-server/lib/canonical-json.cjs +19 -0
  48. package/mcp-server/lib/change-workflow.cjs +596 -44
  49. package/mcp-server/lib/context-envelope.cjs +508 -0
  50. package/mcp-server/lib/decision-records.cjs +337 -0
  51. package/mcp-server/lib/doctor.cjs +91 -33
  52. package/mcp-server/lib/init-project.cjs +78 -39
  53. package/mcp-server/lib/managed-file-write.cjs +116 -0
  54. package/mcp-server/lib/plan-checkpoint.cjs +177 -0
  55. package/mcp-server/lib/project-context.cjs +178 -0
  56. package/mcp-server/lib/stage-checkpoints.cjs +309 -0
  57. package/mcp-server/lib/state-db.cjs +155 -24
  58. package/mcp-server/lib/task-ledger.cjs +522 -13
  59. package/mcp-server/lib/ultra-facade.cjs +630 -442
  60. package/mcp-server/lib/worker-packet.cjs +358 -0
  61. package/mcp-server/server.cjs +200 -1103
  62. package/orchestrator/daemon.cjs +27 -9
  63. package/orchestrator/parallel-orchestrator.cjs +111 -76
  64. package/orchestrator/session-runner.cjs +135 -34
  65. package/package.json +10 -3
  66. package/skills/ultra-change/SKILL.md +9 -8
  67. package/skills/ultra-deliver/SKILL.md +3 -3
  68. package/skills/ultra-dev/SKILL.md +18 -11
  69. package/skills/ultra-doctor/SKILL.md +2 -2
  70. package/skills/ultra-init/SKILL.md +6 -5
  71. package/skills/ultra-init/references/brownfield-adoption.md +11 -8
  72. package/skills/ultra-plan/SKILL.md +10 -8
  73. package/skills/ultra-research/SKILL.md +12 -7
  74. package/skills/ultra-review/SKILL.md +9 -6
  75. package/skills/ultra-review/references/unified-schema.md +2 -0
  76. package/skills/ultra-review/scripts/review_wait.py +8 -0
  77. package/skills/ultra-status/SKILL.md +2 -2
  78. package/skills/ultra-test/SKILL.md +4 -2
  79. package/skills/ultra-think/SKILL.md +7 -7
  80. package/skills/ultra-think/references/interaction-boundary.md +56 -0
  81. package/spec/cli-protocol.md +80 -199
  82. package/spec/mcp-tools.yaml +37 -2804
  83. package/spec/schemas/state-db.sql +107 -4
  84. package/spec/schemas/{task-ledger.v1.schema.json → task-ledger.v2.schema.json} +70 -6
  85. package/ultra-tools/commands/status.cjs +369 -243
  86. package/mcp-server/lib/context-spine.cjs +0 -1208
  87. package/mcp-server/lib/decision-dialogue.cjs +0 -1128
  88. package/mcp-server/lib/project-breadcrumb.cjs +0 -173
  89. package/mcp-server/lib/spec-learning.cjs +0 -284
  90. package/mcp-server/lib/workflow-state.cjs +0 -3129
  91. 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,80 @@ 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
+ 'CONTEXT_ENVELOPE_LIMIT_EXCEEDED',
49
+ 'CONTEXT_ENVELOPE_DIGEST_MISMATCH',
50
+ 'CONTEXT_ENVELOPE_FILE_DRIFT',
51
+ 'DECISION_DIGEST_MISMATCH',
52
+ 'DECISION_FILE_DRIFT',
53
+ 'DECISION_ID_CONFLICT',
54
+ 'DECISION_SCOPE_CONFLICT',
67
55
  'OUTPUT_SCHEMA_DRIFT',
56
+ 'PATH_AUTHORITY_VIOLATION',
57
+ 'PLAN_RECOVERY_REQUIRED',
58
+ 'RUNTIME_ABI_MISMATCH',
59
+ 'RUNTIME_NATIVE_MISSING',
60
+ 'SCHEMA_VERSION_MISMATCH',
61
+ 'SESSION_STATUS_CONFLICT',
62
+ 'STATE_CORRUPT',
63
+ 'STATE_DB_ERROR',
64
+ 'STATE_DB_MISSING',
65
+ 'TASK_LEDGER_BASELINE_CONFLICT',
66
+ 'TASK_LEDGER_CHANGE_CONFLICT',
67
+ 'TASK_LEDGER_HISTORY_CONFLICT',
68
+ 'TASK_LEDGER_TASK_CONFLICT',
69
+ 'WORKER_PACKET_DIGEST_MISMATCH',
70
+ 'WORKER_PACKET_FILE_DRIFT',
68
71
  ]);
69
72
 
70
- function parseJson(value, fallback = null) {
71
- try { return value == null ? fallback : JSON.parse(value); }
72
- catch { return fallback; }
73
- }
74
-
75
73
  function errorCode(error) {
76
- return String(error?.code || error?.name || 'CHECKPOINT_REJECTED');
74
+ return String(error?.code || error?.name || 'NEEDS_ATTENTION');
77
75
  }
78
76
 
79
77
  function isHardError(error) {
80
78
  const code = errorCode(error);
81
79
  return HARD_ERROR_CODES.has(code)
82
80
  || code.startsWith('PATH_')
81
+ || code.startsWith('SQLITE_')
83
82
  || code.startsWith('STATE_CORRUPT')
84
- || code.startsWith('SQLITE_');
83
+ || code.startsWith('TASK_LEDGER_') && code.endsWith('_CONFLICT')
84
+ || code.startsWith('SESSION_CLOSE_') && code.endsWith('_CONFLICT')
85
+ || code.startsWith('WORKTREE_') && (
86
+ code.endsWith('_UNSAFE') || code.endsWith('_CONFLICT')
87
+ );
85
88
  }
86
89
 
87
- function priorFacadeResult(db, idempotencyKey, operation = null) {
88
- if (!idempotencyKey) return null;
90
+ function parseJson(value, fallback = null) {
91
+ try { return value == null ? fallback : JSON.parse(value); }
92
+ catch { return fallback; }
93
+ }
94
+
95
+ function priorResult(db, idempotencyKey, operation) {
96
+ if (!db || !idempotencyKey) return null;
89
97
  const rows = db.prepare(
90
98
  `SELECT payload_json FROM events
91
- WHERE type = 'ultra_facade_call'
99
+ WHERE type = 'ultra_kernel_call'
92
100
  ORDER BY id DESC LIMIT 2000`,
93
101
  ).all();
94
102
  for (const row of rows) {
95
103
  const payload = parseJson(row.payload_json, {});
96
104
  if (payload.idempotency_key === idempotencyKey
97
- && (operation === null || payload.operation === operation)
105
+ && payload.operation === operation
98
106
  && payload.accepted === true) {
99
107
  return payload.result;
100
108
  }
@@ -102,15 +110,16 @@ function priorFacadeResult(db, idempotencyKey, operation = null) {
102
110
  return null;
103
111
  }
104
112
 
105
- function rememberFacadeResult(db, {
113
+ function rememberResult(db, {
106
114
  idempotencyKey,
107
115
  operation,
108
116
  result,
109
117
  changeId = null,
110
118
  taskId = null,
111
119
  }) {
120
+ if (!db || !idempotencyKey) return;
112
121
  ops.appendEvent(db, {
113
- type: 'ultra_facade_call',
122
+ type: 'ultra_kernel_call',
114
123
  change_id: changeId,
115
124
  task_id: taskId,
116
125
  payload: {
@@ -122,197 +131,260 @@ function rememberFacadeResult(db, {
122
131
  });
123
132
  }
124
133
 
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;
134
+ function diagnostic(error, severity = 'needs_attention') {
135
+ return {
136
+ code: errorCode(error),
137
+ severity,
138
+ message: error.message,
139
+ ...(error.details === undefined ? {} : { details: error.details }),
140
+ };
131
141
  }
132
142
 
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;
143
+ function selectedScope(db, input = {}) {
144
+ const scope = input.scope || {};
145
+ if (scope.task_id) {
146
+ const task = ops.readTask(db, scope.task_id);
147
+ if (!task) return { task_id: scope.task_id };
148
+ return {
149
+ change_id: scope.change_id || task.change_id || undefined,
150
+ task_id: task.id,
151
+ };
152
+ }
153
+ if (scope.change_id) return { change_id: scope.change_id };
154
+ const baseline = baselines.readBaseline(db);
155
+ return baseline ? { baseline_id: baseline.id } : { project_id: 'project' };
140
156
  }
141
157
 
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;
158
+ function readContext(db, input = {}, {
159
+ rootDir = process.cwd(),
160
+ runtime = 'unknown',
161
+ } = {}) {
162
+ return contextEnvelope.buildEnvelope(db, input, { rootDir, runtime });
163
+ }
164
+
165
+ function initializeProject(data = {}) {
166
+ const targetDir = path.resolve(data.target_dir || process.cwd());
167
+ const alreadyInitialized = fs.existsSync(path.join(targetDir, '.ultra'));
168
+ return initProject({
169
+ ...data,
170
+ target_dir: targetDir,
171
+ resume: alreadyInitialized || data.resume === true,
172
+ });
173
+ }
174
+
175
+ function baselineRecord(db, action, data, rootDir) {
176
+ if (action === 'initialize') return initializeProject(data);
177
+ if (action === 'start') {
178
+ return { baseline: baselines.startBaseline(db, data, { rootDir }) };
159
179
  }
160
- return workflows.startWorkflow(db, input, { rootDir });
180
+ if (action === 'observe' || action === 'revise') {
181
+ return { baseline: baselines.recordBaseline(db, data, { rootDir }) };
182
+ }
183
+ if (action === 'accept') {
184
+ const result = baselines.convergeBaseline(db, data, { rootDir });
185
+ return {
186
+ ...result,
187
+ diagnostics: result.ready
188
+ ? []
189
+ : (result.blockers || []).map((code) => ({
190
+ code,
191
+ severity: 'needs_attention',
192
+ })),
193
+ };
194
+ }
195
+ throw Object.assign(new Error(`unsupported baseline action: ${action}`), {
196
+ code: 'VALIDATION_ERROR',
197
+ });
161
198
  }
162
199
 
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
- };
200
+ function changeContractRecord(db, action, data, rootDir) {
201
+ if (action === 'open') {
202
+ return changes.createChange(db, data, { rootDir, kernelMode: true });
203
+ }
204
+ if (action === 'revise') {
205
+ return {
206
+ change: changes.updateChange(
207
+ db,
208
+ data.id,
209
+ data.patch || {},
210
+ { rootDir, kernelMode: true },
211
+ ),
212
+ };
213
+ }
214
+ if (action === 'cancel') {
215
+ return {
216
+ change: changes.updateChange(
217
+ db,
218
+ data.id,
219
+ { status: 'cancelled' },
220
+ { rootDir, kernelMode: true },
221
+ ),
222
+ };
223
+ }
224
+ throw Object.assign(new Error(`unsupported change_contract action: ${action}`), {
225
+ code: 'VALIDATION_ERROR',
226
+ });
168
227
  }
169
228
 
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 });
229
+ function taskContractRecord(db, action, data) {
230
+ if (action === 'define') return { task: ops.createTask(db, data) };
231
+ if (action === 'revise') return { task: ops.patchTask(db, data.id, data.patch || {}) };
232
+ if (action === 'remove') return ops.deleteTask(db, data.id, { force: data.force === true });
233
+ throw Object.assign(new Error(`unsupported task_contract action: ${action}`), {
234
+ code: 'VALIDATION_ERROR',
235
+ });
181
236
  }
182
237
 
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(
238
+ function taskOutcomeRecord(db, action, data, rootDir) {
239
+ const status = {
240
+ start: 'in_progress',
241
+ complete: 'completed',
242
+ block: 'blocked',
243
+ reopen: 'in_progress',
244
+ }[action];
245
+ if (!status) {
246
+ throw Object.assign(new Error(`unsupported task_outcome action: ${action}`), {
247
+ code: 'VALIDATION_ERROR',
248
+ });
249
+ }
250
+ if (action === 'complete') {
251
+ const packet = db.prepare(
252
+ `SELECT id, packet_digest, packet_path, output_path
253
+ FROM worker_packets
254
+ WHERE scope_type = 'task' AND scope_id = ? AND status = 'assigned'
255
+ ORDER BY created_at DESC, rowid DESC LIMIT 1`,
256
+ ).get(data.id);
257
+ if (!packet) {
258
+ throw Object.assign(
259
+ new Error(`task ${data.id} has no assigned Worker Packet`),
260
+ { code: 'WORKER_PACKET_REQUIRED' },
261
+ );
262
+ }
263
+ const packetDocument = workerPacket.readWorkerPacket(
194
264
  db,
195
- { limit: input.detail === 'full' ? 500 : 50 },
265
+ packet.id,
196
266
  { 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, {}),
267
+ );
268
+ if (data.packet_digest !== packet.packet_digest) {
269
+ throw Object.assign(
270
+ new Error('task outcome does not reference the exact assigned Worker Packet'),
271
+ { code: 'WORKER_PACKET_DIGEST_MISMATCH' },
272
+ );
223
273
  }
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
- };
274
+ const output = readStableProjectFile(rootDir, packet.output_path, { encoding: 'utf8' });
275
+ let result;
276
+ try {
277
+ result = JSON.parse(output.text);
278
+ } catch (cause) {
279
+ throw Object.assign(
280
+ new Error(`worker output is not valid JSON: ${packet.output_path}`),
281
+ { code: 'WORKER_OUTPUT_INVALID', cause },
282
+ );
283
+ }
284
+ workerPacket.verifyWorkerResult(packetDocument, result);
285
+ artifactRegistry.recordArtifact(db, {
286
+ id: `artifact-worker-outcome-${packet.id}`,
287
+ owner_type: 'task',
288
+ owner_id: data.id,
289
+ task_id: data.id,
290
+ change_id: ops.readTask(db, data.id)?.change_id || null,
291
+ kind: 'task_outcome',
292
+ path: packet.output_path,
293
+ content_digest: output.digest,
294
+ source_refs: [{
295
+ type: 'external',
296
+ id: `worker-packet:${packet.id}`,
297
+ relation: 'produced_from_packet',
298
+ }],
299
+ consumer_refs: [{
300
+ type: 'external',
301
+ id: 'ultra-test',
302
+ relation: 'consumed_by',
303
+ }],
304
+ provenance: {
305
+ writer: 'worker-result',
306
+ packet_digest: packet.packet_digest,
307
+ },
308
+ metadata: {
309
+ worker_packet_id: packet.id,
310
+ packet_path: packet.packet_path,
311
+ },
312
+ }, { rootDir });
313
+ }
314
+ if (data.patch && Object.keys(data.patch).length > 0) {
315
+ ops.patchTask(db, data.id, data.patch);
316
+ }
317
+ return { task: ops.updateTaskStatus(db, data.id, status) };
318
+ }
319
+
320
+ function applyRecord(db, entry, { rootDir }) {
321
+ const { kind, action, data = {} } = entry;
322
+ if (!RECORD_KINDS.includes(kind)) {
323
+ throw Object.assign(new Error(`unsupported record kind: ${kind}`), {
324
+ code: 'VALIDATION_ERROR',
325
+ });
326
+ }
327
+ if (kind === 'baseline') return baselineRecord(db, action, data, rootDir);
328
+ if (kind === 'change_contract') return changeContractRecord(db, action, data, rootDir);
329
+ if (kind === 'decision' && action === 'accept') {
330
+ return { decision: decisionRecords.acceptDecision(db, data, { rootDir }) };
331
+ }
332
+ if (kind === 'task_contract') return taskContractRecord(db, action, data);
333
+ if (kind === 'task_outcome') return taskOutcomeRecord(db, action, data, rootDir);
334
+ if (kind === 'artifact' && action === 'bind') {
335
+ return artifactRegistry.recordArtifact(db, data, { rootDir });
336
+ }
337
+ if (kind === 'event' && action === 'append') {
338
+ return ops.appendEvent(db, data);
339
+ }
340
+ throw Object.assign(
341
+ new Error(`unsupported ${kind} action: ${action}`),
342
+ { code: 'VALIDATION_ERROR' },
343
+ );
237
344
  }
238
345
 
239
346
  async function record(db, input = {}, {
240
347
  rootDir = process.cwd(),
241
- callLegacy,
242
348
  } = {}) {
243
349
  const results = [];
244
350
  for (const entry of input.entries || []) {
245
- const cached = db
246
- ? priorFacadeResult(db, entry.idempotency_key, entry.operation)
247
- : null;
351
+ const operation = `${entry.kind}:${entry.action}`;
352
+ const cached = priorResult(db, entry.idempotency_key, operation);
248
353
  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
- }
354
+ results.push({
355
+ kind: entry.kind,
356
+ action: entry.action,
357
+ accepted: true,
358
+ idempotent: true,
359
+ result: cached,
360
+ });
273
361
  continue;
274
362
  }
275
- if (entry.operation === 'event.append') {
276
- const event = ops.appendEvent(db, entry.data);
277
- rememberFacadeResult(db, {
363
+ try {
364
+ const result = applyRecord(db, entry, { rootDir });
365
+ rememberResult(db, {
278
366
  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,
367
+ operation,
368
+ result,
369
+ changeId: entry.data?.change_id || entry.data?.id || null,
370
+ taskId: entry.kind.startsWith('task_') ? entry.data?.id || null : null,
283
371
  });
284
- results.push({ operation: entry.operation, accepted: true, idempotent: false, result: event });
285
- continue;
286
- }
287
- if (!RECORD_OPERATIONS.has(entry.operation)) {
288
372
  results.push({
289
- operation: entry.operation,
290
- accepted: false,
291
- mutable: true,
292
- blockers: ['ULTRA_RECORD_OPERATION_UNSUPPORTED'],
373
+ kind: entry.kind,
374
+ action: entry.action,
375
+ accepted: true,
376
+ idempotent: false,
377
+ result,
378
+ diagnostics: result?.diagnostics || [],
293
379
  });
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
380
  } catch (error) {
309
381
  if (isHardError(error)) throw error;
310
382
  results.push({
311
- operation: entry.operation,
383
+ kind: entry.kind,
384
+ action: entry.action,
312
385
  accepted: false,
313
386
  mutable: true,
314
- blockers: [errorCode(error)],
315
- diagnostic: { code: errorCode(error), message: error.message, details: error.details },
387
+ diagnostics: [diagnostic(error)],
316
388
  });
317
389
  }
318
390
  }
@@ -323,273 +395,395 @@ async function record(db, input = {}, {
323
395
  };
324
396
  }
325
397
 
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] });
398
+ function checkpointDiagnostics(input, context) {
399
+ const supplied = Array.isArray(input.payload?.diagnostics)
400
+ ? input.payload.diagnostics
401
+ : [];
402
+ const derived = [
403
+ ...(context.envelope?.diagnostics?.warnings || []).map((item) => ({
404
+ ...item,
405
+ severity: 'warning',
406
+ })),
407
+ ...(context.envelope?.diagnostics?.needs_attention || []).map((item) => ({
408
+ ...item,
409
+ severity: 'warning',
410
+ })),
411
+ ...(context.envelope?.diagnostics?.hard_conflicts || []).map((item) => ({
412
+ ...item,
413
+ severity: 'hard_conflict',
414
+ })),
415
+ ];
416
+ return [...supplied, ...derived]
417
+ .filter((item) => item && typeof item === 'object' && item.code)
418
+ .map((item) => ({
419
+ code: String(item.code),
420
+ severity: item.severity || 'needs_attention',
421
+ ...(item.message ? { message: String(item.message) } : {}),
422
+ ...(item.details === undefined ? {} : { details: item.details }),
423
+ }));
330
424
  }
331
425
 
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;
342
- }
343
-
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 || {};
426
+ function stageReadinessDiagnostics(db, input, scope) {
352
427
  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)
428
+ const evidence = Array.isArray(payload.evidence) ? payload.evidence : [];
429
+ const needs = (code, message) => ({ code, severity: 'needs_attention', message });
430
+ if (input.stage === 'research') {
431
+ return evidence.length > 0
432
+ ? []
433
+ : [needs('RESEARCH_EVIDENCE_REQUIRED', 'Research checkpoint requires at least one evidence reference.')];
434
+ }
435
+ if (input.stage === 'dev') {
436
+ const task = scope.task_id ? ops.readTask(db, scope.task_id) : null;
437
+ const outcome = task
438
+ ? db.prepare(
439
+ `SELECT id FROM artifacts
440
+ WHERE task_id = ? AND kind = 'task_outcome' AND status = 'current'
441
+ ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
442
+ ).get(task.id)
372
443
  : null;
373
- if (compiled) {
374
- addStepOutput(
375
- steps,
376
- stage === 'deliver' ? 'verify-candidate' : 'compile-context',
377
- contextOutput(compiled, rootDir),
378
- );
444
+ const diagnostics = [];
445
+ if (!task) diagnostics.push(needs('TASK_NOT_FOUND', 'Dev checkpoint requires a task scope.'));
446
+ else if (task.status !== 'completed') {
447
+ diagnostics.push(needs('TASK_OUTCOME_INCOMPLETE', `Task ${task.id} is not completed.`));
379
448
  }
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
- });
449
+ if (task && !outcome) {
450
+ diagnostics.push(needs(
451
+ 'WORKER_OUTCOME_REQUIRED',
452
+ `Task ${task.id} requires an artifact bound to its exact Worker Packet.`,
453
+ ));
387
454
  }
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);
455
+ return diagnostics;
456
+ }
457
+ if (input.stage === 'test') {
458
+ const result = String(payload.result || '').toLowerCase();
459
+ const diagnostics = [];
460
+ if (!['pass', 'known_red'].includes(result)) {
461
+ diagnostics.push(needs('TEST_RESULT_REQUIRED', 'Test checkpoint requires result=pass or known_red.'));
393
462
  }
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
- };
463
+ if (evidence.length === 0) {
464
+ diagnostics.push(needs('TEST_EVIDENCE_REQUIRED', 'Test checkpoint requires evidence.'));
406
465
  }
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
- };
466
+ return diagnostics;
467
+ }
468
+ if (input.stage === 'review') {
469
+ const verdict = String(payload.verdict || '').toLowerCase();
470
+ const diagnostics = [];
471
+ if (!['approve', 'pass'].includes(verdict)) {
472
+ diagnostics.push(needs('REVIEW_VERDICT_REQUIRED', 'Review checkpoint requires an approved verdict.'));
419
473
  }
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 });
474
+ if (evidence.length === 0) {
475
+ diagnostics.push(needs('REVIEW_EVIDENCE_REQUIRED', 'Review checkpoint requires evidence.'));
449
476
  }
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: {
477
+ return diagnostics;
478
+ }
479
+ if (input.stage === 'deliver') {
480
+ const diagnostics = [];
481
+ for (const stage of ['test', 'review']) {
482
+ if (!stageCheckpoints.currentCheckpoint(
483
+ db,
455
484
  stage,
456
- workflow_id: run?.id || null,
457
- code: errorCode(error),
458
- idempotency_key: input.idempotency_key,
459
- },
460
- });
485
+ { change_id: scope.change_id },
486
+ { includeDraft: false },
487
+ )) {
488
+ diagnostics.push(needs(
489
+ `${stage.toUpperCase()}_CHECKPOINT_REQUIRED`,
490
+ `Deliver checkpoint requires an accepted ${stage} checkpoint.`,
491
+ ));
492
+ }
493
+ }
494
+ if (String(payload.summary || '').trim().length < 3) {
495
+ diagnostics.push(needs('DELIVERY_SUMMARY_REQUIRED', 'Deliver checkpoint requires a summary.'));
496
+ }
497
+ if (evidence.length === 0) {
498
+ diagnostics.push(needs('DELIVERY_EVIDENCE_REQUIRED', 'Deliver checkpoint requires evidence.'));
499
+ }
500
+ return diagnostics;
501
+ }
502
+ return [];
503
+ }
504
+
505
+ async function checkpoint(db, input = {}, {
506
+ rootDir = process.cwd(),
507
+ runtime = 'unknown',
508
+ markPlanRecoveryRequired = null,
509
+ } = {}) {
510
+ const scope = selectedScope(db, input);
511
+ const context = contextEnvelope.persistEnvelope(db, {
512
+ stage: input.stage,
513
+ scope,
514
+ }, { rootDir, runtime });
515
+ const view = contextEnvelope.buildEnvelope(db, {
516
+ stage: input.stage,
517
+ scope,
518
+ detail: 'summary',
519
+ }, { rootDir, runtime });
520
+ const diagnostics = [
521
+ ...checkpointDiagnostics(input, view),
522
+ ...stageReadinessDiagnostics(db, input, scope),
523
+ ];
524
+ let result = null;
525
+
526
+ if (input.stage === 'plan'
527
+ && !diagnostics.some((item) => item.severity !== 'warning')) {
528
+ try {
529
+ result = planCheckpoint.publishPlan(db, {
530
+ change_id: scope.change_id,
531
+ context,
532
+ }, { rootDir, markRecoveryRequired: markPlanRecoveryRequired });
533
+ } catch (error) {
534
+ if (isHardError(error)) throw error;
535
+ diagnostics.push(diagnostic(error));
536
+ }
537
+ }
538
+
539
+ const draft = stageCheckpoints.saveDraft(db, {
540
+ stage: input.stage,
541
+ scope: scope.task_id
542
+ ? { task_id: scope.task_id }
543
+ : scope.change_id
544
+ ? { change_id: scope.change_id }
545
+ : scope.baseline_id
546
+ ? { baseline_id: scope.baseline_id }
547
+ : { project_id: 'project' },
548
+ payload: input.stage === 'plan' && result
549
+ ? { ...(input.payload || {}), plan: result }
550
+ : (input.payload || {}),
551
+ evidence: Array.isArray(input.payload?.evidence) ? input.payload.evidence : [],
552
+ diagnostics,
553
+ context_envelope_id: context.id,
554
+ idempotency_key: `${input.idempotency_key}:draft`,
555
+ });
556
+ if (diagnostics.some((item) => item.severity !== 'warning')) {
461
557
  return {
462
558
  accepted: false,
463
559
  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 },
560
+ diagnostics,
561
+ blockers: diagnostics.map((item) => item.code),
562
+ checkpoint: draft,
563
+ context,
564
+ result,
565
+ };
566
+ }
567
+ const accepted = stageCheckpoints.acceptDraft(db, {
568
+ id: draft.id,
569
+ idempotency_key: `${input.idempotency_key}:accept`,
570
+ });
571
+ if (input.stage === 'plan') {
572
+ result = {
573
+ ...result,
574
+ team_checkpoint: taskLedger.publishTaskLedger(db, {
575
+ rootDir,
576
+ reason: 'plan_checkpoint_accepted',
577
+ }),
467
578
  };
468
579
  }
580
+ return {
581
+ accepted: true,
582
+ mutable: false,
583
+ diagnostics,
584
+ blockers: [],
585
+ checkpoint: accepted,
586
+ context,
587
+ result,
588
+ };
469
589
  }
470
590
 
471
591
  function sync(db, input = {}, { rootDir = process.cwd() } = {}) {
472
- if (input.action === 'inspect') return taskLedger.inspectTaskLedger(db, { rootDir });
592
+ gitBootstrap.ensureExistingProjectStorageBoundary(rootDir);
593
+ if (input.action === 'inspect') {
594
+ try {
595
+ return taskLedger.inspectTaskLedger(db, { rootDir });
596
+ } catch (error) {
597
+ return {
598
+ status: 'migration_required',
599
+ migration: {
600
+ required: true,
601
+ code: errorCode(error),
602
+ action: 'ultra.sync migrate',
603
+ },
604
+ };
605
+ }
606
+ }
473
607
  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, {
608
+ if (input.action === 'migrate') {
609
+ const published = taskLedger.publishTaskLedger(db, {
610
+ rootDir,
611
+ reason: 'legacy_authority_migrated',
612
+ });
613
+ return {
614
+ migrated: Boolean(published.migrated_legacy_projection),
615
+ ...published,
616
+ };
617
+ }
618
+ if (input.action === 'publish') {
619
+ const cached = priorResult(db, input.idempotency_key, 'sync:publish');
620
+ if (cached) return { ...cached, idempotent: true };
621
+ const result = taskLedger.publishTaskLedger(db, {
622
+ rootDir,
623
+ reason: input.reason || 'manual_checkpoint',
624
+ });
625
+ rememberResult(db, {
482
626
  idempotencyKey: input.idempotency_key,
483
627
  operation: 'sync:publish',
484
628
  result,
485
629
  });
630
+ return result;
486
631
  }
487
- return result;
632
+ throw Object.assign(new Error(`unsupported sync action: ${input.action}`), {
633
+ code: 'VALIDATION_ERROR',
634
+ });
488
635
  }
489
636
 
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 } : {}),
637
+ function sessionDiagnostic(error) {
638
+ if (isHardError(error)) throw error;
639
+ return {
640
+ accepted: false,
641
+ mutable: true,
642
+ can_acquire: false,
643
+ diagnostics: [diagnostic(error)],
505
644
  };
506
- if (input.action === 'release' && args.status === undefined) args.status = 'completed';
507
- return callLegacy(operation, args);
508
645
  }
509
646
 
510
- async function archive(db, input = {}, {
647
+ async function session(db, input = {}, {
511
648
  rootDir = process.cwd(),
512
- callLegacy,
649
+ runtime = 'unknown',
650
+ sessionId = null,
513
651
  } = {}) {
514
- const cached = priorFacadeResult(db, input.idempotency_key, 'archive');
515
- if (cached) return { ...cached, idempotent: true };
652
+ const scope = input.scope || {};
516
653
  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);
654
+ if (input.action === 'get') {
655
+ return { session: ops.readSession(db, scope.sid) };
656
+ }
657
+ if (input.action === 'list') {
658
+ const sessions = ops.listActiveSessions(db, { task_id: scope.task_id });
659
+ return { sessions, count: sessions.length };
660
+ }
661
+ if (input.action === 'heartbeat') return ops.heartbeatSession(db, scope.sid);
662
+ if (input.action === 'release') {
663
+ if (sessionId) {
664
+ return sessionDiagnostic(Object.assign(
665
+ new Error('a worker session is settled by its parent host'),
666
+ { code: 'WORKER_SESSION_PARENT_OWNED' },
667
+ ));
544
668
  }
545
- const prepared = workflows.prepareWorkflowCheckpoint(db, {
546
- id: run.id,
547
- steps,
548
- evidence: payload.evidence,
549
- }, { rootDir });
550
- if (!prepared.ready) {
669
+ return sessionRunner.closeSession(
670
+ { db, repoRoot: rootDir, sid: scope.sid },
671
+ {
672
+ status: payload.status || 'completed',
673
+ remove_worktree: payload.remove_worktree === true,
674
+ },
675
+ );
676
+ }
677
+ const taskId = scope.task_id;
678
+ if (input.action === 'admission') {
679
+ try {
680
+ const task = ops.readTask(db, taskId);
681
+ if (!task) throw Object.assign(new Error(`task ${taskId} not found`), { code: 'TASK_NOT_FOUND' });
682
+ const attention = ops.taskContractBlockers(task).map((code) => ({
683
+ code,
684
+ severity: 'needs_attention',
685
+ }));
686
+ const lease = ops.admissionCheck(db, taskId);
551
687
  return {
552
- accepted: false,
688
+ accepted: attention.length === 0 && lease.can_spawn,
553
689
  mutable: true,
554
- blockers: prepared.blockers,
555
- workflow: prepared.workflow,
556
- };
557
- }
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 });
564
- return {
565
- accepted: false,
566
- mutable: true,
567
- blockers: convergence.blockers || ['CHANGE_NOT_READY'],
568
- convergence,
690
+ can_acquire: attention.length === 0 && lease.can_spawn,
691
+ diagnostics: [
692
+ ...attention,
693
+ ...(lease.can_spawn ? [] : [{
694
+ code: 'ACTIVE_SESSION_LEASE_CONFLICT',
695
+ severity: 'hard_conflict',
696
+ details: lease.conflict,
697
+ }]),
698
+ ],
699
+ lease,
569
700
  };
701
+ } catch (error) {
702
+ return sessionDiagnostic(error);
570
703
  }
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,
704
+ }
705
+ if (input.action !== 'acquire') {
706
+ throw Object.assign(new Error(`unsupported session action: ${input.action}`), {
707
+ code: 'VALIDATION_ERROR',
708
+ });
709
+ }
710
+ let packet = null;
711
+ try {
712
+ packet = workerPacket.createWorkerPacket(db, {
713
+ role: payload.role || 'implement',
714
+ task_id: taskId,
715
+ runtime: payload.runtime || runtime,
716
+ output_path: payload.output_path
717
+ || `.ultra/changes/active/${ops.readTask(db, taskId)?.change_id}/delivery/${taskId}-outcome.json`,
718
+ output_schema: payload.output_schema,
719
+ evidence_refs: payload.evidence_refs,
720
+ diff_range: payload.diff_range,
721
+ changed_files: payload.changed_files,
722
+ }, { rootDir });
723
+ const handle = sessionRunner.spawnSession({
724
+ db,
725
+ repoRoot: rootDir,
726
+ task_id: taskId,
727
+ runtime: payload.runtime || runtime,
728
+ takeover: payload.takeover === true,
729
+ worktree_base: payload.worktree_base,
730
+ kernel_mode: true,
731
+ mark_task_started: true,
732
+ packet_digest: packet.packet_digest,
582
733
  });
583
- const completed = workflows.completeWorkflow(db, { id: run.id }, { rootDir });
734
+ workerPacket.markWorkerPacketAssigned(db, packet.id);
735
+ return {
736
+ accepted: true,
737
+ sid: handle.sid,
738
+ worktree_path: handle.worktree_path,
739
+ artifact_dir: handle.artifact_dir,
740
+ lease_expires_at: handle.lease_expires_at,
741
+ packet,
742
+ };
743
+ } catch (error) {
744
+ if (packet?.id) {
745
+ workerPacket.abandonWorkerPacket(db, packet.id, error.code || error.message);
746
+ }
747
+ return sessionDiagnostic(error);
748
+ }
749
+ }
750
+
751
+ async function archive(db, input = {}, {
752
+ rootDir = process.cwd(),
753
+ } = {}) {
754
+ const cached = priorResult(db, input.idempotency_key, 'archive');
755
+ if (cached) return { ...cached, idempotent: true };
756
+ const change = changes.readChange(db, input.change_id);
757
+ if (!change) {
758
+ return {
759
+ accepted: false,
760
+ mutable: true,
761
+ blockers: ['CHANGE_NOT_FOUND'],
762
+ diagnostics: [{
763
+ code: 'CHANGE_NOT_FOUND',
764
+ severity: 'needs_attention',
765
+ message: `change ${input.change_id} not found`,
766
+ }],
767
+ };
768
+ }
769
+ try {
770
+ const result = changes.archiveChange(
771
+ db,
772
+ { id: input.change_id, ...(input.payload || {}) },
773
+ { rootDir, kernelMode: true },
774
+ );
584
775
  const output = {
585
776
  accepted: true,
586
777
  mutable: false,
587
778
  blockers: [],
588
- workflow: completed,
779
+ diagnostics: [],
589
780
  result,
590
- idempotent: false,
781
+ team_checkpoint: taskLedger.publishTaskLedger(db, {
782
+ rootDir,
783
+ reason: 'change_archived',
784
+ }),
591
785
  };
592
- rememberFacadeResult(db, {
786
+ rememberResult(db, {
593
787
  idempotencyKey: input.idempotency_key,
594
788
  operation: 'archive',
595
789
  result: output,
@@ -598,17 +792,11 @@ async function archive(db, input = {}, {
598
792
  return output;
599
793
  } catch (error) {
600
794
  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
795
  return {
608
796
  accepted: false,
609
797
  mutable: true,
610
798
  blockers: [errorCode(error)],
611
- diagnostic: { code: errorCode(error), message: error.message, details: error.details },
799
+ diagnostics: [diagnostic(error)],
612
800
  };
613
801
  }
614
802
  }
@@ -634,7 +822,7 @@ async function dispatch(name, input, db, context = {}) {
634
822
 
635
823
  module.exports = {
636
824
  PUBLIC_TOOLS,
637
- RECORD_OPERATIONS,
825
+ RECORD_KINDS,
638
826
  dispatch,
639
827
  readContext,
640
828
  record,