ultra-builder-pro-cli 0.25.1 → 0.26.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 (243) hide show
  1. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  2. package/.ultra-template/contexts/TEMPLATE.md +94 -0
  3. package/.ultra-template/decisions/.gitkeep +1 -0
  4. package/.ultra-template/evidence/.gitkeep +1 -0
  5. package/.ultra-template/north-star.md +46 -0
  6. package/.ultra-template/research/.gitkeep +1 -0
  7. package/.ultra-template/specs/discovery.md +2 -2
  8. package/.ultra-template/tasks.json +3 -0
  9. package/.ultra-template/test-report.json +27 -0
  10. package/CHANGELOG.md +107 -1
  11. package/README.md +217 -579
  12. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  13. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  14. package/adapters/_shared/file-ops.cjs +76 -2
  15. package/adapters/_shared/host-profile.cjs +97 -0
  16. package/adapters/_shared/plugin-core.cjs +204 -0
  17. package/adapters/_shared/runtime-assets.cjs +31 -96
  18. package/adapters/claude.js +92 -291
  19. package/adapters/codex.js +309 -842
  20. package/adapters/grok.js +143 -1469
  21. package/adapters/kimi.js +124 -1053
  22. package/adapters/opencode.js +230 -742
  23. package/bin/delegate-worker.cjs +328 -0
  24. package/bin/delegate.cjs +463 -0
  25. package/bin/install.js +38 -11
  26. package/docs/ARCHITECTURE.md +125 -216
  27. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  28. package/docs/DECISIONS.md +77 -87
  29. package/docs/PHILOSOPHY.md +208 -0
  30. package/docs/PLUGIN-ISOLATION-CONTRACT.md +65 -39
  31. package/docs/RUNTIME-COMPAT-MATRIX.md +140 -155
  32. package/docs/SKILL-AUTHORING.md +94 -73
  33. package/docs/WORKFLOW-LIFECYCLE.md +97 -189
  34. package/hooks/README.md +19 -37
  35. package/hooks/_common.py +113 -0
  36. package/hooks/adapters/codex.py +9 -16
  37. package/hooks/adapters/grok.py +5 -5
  38. package/hooks/adapters/kimi.py +8 -14
  39. package/hooks/block_dangerous_commands.py +201 -0
  40. package/hooks/compact_context.py +65 -0
  41. package/hooks/mid_workflow_recall.py +25 -0
  42. package/hooks/post_edit_guard.py +105 -0
  43. package/hooks/session_context.py +30 -0
  44. package/package.json +17 -52
  45. package/skills/ultra-change/SKILL.md +115 -50
  46. package/skills/ultra-change/references/change-contract.md +68 -3
  47. package/skills/ultra-delegate/SKILL.md +89 -0
  48. package/skills/ultra-delegate/references/delegation-contract.md +59 -0
  49. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  50. package/skills/ultra-deliver/SKILL.md +98 -42
  51. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  52. package/skills/ultra-dev/SKILL.md +115 -62
  53. package/skills/ultra-dev/references/debugging.md +22 -0
  54. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  55. package/skills/ultra-grilling/SKILL.md +118 -0
  56. package/skills/ultra-grilling/references/reframing.md +72 -0
  57. package/skills/ultra-init/SKILL.md +102 -37
  58. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  59. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  60. package/skills/ultra-plan/SKILL.md +101 -68
  61. package/skills/ultra-research/SKILL.md +68 -48
  62. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  63. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  64. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  65. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  66. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  67. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  68. package/skills/ultra-research/references/10-user-personas.md +3 -2
  69. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  70. package/skills/ultra-research/references/20-user-stories.md +4 -3
  71. package/skills/ultra-research/references/21-features-scope.md +3 -2
  72. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  73. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  74. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  75. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  76. package/skills/ultra-research/references/40-deployment.md +3 -3
  77. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  78. package/skills/ultra-research/references/99-synthesis.md +7 -7
  79. package/skills/ultra-review/SKILL.md +84 -32
  80. package/skills/ultra-review/references/code.md +17 -0
  81. package/skills/ultra-review/references/comments.md +12 -0
  82. package/skills/ultra-review/references/design.md +12 -0
  83. package/skills/ultra-review/references/errors.md +13 -0
  84. package/skills/ultra-review/references/spec.md +13 -0
  85. package/skills/ultra-review/references/tests.md +12 -0
  86. package/skills/ultra-review/references/worker-packet.md +40 -0
  87. package/skills/ultra-review/scripts/review_wait.py +26 -15
  88. package/skills/ultra-status/SKILL.md +61 -36
  89. package/skills/ultra-tdd/SKILL.md +81 -0
  90. package/skills/ultra-tdd/references/templates/README.md +20 -0
  91. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  92. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  93. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  94. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  95. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  96. package/skills/ultra-tdd/references/test-execution.md +18 -0
  97. package/skills/ultra-test/SKILL.md +75 -30
  98. package/skills/ultra-test/references/export-syntax.md +18 -0
  99. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  100. package/skills/ultra-think/SKILL.md +49 -27
  101. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  102. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  103. package/.ultra-template/docs/research/README.md +0 -55
  104. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  105. package/.ultra-template/reports/templates/test-report.json +0 -18
  106. package/.ultra-template/tasks/tasks.json +0 -15
  107. package/.ultra-template/templates/task-context.md +0 -19
  108. package/adapters/_shared/codex-assets.cjs +0 -725
  109. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  110. package/adapters/_shared/interaction-contract.cjs +0 -129
  111. package/adapters/_shared/path-rewrite.cjs +0 -79
  112. package/adapters/_shared/runtime-native.cjs +0 -132
  113. package/adapters/_shared/settings-merge.cjs +0 -76
  114. package/agents/code-reviewer.md +0 -42
  115. package/agents/debugger.md +0 -40
  116. package/agents/review-code.md +0 -49
  117. package/agents/review-comments.md +0 -38
  118. package/agents/review-coordinator.md +0 -56
  119. package/agents/review-design.md +0 -40
  120. package/agents/review-errors.md +0 -43
  121. package/agents/review-spec.md +0 -39
  122. package/agents/review-tests.md +0 -40
  123. package/agents/tdd-runner.md +0 -38
  124. package/bin/orchestrator.js +0 -383
  125. package/commands/ultra-change.md +0 -10
  126. package/commands/ultra-deliver.md +0 -10
  127. package/commands/ultra-dev.md +0 -10
  128. package/commands/ultra-doctor.md +0 -10
  129. package/commands/ultra-init.md +0 -10
  130. package/commands/ultra-plan.md +0 -10
  131. package/commands/ultra-research.md +0 -10
  132. package/commands/ultra-review.md +0 -10
  133. package/commands/ultra-status.md +0 -10
  134. package/commands/ultra-test.md +0 -10
  135. package/commands/ultra-think.md +0 -10
  136. package/docs/AGENT-CONTEXT.md +0 -191
  137. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  138. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  139. package/docs/ROADMAP.md +0 -101
  140. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  141. package/hooks/active_task_context.py +0 -152
  142. package/hooks/context_envelope.py +0 -100
  143. package/hooks/health_check.py +0 -196
  144. package/hooks/pre_stop_check.py +0 -66
  145. package/hooks/runtime_paths.py +0 -396
  146. package/hooks/subagent_tracker.py +0 -88
  147. package/hooks/workflow_checkpoint.py +0 -76
  148. package/hooks/workflow_context.py +0 -48
  149. package/hooks/workflow_resume.py +0 -84
  150. package/mcp-server/hook-context.cjs +0 -38
  151. package/mcp-server/hook-event.cjs +0 -34
  152. package/mcp-server/lib/archive-journal.cjs +0 -1119
  153. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  154. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  155. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  156. package/mcp-server/lib/canonical-json.cjs +0 -19
  157. package/mcp-server/lib/change-authority.cjs +0 -26
  158. package/mcp-server/lib/change-packet.cjs +0 -790
  159. package/mcp-server/lib/change-workflow.cjs +0 -65
  160. package/mcp-server/lib/context-envelope.cjs +0 -581
  161. package/mcp-server/lib/context-paths.cjs +0 -142
  162. package/mcp-server/lib/decision-records.cjs +0 -423
  163. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  164. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  165. package/mcp-server/lib/doctor.cjs +0 -627
  166. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  167. package/mcp-server/lib/init-project.cjs +0 -1021
  168. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  169. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  170. package/mcp-server/lib/managed-file-write.cjs +0 -172
  171. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  172. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  173. package/mcp-server/lib/plan-store.cjs +0 -794
  174. package/mcp-server/lib/prd-parser.cjs +0 -83
  175. package/mcp-server/lib/pricing.cjs +0 -44
  176. package/mcp-server/lib/project-context.cjs +0 -178
  177. package/mcp-server/lib/projector.cjs +0 -386
  178. package/mcp-server/lib/provider-refs.cjs +0 -42
  179. package/mcp-server/lib/runtime-paths.cjs +0 -1714
  180. package/mcp-server/lib/runtime-state.cjs +0 -222
  181. package/mcp-server/lib/safe-project-file.cjs +0 -385
  182. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  183. package/mcp-server/lib/state-authority.cjs +0 -95
  184. package/mcp-server/lib/state-db.cjs +0 -1580
  185. package/mcp-server/lib/state-ops.cjs +0 -1428
  186. package/mcp-server/lib/task-contract.cjs +0 -404
  187. package/mcp-server/lib/task-expander.cjs +0 -105
  188. package/mcp-server/lib/task-ledger.cjs +0 -1964
  189. package/mcp-server/lib/telemetry.cjs +0 -85
  190. package/mcp-server/lib/topo.cjs +0 -131
  191. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  192. package/mcp-server/lib/worker-packet.cjs +0 -358
  193. package/mcp-server/server.cjs +0 -437
  194. package/orchestrator/auto-merge.cjs +0 -128
  195. package/orchestrator/daemon.cjs +0 -264
  196. package/orchestrator/dispatch-rules.cjs +0 -78
  197. package/orchestrator/parallel-orchestrator.cjs +0 -903
  198. package/orchestrator/planner/plan-builder.cjs +0 -125
  199. package/orchestrator/recovery.cjs +0 -188
  200. package/orchestrator/session-close-journal-worker.cjs +0 -5
  201. package/orchestrator/session-close-journal.cjs +0 -1014
  202. package/orchestrator/session-runner.cjs +0 -1153
  203. package/orchestrator/worktree-manager.cjs +0 -282
  204. package/settings.json +0 -7
  205. package/skills/cc-collab/SKILL.md +0 -42
  206. package/skills/code-review-expert/SKILL.md +0 -43
  207. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  208. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  209. package/skills/code-review-expert/references/removal-plan.md +0 -24
  210. package/skills/codex-collab/SKILL.md +0 -49
  211. package/skills/codex-collab/evals/evals.json +0 -35
  212. package/skills/integration-rules/SKILL.md +0 -32
  213. package/skills/security-rules/SKILL.md +0 -38
  214. package/skills/testing-rules/SKILL.md +0 -32
  215. package/skills/ultra-doctor/SKILL.md +0 -41
  216. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  217. package/skills/ultra-verify/SKILL.md +0 -46
  218. package/skills/ultra-verify/evals/evals.json +0 -46
  219. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  220. package/skills/ultra-verify/references/evidence-status.md +0 -21
  221. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  222. package/spec/cli-protocol.md +0 -102
  223. package/spec/mcp-tools.yaml +0 -312
  224. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  225. package/spec/schemas/change-delta.v1.schema.json +0 -153
  226. package/spec/schemas/command-manifest.schema.json +0 -29
  227. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  228. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  229. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  230. package/spec/schemas/mcp-tools.schema.json +0 -136
  231. package/spec/schemas/skill-manifest.schema.json +0 -24
  232. package/spec/schemas/state-db.sql +0 -641
  233. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  234. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  235. package/spec/schemas/test-report.v1.schema.json +0 -225
  236. package/ultra-tools/cli.cjs +0 -129
  237. package/ultra-tools/commands/db.cjs +0 -226
  238. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  239. package/ultra-tools/commands/migrate.cjs +0 -1386
  240. package/ultra-tools/commands/session.cjs +0 -213
  241. package/ultra-tools/commands/status.cjs +0 -559
  242. package/ultra-tools/commands/system.cjs +0 -498
  243. package/ultra-tools/commands/task.cjs +0 -105
@@ -1,2684 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { spawnSync } = require('node:child_process');
6
-
7
- const artifactRegistry = require('./artifact-registry.cjs');
8
- const baselines = require('./baseline-workflow.cjs');
9
- const canonical = require('./canonical-json.cjs');
10
- const changes = require('./change-workflow.cjs');
11
- const contextEnvelope = require('./context-envelope.cjs');
12
- const decisionRecords = require('./decision-records.cjs');
13
- const deliveryTransaction = require('./delivery-transaction.cjs');
14
- const doctor = require('./doctor.cjs');
15
- const gitBootstrap = require('./git-bootstrap.cjs');
16
- const runtimePaths = require('./runtime-paths.cjs');
17
- const { initProject } = require('./init-project.cjs');
18
- const ops = require('./state-ops.cjs');
19
- const planCheckpoint = require('./plan-checkpoint.cjs');
20
- const { readStableProjectFile } = require('./safe-project-file.cjs');
21
- const stageCheckpoints = require('./stage-checkpoints.cjs');
22
- const taskContract = require('./task-contract.cjs');
23
- const taskLedger = require('./task-ledger.cjs');
24
- const workerPacket = require('./worker-packet.cjs');
25
- const sessionRunner = require('../../orchestrator/session-runner.cjs');
26
-
27
- const PUBLIC_TOOLS = Object.freeze([
28
- 'ultra.context',
29
- 'ultra.record',
30
- 'ultra.checkpoint',
31
- 'ultra.sync',
32
- 'ultra.session',
33
- 'ultra.archive',
34
- 'ultra.doctor',
35
- ]);
36
-
37
- const RECORD_KINDS = Object.freeze([
38
- 'baseline',
39
- 'change_contract',
40
- 'decision',
41
- 'task_contract',
42
- 'task_outcome',
43
- 'artifact',
44
- 'event',
45
- ]);
46
-
47
- const HARD_ERROR_CODES = new Set([
48
- 'ARCHIVE_PATH_UNSAFE',
49
- 'ARCHIVE_RECOVERY_REQUIRED',
50
- 'ARCHIVE_RUNTIME_UNAVAILABLE',
51
- 'BACKUP_FAILED',
52
- 'CHECKPOINT_DIGEST_MISMATCH',
53
- 'CHECKPOINT_EVIDENCE_AUTHORITY_MISSING',
54
- 'CHECKPOINT_EVIDENCE_DIGEST_MISMATCH',
55
- 'CHECKPOINT_SCOPE_MISMATCH',
56
- 'CHECKPOINT_SCOPE_NOT_FOUND',
57
- 'CONTEXT_ENVELOPE_LIMIT_EXCEEDED',
58
- 'CONTEXT_ENVELOPE_DIGEST_MISMATCH',
59
- 'CONTEXT_ENVELOPE_FILE_DRIFT',
60
- 'DECISION_DIGEST_MISMATCH',
61
- 'DECISION_FILE_DRIFT',
62
- 'DECISION_ID_CONFLICT',
63
- 'DECISION_SCOPE_CONFLICT',
64
- 'OUTPUT_SCHEMA_DRIFT',
65
- 'PATH_AUTHORITY_VIOLATION',
66
- 'PLAN_RECOVERY_REQUIRED',
67
- 'RECORD_FILE_ROLLBACK_REQUIRED',
68
- 'RUNTIME_ABI_MISMATCH',
69
- 'RUNTIME_NATIVE_MISSING',
70
- 'SCHEMA_VERSION_MISMATCH',
71
- 'SESSION_RECEIPT_RECOVERY_REQUIRED',
72
- 'SESSION_STATUS_CONFLICT',
73
- 'STATE_CORRUPT',
74
- 'STATE_DB_ERROR',
75
- 'STATE_DB_MISSING',
76
- 'STATE_PERSISTENCE_FAILED',
77
- 'TASK_LEDGER_BASELINE_CONFLICT',
78
- 'TASK_LEDGER_CHANGE_CONFLICT',
79
- 'TASK_LEDGER_HISTORY_CONFLICT',
80
- 'TASK_LEDGER_TASK_CONFLICT',
81
- 'WORKER_PACKET_DIGEST_MISMATCH',
82
- 'WORKER_PACKET_FILE_DRIFT',
83
- ]);
84
-
85
- function errorCode(error) {
86
- return String(error?.code || error?.name || 'NEEDS_ATTENTION');
87
- }
88
-
89
- function isHardError(error) {
90
- const code = errorCode(error);
91
- return HARD_ERROR_CODES.has(code)
92
- || code.startsWith('PATH_')
93
- || code.startsWith('SQLITE_')
94
- || code.startsWith('STATE_CORRUPT')
95
- || code.startsWith('TASK_LEDGER_') && code.endsWith('_CONFLICT')
96
- || code.startsWith('SESSION_CLOSE_') && code.endsWith('_CONFLICT')
97
- || code.startsWith('WORKTREE_') && (
98
- code.endsWith('_UNSAFE') || code.endsWith('_CONFLICT')
99
- );
100
- }
101
-
102
- function parseJson(value, fallback = null) {
103
- try { return value == null ? fallback : JSON.parse(value); }
104
- catch { return fallback; }
105
- }
106
-
107
- function requestDigest(operation, request) {
108
- return canonical.digest({ operation, request });
109
- }
110
-
111
- function priorResult(db, {
112
- idempotencyKey,
113
- operation,
114
- requestDigest: expectedDigest,
115
- }) {
116
- if (!db || !idempotencyKey || !expectedDigest) return { found: false };
117
- const rows = db.prepare(
118
- `SELECT payload_json FROM events
119
- WHERE type = 'ultra_kernel_call'
120
- ORDER BY id DESC`,
121
- ).all();
122
- for (const row of rows) {
123
- const payload = parseJson(row.payload_json, {});
124
- if (payload.idempotency_key !== idempotencyKey || payload.accepted !== true) continue;
125
- if (payload.operation === operation
126
- && (payload.request_digest === expectedDigest
127
- || typeof payload.request_digest !== 'string')) {
128
- return { found: true, result: payload.result };
129
- }
130
- throw Object.assign(
131
- new Error(`idempotency key ${idempotencyKey} was already used for another request`),
132
- {
133
- code: 'IDEMPOTENCY_KEY_CONFLICT',
134
- details: {
135
- idempotency_key: idempotencyKey,
136
- prior_operation: payload.operation || null,
137
- requested_operation: operation,
138
- prior_request_digest: payload.request_digest,
139
- requested_request_digest: expectedDigest,
140
- },
141
- },
142
- );
143
- }
144
- return { found: false };
145
- }
146
-
147
- function rememberResultInTx(db, {
148
- idempotencyKey,
149
- operation,
150
- requestDigest: acceptedRequestDigest,
151
- result,
152
- changeId = null,
153
- taskId = null,
154
- }) {
155
- if (!db || !idempotencyKey) return;
156
- ops.appendEventInTx(db, {
157
- type: 'ultra_kernel_call',
158
- change_id: changeId,
159
- task_id: taskId,
160
- payload: {
161
- idempotency_key: idempotencyKey,
162
- operation,
163
- request_digest: acceptedRequestDigest,
164
- accepted: true,
165
- result,
166
- },
167
- });
168
- }
169
-
170
- function rememberRejectedAttempt(db, tool, input, result) {
171
- if (!db || result?.accepted !== false) return result;
172
- const nested = Array.isArray(result.results)
173
- ? result.results.flatMap((item) => item.diagnostics || [])
174
- : [];
175
- const diagnostics = [...(result.diagnostics || []), ...nested]
176
- .map((item) => ({
177
- code: item.code || 'NEEDS_ATTENTION',
178
- severity: item.severity || 'needs_attention',
179
- message: item.message || null,
180
- ...(item.details === undefined ? {} : { details: item.details }),
181
- }));
182
- const entries = Array.isArray(input.entries) ? input.entries : [];
183
- const changeIds = [...new Set(entries.flatMap((entry) => [
184
- entry.data?.change_id,
185
- entry.kind === 'change_contract' ? entry.data?.id : null,
186
- ]).filter(Boolean))];
187
- const taskIds = [...new Set(entries.flatMap((entry) => [
188
- entry.kind.startsWith('task_') ? entry.data?.id : null,
189
- input.scope?.task_id,
190
- ]).filter(Boolean))];
191
- const scopeChangeId = input.scope?.change_id || input.change_id || null;
192
- ops.appendEvent(db, {
193
- type: 'ultra_kernel_attempt',
194
- change_id: changeIds.length === 1 ? changeIds[0] : scopeChangeId,
195
- task_id: taskIds.length === 1 ? taskIds[0] : null,
196
- payload: {
197
- tool,
198
- accepted: false,
199
- mutable: result.mutable !== false,
200
- operations: entries.map((entry) => `${entry.kind}:${entry.action}`),
201
- idempotency_keys: entries.length > 0
202
- ? entries.map((entry) => entry.idempotency_key).filter(Boolean)
203
- : [input.idempotency_key].filter(Boolean),
204
- scope: input.scope || (input.change_id ? { change_id: input.change_id } : {}),
205
- blockers: result.blockers || [],
206
- diagnostics,
207
- },
208
- });
209
- return result;
210
- }
211
-
212
- function diagnostic(error, severity = 'needs_attention') {
213
- return {
214
- code: errorCode(error),
215
- severity,
216
- message: error.message,
217
- ...(error.details === undefined ? {} : { details: error.details }),
218
- };
219
- }
220
-
221
- function validationError(message, details) {
222
- return Object.assign(new Error(message), {
223
- code: 'VALIDATION_ERROR',
224
- ...(details === undefined ? {} : { details }),
225
- });
226
- }
227
-
228
- function stablePersistenceError(error, operation) {
229
- const code = errorCode(error);
230
- if (!code.startsWith('SQLITE_') && code !== 'STATE_DB_ERROR') return error;
231
- return Object.assign(
232
- new Error(`${operation} could not persist its atomic receipt`),
233
- {
234
- code: 'STATE_PERSISTENCE_FAILED',
235
- cause: error,
236
- details: { cause: code },
237
- },
238
- );
239
- }
240
-
241
- function assertExactFields(value, allowed, field) {
242
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
243
- throw validationError(`${field} must be an object`);
244
- }
245
- const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
246
- if (unknown.length > 0) {
247
- throw validationError(`${field}.${unknown[0]} is not allowed`, {
248
- field: `${field}.${unknown[0]}`,
249
- });
250
- }
251
- }
252
-
253
- const RECORD_DATA_FIELDS = Object.freeze({
254
- 'baseline:initialize': [
255
- 'target_dir', 'project_name', 'project_type', 'stack', 'mode', 'scope',
256
- 'resume', 'overwrite', 'git_mode', 'source_template',
257
- ],
258
- 'baseline:start': [
259
- 'id', 'project_name', 'project_type', 'stack', 'mode', 'repository_revision',
260
- 'replace_migrated', 'replace_ready', 'replacement_authorization', 'scope',
261
- 'provider_refs', 'classification',
262
- ],
263
- 'baseline:observe': [
264
- 'id', 'repository_revision', 'scope', 'spec_refs', 'evidence', 'verification',
265
- 'unknowns', 'gaps', 'classification', 'provider_refs',
266
- ],
267
- 'baseline:revise': [
268
- 'id', 'repository_revision', 'scope', 'spec_refs', 'evidence', 'verification',
269
- 'unknowns', 'gaps', 'classification', 'provider_refs',
270
- ],
271
- 'baseline:accept': [
272
- 'id', 'expected_revision', 'approved_by', 'approval_note',
273
- 'accept_dirty_worktree', 'accept_known_red',
274
- ],
275
- 'change_contract:open': [
276
- 'id', 'title', 'kind', 'intent', 'docs_impact', 'provider_refs',
277
- 'baseline_bypass', 'contract', 'classification', 'research_disposition',
278
- 'base_commit',
279
- ],
280
- 'change_contract:revise': ['id', 'patch'],
281
- 'change_contract:cancel': ['id'],
282
- 'change_contract:supersede': ['id', 'successor_id', 'title', 'kind', 'intent'],
283
- 'decision:accept': [
284
- 'id', 'scope', 'question', 'recommendation', 'selection', 'effects',
285
- 'non_goals', 'owner', 'source', 'provenance', 'applied_refs', 'supersedes_id',
286
- ],
287
- 'task_contract:define': taskContract.TASK_CONTRACT_DEFINE_FIELDS,
288
- 'task_contract:revise': ['id', 'patch'],
289
- 'task_contract:remove': ['id'],
290
- 'task_outcome:start': ['id'],
291
- 'task_outcome:complete': ['id', 'packet_digest'],
292
- 'task_outcome:block': ['id'],
293
- 'task_outcome:reopen': ['id'],
294
- 'task_outcome:attest_commit': ['id', 'completion_commit'],
295
- 'artifact:bind': [
296
- 'id', 'owner_type', 'owner_id', 'change_id', 'task_id', 'kind', 'path',
297
- 'status', 'source_refs', 'consumer_refs', 'provenance', 'metadata',
298
- 'content_digest', 'expected_before_digest',
299
- ],
300
- 'event:append': ['type', 'task_id', 'change_id', 'session_id', 'runtime', 'payload'],
301
- });
302
-
303
- function requireText(value, field, { nullable = false } = {}) {
304
- if (value === null && nullable) return;
305
- if (typeof value !== 'string') {
306
- throw validationError(`${field} must be a string${nullable ? ' or null' : ''}`, { field });
307
- }
308
- }
309
-
310
- function optionalText(value, key, field, options) {
311
- if (Object.hasOwn(value, key)) requireText(value[key], `${field}.${key}`, options);
312
- }
313
-
314
- function optionalBoolean(value, key, field) {
315
- if (Object.hasOwn(value, key) && typeof value[key] !== 'boolean') {
316
- throw validationError(`${field}.${key} must be a boolean`, { field: `${field}.${key}` });
317
- }
318
- }
319
-
320
- function optionalObject(value, key, field) {
321
- if (!Object.hasOwn(value, key)) return;
322
- const candidate = value[key];
323
- if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
324
- throw validationError(`${field}.${key} must be an object`, { field: `${field}.${key}` });
325
- }
326
- }
327
-
328
- function optionalArray(value, key, field) {
329
- if (Object.hasOwn(value, key) && !Array.isArray(value[key])) {
330
- throw validationError(`${field}.${key} must be an array`, { field: `${field}.${key}` });
331
- }
332
- }
333
-
334
- function validateStringArray(value, field) {
335
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
336
- throw validationError(`${field} must be an array of strings`, { field });
337
- }
338
- }
339
-
340
- function validateObjectArray(value, field, allowed = null) {
341
- if (!Array.isArray(value)) {
342
- throw validationError(`${field} must be an array`, { field });
343
- }
344
- value.forEach((item, index) => {
345
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
346
- throw validationError(`${field}[${index}] must be an object`, {
347
- field: `${field}[${index}]`,
348
- });
349
- }
350
- if (allowed) assertExactFields(item, allowed, `${field}[${index}]`);
351
- });
352
- }
353
-
354
- function validateDocsImpact(value, field) {
355
- assertExactFields(value, ['status', 'files', 'rationale'], field);
356
- optionalText(value, 'status', field);
357
- if (Object.hasOwn(value, 'files')) validateStringArray(value.files, `${field}.files`);
358
- optionalText(value, 'rationale', field, { nullable: true });
359
- }
360
-
361
- function validateChangeAuthority(value, field) {
362
- for (const key of ['title', 'intent', 'status', 'base_commit']) {
363
- optionalText(value, key, field);
364
- }
365
- for (const key of [
366
- 'docs_impact', 'provider_refs', 'baseline_bypass', 'contract',
367
- 'classification', 'research_disposition',
368
- ]) {
369
- optionalObject(value, key, field);
370
- }
371
- if (Object.hasOwn(value, 'docs_impact')) {
372
- validateDocsImpact(value.docs_impact, `${field}.docs_impact`);
373
- }
374
- if (Object.hasOwn(value, 'contract')) {
375
- const contractField = `${field}.contract`;
376
- assertExactFields(
377
- value.contract,
378
- ['outcome', 'acceptance', 'non_goals', 'public_seams', 'recovery', 'unresolved_decisions'],
379
- contractField,
380
- );
381
- optionalText(value.contract, 'outcome', contractField);
382
- for (const key of ['acceptance', 'non_goals', 'public_seams', 'unresolved_decisions']) {
383
- optionalArray(value.contract, key, contractField);
384
- }
385
- for (const key of ['non_goals', 'public_seams']) {
386
- if (Object.hasOwn(value.contract, key)) {
387
- validateStringArray(value.contract[key], `${contractField}.${key}`);
388
- }
389
- }
390
- if (Object.hasOwn(value.contract, 'acceptance')) {
391
- validateObjectArray(
392
- value.contract.acceptance,
393
- `${contractField}.acceptance`,
394
- ['id', 'criterion', 'verification'],
395
- );
396
- value.contract.acceptance.forEach((item, index) => {
397
- for (const key of ['id', 'criterion', 'verification']) {
398
- optionalText(item, key, `${contractField}.acceptance[${index}]`);
399
- }
400
- });
401
- }
402
- if (Object.hasOwn(value.contract, 'recovery')) {
403
- assertExactFields(
404
- value.contract.recovery,
405
- ['strategy', 'verification'],
406
- `${contractField}.recovery`,
407
- );
408
- for (const key of ['strategy', 'verification']) {
409
- optionalText(value.contract.recovery, key, `${contractField}.recovery`);
410
- }
411
- }
412
- if (Object.hasOwn(value.contract, 'unresolved_decisions')) {
413
- validateObjectArray(
414
- value.contract.unresolved_decisions,
415
- `${contractField}.unresolved_decisions`,
416
- ['id', 'summary', 'blocking', 'owner'],
417
- );
418
- value.contract.unresolved_decisions.forEach((item, index) => {
419
- for (const key of ['id', 'summary']) {
420
- optionalText(item, key, `${contractField}.unresolved_decisions[${index}]`);
421
- }
422
- optionalBoolean(
423
- item,
424
- 'blocking',
425
- `${contractField}.unresolved_decisions[${index}]`,
426
- );
427
- optionalText(
428
- item,
429
- 'owner',
430
- `${contractField}.unresolved_decisions[${index}]`,
431
- { nullable: true },
432
- );
433
- });
434
- }
435
- }
436
- if (Object.hasOwn(value, 'classification')) {
437
- const classificationField = `${field}.classification`;
438
- assertExactFields(value.classification, ['rationale', 'risk_flags'], classificationField);
439
- optionalText(value.classification, 'rationale', classificationField);
440
- if (Object.hasOwn(value.classification, 'risk_flags')) {
441
- validateStringArray(value.classification.risk_flags, `${classificationField}.risk_flags`);
442
- }
443
- }
444
- if (Object.hasOwn(value, 'research_disposition')) {
445
- const researchField = `${field}.research_disposition`;
446
- assertExactFields(
447
- value.research_disposition,
448
- ['status', 'mode', 'selected_steps', 'rationale'],
449
- researchField,
450
- );
451
- optionalText(value.research_disposition, 'status', researchField);
452
- optionalText(value.research_disposition, 'mode', researchField, { nullable: true });
453
- optionalText(value.research_disposition, 'rationale', researchField);
454
- if (Object.hasOwn(value.research_disposition, 'selected_steps')) {
455
- validateStringArray(
456
- value.research_disposition.selected_steps,
457
- `${researchField}.selected_steps`,
458
- );
459
- }
460
- }
461
- if (Object.hasOwn(value, 'baseline_bypass')) {
462
- const bypassField = `${field}.baseline_bypass`;
463
- assertExactFields(value.baseline_bypass, ['reason', 'approved_by'], bypassField);
464
- optionalText(value.baseline_bypass, 'reason', bypassField);
465
- optionalText(value.baseline_bypass, 'approved_by', bypassField);
466
- }
467
- }
468
-
469
- function validateBaselineData(action, data, field) {
470
- const textFields = action === 'initialize'
471
- ? ['target_dir', 'project_name', 'project_type', 'stack', 'mode', 'git_mode', 'source_template']
472
- : action === 'start'
473
- ? ['id', 'project_name', 'project_type', 'stack', 'mode', 'repository_revision']
474
- : action === 'accept'
475
- ? ['id', 'expected_revision', 'approved_by', 'approval_note']
476
- : ['id', 'repository_revision'];
477
- textFields.forEach((key) => optionalText(data, key, field));
478
- if (action === 'initialize') {
479
- optionalBoolean(data, 'resume', field);
480
- optionalBoolean(data, 'overwrite', field);
481
- }
482
- if (action === 'start') {
483
- optionalBoolean(data, 'replace_migrated', field);
484
- optionalBoolean(data, 'replace_ready', field);
485
- }
486
- if (action === 'accept') {
487
- optionalBoolean(data, 'accept_dirty_worktree', field);
488
- optionalBoolean(data, 'accept_known_red', field);
489
- }
490
- for (const key of ['classification', 'provider_refs', 'replacement_authorization']) {
491
- optionalObject(data, key, field);
492
- }
493
- if (Object.hasOwn(data, 'replacement_authorization')) {
494
- const authorizationField = `${field}.replacement_authorization`;
495
- assertExactFields(
496
- data.replacement_authorization,
497
- ['approved_by', 'reason'],
498
- authorizationField,
499
- );
500
- optionalText(data.replacement_authorization, 'approved_by', authorizationField);
501
- optionalText(data.replacement_authorization, 'reason', authorizationField);
502
- }
503
- for (const key of [
504
- 'scope', 'spec_refs', 'evidence', 'verification', 'unknowns', 'gaps',
505
- ]) {
506
- optionalArray(data, key, field);
507
- }
508
- if (Object.hasOwn(data, 'scope')) validateStringArray(data.scope, `${field}.scope`);
509
- if (Object.hasOwn(data, 'spec_refs')) {
510
- validateObjectArray(data.spec_refs, `${field}.spec_refs`, ['kind', 'path']);
511
- data.spec_refs.forEach((item, index) => {
512
- optionalText(item, 'kind', `${field}.spec_refs[${index}]`);
513
- optionalText(item, 'path', `${field}.spec_refs[${index}]`);
514
- });
515
- }
516
- if (Object.hasOwn(data, 'evidence')) {
517
- validateObjectArray(data.evidence, `${field}.evidence`, ['kind', 'ref', 'summary']);
518
- data.evidence.forEach((item, index) => {
519
- for (const key of ['kind', 'ref', 'summary']) {
520
- optionalText(item, key, `${field}.evidence[${index}]`);
521
- }
522
- });
523
- }
524
- if (Object.hasOwn(data, 'verification')) {
525
- validateObjectArray(
526
- data.verification,
527
- `${field}.verification`,
528
- ['name', 'command', 'status', 'evidence', 'rationale'],
529
- );
530
- data.verification.forEach((item, index) => {
531
- for (const key of ['name', 'command', 'status', 'evidence', 'rationale']) {
532
- optionalText(item, key, `${field}.verification[${index}]`);
533
- }
534
- });
535
- }
536
- if (Object.hasOwn(data, 'unknowns')) {
537
- validateObjectArray(
538
- data.unknowns,
539
- `${field}.unknowns`,
540
- ['summary', 'blocking', 'owner'],
541
- );
542
- data.unknowns.forEach((item, index) => {
543
- optionalText(item, 'summary', `${field}.unknowns[${index}]`);
544
- optionalBoolean(item, 'blocking', `${field}.unknowns[${index}]`);
545
- optionalText(item, 'owner', `${field}.unknowns[${index}]`);
546
- });
547
- }
548
- if (Object.hasOwn(data, 'gaps')) {
549
- validateObjectArray(
550
- data.gaps,
551
- `${field}.gaps`,
552
- ['id', 'category', 'status', 'blocking', 'summary', 'evidence_refs', 'owner', 'resolution'],
553
- );
554
- data.gaps.forEach((item, index) => {
555
- for (const key of ['id', 'category', 'status', 'summary']) {
556
- optionalText(item, key, `${field}.gaps[${index}]`);
557
- }
558
- optionalBoolean(item, 'blocking', `${field}.gaps[${index}]`);
559
- optionalText(item, 'owner', `${field}.gaps[${index}]`, { nullable: true });
560
- optionalText(item, 'resolution', `${field}.gaps[${index}]`, { nullable: true });
561
- if (Object.hasOwn(item, 'evidence_refs')) {
562
- validateStringArray(item.evidence_refs, `${field}.gaps[${index}].evidence_refs`);
563
- }
564
- });
565
- }
566
- }
567
-
568
- function validateDecisionData(data, field) {
569
- for (const key of ['id', 'question', 'recommendation', 'selection', 'owner', 'source']) {
570
- optionalText(data, key, field);
571
- }
572
- optionalText(data, 'supersedes_id', field, { nullable: true });
573
- optionalObject(data, 'scope', field);
574
- optionalObject(data, 'effects', field);
575
- optionalObject(data, 'provenance', field);
576
- optionalArray(data, 'non_goals', field);
577
- optionalArray(data, 'applied_refs', field);
578
- if (Object.hasOwn(data, 'non_goals')) {
579
- validateStringArray(data.non_goals, `${field}.non_goals`);
580
- }
581
- if (Object.hasOwn(data, 'scope')) {
582
- assertExactFields(data.scope, ['baseline_id', 'change_id'], `${field}.scope`);
583
- optionalText(data.scope, 'baseline_id', `${field}.scope`);
584
- optionalText(data.scope, 'change_id', `${field}.scope`);
585
- }
586
- if (Object.hasOwn(data, 'applied_refs')) {
587
- validateObjectArray(
588
- data.applied_refs,
589
- `${field}.applied_refs`,
590
- ['ref', 'field', 'digest'],
591
- );
592
- data.applied_refs.forEach((item, index) => {
593
- for (const key of ['ref', 'field', 'digest']) {
594
- optionalText(item, key, `${field}.applied_refs[${index}]`);
595
- }
596
- });
597
- }
598
- }
599
-
600
- function validateArtifactData(data, field) {
601
- for (const key of ['id', 'owner_type', 'owner_id', 'kind', 'path', 'status', 'content_digest']) {
602
- optionalText(data, key, field);
603
- }
604
- for (const key of ['change_id', 'task_id', 'expected_before_digest']) {
605
- optionalText(data, key, field, { nullable: true });
606
- }
607
- for (const key of ['source_refs', 'consumer_refs']) {
608
- optionalArray(data, key, field);
609
- if (!Object.hasOwn(data, key)) continue;
610
- validateObjectArray(data[key], `${field}.${key}`, ['type', 'id', 'relation']);
611
- data[key].forEach((item, index) => {
612
- for (const referenceField of ['type', 'id', 'relation']) {
613
- optionalText(item, referenceField, `${field}.${key}[${index}]`);
614
- }
615
- });
616
- }
617
- optionalObject(data, 'provenance', field);
618
- optionalObject(data, 'metadata', field);
619
- }
620
-
621
- function validateRecordEntry(entry) {
622
- const operation = `${entry.kind}:${entry.action}`;
623
- const allowed = RECORD_DATA_FIELDS[operation];
624
- if (!allowed) {
625
- throw validationError(`unsupported ${entry.kind} action: ${entry.action}`);
626
- }
627
- const field = `${operation}.data`;
628
- assertExactFields(entry.data, allowed, field);
629
- if (entry.kind === 'baseline') validateBaselineData(entry.action, entry.data, field);
630
- else if (entry.kind === 'change_contract') {
631
- optionalText(entry.data, 'id', field);
632
- if (entry.action === 'open') {
633
- for (const key of ['title', 'kind', 'intent']) optionalText(entry.data, key, field);
634
- validateChangeAuthority(entry.data, field);
635
- } else if (entry.action === 'revise') {
636
- assertExactFields(
637
- entry.data.patch,
638
- [
639
- 'title', 'intent', 'status', 'docs_impact', 'provider_refs',
640
- 'contract', 'classification', 'research_disposition',
641
- ],
642
- `${field}.patch`,
643
- );
644
- validateChangeAuthority(entry.data.patch, `${field}.patch`);
645
- } else if (entry.action === 'supersede') {
646
- requireNonEmptyText(entry.data.id, `${field}.id`);
647
- requireNonEmptyText(entry.data.successor_id, `${field}.successor_id`);
648
- for (const key of ['title', 'kind', 'intent']) {
649
- optionalText(entry.data, key, field);
650
- }
651
- }
652
- } else if (entry.kind === 'decision') validateDecisionData(entry.data, field);
653
- else if (entry.kind === 'artifact') validateArtifactData(entry.data, field);
654
- else if (entry.kind === 'event') {
655
- optionalText(entry.data, 'type', field);
656
- for (const key of ['task_id', 'change_id', 'session_id', 'runtime']) {
657
- optionalText(entry.data, key, field, { nullable: true });
658
- }
659
- optionalObject(entry.data, 'payload', field);
660
- }
661
- }
662
-
663
- function snapshotRecordFile(file) {
664
- const stat = fs.lstatSync(file, { throwIfNoEntry: false });
665
- if (!stat) return { existed: false, bytes: null, mode: null };
666
- if (!stat.isFile() || stat.isSymbolicLink()) {
667
- throw Object.assign(
668
- new Error(`record file target is not a regular file: ${file}`),
669
- { code: 'PATH_AUTHORITY_VIOLATION' },
670
- );
671
- }
672
- return { existed: true, bytes: fs.readFileSync(file), mode: stat.mode };
673
- }
674
-
675
- function restoreRecordFile(file, snapshot) {
676
- const current = fs.lstatSync(file, { throwIfNoEntry: false });
677
- if (!snapshot.existed) {
678
- if (!current) return;
679
- if (!current.isFile() && !current.isSymbolicLink()) {
680
- throw new Error(`record rollback target changed type: ${file}`);
681
- }
682
- fs.rmSync(file, { force: true });
683
- return;
684
- }
685
- if (current?.isFile() && !current.isSymbolicLink()) {
686
- if (fs.readFileSync(file).equals(snapshot.bytes)) return;
687
- } else if (current) {
688
- throw new Error(`record rollback target changed type: ${file}`);
689
- }
690
- fs.mkdirSync(path.dirname(file), { recursive: true });
691
- fs.writeFileSync(file, snapshot.bytes, { mode: snapshot.mode });
692
- }
693
-
694
- function prepareRecordFileRollback(entry, rootDir) {
695
- const id = entry.data?.id;
696
- if (typeof id !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(id)) return null;
697
- if (entry.kind === 'change_contract' && entry.action === 'open') {
698
- const directory = path.resolve(rootDir, '.ultra', 'changes', 'active', id);
699
- const existed = fs.existsSync(directory);
700
- return () => {
701
- if (!existed && fs.existsSync(directory)) {
702
- fs.rmSync(directory, { recursive: true, force: true });
703
- }
704
- };
705
- }
706
- if (entry.kind === 'change_contract' && entry.action === 'revise') {
707
- const file = path.resolve(
708
- rootDir,
709
- '.ultra',
710
- 'changes',
711
- 'active',
712
- id,
713
- 'intent.md',
714
- );
715
- const snapshot = snapshotRecordFile(file);
716
- return () => restoreRecordFile(file, snapshot);
717
- }
718
- if (entry.kind === 'decision' && entry.action === 'accept') {
719
- const scope = entry.data.scope;
720
- let relative = null;
721
- if (typeof scope?.baseline_id === 'string') {
722
- relative = path.join('.ultra', 'decisions', 'baseline', `${id}.json`);
723
- } else if (typeof scope?.change_id === 'string'
724
- && /^[a-zA-Z0-9_-]+$/.test(scope.change_id)) {
725
- relative = path.join(
726
- '.ultra',
727
- 'changes',
728
- 'active',
729
- scope.change_id,
730
- 'decisions',
731
- `${id}.json`,
732
- );
733
- }
734
- if (!relative) return null;
735
- const file = path.resolve(rootDir, relative);
736
- const snapshot = snapshotRecordFile(file);
737
- return () => restoreRecordFile(file, snapshot);
738
- }
739
- return null;
740
- }
741
-
742
- function rollbackRecordFiles(rollback, error) {
743
- if (!rollback) return;
744
- try {
745
- rollback();
746
- } catch (cause) {
747
- throw Object.assign(
748
- new Error(
749
- `record failed and managed file rollback requires recovery: `
750
- + `${error.message}; ${cause.message}`,
751
- ),
752
- {
753
- code: 'RECORD_FILE_ROLLBACK_REQUIRED',
754
- cause: error,
755
- details: {
756
- original_error: errorCode(error),
757
- rollback_error: cause.message,
758
- },
759
- },
760
- );
761
- }
762
- }
763
-
764
- function rejectedAttemptAudit(db) {
765
- return db.prepare(
766
- `SELECT id, ts, task_id, change_id, payload_json
767
- FROM events
768
- WHERE type = 'ultra_kernel_attempt'
769
- ORDER BY id DESC
770
- LIMIT 5`,
771
- ).all().map((row) => {
772
- const payload = parseJson(row.payload_json, {});
773
- return {
774
- event_id: row.id,
775
- recorded_at: row.ts,
776
- tool: payload.tool || null,
777
- task_id: row.task_id || null,
778
- change_id: row.change_id || null,
779
- operations: (payload.operations || []).slice(0, 12),
780
- blockers: (payload.blockers || []).slice(0, 12),
781
- diagnostics: (payload.diagnostics || []).slice(0, 8).map((item) => ({
782
- code: item.code || 'NEEDS_ATTENTION',
783
- severity: item.severity || 'needs_attention',
784
- message: typeof item.message === 'string'
785
- ? item.message.slice(0, 500)
786
- : null,
787
- })),
788
- };
789
- });
790
- }
791
-
792
- function requireNonEmptyText(value, field, { minLength = 1 } = {}) {
793
- requireText(value, field);
794
- if (value.trim().length < minLength) {
795
- throw validationError(`${field} must contain at least ${minLength} characters`, { field });
796
- }
797
- }
798
-
799
- function validateContextInput(input) {
800
- assertExactFields(input, ['stage', 'scope', 'detail'], 'context');
801
- if (Object.hasOwn(input, 'stage')) {
802
- requireText(input.stage, 'context.stage');
803
- if (!['project', 'research', 'plan', 'dev', 'test', 'review', 'deliver'].includes(input.stage)) {
804
- throw validationError(`unsupported context stage: ${input.stage}`);
805
- }
806
- }
807
- if (Object.hasOwn(input, 'detail')) {
808
- requireText(input.detail, 'context.detail');
809
- if (!['summary', 'full'].includes(input.detail)) {
810
- throw validationError(`unsupported context detail: ${input.detail}`);
811
- }
812
- }
813
- if (Object.hasOwn(input, 'scope')) {
814
- assertExactFields(input.scope, ['change_id', 'task_id'], 'context.scope');
815
- for (const key of ['change_id', 'task_id']) {
816
- if (Object.hasOwn(input.scope, key)) {
817
- requireNonEmptyText(input.scope[key], `context.scope.${key}`);
818
- }
819
- }
820
- }
821
- }
822
-
823
- function validateCheckpointInput(input) {
824
- assertExactFields(
825
- input,
826
- ['stage', 'scope', 'payload', 'idempotency_key'],
827
- 'checkpoint',
828
- );
829
- requireText(input.stage, 'checkpoint.stage');
830
- if (!['research', 'plan', 'dev', 'test', 'review', 'deliver'].includes(input.stage)) {
831
- throw validationError(`unsupported checkpoint stage: ${input.stage}`);
832
- }
833
- assertExactFields(input.scope, ['change_id', 'task_id'], 'checkpoint.scope');
834
- for (const key of ['change_id', 'task_id']) {
835
- if (Object.hasOwn(input.scope, key)) {
836
- requireNonEmptyText(input.scope[key], `checkpoint.scope.${key}`);
837
- }
838
- }
839
- assertExactFields(
840
- input.payload,
841
- Object.keys(input.payload || {}),
842
- 'checkpoint.payload',
843
- );
844
- requireNonEmptyText(input.idempotency_key, 'checkpoint.idempotency_key', {
845
- minLength: 3,
846
- });
847
- if (Object.hasOwn(input.payload, 'evidence')) {
848
- validateObjectArray(
849
- input.payload.evidence,
850
- 'checkpoint.payload.evidence',
851
- ['kind', 'ref', 'summary', 'digest', 'artifact_id'],
852
- );
853
- input.payload.evidence.forEach((item, index) => {
854
- for (const key of ['kind', 'ref', 'summary', 'digest', 'artifact_id']) {
855
- optionalText(item, key, `checkpoint.payload.evidence[${index}]`);
856
- }
857
- });
858
- }
859
- if (Object.hasOwn(input.payload, 'diagnostics')) {
860
- validateObjectArray(
861
- input.payload.diagnostics,
862
- 'checkpoint.payload.diagnostics',
863
- ['code', 'severity', 'message', 'details'],
864
- );
865
- input.payload.diagnostics.forEach((item, index) => {
866
- requireNonEmptyText(item.code, `checkpoint.payload.diagnostics[${index}].code`);
867
- optionalText(item, 'severity', `checkpoint.payload.diagnostics[${index}]`);
868
- optionalText(item, 'message', `checkpoint.payload.diagnostics[${index}]`);
869
- });
870
- }
871
- }
872
-
873
- function validateSyncInput(input) {
874
- requireText(input.action, 'sync.action');
875
- const allowed = {
876
- inspect: ['action'],
877
- import: ['action'],
878
- migrate: ['action'],
879
- publish: ['action', 'reason', 'idempotency_key'],
880
- }[input.action];
881
- if (!allowed) throw validationError(`unsupported sync action: ${input.action}`);
882
- assertExactFields(input, allowed, `sync:${input.action}`);
883
- if (input.action === 'publish') {
884
- optionalText(input, 'reason', 'sync:publish');
885
- requireNonEmptyText(input.idempotency_key, 'sync:publish.idempotency_key', {
886
- minLength: 3,
887
- });
888
- }
889
- }
890
-
891
- const SESSION_PAYLOAD_FIELDS = Object.freeze({
892
- admission: [],
893
- acquire: [
894
- 'role', 'runtime', 'output_path', 'output_schema', 'evidence_refs',
895
- 'diff_range', 'changed_files', 'takeover', 'worktree_base',
896
- ],
897
- get: [],
898
- list: [],
899
- heartbeat: [],
900
- release: ['status', 'remove_worktree'],
901
- });
902
-
903
- function validateSessionInput(input) {
904
- requireText(input.action, 'session.action');
905
- const payloadFields = SESSION_PAYLOAD_FIELDS[input.action];
906
- if (!payloadFields) throw validationError(`unsupported session action: ${input.action}`);
907
- const mutating = ['acquire', 'heartbeat', 'release'].includes(input.action);
908
- assertExactFields(
909
- input,
910
- mutating
911
- ? ['action', 'scope', 'payload', 'idempotency_key']
912
- : ['action', 'scope', 'payload'],
913
- `session:${input.action}`,
914
- );
915
- const scope = input.scope || {};
916
- const payload = input.payload || {};
917
- const scopeFields = ['get', 'heartbeat', 'release'].includes(input.action)
918
- ? ['sid']
919
- : ['task_id'];
920
- assertExactFields(scope, scopeFields, `session:${input.action}.scope`);
921
- assertExactFields(payload, payloadFields, `session:${input.action}.payload`);
922
- if (input.action !== 'list' || Object.hasOwn(scope, 'task_id')) {
923
- const key = scopeFields[0];
924
- requireNonEmptyText(scope[key], `session:${input.action}.scope.${key}`);
925
- }
926
- if (mutating) {
927
- requireNonEmptyText(
928
- input.idempotency_key,
929
- `session:${input.action}.idempotency_key`,
930
- { minLength: 3 },
931
- );
932
- }
933
- if (input.action === 'release') {
934
- if (Object.hasOwn(payload, 'status')) {
935
- requireText(payload.status, 'session:release.payload.status');
936
- if (!['completed', 'crashed'].includes(payload.status)) {
937
- throw validationError(`unsupported session release status: ${payload.status}`);
938
- }
939
- }
940
- optionalBoolean(payload, 'remove_worktree', 'session:release.payload');
941
- }
942
- if (input.action === 'acquire') {
943
- for (const key of ['role', 'runtime', 'output_path', 'diff_range', 'worktree_base']) {
944
- optionalText(payload, key, 'session:acquire.payload');
945
- }
946
- optionalObject(payload, 'output_schema', 'session:acquire.payload');
947
- optionalArray(payload, 'evidence_refs', 'session:acquire.payload');
948
- optionalArray(payload, 'changed_files', 'session:acquire.payload');
949
- optionalBoolean(payload, 'takeover', 'session:acquire.payload');
950
- if (Object.hasOwn(payload, 'changed_files')) {
951
- validateStringArray(payload.changed_files, 'session:acquire.payload.changed_files');
952
- }
953
- if (Object.hasOwn(payload, 'evidence_refs')) {
954
- payload.evidence_refs.forEach((item, index) => {
955
- if (typeof item === 'string') return;
956
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
957
- throw validationError(
958
- `session:acquire.payload.evidence_refs[${index}] must be a string or object`,
959
- );
960
- }
961
- });
962
- }
963
- }
964
- }
965
-
966
- function validateArchiveInput(input) {
967
- assertExactFields(input, ['change_id', 'payload', 'idempotency_key'], 'archive');
968
- requireNonEmptyText(input.change_id, 'archive.change_id');
969
- requireNonEmptyText(input.idempotency_key, 'archive.idempotency_key', { minLength: 3 });
970
- assertExactFields(
971
- input.payload,
972
- [
973
- 'summary', 'baseline_updates', 'no_baseline_change_reason',
974
- 'reconciliation_path',
975
- ],
976
- 'archive.payload',
977
- );
978
- for (const key of ['summary', 'no_baseline_change_reason', 'reconciliation_path']) {
979
- optionalText(input.payload, key, 'archive.payload');
980
- }
981
- if (Object.hasOwn(input.payload, 'baseline_updates')) {
982
- validateStringArray(input.payload.baseline_updates, 'archive.payload.baseline_updates');
983
- }
984
- }
985
-
986
- function validateRecordInput(input) {
987
- assertExactFields(input, ['entries'], 'record');
988
- if (!Array.isArray(input.entries) || input.entries.length === 0) {
989
- throw validationError('record.entries must be a non-empty array', {
990
- field: 'record.entries',
991
- });
992
- }
993
- input.entries.forEach((entry, index) => {
994
- const field = `record.entries[${index}]`;
995
- assertExactFields(entry, ['kind', 'action', 'data', 'idempotency_key'], field);
996
- requireNonEmptyText(entry.kind, `${field}.kind`);
997
- requireNonEmptyText(entry.action, `${field}.action`);
998
- assertExactFields(
999
- entry.data,
1000
- Object.keys(entry.data || {}),
1001
- `${field}.data`,
1002
- );
1003
- requireNonEmptyText(entry.idempotency_key, `${field}.idempotency_key`, {
1004
- minLength: 3,
1005
- });
1006
- });
1007
- }
1008
-
1009
- function validateDoctorInput(input) {
1010
- assertExactFields(input, ['repair'], 'doctor');
1011
- optionalBoolean(input, 'repair', 'doctor');
1012
- }
1013
-
1014
- function selectedScope(db, input = {}) {
1015
- const scope = input.scope || {};
1016
- const task = scope.task_id ? ops.readTask(db, scope.task_id) : null;
1017
- if (scope.task_id && !task) {
1018
- throw Object.assign(
1019
- new Error(`checkpoint task scope does not exist: ${scope.task_id}`),
1020
- {
1021
- code: 'CHECKPOINT_SCOPE_NOT_FOUND',
1022
- details: { scope_type: 'task', scope_id: scope.task_id },
1023
- },
1024
- );
1025
- }
1026
- const changeId = scope.change_id || task?.change_id || null;
1027
- const change = changeId ? changes.readChange(db, changeId) : null;
1028
- if (changeId && !change) {
1029
- throw Object.assign(
1030
- new Error(`checkpoint Change scope does not exist: ${changeId}`),
1031
- {
1032
- code: 'CHECKPOINT_SCOPE_NOT_FOUND',
1033
- details: { scope_type: 'change', scope_id: changeId },
1034
- },
1035
- );
1036
- }
1037
- if (task && scope.change_id && task.change_id !== scope.change_id) {
1038
- throw Object.assign(
1039
- new Error(
1040
- `checkpoint task ${task.id} belongs to Change ${task.change_id || '(none)'}, not ${scope.change_id}`,
1041
- ),
1042
- {
1043
- code: 'CHECKPOINT_SCOPE_MISMATCH',
1044
- details: {
1045
- task_id: task.id,
1046
- task_change_id: task.change_id || null,
1047
- requested_change_id: scope.change_id,
1048
- },
1049
- },
1050
- );
1051
- }
1052
- if (task) {
1053
- return {
1054
- ...(changeId ? { change_id: changeId } : {}),
1055
- task_id: task.id,
1056
- };
1057
- }
1058
- if (change) return { change_id: change.id };
1059
- const baseline = baselines.readBaseline(db);
1060
- return baseline ? { baseline_id: baseline.id } : { project_id: 'project' };
1061
- }
1062
-
1063
- function readContext(db, input = {}, {
1064
- rootDir = process.cwd(),
1065
- runtime = 'unknown',
1066
- } = {}) {
1067
- validateContextInput(input);
1068
- const result = contextEnvelope.buildEnvelope(db, input, { rootDir, runtime });
1069
- const rejectedAttempts = rejectedAttemptAudit(db);
1070
- let visible = {
1071
- ...result,
1072
- audit: {
1073
- rejected_attempts: rejectedAttempts,
1074
- },
1075
- };
1076
- delete visible.bytes;
1077
- const limit = input.detail === 'full'
1078
- ? contextEnvelope.FULL_LIMIT
1079
- : contextEnvelope.SUMMARY_LIMIT;
1080
- while (Buffer.byteLength(JSON.stringify(visible)) > limit
1081
- && rejectedAttempts.length > 0) {
1082
- rejectedAttempts.pop();
1083
- }
1084
- if (Buffer.byteLength(JSON.stringify(visible)) > limit) {
1085
- visible = { ...result };
1086
- delete visible.bytes;
1087
- }
1088
- visible.bytes = Buffer.byteLength(JSON.stringify(visible));
1089
- return visible;
1090
- }
1091
-
1092
- function initializeProject(data = {}) {
1093
- const targetDir = path.resolve(data.target_dir || process.cwd());
1094
- const alreadyInitialized = fs.existsSync(path.join(targetDir, '.ultra'));
1095
- return initProject({
1096
- ...data,
1097
- target_dir: targetDir,
1098
- resume: alreadyInitialized || data.resume === true,
1099
- });
1100
- }
1101
-
1102
- function baselineRecord(db, action, data, rootDir) {
1103
- if (action === 'initialize') return initializeProject(data);
1104
- if (action === 'start') {
1105
- return { baseline: baselines.startBaseline(db, data, { rootDir }) };
1106
- }
1107
- if (action === 'observe' || action === 'revise') {
1108
- return { baseline: baselines.recordBaseline(db, data, { rootDir }) };
1109
- }
1110
- if (action === 'accept') {
1111
- const result = baselines.convergeBaseline(db, data, { rootDir });
1112
- return {
1113
- ...result,
1114
- diagnostics: [
1115
- ...(result.blockers || []).map((code) => ({
1116
- code,
1117
- severity: 'needs_attention',
1118
- })),
1119
- ...(result.warnings || []).map((code) => ({
1120
- code,
1121
- severity: 'warning',
1122
- })),
1123
- ],
1124
- };
1125
- }
1126
- throw Object.assign(new Error(`unsupported baseline action: ${action}`), {
1127
- code: 'VALIDATION_ERROR',
1128
- });
1129
- }
1130
-
1131
- function changeContractRecord(db, action, data, rootDir) {
1132
- if (action === 'open') {
1133
- return changes.createChange(db, data, { rootDir });
1134
- }
1135
- if (action === 'revise') {
1136
- return {
1137
- change: changes.updateChange(
1138
- db,
1139
- data.id,
1140
- data.patch,
1141
- { rootDir },
1142
- ),
1143
- };
1144
- }
1145
- if (action === 'cancel') {
1146
- return {
1147
- change: changes.updateChange(
1148
- db,
1149
- data.id,
1150
- { status: 'cancelled' },
1151
- { rootDir },
1152
- ),
1153
- };
1154
- }
1155
- if (action === 'supersede') {
1156
- return changes.supersedeChange(db, data, { rootDir });
1157
- }
1158
- throw Object.assign(new Error(`unsupported change_contract action: ${action}`), {
1159
- code: 'VALIDATION_ERROR',
1160
- });
1161
- }
1162
-
1163
- function taskContractRecord(db, action, data, rootDir) {
1164
- if (action === 'define') {
1165
- return { task: ops.createTask(db, taskContract.normalizeTaskDefinition(data)) };
1166
- }
1167
- if (action === 'revise') {
1168
- assertExactFields(data, ['id', 'patch'], 'task_contract.revise.data');
1169
- const id = taskContract.normalizeTaskId(data.id);
1170
- return {
1171
- task: ops.patchTask(db, id, taskContract.normalizeTaskPatch(data.patch)),
1172
- };
1173
- }
1174
- if (action === 'remove') {
1175
- assertExactFields(data, ['id'], 'task_contract.remove.data');
1176
- return ops.deleteTask(
1177
- db,
1178
- taskContract.normalizeTaskId(data.id),
1179
- { rootDir },
1180
- );
1181
- }
1182
- throw Object.assign(new Error(`unsupported task_contract action: ${action}`), {
1183
- code: 'VALIDATION_ERROR',
1184
- });
1185
- }
1186
-
1187
- function resolveIntegratedCommit(rootDir, completionCommit) {
1188
- if (typeof completionCommit !== 'string'
1189
- || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(completionCommit)) {
1190
- throw Object.assign(
1191
- new Error('completion_commit must be a lowercase Git commit SHA'),
1192
- { code: 'COMPLETION_COMMIT_INVALID' },
1193
- );
1194
- }
1195
- const resolved = spawnSync(
1196
- 'git',
1197
- ['rev-parse', '--verify', `${completionCommit}^{commit}`],
1198
- {
1199
- cwd: rootDir,
1200
- encoding: 'utf8',
1201
- stdio: ['ignore', 'pipe', 'pipe'],
1202
- },
1203
- );
1204
- if (resolved.status !== 0) {
1205
- throw Object.assign(
1206
- new Error(`completion commit ${completionCommit} was not found in this repository`),
1207
- { code: 'COMPLETION_COMMIT_NOT_FOUND' },
1208
- );
1209
- }
1210
- const commit = String(resolved.stdout || '').trim();
1211
- if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(commit)) {
1212
- throw Object.assign(
1213
- new Error(`completion commit ${completionCommit} could not be resolved`),
1214
- { code: 'COMPLETION_COMMIT_NOT_FOUND' },
1215
- );
1216
- }
1217
- const integrated = spawnSync(
1218
- 'git',
1219
- ['merge-base', '--is-ancestor', commit, 'HEAD'],
1220
- {
1221
- cwd: rootDir,
1222
- encoding: 'utf8',
1223
- stdio: ['ignore', 'pipe', 'pipe'],
1224
- },
1225
- );
1226
- if (integrated.status !== 0) {
1227
- throw Object.assign(
1228
- new Error(`completion commit ${commit} is not an ancestor of the current HEAD`),
1229
- { code: 'COMPLETION_COMMIT_NOT_INTEGRATED' },
1230
- );
1231
- }
1232
- return commit;
1233
- }
1234
-
1235
- function attestTaskCompletionCommit(db, data, rootDir) {
1236
- assertExactFields(data, ['id', 'completion_commit'], 'task_outcome.attest_commit.data');
1237
- const id = taskContract.normalizeTaskId(data.id);
1238
- const task = ops.readTask(db, id);
1239
- if (!task) {
1240
- throw Object.assign(new Error(`task ${id} does not exist`), { code: 'TASK_NOT_FOUND' });
1241
- }
1242
- if (task.status !== 'completed') {
1243
- throw Object.assign(
1244
- new Error(`task ${id} must be completed before its commit can be attested`),
1245
- {
1246
- code: 'TASK_NOT_COMPLETED',
1247
- details: { task_id: id, status: task.status },
1248
- },
1249
- );
1250
- }
1251
- const commit = resolveIntegratedCommit(rootDir, data.completion_commit);
1252
- if (task.completion_commit) {
1253
- if (task.completion_commit === commit) return { task };
1254
- throw Object.assign(
1255
- new Error(`task ${id} already attests completion commit ${task.completion_commit}`),
1256
- {
1257
- code: 'COMPLETION_COMMIT_CONFLICT',
1258
- details: {
1259
- task_id: id,
1260
- existing_commit: task.completion_commit,
1261
- requested_commit: commit,
1262
- },
1263
- },
1264
- );
1265
- }
1266
- return { task: ops.patchTask(db, id, { completion_commit: commit }) };
1267
- }
1268
-
1269
- function taskOutcomeRecord(db, action, data, rootDir) {
1270
- if (action === 'attest_commit') {
1271
- return attestTaskCompletionCommit(db, data, rootDir);
1272
- }
1273
- const status = {
1274
- start: 'in_progress',
1275
- complete: 'completed',
1276
- block: 'blocked',
1277
- reopen: 'in_progress',
1278
- }[action];
1279
- if (!status) {
1280
- throw Object.assign(new Error(`unsupported task_outcome action: ${action}`), {
1281
- code: 'VALIDATION_ERROR',
1282
- });
1283
- }
1284
- if (action === 'complete') {
1285
- assertExactFields(data, ['id', 'packet_digest'], 'task_outcome.complete.data');
1286
- taskContract.normalizeTaskId(data.id);
1287
- if (typeof data.packet_digest !== 'string' || !data.packet_digest) {
1288
- throw validationError('task_outcome.complete.data.packet_digest must be a string');
1289
- }
1290
- const packet = db.prepare(
1291
- `SELECT id, packet_digest, packet_path, output_path
1292
- FROM worker_packets
1293
- WHERE scope_type = 'task' AND scope_id = ? AND status = 'assigned'
1294
- ORDER BY created_at DESC, rowid DESC LIMIT 1`,
1295
- ).get(data.id);
1296
- if (!packet) {
1297
- throw Object.assign(
1298
- new Error(`task ${data.id} has no assigned Worker Packet`),
1299
- { code: 'WORKER_PACKET_REQUIRED' },
1300
- );
1301
- }
1302
- const packetDocument = workerPacket.readWorkerPacket(
1303
- db,
1304
- packet.id,
1305
- { rootDir },
1306
- );
1307
- if (data.packet_digest !== packet.packet_digest) {
1308
- throw Object.assign(
1309
- new Error('task outcome does not reference the exact assigned Worker Packet'),
1310
- { code: 'WORKER_PACKET_DIGEST_MISMATCH' },
1311
- );
1312
- }
1313
- const output = readStableProjectFile(rootDir, packet.output_path, { encoding: 'utf8' });
1314
- let result;
1315
- try {
1316
- result = JSON.parse(output.text);
1317
- } catch (cause) {
1318
- throw Object.assign(
1319
- new Error(`worker output is not valid JSON: ${packet.output_path}`),
1320
- { code: 'WORKER_OUTPUT_INVALID', cause },
1321
- );
1322
- }
1323
- workerPacket.verifyWorkerResult(packetDocument, result);
1324
- artifactRegistry.recordArtifact(db, {
1325
- id: `artifact-worker-outcome-${packet.id}`,
1326
- owner_type: 'task',
1327
- owner_id: data.id,
1328
- task_id: data.id,
1329
- change_id: ops.readTask(db, data.id)?.change_id || null,
1330
- kind: 'task_outcome',
1331
- path: packet.output_path,
1332
- content_digest: output.digest,
1333
- source_refs: [{
1334
- type: 'external',
1335
- id: `worker-packet:${packet.id}`,
1336
- relation: 'produced_from_packet',
1337
- }],
1338
- consumer_refs: [{
1339
- type: 'external',
1340
- id: 'ultra-test',
1341
- relation: 'consumed_by',
1342
- }],
1343
- provenance: {
1344
- writer: 'worker-result',
1345
- packet_digest: packet.packet_digest,
1346
- },
1347
- metadata: {
1348
- worker_packet_id: packet.id,
1349
- packet_path: packet.packet_path,
1350
- },
1351
- }, { rootDir });
1352
- } else {
1353
- assertExactFields(data, ['id'], `task_outcome.${action}.data`);
1354
- taskContract.normalizeTaskId(data.id);
1355
- }
1356
- return { task: ops.updateTaskStatus(db, data.id, status) };
1357
- }
1358
-
1359
- function applyRecord(db, entry, { rootDir }) {
1360
- const { kind, action, data = {} } = entry;
1361
- if (!RECORD_KINDS.includes(kind)) {
1362
- throw Object.assign(new Error(`unsupported record kind: ${kind}`), {
1363
- code: 'VALIDATION_ERROR',
1364
- });
1365
- }
1366
- validateRecordEntry({ kind, action, data });
1367
- if (kind === 'baseline') return baselineRecord(db, action, data, rootDir);
1368
- if (kind === 'change_contract') return changeContractRecord(db, action, data, rootDir);
1369
- if (kind === 'decision' && action === 'accept') {
1370
- return { decision: decisionRecords.acceptDecision(db, data, { rootDir }) };
1371
- }
1372
- if (kind === 'task_contract') return taskContractRecord(db, action, data, rootDir);
1373
- if (kind === 'task_outcome') return taskOutcomeRecord(db, action, data, rootDir);
1374
- if (kind === 'artifact' && action === 'bind') {
1375
- const legacyWorkflowReference = data.owner_type === 'workflow'
1376
- || [...(data.source_refs || []), ...(data.consumer_refs || [])]
1377
- .some((reference) => reference?.type === 'workflow');
1378
- if (legacyWorkflowReference) {
1379
- throw Object.assign(
1380
- new Error(
1381
- 'workflow-owned artifact authority is retired and remains read-only history',
1382
- ),
1383
- { code: 'LEGACY_AUTHORITY_READ_ONLY' },
1384
- );
1385
- }
1386
- return artifactRegistry.recordArtifact(db, data, { rootDir });
1387
- }
1388
- if (kind === 'event' && action === 'append') {
1389
- return ops.appendEvent(db, data);
1390
- }
1391
- throw Object.assign(
1392
- new Error(`unsupported ${kind} action: ${action}`),
1393
- { code: 'VALIDATION_ERROR' },
1394
- );
1395
- }
1396
-
1397
- async function record(db, input = {}, {
1398
- rootDir = process.cwd(),
1399
- } = {}) {
1400
- validateRecordInput(input);
1401
- const entries = input.entries;
1402
- const runEntry = (entry) => {
1403
- const data = entry.data === undefined ? {} : entry.data;
1404
- const normalizedEntry = { ...entry, data };
1405
- validateRecordEntry(normalizedEntry);
1406
- const operation = `${entry.kind}:${entry.action}`;
1407
- const acceptedRequestDigest = requestDigest(operation, {
1408
- kind: entry.kind,
1409
- action: entry.action,
1410
- data,
1411
- });
1412
- const cached = priorResult(db, {
1413
- idempotencyKey: entry.idempotency_key,
1414
- operation,
1415
- requestDigest: acceptedRequestDigest,
1416
- });
1417
- if (cached.found) {
1418
- return {
1419
- kind: entry.kind,
1420
- action: entry.action,
1421
- accepted: true,
1422
- idempotent: true,
1423
- result: cached.result,
1424
- };
1425
- }
1426
- const rollbackFiles = prepareRecordFileRollback(normalizedEntry, rootDir);
1427
- try {
1428
- const result = applyRecord(db, normalizedEntry, { rootDir });
1429
- rememberResultInTx(db, {
1430
- idempotencyKey: entry.idempotency_key,
1431
- operation,
1432
- requestDigest: acceptedRequestDigest,
1433
- result,
1434
- changeId: data.change_id || data.id || null,
1435
- taskId: entry.kind.startsWith('task_') ? data.id || null : null,
1436
- });
1437
- return {
1438
- kind: entry.kind,
1439
- action: entry.action,
1440
- accepted: true,
1441
- idempotent: false,
1442
- result,
1443
- diagnostics: result?.diagnostics || [],
1444
- };
1445
- } catch (error) {
1446
- rollbackRecordFiles(rollbackFiles, error);
1447
- throw error;
1448
- }
1449
- };
1450
-
1451
- if (!db) {
1452
- const results = entries.map((entry) => {
1453
- try {
1454
- return runEntry(entry);
1455
- } catch (error) {
1456
- if (isHardError(error)) throw error;
1457
- return {
1458
- kind: entry.kind,
1459
- action: entry.action,
1460
- accepted: false,
1461
- mutable: true,
1462
- diagnostics: [diagnostic(error)],
1463
- };
1464
- }
1465
- });
1466
- return {
1467
- accepted: results.every((item) => item.accepted),
1468
- mutable: true,
1469
- results,
1470
- };
1471
- }
1472
-
1473
- const attempted = entries.map((entry) => {
1474
- try {
1475
- return ops.tx(db, () => runEntry(entry));
1476
- } catch (error) {
1477
- const persistenceError = stablePersistenceError(
1478
- error,
1479
- `${entry.kind}:${entry.action} record`,
1480
- );
1481
- if (persistenceError !== error) throw persistenceError;
1482
- if (isHardError(error)) throw error;
1483
- return {
1484
- kind: entry.kind,
1485
- action: entry.action,
1486
- accepted: false,
1487
- mutable: true,
1488
- diagnostics: [diagnostic(error)],
1489
- };
1490
- }
1491
- });
1492
- return {
1493
- accepted: attempted.every((item) => item.accepted),
1494
- mutable: true,
1495
- results: attempted,
1496
- };
1497
- }
1498
-
1499
- function checkpointDiagnostics(input, context) {
1500
- const supplied = Array.isArray(input.payload?.diagnostics)
1501
- ? input.payload.diagnostics
1502
- : [];
1503
- const derived = [
1504
- ...(context.envelope?.diagnostics?.warnings || []).map((item) => ({
1505
- ...item,
1506
- severity: 'warning',
1507
- })),
1508
- ...(context.envelope?.diagnostics?.needs_attention || []).map((item) => ({
1509
- ...item,
1510
- severity: 'warning',
1511
- })),
1512
- ...(context.envelope?.diagnostics?.hard_conflicts || []).map((item) => ({
1513
- ...item,
1514
- severity: 'hard_conflict',
1515
- })),
1516
- ];
1517
- return [...supplied, ...derived]
1518
- .filter((item) => item && typeof item === 'object' && item.code)
1519
- .map((item) => ({
1520
- code: String(item.code),
1521
- severity: item.severity || 'needs_attention',
1522
- ...(item.message ? { message: String(item.message) } : {}),
1523
- ...(item.details === undefined ? {} : { details: item.details }),
1524
- }));
1525
- }
1526
-
1527
- function stageAdvisoryDiagnostics(db, input, scope) {
1528
- const payload = input.payload || {};
1529
- const evidence = Array.isArray(payload.evidence) ? payload.evidence : [];
1530
- const warning = (code, message) => ({ code, severity: 'warning', message });
1531
- if (input.stage === 'research') {
1532
- return evidence.length > 0
1533
- ? []
1534
- : [warning(
1535
- 'RESEARCH_EVIDENCE_MISSING',
1536
- 'No research evidence was declared; the caller remains responsible for semantic sufficiency.',
1537
- )];
1538
- }
1539
- if (input.stage === 'dev') {
1540
- const task = scope.task_id ? ops.readTask(db, scope.task_id) : null;
1541
- const outcome = task
1542
- ? db.prepare(
1543
- `SELECT id FROM artifacts
1544
- WHERE task_id = ? AND kind = 'task_outcome' AND status = 'current'
1545
- ORDER BY updated_at DESC, rowid DESC LIMIT 1`,
1546
- ).get(task.id)
1547
- : null;
1548
- const diagnostics = [];
1549
- if (!task) diagnostics.push(warning('TASK_NOT_FOUND', 'No task scope was found for this Dev checkpoint.'));
1550
- else if (task.status !== 'completed') {
1551
- diagnostics.push(warning('TASK_OUTCOME_INCOMPLETE', `Task ${task.id} is not completed.`));
1552
- }
1553
- if (task && !outcome) {
1554
- diagnostics.push(warning(
1555
- 'WORKER_OUTCOME_MISSING',
1556
- `Task ${task.id} has no current Worker Packet outcome artifact.`,
1557
- ));
1558
- }
1559
- return diagnostics;
1560
- }
1561
- if (input.stage === 'test') {
1562
- const result = String(payload.result || '').toLowerCase();
1563
- const diagnostics = [];
1564
- if (!result) {
1565
- diagnostics.push(warning('TEST_RESULT_MISSING', 'No test result was declared.'));
1566
- } else if (!['pass', 'known_red'].includes(result)) {
1567
- diagnostics.push(warning(
1568
- 'TEST_RESULT_RECORDED',
1569
- `The caller recorded test result ${result}; MCP does not reinterpret it.`,
1570
- ));
1571
- }
1572
- if (evidence.length === 0) {
1573
- diagnostics.push(warning('TEST_EVIDENCE_MISSING', 'No test evidence was declared.'));
1574
- }
1575
- return diagnostics;
1576
- }
1577
- if (input.stage === 'review') {
1578
- const verdict = String(payload.verdict || '').toLowerCase();
1579
- const diagnostics = [];
1580
- if (!verdict) {
1581
- diagnostics.push(warning('REVIEW_VERDICT_MISSING', 'No review verdict was declared.'));
1582
- } else if (!['approve', 'pass'].includes(verdict)) {
1583
- diagnostics.push(warning(
1584
- 'REVIEW_VERDICT_RECORDED',
1585
- `The caller recorded review verdict ${verdict}; MCP does not reinterpret it.`,
1586
- ));
1587
- }
1588
- if (evidence.length === 0) {
1589
- diagnostics.push(warning('REVIEW_EVIDENCE_MISSING', 'No review evidence was declared.'));
1590
- }
1591
- return diagnostics;
1592
- }
1593
- if (input.stage === 'deliver') {
1594
- const diagnostics = [];
1595
- for (const stage of ['test', 'review']) {
1596
- if (!stageCheckpoints.currentCheckpoint(
1597
- db,
1598
- stage,
1599
- { change_id: scope.change_id },
1600
- { includeDraft: false },
1601
- )) {
1602
- diagnostics.push(warning(
1603
- `${stage.toUpperCase()}_CHECKPOINT_MISSING`,
1604
- `No accepted ${stage} checkpoint was found; the caller owns the delivery decision.`,
1605
- ));
1606
- }
1607
- }
1608
- if (String(payload.summary || '').trim().length < 3) {
1609
- diagnostics.push(warning('DELIVERY_SUMMARY_MISSING', 'No durable delivery summary was declared.'));
1610
- }
1611
- if (evidence.length === 0) {
1612
- diagnostics.push(warning('DELIVERY_EVIDENCE_MISSING', 'No delivery evidence was declared.'));
1613
- }
1614
- return diagnostics;
1615
- }
1616
- return [];
1617
- }
1618
-
1619
- function normalizeCheckpointEvidence(db, input, rootDir) {
1620
- const evidence = Array.isArray(input.payload?.evidence) ? input.payload.evidence : [];
1621
- return evidence.map((item, index) => {
1622
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
1623
- const error = new Error(`checkpoint evidence[${index}] must be an object`);
1624
- error.code = 'VALIDATION_ERROR';
1625
- throw error;
1626
- }
1627
- const normalized = { ...item };
1628
- let relative = typeof item.ref === 'string' ? item.ref.trim() : '';
1629
- let artifact = null;
1630
- if (item.artifact_id) {
1631
- try {
1632
- artifact = artifactRegistry.getArtifact(db, { id: String(item.artifact_id) });
1633
- } catch (error) {
1634
- if (error?.code !== 'ARTIFACT_NOT_FOUND') throw error;
1635
- }
1636
- if (!artifact || artifact.status !== 'current') {
1637
- const error = new Error(`checkpoint evidence artifact is not current: ${item.artifact_id}`);
1638
- error.code = 'CHECKPOINT_EVIDENCE_AUTHORITY_MISSING';
1639
- throw error;
1640
- }
1641
- if (relative && artifact.path !== relative) {
1642
- const error = new Error(`checkpoint evidence path does not match artifact ${item.artifact_id}`);
1643
- error.code = 'CHECKPOINT_EVIDENCE_AUTHORITY_MISSING';
1644
- throw error;
1645
- }
1646
- relative = artifact.path;
1647
- normalized.artifact_id = artifact.id;
1648
- if (item.digest && String(item.digest) !== artifact.digest) {
1649
- const error = new Error(
1650
- `checkpoint evidence digest contradicts artifact ${item.artifact_id}`,
1651
- );
1652
- error.code = 'CHECKPOINT_EVIDENCE_DIGEST_MISMATCH';
1653
- error.details = {
1654
- path: relative,
1655
- expected: artifact.digest,
1656
- actual: String(item.digest),
1657
- };
1658
- throw error;
1659
- }
1660
- }
1661
- if (!item.digest && !item.artifact_id) return normalized;
1662
- if (!relative) {
1663
- const error = new Error(`checkpoint evidence[${index}] requires ref when digest is supplied`);
1664
- error.code = 'VALIDATION_ERROR';
1665
- throw error;
1666
- }
1667
- const read = readStableProjectFile(rootDir, relative);
1668
- const expected = item.artifact_id
1669
- ? artifact.digest
1670
- : String(item.digest);
1671
- if (read.digest !== expected) {
1672
- const error = new Error(`checkpoint evidence digest mismatch: ${relative}`);
1673
- error.code = 'CHECKPOINT_EVIDENCE_DIGEST_MISMATCH';
1674
- error.details = { path: relative, expected, actual: read.digest };
1675
- throw error;
1676
- }
1677
- normalized.ref = relative;
1678
- normalized.digest = read.digest;
1679
- return normalized;
1680
- });
1681
- }
1682
-
1683
- function checkpointMutation(db, input = {}, {
1684
- rootDir = process.cwd(),
1685
- runtime = 'unknown',
1686
- markPlanRecoveryRequired = null,
1687
- } = {}) {
1688
- const scope = selectedScope(db, input);
1689
- const checkpointScope = scope.task_id
1690
- ? { task_id: scope.task_id }
1691
- : scope.change_id
1692
- ? { change_id: scope.change_id }
1693
- : scope.baseline_id
1694
- ? { baseline_id: scope.baseline_id }
1695
- : { project_id: 'project' };
1696
- const retry = stageCheckpoints.findIdempotentDraft(db, {
1697
- stage: input.stage,
1698
- scope: checkpointScope,
1699
- payload: input.payload || {},
1700
- idempotency_key: `${input.idempotency_key}:draft`,
1701
- });
1702
- if (retry) {
1703
- const hasHardConflict = retry.diagnostics.some(
1704
- (item) => item.severity === 'hard_conflict',
1705
- );
1706
- const accepted = retry.status === 'draft' && !hasHardConflict
1707
- ? stageCheckpoints.acceptDraft(db, {
1708
- id: retry.id,
1709
- idempotency_key: `${input.idempotency_key}:accept`,
1710
- })
1711
- : retry;
1712
- let result = retry.payload?.plan || null;
1713
- if (input.stage === 'plan' && accepted.status === 'accepted') {
1714
- result = {
1715
- ...result,
1716
- team_checkpoint: taskLedger.publishTaskLedger(db, {
1717
- rootDir,
1718
- reason: 'plan_checkpoint_retry',
1719
- }),
1720
- };
1721
- }
1722
- return {
1723
- accepted: accepted.status === 'accepted',
1724
- mutable: accepted.status !== 'accepted',
1725
- idempotent: true,
1726
- diagnostics: accepted.diagnostics,
1727
- blockers: accepted.diagnostics
1728
- .filter((item) => item.severity === 'hard_conflict')
1729
- .map((item) => item.code),
1730
- checkpoint: accepted,
1731
- context: accepted.context_envelope_id
1732
- ? contextEnvelope.readEnvelope(db, accepted.context_envelope_id, { rootDir })
1733
- : null,
1734
- result,
1735
- };
1736
- }
1737
- const context = contextEnvelope.persistEnvelope(db, {
1738
- stage: input.stage,
1739
- scope,
1740
- }, { rootDir, runtime });
1741
- const view = contextEnvelope.buildEnvelope(db, {
1742
- stage: input.stage,
1743
- scope,
1744
- detail: 'summary',
1745
- }, { rootDir, runtime });
1746
- const evidence = normalizeCheckpointEvidence(db, input, rootDir);
1747
- const diagnostics = [
1748
- ...checkpointDiagnostics(input, view),
1749
- ...stageAdvisoryDiagnostics(db, input, scope),
1750
- ...evidence
1751
- .filter((item) => !item.digest && !item.artifact_id)
1752
- .map((item, index) => ({
1753
- code: 'CHECKPOINT_EVIDENCE_UNBOUND',
1754
- severity: 'warning',
1755
- message: `Evidence declaration ${item.ref || item.kind || index} has no current digest or managed artifact binding.`,
1756
- })),
1757
- ];
1758
- let result = null;
1759
-
1760
- if (input.stage === 'plan'
1761
- && !diagnostics.some((item) => item.severity === 'hard_conflict')) {
1762
- try {
1763
- result = planCheckpoint.publishPlan(db, {
1764
- change_id: scope.change_id,
1765
- context,
1766
- }, { rootDir, markRecoveryRequired: markPlanRecoveryRequired });
1767
- } catch (error) {
1768
- if (isHardError(error)) throw error;
1769
- diagnostics.push(diagnostic(error, 'hard_conflict'));
1770
- }
1771
- }
1772
-
1773
- const draft = stageCheckpoints.saveDraft(db, {
1774
- stage: input.stage,
1775
- scope: checkpointScope,
1776
- payload: input.stage === 'plan' && result
1777
- ? { ...(input.payload || {}), plan: result }
1778
- : (input.payload || {}),
1779
- evidence,
1780
- diagnostics,
1781
- context_envelope_id: context.id,
1782
- idempotency_key: `${input.idempotency_key}:draft`,
1783
- });
1784
- if (diagnostics.some((item) => item.severity === 'hard_conflict')) {
1785
- return {
1786
- accepted: false,
1787
- mutable: true,
1788
- diagnostics,
1789
- blockers: diagnostics.map((item) => item.code),
1790
- checkpoint: draft,
1791
- context,
1792
- result,
1793
- };
1794
- }
1795
- const accepted = stageCheckpoints.acceptDraft(db, {
1796
- id: draft.id,
1797
- idempotency_key: `${input.idempotency_key}:accept`,
1798
- });
1799
- if (input.stage === 'plan') {
1800
- result = {
1801
- ...result,
1802
- team_checkpoint: taskLedger.publishTaskLedger(db, {
1803
- rootDir,
1804
- reason: 'plan_checkpoint_accepted',
1805
- }),
1806
- };
1807
- }
1808
- return {
1809
- accepted: true,
1810
- mutable: false,
1811
- diagnostics,
1812
- blockers: [],
1813
- checkpoint: accepted,
1814
- context,
1815
- result,
1816
- };
1817
- }
1818
-
1819
- function checkpoint(db, input = {}, options = {}) {
1820
- validateCheckpointInput(input);
1821
- try {
1822
- return checkpointMutation(db, input, options);
1823
- } catch (error) {
1824
- throw stablePersistenceError(error, 'checkpoint publication');
1825
- }
1826
- }
1827
-
1828
- function migrationRequired(error) {
1829
- return {
1830
- status: 'migration_required',
1831
- migration: {
1832
- required: true,
1833
- code: errorCode(error),
1834
- action: 'ultra.sync migrate',
1835
- },
1836
- };
1837
- }
1838
-
1839
- function corruptLedgerInspection(error, rootDir) {
1840
- return {
1841
- status: 'corrupt',
1842
- path: taskLedger.ledgerPath(rootDir),
1843
- diagnostics: [diagnostic(error, 'hard_conflict')],
1844
- recovery: {
1845
- actions: [
1846
- {
1847
- action: 'doctor',
1848
- command: 'ultra.doctor',
1849
- description: 'Inspect the managed authority and its deterministic repairs.',
1850
- },
1851
- {
1852
- action: 'restore',
1853
- path: taskLedger.LEDGER_RELATIVE_PATH,
1854
- description: 'Restore the last trusted ledger bytes from version control or backup.',
1855
- },
1856
- ],
1857
- },
1858
- };
1859
- }
1860
-
1861
- function sync(db, input = {}, { rootDir = process.cwd() } = {}) {
1862
- validateSyncInput(input);
1863
- gitBootstrap.ensureExistingProjectStorageBoundary(rootDir);
1864
- if (input.action === 'inspect') {
1865
- try {
1866
- return taskLedger.inspectTaskLedger(db, { rootDir });
1867
- } catch (error) {
1868
- if (errorCode(error) === 'TASK_LEDGER_SCHEMA_MIGRATION_REQUIRED') {
1869
- return migrationRequired(error);
1870
- }
1871
- if (['TASK_LEDGER_INVALID', 'TASK_LEDGER_DIGEST_MISMATCH'].includes(errorCode(error))) {
1872
- try {
1873
- const legacy = taskLedger.syncTaskLedger(db, { rootDir });
1874
- if (legacy.status === 'legacy') return migrationRequired(error);
1875
- } catch {
1876
- // Keep the original validated read error as the public diagnostic.
1877
- }
1878
- return corruptLedgerInspection(error, rootDir);
1879
- }
1880
- throw error;
1881
- }
1882
- }
1883
- if (input.action === 'import') return taskLedger.importTaskLedger(db, { rootDir });
1884
- if (input.action === 'migrate') {
1885
- const published = taskLedger.publishTaskLedger(db, {
1886
- rootDir,
1887
- reason: 'legacy_authority_migrated',
1888
- });
1889
- return {
1890
- migrated: Boolean(published.migrated_legacy_projection),
1891
- ...published,
1892
- };
1893
- }
1894
- if (input.action === 'publish') {
1895
- const operation = 'sync:publish';
1896
- const reason = input.reason || 'manual_checkpoint';
1897
- const acceptedRequestDigest = requestDigest(operation, {
1898
- action: input.action,
1899
- reason,
1900
- });
1901
- let publication = null;
1902
- try {
1903
- return ops.tx(db, () => {
1904
- const cached = priorResult(db, {
1905
- idempotencyKey: input.idempotency_key,
1906
- operation,
1907
- requestDigest: acceptedRequestDigest,
1908
- });
1909
- if (cached.found) return { ...cached.result, idempotent: true };
1910
- publication = taskLedger.publishTaskLedger(db, { rootDir, reason });
1911
- rememberResultInTx(db, {
1912
- idempotencyKey: input.idempotency_key,
1913
- operation,
1914
- requestDigest: acceptedRequestDigest,
1915
- result: publication,
1916
- });
1917
- return publication;
1918
- });
1919
- } catch (error) {
1920
- if (publication) {
1921
- try {
1922
- taskLedger.rollbackTaskLedgerPublication(publication);
1923
- } catch (rollbackError) {
1924
- throw Object.assign(
1925
- new Error(
1926
- `sync publication failed and the team ledger could not be restored: `
1927
- + `${error.message}; ${rollbackError.message}`,
1928
- ),
1929
- {
1930
- code: 'STATE_CORRUPT',
1931
- cause: error,
1932
- details: {
1933
- original_error: errorCode(error),
1934
- rollback_error: errorCode(rollbackError),
1935
- path: taskLedger.LEDGER_RELATIVE_PATH,
1936
- },
1937
- },
1938
- );
1939
- }
1940
- }
1941
- throw stablePersistenceError(error, 'sync publication');
1942
- }
1943
- }
1944
- throw validationError(`unsupported sync action: ${input.action}`);
1945
- }
1946
-
1947
- function sessionDiagnostic(error) {
1948
- if (isHardError(error)) throw error;
1949
- return {
1950
- accepted: false,
1951
- mutable: true,
1952
- can_acquire: false,
1953
- diagnostics: [diagnostic(error)],
1954
- };
1955
- }
1956
-
1957
- function attachRecovery(error, recovery) {
1958
- if (!recovery) return error;
1959
- error.details = {
1960
- ...(error.details || {}),
1961
- recovery,
1962
- };
1963
- return error;
1964
- }
1965
-
1966
- function idempotentSessionMutation(db, input, mutate, {
1967
- rollbackEffect = null,
1968
- prepareEffect = null,
1969
- } = {}) {
1970
- const operation = `session:${input.action}`;
1971
- const request = Object.fromEntries(
1972
- ['action', 'scope', 'payload']
1973
- .filter((key) => Object.hasOwn(input, key))
1974
- .map((key) => [key, input[key]]),
1975
- );
1976
- const acceptedRequestDigest = requestDigest(operation, request);
1977
- let mutationCompleted = false;
1978
- let effectPrepared = false;
1979
- let result = null;
1980
- try {
1981
- const transactionalMutation = () => {
1982
- const cached = priorResult(db, {
1983
- idempotencyKey: input.idempotency_key,
1984
- operation,
1985
- requestDigest: acceptedRequestDigest,
1986
- });
1987
- if (cached.found) return { ...cached.result, idempotent: true };
1988
- if (prepareEffect) {
1989
- prepareEffect();
1990
- effectPrepared = true;
1991
- }
1992
- result = mutate();
1993
- mutationCompleted = true;
1994
- rememberResultInTx(db, {
1995
- idempotencyKey: input.idempotency_key,
1996
- operation,
1997
- requestDigest: acceptedRequestDigest,
1998
- result,
1999
- taskId: input.scope?.task_id || null,
2000
- });
2001
- return result;
2002
- };
2003
- return ops.tx(db, transactionalMutation);
2004
- } catch (error) {
2005
- let recovery = null;
2006
- if ((effectPrepared || mutationCompleted) && rollbackEffect) {
2007
- recovery = rollbackEffect(result, error);
2008
- }
2009
- const recovered = attachRecovery(
2010
- stablePersistenceError(error, `${operation} receipt`),
2011
- recovery,
2012
- );
2013
- if (recovered !== error || isHardError(recovered)) throw recovered;
2014
- return sessionDiagnostic(recovered);
2015
- }
2016
- }
2017
-
2018
- function removeRolledBackManagedFile(db, {
2019
- rootDir,
2020
- table,
2021
- idField,
2022
- id,
2023
- relative,
2024
- expectedPrefix,
2025
- expectedBasename,
2026
- }) {
2027
- if (!relative || db.prepare(
2028
- `SELECT 1 FROM ${table} WHERE ${idField} = ?`,
2029
- ).get(id)) return;
2030
- if (path.isAbsolute(relative)) {
2031
- throw validationError(`managed rollback path must be project-relative: ${relative}`);
2032
- }
2033
- const absolute = path.resolve(rootDir, relative);
2034
- const prefix = path.resolve(rootDir, expectedPrefix);
2035
- if (!absolute.startsWith(`${prefix}${path.sep}`)
2036
- || path.basename(absolute) !== expectedBasename) {
2037
- throw Object.assign(
2038
- new Error(`managed rollback path escaped its authority: ${relative}`),
2039
- { code: 'PATH_AUTHORITY_VIOLATION' },
2040
- );
2041
- }
2042
- restoreRecordFile(absolute, { existed: false, bytes: null, mode: null });
2043
- }
2044
-
2045
- function snapshotAcquisitionEffect(db, taskId) {
2046
- return {
2047
- context_ids: new Set(db.prepare(
2048
- `SELECT * FROM context_envelopes
2049
- WHERE scope_type = 'task' AND scope_id = ?`,
2050
- ).all(taskId).map((row) => row.id)),
2051
- prior_session: db.prepare(
2052
- `SELECT * FROM sessions
2053
- WHERE task_id = ? AND status = 'running'
2054
- ORDER BY rowid DESC LIMIT 1`,
2055
- ).get(taskId) || null,
2056
- };
2057
- }
2058
-
2059
- function rollbackAcquisitionEffect(db, rootDir, acquisition, error) {
2060
- const issues = [];
2061
- let priorSessionReconciled = false;
2062
- const attempt = (action, run) => {
2063
- try { run(); }
2064
- catch (cause) {
2065
- issues.push({
2066
- action,
2067
- code: errorCode(cause),
2068
- message: cause.message,
2069
- });
2070
- }
2071
- };
2072
- if (acquisition.handle?.sid && acquisition.handle?.worktree_path) {
2073
- attempt('remove_worktree', () => {
2074
- sessionRunner._internal.reconcileRemovedWorktree(
2075
- rootDir,
2076
- acquisition.handle.worktree_path,
2077
- { sid: acquisition.handle.sid },
2078
- );
2079
- });
2080
- }
2081
- if (acquisition.handle?.sid && acquisition.handle?.artifact_dir) {
2082
- attempt('remove_artifact_dir', () => {
2083
- const expected = path.resolve(
2084
- rootDir,
2085
- '.ultra',
2086
- '.runtime',
2087
- 'sessions',
2088
- acquisition.handle.sid,
2089
- );
2090
- const actual = path.resolve(acquisition.handle.artifact_dir);
2091
- if (actual !== expected) {
2092
- throw Object.assign(
2093
- new Error(`session artifact rollback escaped its authority: ${actual}`),
2094
- { code: 'PATH_AUTHORITY_VIOLATION' },
2095
- );
2096
- }
2097
- const stat = fs.lstatSync(actual, { throwIfNoEntry: false });
2098
- if (stat?.isSymbolicLink() || stat && !stat.isDirectory()) {
2099
- throw Object.assign(
2100
- new Error(`session artifact rollback target is unsafe: ${actual}`),
2101
- { code: 'PATH_AUTHORITY_VIOLATION' },
2102
- );
2103
- }
2104
- if (stat) fs.rmSync(actual, { recursive: true });
2105
- });
2106
- }
2107
- if (acquisition.packet?.created) {
2108
- const packet = acquisition.packet;
2109
- attempt('remove_worker_packet', () => removeRolledBackManagedFile(db, {
2110
- rootDir,
2111
- table: 'worker_packets',
2112
- idField: 'id',
2113
- id: packet.id,
2114
- relative: packet.packet_path,
2115
- expectedPrefix: path.join('.ultra', '.runtime', 'worker-packets'),
2116
- expectedBasename: `${packet.id}.json`,
2117
- }));
2118
- }
2119
- const context = acquisition.packet?.context_envelope;
2120
- if (context?.id && !acquisition.snapshot?.context_ids.has(context.id)) {
2121
- attempt('remove_context_envelope', () => removeRolledBackManagedFile(db, {
2122
- rootDir,
2123
- table: 'context_envelopes',
2124
- idField: 'id',
2125
- id: context.id,
2126
- relative: context.path,
2127
- expectedPrefix: '.ultra',
2128
- expectedBasename: `${context.id}.json`,
2129
- }));
2130
- }
2131
- const priorSession = acquisition.snapshot?.prior_session;
2132
- if (priorSession?.pid
2133
- && !sessionRunner._internal.processIsExecuting(priorSession.pid)) {
2134
- attempt('reconcile_terminated_prior_session', () => {
2135
- const current = ops.readSession(db, priorSession.sid);
2136
- if (current?.status === 'running' && current.pid === priorSession.pid) {
2137
- ops.updateSession(db, priorSession.sid, { status: 'crashed', pid: null });
2138
- priorSessionReconciled = true;
2139
- }
2140
- });
2141
- }
2142
- if (issues.length > 0) {
2143
- throw Object.assign(
2144
- new Error(
2145
- `session acquisition receipt failed and effect rollback requires recovery: `
2146
- + `${error.message}`,
2147
- ),
2148
- {
2149
- code: 'SESSION_RECEIPT_RECOVERY_REQUIRED',
2150
- cause: error,
2151
- details: {
2152
- issues,
2153
- retry: 'Run ultra.doctor with repair=true, then retry the exact ultra.session request.',
2154
- },
2155
- },
2156
- );
2157
- }
2158
- return {
2159
- status: 'rolled_back',
2160
- prior_session_reconciled: priorSessionReconciled,
2161
- retry: 'Retry the exact ultra.session request with the same idempotency_key.',
2162
- };
2163
- }
2164
-
2165
- function recoverReleaseReceiptFailure(db, rootDir, session, payload, error) {
2166
- if (!session) {
2167
- throw Object.assign(
2168
- new Error('session release receipt failed after its session authority disappeared'),
2169
- {
2170
- code: 'SESSION_RECEIPT_RECOVERY_REQUIRED',
2171
- cause: error,
2172
- details: { retry: 'Run ultra.doctor with repair=true before retrying release.' },
2173
- },
2174
- );
2175
- }
2176
- const requestedStatus = payload.status || 'completed';
2177
- const removedWorktree = payload.remove_worktree === true;
2178
- if (!removedWorktree && !session.pid) {
2179
- return {
2180
- status: 'rolled_back',
2181
- retry: 'Retry the exact ultra.session release request with the same idempotency_key.',
2182
- };
2183
- }
2184
- const closeJournal = sessionRunner._internal.closeJournal;
2185
- try {
2186
- if (removedWorktree) {
2187
- closeJournal.prepare(rootDir, {
2188
- sid: session.sid,
2189
- task_id: session.task_id,
2190
- requested_status: requestedStatus,
2191
- worktree_path: session.worktree_path,
2192
- });
2193
- sessionRunner._internal.reconcileRemovedWorktree(
2194
- rootDir,
2195
- session.worktree_path,
2196
- { sid: session.sid },
2197
- );
2198
- closeJournal.update(rootDir, session.sid, {
2199
- phase: 'worktree_removed',
2200
- error: null,
2201
- });
2202
- }
2203
- ops.updateSession(db, session.sid, { status: requestedStatus, pid: null });
2204
- if (removedWorktree) closeJournal.discard(rootDir, session.sid);
2205
- } catch (cause) {
2206
- throw Object.assign(
2207
- new Error(
2208
- `session release receipt failed and close recovery requires attention: `
2209
- + `${error.message}; ${cause.message}`,
2210
- ),
2211
- {
2212
- code: 'SESSION_RECEIPT_RECOVERY_REQUIRED',
2213
- cause: error,
2214
- details: {
2215
- recovery_error: errorCode(cause),
2216
- sid: session.sid,
2217
- retry: 'Run ultra.doctor with repair=true, then retry the exact release request.',
2218
- },
2219
- },
2220
- );
2221
- }
2222
- return {
2223
- status: 'terminal_recovered',
2224
- sid: session.sid,
2225
- session_status: requestedStatus,
2226
- retry: 'Retry the exact ultra.session release request to persist its receipt.',
2227
- };
2228
- }
2229
-
2230
- async function session(db, input = {}, {
2231
- rootDir = process.cwd(),
2232
- runtime = 'unknown',
2233
- sessionId = null,
2234
- } = {}) {
2235
- validateSessionInput(input);
2236
- const scope = input.scope || {};
2237
- const payload = input.payload || {};
2238
- if (input.action === 'get') {
2239
- return { session: ops.readSession(db, scope.sid) };
2240
- }
2241
- if (input.action === 'list') {
2242
- const sessions = ops.listActiveSessions(db, { task_id: scope.task_id });
2243
- return { sessions, count: sessions.length };
2244
- }
2245
- if (input.action === 'heartbeat') {
2246
- return idempotentSessionMutation(
2247
- db,
2248
- input,
2249
- () => ops.heartbeatSession(db, scope.sid),
2250
- );
2251
- }
2252
- if (input.action === 'release') {
2253
- const releasingSession = ops.readSession(db, scope.sid);
2254
- return idempotentSessionMutation(db, input, () => {
2255
- if (sessionId) {
2256
- return sessionDiagnostic(Object.assign(
2257
- new Error('a worker session is settled by its parent host'),
2258
- { code: 'WORKER_SESSION_PARENT_OWNED' },
2259
- ));
2260
- }
2261
- return sessionRunner.closeSession(
2262
- { db, repoRoot: rootDir, sid: scope.sid },
2263
- {
2264
- status: payload.status || 'completed',
2265
- remove_worktree: payload.remove_worktree === true,
2266
- },
2267
- );
2268
- }, {
2269
- rollbackEffect: (result, error) => (
2270
- result?.accepted === false
2271
- ? null
2272
- : recoverReleaseReceiptFailure(db, rootDir, releasingSession, payload, error)
2273
- ),
2274
- });
2275
- }
2276
- const taskId = scope.task_id;
2277
- if (input.action === 'admission') {
2278
- try {
2279
- const task = ops.readTask(db, taskId);
2280
- if (!task) throw Object.assign(new Error(`task ${taskId} not found`), { code: 'TASK_NOT_FOUND' });
2281
- const attention = ops.taskContractBlockers(task).map((code) => ({
2282
- code,
2283
- severity: 'warning',
2284
- }));
2285
- const lease = ops.admissionCheck(db, taskId);
2286
- return {
2287
- accepted: lease.can_spawn,
2288
- mutable: true,
2289
- can_acquire: lease.can_spawn,
2290
- diagnostics: [
2291
- ...attention,
2292
- ...(lease.can_spawn ? [] : [{
2293
- code: 'ACTIVE_SESSION_LEASE_CONFLICT',
2294
- severity: 'hard_conflict',
2295
- details: lease.conflict,
2296
- }]),
2297
- ],
2298
- lease,
2299
- };
2300
- } catch (error) {
2301
- return sessionDiagnostic(error);
2302
- }
2303
- }
2304
- const acquisition = {
2305
- task_id: taskId,
2306
- packet: null,
2307
- handle: null,
2308
- snapshot: null,
2309
- authority: null,
2310
- };
2311
- try {
2312
- acquisition.authority = runtimePaths.ensureRuntimeState(rootDir, {
2313
- admitStorageBoundary: () => gitBootstrap.ensureExistingProjectStorageBoundary(rootDir),
2314
- });
2315
- } catch (error) {
2316
- return sessionDiagnostic(error);
2317
- }
2318
- return idempotentSessionMutation(db, input, () => {
2319
- try {
2320
- acquisition.packet = workerPacket.createWorkerPacket(db, {
2321
- role: payload.role || 'implement',
2322
- task_id: taskId,
2323
- runtime: payload.runtime || runtime,
2324
- output_path: payload.output_path
2325
- || `.ultra/changes/active/${ops.readTask(db, taskId)?.change_id}/delivery/${taskId}-outcome.json`,
2326
- output_schema: payload.output_schema,
2327
- evidence_refs: payload.evidence_refs,
2328
- diff_range: payload.diff_range,
2329
- changed_files: payload.changed_files,
2330
- }, { rootDir });
2331
- acquisition.handle = sessionRunner.spawnSession({
2332
- db,
2333
- repoRoot: rootDir,
2334
- task_id: taskId,
2335
- runtime: payload.runtime || runtime,
2336
- authority: acquisition.authority,
2337
- takeover: payload.takeover === true,
2338
- worktree_base: payload.worktree_base,
2339
- kernel_mode: true,
2340
- mark_task_started: true,
2341
- packet_digest: acquisition.packet.packet_digest,
2342
- });
2343
- workerPacket.markWorkerPacketAssigned(db, acquisition.packet.id);
2344
- return {
2345
- accepted: true,
2346
- sid: acquisition.handle.sid,
2347
- worktree_path: acquisition.handle.worktree_path,
2348
- artifact_dir: acquisition.handle.artifact_dir,
2349
- lease_expires_at: acquisition.handle.lease_expires_at,
2350
- packet: acquisition.packet,
2351
- };
2352
- } catch (error) {
2353
- if (acquisition.packet?.id) {
2354
- throw error;
2355
- }
2356
- return sessionDiagnostic(error);
2357
- }
2358
- }, {
2359
- prepareEffect: () => {
2360
- acquisition.snapshot = snapshotAcquisitionEffect(db, taskId);
2361
- },
2362
- rollbackEffect: (_result, error) => rollbackAcquisitionEffect(
2363
- db,
2364
- rootDir,
2365
- acquisition,
2366
- error,
2367
- ),
2368
- });
2369
- }
2370
-
2371
- function snapshotManagedDirectory(directory) {
2372
- const rootStat = fs.lstatSync(directory, { throwIfNoEntry: false });
2373
- if (!rootStat || rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
2374
- throw Object.assign(
2375
- new Error(`archive source must be a managed directory: ${directory}`),
2376
- { code: 'ARCHIVE_PATH_UNSAFE' },
2377
- );
2378
- }
2379
- const entries = [];
2380
- const walk = (parent, relativeParent = '') => {
2381
- for (const entry of fs.readdirSync(parent, { withFileTypes: true })) {
2382
- const relative = path.join(relativeParent, entry.name);
2383
- const absolute = path.join(parent, entry.name);
2384
- const stat = fs.lstatSync(absolute);
2385
- if (stat.isSymbolicLink()) {
2386
- throw Object.assign(
2387
- new Error(`archive source contains a symbolic link: ${relative}`),
2388
- { code: 'ARCHIVE_PATH_UNSAFE' },
2389
- );
2390
- }
2391
- if (stat.isDirectory()) {
2392
- entries.push({ type: 'directory', relative, mode: stat.mode });
2393
- walk(absolute, relative);
2394
- } else if (stat.isFile()) {
2395
- entries.push({
2396
- type: 'file',
2397
- relative,
2398
- mode: stat.mode,
2399
- bytes: fs.readFileSync(absolute),
2400
- });
2401
- } else {
2402
- throw Object.assign(
2403
- new Error(`archive source contains an unsupported entry: ${relative}`),
2404
- { code: 'ARCHIVE_PATH_UNSAFE' },
2405
- );
2406
- }
2407
- }
2408
- };
2409
- walk(directory);
2410
- return {
2411
- source: directory,
2412
- mode: rootStat.mode,
2413
- entries,
2414
- destination: null,
2415
- };
2416
- }
2417
-
2418
- function prepareArchiveDirectoryRollback(change, rootDir) {
2419
- if (!change || change.status === 'archived') return null;
2420
- const expected = path.resolve(
2421
- rootDir,
2422
- '.ultra',
2423
- 'changes',
2424
- 'active',
2425
- change.id,
2426
- );
2427
- const actual = path.resolve(rootDir, change.artifact_root);
2428
- if (actual !== expected) {
2429
- throw Object.assign(
2430
- new Error(`active Change artifact root escaped its authority: ${change.artifact_root}`),
2431
- { code: 'ARCHIVE_PATH_UNSAFE' },
2432
- );
2433
- }
2434
- return snapshotManagedDirectory(actual);
2435
- }
2436
-
2437
- function restoreArchiveDirectory(rootDir, changeId, rollback) {
2438
- if (!rollback?.destination) return;
2439
- const archiveRoot = path.resolve(rootDir, '.ultra', 'changes', 'archive');
2440
- const destination = path.resolve(rollback.destination);
2441
- if (path.dirname(destination) !== archiveRoot
2442
- || !path.basename(destination).endsWith(`-${changeId}`)) {
2443
- throw Object.assign(
2444
- new Error(`archive rollback destination escaped its authority: ${destination}`),
2445
- { code: 'ARCHIVE_PATH_UNSAFE' },
2446
- );
2447
- }
2448
- const destinationStat = fs.lstatSync(destination, { throwIfNoEntry: false });
2449
- if (!destinationStat || destinationStat.isSymbolicLink() || !destinationStat.isDirectory()) {
2450
- throw Object.assign(
2451
- new Error(`archive rollback destination is unavailable or unsafe: ${destination}`),
2452
- { code: 'ARCHIVE_RECOVERY_REQUIRED' },
2453
- );
2454
- }
2455
- if (fs.lstatSync(rollback.source, { throwIfNoEntry: false })) {
2456
- throw Object.assign(
2457
- new Error(`archive rollback source already exists: ${rollback.source}`),
2458
- { code: 'ARCHIVE_RECOVERY_REQUIRED' },
2459
- );
2460
- }
2461
- fs.renameSync(destination, rollback.source);
2462
- for (const entry of fs.readdirSync(rollback.source)) {
2463
- fs.rmSync(path.join(rollback.source, entry), { recursive: true });
2464
- }
2465
- const directories = rollback.entries
2466
- .filter((entry) => entry.type === 'directory')
2467
- .sort((left, right) => left.relative.length - right.relative.length);
2468
- for (const entry of directories) {
2469
- const target = path.join(rollback.source, entry.relative);
2470
- fs.mkdirSync(target, { recursive: true, mode: entry.mode });
2471
- }
2472
- for (const entry of rollback.entries.filter((item) => item.type === 'file')) {
2473
- const target = path.join(rollback.source, entry.relative);
2474
- fs.mkdirSync(path.dirname(target), { recursive: true });
2475
- fs.writeFileSync(target, entry.bytes, { mode: entry.mode });
2476
- }
2477
- fs.chmodSync(rollback.source, rollback.mode);
2478
- }
2479
-
2480
- function rollbackArchivePublication({
2481
- publication,
2482
- directory,
2483
- rootDir,
2484
- changeId,
2485
- }, error) {
2486
- const issues = [];
2487
- try {
2488
- deliveryTransaction.rollbackDeliveryTransaction({
2489
- rootDir,
2490
- changeId,
2491
- });
2492
- } catch (cause) {
2493
- issues.push({
2494
- action: 'restore_baseline_delivery',
2495
- code: errorCode(cause),
2496
- message: cause.message,
2497
- });
2498
- }
2499
- if (publication) {
2500
- try { taskLedger.rollbackTaskLedgerPublication(publication); }
2501
- catch (cause) {
2502
- issues.push({
2503
- action: 'restore_team_ledger',
2504
- code: errorCode(cause),
2505
- message: cause.message,
2506
- });
2507
- }
2508
- }
2509
- if (directory?.destination) {
2510
- try { restoreArchiveDirectory(rootDir, changeId, directory); }
2511
- catch (cause) {
2512
- issues.push({
2513
- action: 'restore_active_change',
2514
- code: errorCode(cause),
2515
- message: cause.message,
2516
- });
2517
- }
2518
- }
2519
- if (issues.length > 0) {
2520
- throw Object.assign(
2521
- new Error(
2522
- `archive publication failed and filesystem recovery requires attention: `
2523
- + `${error.message}`,
2524
- ),
2525
- {
2526
- code: 'ARCHIVE_RECOVERY_REQUIRED',
2527
- cause: error,
2528
- details: {
2529
- change_id: changeId,
2530
- issues,
2531
- recovery: 'Run ultra.doctor with repair=true before retrying archive.',
2532
- },
2533
- },
2534
- );
2535
- }
2536
- }
2537
-
2538
- function finishArchiveDeliveryReceipt(rootDir, changeId, output) {
2539
- try {
2540
- deliveryTransaction.completeDeliveryTransaction({
2541
- rootDir,
2542
- changeId,
2543
- });
2544
- return output;
2545
- } catch (error) {
2546
- return {
2547
- ...output,
2548
- recovery_warning: [
2549
- output.recovery_warning,
2550
- `DELIVERY_JOURNAL_CLEANUP_PENDING:${error.message}`,
2551
- ].filter(Boolean).join(';'),
2552
- };
2553
- }
2554
- }
2555
-
2556
- async function archive(db, input = {}, {
2557
- rootDir = process.cwd(),
2558
- } = {}) {
2559
- validateArchiveInput(input);
2560
- const operation = 'archive';
2561
- const acceptedRequestDigest = requestDigest(operation, {
2562
- change_id: input.change_id,
2563
- payload: input.payload,
2564
- });
2565
- let publication = null;
2566
- const existing = priorResult(db, {
2567
- idempotencyKey: input.idempotency_key,
2568
- operation,
2569
- requestDigest: acceptedRequestDigest,
2570
- });
2571
- if (existing.found) {
2572
- return finishArchiveDeliveryReceipt(
2573
- rootDir,
2574
- input.change_id,
2575
- { ...existing.result, idempotent: true },
2576
- );
2577
- }
2578
- const directoryRollback = prepareArchiveDirectoryRollback(
2579
- changes.readChange(db, input.change_id),
2580
- rootDir,
2581
- );
2582
- try {
2583
- const output = ops.tx(db, () => {
2584
- const cached = priorResult(db, {
2585
- idempotencyKey: input.idempotency_key,
2586
- operation,
2587
- requestDigest: acceptedRequestDigest,
2588
- });
2589
- if (cached.found) return { ...cached.result, idempotent: true };
2590
- const change = changes.readChange(db, input.change_id);
2591
- if (!change) {
2592
- return {
2593
- accepted: false,
2594
- mutable: true,
2595
- blockers: ['CHANGE_NOT_FOUND'],
2596
- diagnostics: [{
2597
- code: 'CHANGE_NOT_FOUND',
2598
- severity: 'needs_attention',
2599
- message: `change ${input.change_id} not found`,
2600
- }],
2601
- };
2602
- }
2603
- const result = changes.archiveChange(
2604
- db,
2605
- { ...input.payload, id: input.change_id },
2606
- { rootDir, deferDeliveryCleanup: true },
2607
- );
2608
- if (directoryRollback) directoryRollback.destination = result.archive_path;
2609
- const output = {
2610
- accepted: true,
2611
- mutable: false,
2612
- blockers: [],
2613
- diagnostics: [],
2614
- result,
2615
- team_checkpoint: (publication = taskLedger.publishTaskLedger(db, {
2616
- rootDir,
2617
- reason: 'change_archived',
2618
- })),
2619
- };
2620
- rememberResultInTx(db, {
2621
- idempotencyKey: input.idempotency_key,
2622
- operation,
2623
- requestDigest: acceptedRequestDigest,
2624
- result: output,
2625
- changeId: input.change_id,
2626
- });
2627
- return output;
2628
- });
2629
- return finishArchiveDeliveryReceipt(rootDir, input.change_id, output);
2630
- } catch (error) {
2631
- rollbackArchivePublication({
2632
- publication,
2633
- directory: directoryRollback,
2634
- rootDir,
2635
- changeId: input.change_id,
2636
- }, error);
2637
- const persistenceError = stablePersistenceError(error, 'archive publication');
2638
- if (persistenceError !== error) throw persistenceError;
2639
- if (isHardError(error)) throw error;
2640
- return {
2641
- accepted: false,
2642
- mutable: true,
2643
- blockers: [errorCode(error)],
2644
- diagnostics: [diagnostic(error)],
2645
- };
2646
- }
2647
- }
2648
-
2649
- async function dispatch(name, input, db, context = {}) {
2650
- let result;
2651
- if (name === 'ultra.context') result = readContext(db, input, context);
2652
- else if (name === 'ultra.record') result = await record(db, input, context);
2653
- else if (name === 'ultra.checkpoint') result = await checkpoint(db, input, context);
2654
- else if (name === 'ultra.sync') result = sync(db, input, context);
2655
- else if (name === 'ultra.session') result = await session(db, input, context);
2656
- else if (name === 'ultra.archive') result = await archive(db, input, context);
2657
- if (name === 'ultra.doctor') {
2658
- validateDoctorInput(input);
2659
- result = doctor.runDoctor(db, {
2660
- rootDir: context.rootDir || process.cwd(),
2661
- repair: input.repair === true,
2662
- project: context.projector,
2663
- });
2664
- }
2665
- if (result === undefined) {
2666
- const error = new Error(`unhandled public tool ${name}`);
2667
- error.code = 'UNKNOWN_TOOL';
2668
- throw error;
2669
- }
2670
- return rememberRejectedAttempt(db, name, input, result);
2671
- }
2672
-
2673
- module.exports = {
2674
- PUBLIC_TOOLS,
2675
- RECORD_KINDS,
2676
- dispatch,
2677
- readContext,
2678
- record,
2679
- checkpoint,
2680
- sync,
2681
- session,
2682
- archive,
2683
- isHardError,
2684
- };