@agilno-tech/rivet 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +59 -0
  4. package/bin/cli.js +42 -0
  5. package/dist/agents/backend-django-agent/SKILL.md +35 -0
  6. package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
  7. package/dist/agents/boss-agent/SKILL.md +28 -0
  8. package/dist/agents/bounded-worker/SKILL.md +28 -0
  9. package/dist/agents/data-performance-agent/SKILL.md +32 -0
  10. package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
  11. package/dist/agents/devops-agent/SKILL.md +34 -0
  12. package/dist/agents/engineering-manager/SKILL.md +28 -0
  13. package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
  14. package/dist/agents/frontend-web-agent/SKILL.md +31 -0
  15. package/dist/agents/incident-response-agent/SKILL.md +42 -0
  16. package/dist/agents/jest-agent/SKILL.md +77 -0
  17. package/dist/agents/mobile-agent/SKILL.md +32 -0
  18. package/dist/agents/playwright-agent/SKILL.md +54 -0
  19. package/dist/agents/product-design-manager/SKILL.md +27 -0
  20. package/dist/agents/qa-agent/SKILL.md +29 -0
  21. package/dist/agents/quality-manager/SKILL.md +28 -0
  22. package/dist/agents/vitest-agent/SKILL.md +77 -0
  23. package/dist/governance/pre-push-rules.md +75 -0
  24. package/dist/governance/prompt-hygiene.md +22 -0
  25. package/dist/governance/review-checklist.md +24 -0
  26. package/dist/governance/safety-and-data.md +27 -0
  27. package/dist/governance/usage-rules.md +21 -0
  28. package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
  29. package/dist/mandatory/agentic-goal/SKILL.md +42 -0
  30. package/dist/mandatory/agentic-status/SKILL.md +74 -0
  31. package/dist/mandatory/apply-design/SKILL.md +228 -0
  32. package/dist/mandatory/check-ac/SKILL.md +124 -0
  33. package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
  34. package/dist/mandatory/design/SKILL.md +312 -0
  35. package/dist/mandatory/feature-workflow/SKILL.md +87 -0
  36. package/dist/mandatory/hotfix/SKILL.md +195 -0
  37. package/dist/mandatory/pre-push/SKILL.md +272 -0
  38. package/dist/mandatory/project-context/SKILL.md +180 -0
  39. package/dist/mandatory/release-docs/SKILL.md +191 -0
  40. package/dist/mandatory/review-pr/SKILL.md +323 -0
  41. package/dist/mandatory/security-review/SKILL.md +157 -0
  42. package/dist/skills/api-contract/SKILL.md +77 -0
  43. package/dist/skills/backend-django/SKILL.md +27 -0
  44. package/dist/skills/backend-nestjs/SKILL.md +65 -0
  45. package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
  46. package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
  47. package/dist/skills/database-migration/SKILL.md +97 -0
  48. package/dist/skills/debugging/SKILL.md +32 -0
  49. package/dist/skills/devops-infra/SKILL.md +26 -0
  50. package/dist/skills/documentation/SKILL.md +24 -0
  51. package/dist/skills/frontend-nextjs/SKILL.md +27 -0
  52. package/dist/skills/incident-postmortem/SKILL.md +38 -0
  53. package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
  54. package/dist/skills/mobile-react-native/SKILL.md +37 -0
  55. package/dist/skills/postgres-analytics/SKILL.md +29 -0
  56. package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
  57. package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
  58. package/dist/skills/refactoring/SKILL.md +32 -0
  59. package/dist/skills/sprint-planning/SKILL.md +32 -0
  60. package/dist/skills/testing-quality/SKILL.md +62 -0
  61. package/dist/v2/protocols/agent-orchestration.md +49 -0
  62. package/dist/v2/protocols/delivery.md +45 -0
  63. package/dist/v2/protocols/design-authority.md +29 -0
  64. package/dist/v2/protocols/goal-graph.md +44 -0
  65. package/dist/v2/protocols/pattern-first-development.md +29 -0
  66. package/dist/v2/protocols/qa-evidence.md +29 -0
  67. package/dist/v2/protocols/security.md +29 -0
  68. package/dist/v2/schemas/event.schema.json +86 -0
  69. package/dist/v2/schemas/evidence.schema.json +94 -0
  70. package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
  71. package/dist/v2/schemas/feature-plan.schema.json +95 -0
  72. package/dist/v2/schemas/goal-graph.schema.json +61 -0
  73. package/dist/v2/schemas/integration.schema.json +119 -0
  74. package/dist/v2/schemas/orchestration.schema.json +69 -0
  75. package/dist/v2/schemas/project.schema.json +209 -0
  76. package/dist/v2/schemas/providers.schema.json +142 -0
  77. package/dist/v2/schemas/quality.schema.json +50 -0
  78. package/dist/v2/schemas/work-action.schema.json +44 -0
  79. package/dist/v2/schemas/work-request.schema.json +253 -0
  80. package/dist/v2/templates/evidence/qa-bundle.json +194 -0
  81. package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
  82. package/dist/v2/templates/harness/SKILL.md +90 -0
  83. package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
  84. package/dist/v2/templates/project/.rivet/project.yaml +14 -0
  85. package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
  86. package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
  87. package/package.json +58 -0
  88. package/protocols/agent-orchestration.md +49 -0
  89. package/protocols/delivery.md +45 -0
  90. package/protocols/design-authority.md +29 -0
  91. package/protocols/goal-graph.md +44 -0
  92. package/protocols/pattern-first-development.md +29 -0
  93. package/protocols/qa-evidence.md +29 -0
  94. package/protocols/security.md +29 -0
  95. package/schemas/event.schema.json +86 -0
  96. package/schemas/evidence.schema.json +94 -0
  97. package/schemas/feature-decomposition.schema.json +48 -0
  98. package/schemas/feature-plan.schema.json +95 -0
  99. package/schemas/goal-graph.schema.json +61 -0
  100. package/schemas/integration.schema.json +119 -0
  101. package/schemas/orchestration.schema.json +69 -0
  102. package/schemas/project.schema.json +209 -0
  103. package/schemas/providers.schema.json +142 -0
  104. package/schemas/quality.schema.json +50 -0
  105. package/schemas/work-action.schema.json +44 -0
  106. package/schemas/work-request.schema.json +253 -0
  107. package/src/adapters/confluence.js +138 -0
  108. package/src/adapters/contract.js +657 -0
  109. package/src/adapters/factory.js +122 -0
  110. package/src/adapters/figma.js +178 -0
  111. package/src/adapters/fixtures.js +183 -0
  112. package/src/adapters/github.js +314 -0
  113. package/src/adapters/http.js +704 -0
  114. package/src/adapters/jira.js +195 -0
  115. package/src/adapters/linear.js +171 -0
  116. package/src/adapters/node-transport.js +59 -0
  117. package/src/cli/integration-setup-prompt.js +32 -0
  118. package/src/cli/interrupt.js +25 -0
  119. package/src/cli/main.js +596 -0
  120. package/src/cli/output.js +174 -0
  121. package/src/cli/parse-args.js +246 -0
  122. package/src/cli/project-discovery.js +70 -0
  123. package/src/cli/task-confirmation.js +38 -0
  124. package/src/cli/task-presentation.js +22 -0
  125. package/src/clients/claude.js +214 -0
  126. package/src/clients/codex.js +180 -0
  127. package/src/clients/compatibility.js +23 -0
  128. package/src/clients/contract.js +224 -0
  129. package/src/clients/fake.js +170 -0
  130. package/src/clients/process-runner.js +657 -0
  131. package/src/clients/result-contract.js +78 -0
  132. package/src/commands/delivery-publish.js +80 -0
  133. package/src/commands/delivery-remote.js +230 -0
  134. package/src/commands/delivery-review-update.js +39 -0
  135. package/src/commands/delivery-tracker.js +94 -0
  136. package/src/commands/delivery-transition.js +79 -0
  137. package/src/commands/delivery.js +223 -0
  138. package/src/commands/dependency-approval.js +21 -0
  139. package/src/commands/doctor.js +191 -0
  140. package/src/commands/evidence.js +44 -0
  141. package/src/commands/feature.js +212 -0
  142. package/src/commands/goals.js +231 -0
  143. package/src/commands/human-run.js +208 -0
  144. package/src/commands/human-task.js +278 -0
  145. package/src/commands/init.js +848 -0
  146. package/src/commands/install.js +814 -0
  147. package/src/commands/integration-setup.js +65 -0
  148. package/src/commands/integrations.js +54 -0
  149. package/src/commands/models.js +158 -0
  150. package/src/commands/orchestrate.js +61 -0
  151. package/src/commands/preflight.js +125 -0
  152. package/src/commands/protocols.js +228 -0
  153. package/src/commands/repositories.js +77 -0
  154. package/src/commands/setup-remote.js +41 -0
  155. package/src/commands/setup.js +214 -0
  156. package/src/commands/status.js +224 -0
  157. package/src/commands/support.js +44 -0
  158. package/src/commands/task-completion.js +104 -0
  159. package/src/commands/uninstall.js +234 -0
  160. package/src/commands/verify.js +171 -0
  161. package/src/commands/work.js +220 -0
  162. package/src/commands/worktrees.js +52 -0
  163. package/src/config/command-readiness.js +218 -0
  164. package/src/config/commands.js +206 -0
  165. package/src/config/defaults.js +55 -0
  166. package/src/config/load.js +230 -0
  167. package/src/config/validate.js +560 -0
  168. package/src/delivery/bitbucket-review.js +123 -0
  169. package/src/delivery/branch-publication.js +23 -0
  170. package/src/delivery/contract.js +379 -0
  171. package/src/delivery/github-deployment.js +132 -0
  172. package/src/delivery/github-review.js +5 -0
  173. package/src/delivery/github.js +382 -0
  174. package/src/delivery/gitlab-review.js +5 -0
  175. package/src/delivery/gitlab.js +395 -0
  176. package/src/delivery/prepare.js +90 -0
  177. package/src/delivery/publication-process.js +33 -0
  178. package/src/delivery/publication-transport.js +112 -0
  179. package/src/delivery/review-request.js +184 -0
  180. package/src/delivery/review-update.js +74 -0
  181. package/src/delivery/service.js +400 -0
  182. package/src/delivery/store.js +76 -0
  183. package/src/delivery/tracker-target.js +51 -0
  184. package/src/delivery/tracker-transition.js +158 -0
  185. package/src/delivery/tracker.js +164 -0
  186. package/src/discovery/git.js +85 -0
  187. package/src/discovery/portable.js +108 -0
  188. package/src/discovery/project.js +345 -0
  189. package/src/discovery/tools.js +145 -0
  190. package/src/evaluations/approval.js +20 -0
  191. package/src/evaluations/cost-policy.js +20 -0
  192. package/src/evaluations/harness-attempt.js +113 -0
  193. package/src/evaluations/live-runner.js +55 -0
  194. package/src/evaluations/profile.js +28 -0
  195. package/src/evaluations/report.js +11 -0
  196. package/src/evaluations/text-attempt.js +18 -0
  197. package/src/evidence/checksum.js +87 -0
  198. package/src/evidence/collect.js +727 -0
  199. package/src/evidence/validate.js +354 -0
  200. package/src/feature/accepted-integration.js +46 -0
  201. package/src/feature/actions.js +37 -0
  202. package/src/feature/client-profile.js +56 -0
  203. package/src/feature/decomposition-contract.js +58 -0
  204. package/src/feature/host-execution.js +633 -0
  205. package/src/feature/host-lock-recovery.js +96 -0
  206. package/src/feature/host-run-lock.js +32 -0
  207. package/src/feature/local-approval.js +183 -0
  208. package/src/feature/plan-contract.js +215 -0
  209. package/src/feature/planner.js +253 -0
  210. package/src/feature/run-store.js +241 -0
  211. package/src/feature/runtime-bridge.js +840 -0
  212. package/src/feature/verification-report.js +168 -0
  213. package/src/feature/workflow.js +375 -0
  214. package/src/git/client.js +509 -0
  215. package/src/git/integration-worktree.js +153 -0
  216. package/src/git/reconcile.js +222 -0
  217. package/src/git/reservations.js +450 -0
  218. package/src/git/worktrees.js +478 -0
  219. package/src/graph/completion.js +189 -0
  220. package/src/graph/fixtures.js +116 -0
  221. package/src/graph/reducer.js +124 -0
  222. package/src/graph/scheduler.js +252 -0
  223. package/src/graph/validate.js +106 -0
  224. package/src/install/managed.js +579 -0
  225. package/src/install/project-reference.js +33 -0
  226. package/src/install/project-runtime.js +142 -0
  227. package/src/install/runtime-integrity.cjs +141 -0
  228. package/src/integrations/capabilities.js +69 -0
  229. package/src/integrations/host-observation.js +26 -0
  230. package/src/integrations/registry.js +56 -0
  231. package/src/models/delegate.js +102 -0
  232. package/src/models/profiles.js +53 -0
  233. package/src/models/protocols.js +208 -0
  234. package/src/models/registry.js +83 -0
  235. package/src/models/transport.js +59 -0
  236. package/src/policy/approvals.js +251 -0
  237. package/src/policy/authority.js +282 -0
  238. package/src/policy/budget.js +149 -0
  239. package/src/policy/command-bootstrap.js +219 -0
  240. package/src/policy/commands.js +803 -0
  241. package/src/prompts/launch-contract.js +43 -0
  242. package/src/prompts/planning-contract.js +92 -0
  243. package/src/protocols/presentation.js +53 -0
  244. package/src/protocols/project.js +289 -0
  245. package/src/quality/runner.js +497 -0
  246. package/src/quality/traceability.js +215 -0
  247. package/src/repositories/identity.js +59 -0
  248. package/src/repositories/index.js +2 -0
  249. package/src/repositories/provider.js +210 -0
  250. package/src/runtime/application.js +353 -0
  251. package/src/runtime/dependency-directory.js +33 -0
  252. package/src/runtime/harness-discovery.js +121 -0
  253. package/src/runtime/heartbeat.js +47 -0
  254. package/src/runtime/instance-store.js +112 -0
  255. package/src/runtime/orchestrator.js +1133 -0
  256. package/src/runtime/portable-dependencies.js +94 -0
  257. package/src/runtime/recovery.js +190 -0
  258. package/src/runtime/retry.js +54 -0
  259. package/src/runtime/supervisor.js +166 -0
  260. package/src/runtime/worktree-bootstrap.js +169 -0
  261. package/src/state/event-store.js +333 -0
  262. package/src/state/lock.js +266 -0
  263. package/src/state/paths.js +274 -0
  264. package/src/state/redact.js +106 -0
  265. package/src/state/snapshot-store.js +310 -0
  266. package/src/status/public/app.js +96 -0
  267. package/src/status/public/index.html +37 -0
  268. package/src/status/public/styles.css +50 -0
  269. package/src/status/server.js +323 -0
  270. package/src/status/view-model.js +356 -0
  271. package/src/support/bundle.js +195 -0
  272. package/src/support/failure-report.js +96 -0
  273. package/src/work-request/contract.js +250 -0
  274. package/src/work-request/host.js +78 -0
  275. package/src/work-request/local.js +163 -0
  276. package/src/work-request/tracker.js +105 -0
  277. package/templates/evidence/qa-bundle.json +194 -0
  278. package/templates/github-actions/rivet-deploy.yml +98 -0
  279. package/templates/harness/SKILL.md +90 -0
  280. package/templates/project/.rivet/orchestration.yaml +42 -0
  281. package/templates/project/.rivet/project.yaml +14 -0
  282. package/templates/project/.rivet/providers.yaml +8 -0
  283. package/templates/project/.rivet/quality.yaml +16 -0
@@ -0,0 +1,633 @@
1
+ import {assertSelectedProtocolRefs, selectedProtocolStatus} from '../protocols/project.js';
2
+ import {createProtocolPresentation, protocolLookupContext} from '../protocols/presentation.js';
3
+ import { lstat, realpath } from 'node:fs/promises';
4
+ import { basename, dirname, join } from 'node:path';
5
+
6
+ import { immutableJson } from '../clients/contract.js';
7
+ import { loadProjectConfig } from '../config/load.js';
8
+ import { assertGitClient } from '../git/client.js';
9
+ import { prepareIntegrationWorktree } from '../git/integration-worktree.js';
10
+ import { reconcileWorktree } from '../git/reconcile.js';
11
+ import { createReservationStore } from '../git/reservations.js';
12
+ import { createReservedWorktree } from '../git/worktrees.js';
13
+ import { buildLaunchContract } from '../prompts/launch-contract.js';
14
+ import { runQualityGates } from '../quality/runner.js';
15
+ import { createRuntimeInstance } from '../runtime/instance-store.js';
16
+ import { createOrchestrator } from '../runtime/orchestrator.js';
17
+ import {
18
+ acceptedIntegrationPaths,
19
+ resolveExistingFeatureRunPaths,
20
+ resolveExistingStatePaths,
21
+ resolveFeatureRunPaths,
22
+ resolveStatePaths,
23
+ verificationReportPaths,
24
+ } from '../state/paths.js';
25
+ import { readSnapshotWithoutLock } from '../state/snapshot-store.js';
26
+ import { createWorkAction, validateWorkAction } from './actions.js';
27
+ import { createAcceptedIntegrationStore } from './accepted-integration.js';
28
+ import { recoverHostRunLocks } from './host-lock-recovery.js';
29
+ import { acquireHostRunLock } from './host-run-lock.js';
30
+ import { createFeatureRunStore } from './run-store.js';
31
+ import { createVerificationReportStore, verificationReport } from './verification-report.js';
32
+ import {
33
+ createFeatureLaunchInput,
34
+ createFeatureRuntimeControls,
35
+ createFeatureRuntimeState,
36
+ configuredFeatureGates,
37
+ featureBranchFor,
38
+ featureNowMilliseconds,
39
+ featureQualityAuthority,
40
+ finalizeFeatureWorkerCommit,
41
+ inspectFeatureRuntime,
42
+ prepareFeatureWorkerParent,
43
+ reusableWorkerCheckout,
44
+ } from './runtime-bridge.js';
45
+
46
+ const RUN_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
47
+
48
+ export class HostExecutionError extends Error {
49
+ constructor(reason = 'invalid-input') {
50
+ const messages = {
51
+ 'invalid-input': 'Host execution input is invalid.',
52
+ 'run-missing': 'Feature run was not found.',
53
+ 'state-conflict': 'Host execution state changed or does not permit this operation.',
54
+ repository: 'Host execution requires the exact clean approved repository baseline.',
55
+ 'verification-failed': 'Configured verification failed. Read work status for the recorded checks and recovery guidance.',
56
+ };
57
+ super(messages[reason] ?? messages['invalid-input']);
58
+ this.name = 'HostExecutionError';
59
+ this.code = `ERR_HOST_EXECUTION_${reason.replaceAll('-', '_').toUpperCase()}`;
60
+ this.safeMessage = this.message;
61
+ this.details = Object.freeze({ reason });
62
+ }
63
+ }
64
+
65
+ function fail(reason) { throw new HostExecutionError(reason); }
66
+
67
+ function capture(input, allowed, required = allowed) {
68
+ try {
69
+ if (!input || typeof input !== 'object' || Array.isArray(input)
70
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(input))) fail('invalid-input');
71
+ const keys = Reflect.ownKeys(input);
72
+ if (keys.length > allowed.size || keys.some(key => typeof key !== 'string' || !allowed.has(key))
73
+ || [...required].some(key => !keys.includes(key))) fail('invalid-input');
74
+ const result = Object.create(null);
75
+ for (const key of keys) {
76
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
77
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) fail('invalid-input');
78
+ result[key] = descriptor.value;
79
+ }
80
+ return result;
81
+ } catch (error) {
82
+ if (error instanceof HostExecutionError) throw error;
83
+ fail('invalid-input');
84
+ }
85
+ }
86
+
87
+ function request(input, versionName = null) {
88
+ const allowed = new Set(['project', 'runId', ...(versionName ? [versionName] : [])]);
89
+ const value = capture(input, allowed);
90
+ if (typeof value.project !== 'string' || !value.project.startsWith('/') || value.project.length > 4096
91
+ || typeof value.runId !== 'string' || value.runId.length > 64 || !RUN_ID.test(value.runId)
92
+ || (versionName && (!Number.isSafeInteger(value[versionName]) || value[versionName] < 1))) fail('invalid-input');
93
+ return value;
94
+ }
95
+
96
+ function runtimeSummary(state) {
97
+ return immutableJson({
98
+ version: state.version,
99
+ terminal: state.terminal,
100
+ activated: state.activated,
101
+ graphStatus: state.graph.status,
102
+ nodes: state.graph.nodes.map(node => ({ id: node.id, status: node.status, owner: node.owner })),
103
+ usage: state.usage,
104
+ });
105
+ }
106
+
107
+ function exactStrings(left, right) {
108
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
109
+ const first = [...left].sort();
110
+ const second = [...right].sort();
111
+ return first.every((value, index) => value === second[index]);
112
+ }
113
+
114
+ export function createHostExecution(input) {
115
+ const configured = capture(
116
+ input,
117
+ new Set(['gitClient', 'now', 'resolveCommandExecutable', 'environment']),
118
+ new Set(['gitClient']),
119
+ );
120
+ try { assertGitClient(configured.gitClient); } catch { fail('invalid-input'); }
121
+ const gitClient = configured.gitClient;
122
+ const now = configured.now ?? (() => new Date().toISOString());
123
+ const resolveCommandExecutable = configured.resolveCommandExecutable;
124
+ const environment = configured.environment === undefined ? Object.freeze(Object.create(null)) : immutableJson(configured.environment);
125
+ if (typeof now !== 'function'
126
+ || (resolveCommandExecutable !== undefined && typeof resolveCommandExecutable !== 'function')) fail('invalid-input');
127
+
128
+ async function runRecord(project, runId) {
129
+ const paths = await resolveFeatureRunPaths(project, runId);
130
+ const store = createFeatureRunStore(paths);
131
+ const run = await store.read();
132
+ if (run === null) fail('run-missing');
133
+ if (run.featurePlan.client !== 'host') fail('state-conflict');
134
+ return { paths, store, run };
135
+ }
136
+
137
+ async function runtimeContext(project, run) {
138
+ assertSelectedProtocolRefs(project, run.workRequest.contextRefs);
139
+ const [repository, config] = await Promise.all([
140
+ gitClient.inspectRepository(project),
141
+ loadProjectConfig(project),
142
+ ]).catch(() => fail('repository'));
143
+ if (repository.root !== project || repository.detached || repository.dirty
144
+ || repository.branch !== config.project.repository.defaultBranch
145
+ || repository.headSha !== run.featurePlan.baselineCommit) fail('repository');
146
+ assertSelectedProtocolRefs(project, run.workRequest.contextRefs);
147
+ const initialState = createFeatureRuntimeState({ config, run });
148
+ const statePaths = await resolveStatePaths(project, run.runId);
149
+ const integrationBranch = featureBranchFor(config, run.featurePlan, run.runId);
150
+ const worktreeRunRoot = join(dirname(project), '.rivet-worktrees', repository.repositoryId, basename(statePaths.instanceDir));
151
+ const integration = await prepareIntegrationWorktree({
152
+ projectRoot: project,
153
+ statePaths,
154
+ branch: integrationBranch,
155
+ worktreePath: join(worktreeRunRoot, 'integration'),
156
+ baseSha: run.featurePlan.baselineCommit,
157
+ }, { gitClient });
158
+ const parent = await prepareFeatureWorkerParent(worktreeRunRoot);
159
+ const instance = createRuntimeInstance({ id: run.runId, paths: statePaths, initialState });
160
+ const planNodes = new Map(run.featurePlan.nodes.map(node => [node.id, node]));
161
+ const nowMs = () => featureNowMilliseconds(now);
162
+ const runtime = createOrchestrator({
163
+ client: Object.freeze({ async launch() { fail('state-conflict'); } }),
164
+ now: nowMs,
165
+ async prepareWorktree(node, intent) {
166
+ const planNode = planNodes.get(node.id);
167
+ if (!planNode || planNode.role !== 'worker' || intent.attempt !== 1) fail('state-conflict');
168
+ const current = await gitClient.inspectRepository(integration.path);
169
+ if (current.branch !== integration.branch || current.dirty) fail('repository');
170
+ const branch = `worker/${run.runId}/${node.id}`;
171
+ const worktreePath = join(parent, node.id);
172
+ const reservations = await createReservationStore(statePaths).list();
173
+ const existing = reservations.reservations.filter(item => item.nodeId === node.id
174
+ && item.ownerId === node.owner.id && item.status === 'active');
175
+ if (existing.length > 1) fail('state-conflict');
176
+ if (existing.length === 1) {
177
+ const reused = await reusableWorkerCheckout({
178
+ gitClient,
179
+ projectRoot: integration.path,
180
+ statePaths,
181
+ node,
182
+ planNode,
183
+ branch,
184
+ worktreePath,
185
+ baseSha: current.headSha,
186
+ nowMs: nowMs(),
187
+ });
188
+ return Object.freeze({
189
+ path: reused.worker.root,
190
+ dev: reused.worker.rootIdentity.dev,
191
+ ino: reused.worker.rootIdentity.ino,
192
+ reservationId: intent.reservationId,
193
+ });
194
+ }
195
+ const created = await createReservedWorktree({
196
+ projectRoot: integration.path,
197
+ statePaths,
198
+ nodeId: node.id,
199
+ branch,
200
+ worktreePath,
201
+ ownerId: node.owner.id,
202
+ baseSha: current.headSha,
203
+ responsibilities: planNode.ownedPaths,
204
+ intendedPaths: planNode.ownedPaths,
205
+ expiresAt: new Date(nowMs() + 24 * 60 * 60 * 1000).toISOString(),
206
+ }, { gitClient, nowMs: nowMs() });
207
+ const worker = await gitClient.inspectRepository(created.reservation.worktreePath);
208
+ return Object.freeze({
209
+ path: worker.root,
210
+ dev: worker.rootIdentity.dev,
211
+ ino: worker.rootIdentity.ino,
212
+ reservationId: intent.reservationId,
213
+ });
214
+ },
215
+ launchFor(node, intent) {
216
+ assertSelectedProtocolRefs(project, run.workRequest.contextRefs);
217
+ const planNode = planNodes.get(node.id);
218
+ if (!planNode) fail('state-conflict');
219
+ return createFeatureLaunchInput(node, intent, planNode, run);
220
+ },
221
+ async reconcile(node, intent, result) {
222
+ assertSelectedProtocolRefs(project, run.workRequest.contextRefs);
223
+ const reservations = await createReservationStore(statePaths).list();
224
+ const matches = reservations.reservations.filter(item => item.nodeId === node.id
225
+ && item.ownerId === node.owner.id && item.status === 'active');
226
+ if (matches.length !== 1) fail('state-conflict');
227
+ const leaseId = matches[0].leaseId;
228
+ const evidenceMatches = result.status !== 'success' || exactStrings(result.output?.evidence, node.evidenceRefs);
229
+ if (result.status === 'success') {
230
+ await finalizeFeatureWorkerCommit({
231
+ gitClient,
232
+ projectRoot: integration.path,
233
+ statePaths,
234
+ node,
235
+ planNode: planNodes.get(node.id),
236
+ leaseId,
237
+ nowMs: nowMs(),
238
+ });
239
+ }
240
+ const report = await reconcileWorktree({
241
+ projectRoot: integration.path,
242
+ statePaths,
243
+ nodeId: node.id,
244
+ ownerId: node.owner.id,
245
+ leaseId,
246
+ integrationBranch: integration.branch,
247
+ integrate: evidenceMatches,
248
+ }, { gitClient, nowMs: nowMs() });
249
+ return evidenceMatches ? report : Object.freeze({
250
+ ...report,
251
+ status: 'blocked',
252
+ reason: 'result-evidence-mismatch',
253
+ });
254
+ },
255
+ });
256
+ return { instance, runtime, integration, config, planNodes };
257
+ }
258
+
259
+ async function prepareLocked(inputValue) {
260
+ const value = request(inputValue, 'expectedRunVersion');
261
+ const { store, run } = await runRecord(value.project, value.runId);
262
+ if (!['approved', 'running'].includes(run.status) || run.version !== value.expectedRunVersion) fail('state-conflict');
263
+ const preparedContext = await runtimeContext(value.project, run);
264
+ const running = run.status === 'running' ? run : await store.update({
265
+ status: 'running',
266
+ updatedAt: now(),
267
+ runtimeRefs: [...new Set([...run.runtimeRefs, `runtime:${run.runId}`])],
268
+ evidenceRefs: run.evidenceRefs,
269
+ }, { expectedVersion: run.version });
270
+ const context = run.status === 'running' ? preparedContext : await runtimeContext(value.project, running);
271
+ let state = await inspectFeatureRuntime(context.instance);
272
+ if (!state.activated) {
273
+ state = await context.runtime.activate(context.instance, {
274
+ expectedVersion: state.version,
275
+ ...createFeatureRuntimeControls({ run: running, nowMs: featureNowMilliseconds(now) }),
276
+ });
277
+ }
278
+ return immutableJson({ status: 'ready', run: running, runtimeVersion: state.version });
279
+ }
280
+
281
+ async function prepare(inputValue) {
282
+ const value = request(inputValue, 'expectedRunVersion');
283
+ const paths = await resolveExistingFeatureRunPaths(value.project, value.runId);
284
+ if (paths === null) fail('run-missing');
285
+ const lock = await acquireHostRunLock(paths);
286
+ try { return await prepareLocked(inputValue); }
287
+ finally { await lock.release(); }
288
+ }
289
+
290
+ async function recover(inputValue) {
291
+ const value = request(inputValue);
292
+ return recoverHostRunLocks(value.project, value.runId);
293
+ }
294
+
295
+ async function nextAction(inputValue) {
296
+ const value = request(inputValue, 'expectedRuntimeVersion');
297
+ const hostPaths = await resolveFeatureRunPaths(value.project, value.runId);
298
+ const hostLock = await acquireHostRunLock(hostPaths);
299
+ try {
300
+ const { run } = await runRecord(value.project, value.runId);
301
+ if (run.status !== 'running') fail('state-conflict');
302
+ const { instance, runtime } = await runtimeContext(value.project, run);
303
+ const prepared = await runtime.prepareAction(instance, { expectedVersion: value.expectedRuntimeVersion });
304
+ if (prepared.action === null) {
305
+ return immutableJson({ status: 'idle', runtimeVersion: prepared.version, action: null });
306
+ }
307
+ const protocolContext = run.workRequest.contextRefs.some(ref => ref.startsWith('protocol:'))
308
+ ? protocolLookupContext(createProtocolPresentation({sourceRoot: value.project, refs: run.workRequest.contextRefs})) : null;
309
+ const action = createWorkAction({
310
+ runId: run.runId,
311
+ runtimeVersion: prepared.version,
312
+ nodeId: prepared.action.node.id,
313
+ intentId: prepared.action.intent.id,
314
+ idempotencyKey: prepared.action.intent.idempotencyKey,
315
+ reservationId: prepared.action.intent.reservationId,
316
+ attempt: prepared.action.intent.attempt,
317
+ payload: buildLaunchContract(prepared.action.launch),
318
+ });
319
+ const wasAlreadyStarted = value.expectedRuntimeVersion === prepared.version;
320
+ return immutableJson({
321
+ status: wasAlreadyStarted ? 'waiting-for-result' : 'action',
322
+ ...(protocolContext ? {protocolContext} : {}),
323
+ runtimeVersion: prepared.version,
324
+ action,
325
+ });
326
+ } finally { await hostLock.release(); }
327
+ }
328
+
329
+ async function submitResult(inputValue) {
330
+ const value = capture(inputValue, new Set([
331
+ 'project', 'runId', 'expectedRuntimeVersion', 'action', 'result',
332
+ ]));
333
+ request({
334
+ project: value.project,
335
+ runId: value.runId,
336
+ expectedRuntimeVersion: value.expectedRuntimeVersion,
337
+ }, 'expectedRuntimeVersion');
338
+ const action = validateWorkAction(value.action);
339
+ if (action.runId !== value.runId || action.runtimeVersion !== value.expectedRuntimeVersion) fail('state-conflict');
340
+ const hostPaths = await resolveFeatureRunPaths(value.project, value.runId);
341
+ const hostLock = await acquireHostRunLock(hostPaths);
342
+ try {
343
+ const { store, run } = await runRecord(value.project, value.runId);
344
+ if (run.status !== 'running') fail('state-conflict');
345
+ const { instance, runtime, planNodes, integration } = await runtimeContext(value.project, run);
346
+ const state = await inspectFeatureRuntime(instance);
347
+ if (state.version !== value.expectedRuntimeVersion) fail('state-conflict');
348
+ const node = state.graph.nodes.find(item => item.id === action.nodeId && item.status === 'running');
349
+ const intent = state.launchIntents[action.nodeId];
350
+ const planNode = planNodes.get(action.nodeId);
351
+ if (!node || intent?.status !== 'started' || !planNode) fail('state-conflict');
352
+ const expectedAction = createWorkAction({
353
+ runId: run.runId,
354
+ runtimeVersion: state.version,
355
+ nodeId: node.id,
356
+ intentId: intent.id,
357
+ idempotencyKey: intent.idempotencyKey,
358
+ reservationId: intent.reservationId,
359
+ attempt: intent.attempt,
360
+ payload: buildLaunchContract(createFeatureLaunchInput(node, intent, planNode, run)),
361
+ });
362
+ if (JSON.stringify(action) !== JSON.stringify(expectedAction)) fail('state-conflict');
363
+ const submitted = await runtime.submitAction(instance, {
364
+ expectedVersion: value.expectedRuntimeVersion,
365
+ nodeId: action.nodeId,
366
+ intentId: action.intentId,
367
+ idempotencyKey: action.idempotencyKey,
368
+ reservationId: action.reservationId,
369
+ result: value.result,
370
+ });
371
+ if (submitted.nodeStatus === 'completed' && submitted.report?.status === 'integrated') {
372
+ const integrated = await gitClient.inspectRepository(integration.path);
373
+ if (integrated.dirty || integrated.branch !== integration.branch
374
+ || integrated.headSha !== submitted.report.integrationTip) fail('repository');
375
+ const accepted = createAcceptedIntegrationStore(await acceptedIntegrationPaths(hostPaths));
376
+ await accepted.write({
377
+ schemaVersion: 1,
378
+ runId: run.runId,
379
+ baselineCommit: run.featurePlan.baselineCommit,
380
+ commitSha: submitted.report.integrationTip,
381
+ runtimeVersion: submitted.version,
382
+ path: integration.path,
383
+ branch: integration.branch,
384
+ });
385
+ }
386
+ if (submitted.nodeStatus === 'blocked' || ['blocked', 'failed', 'budget-exhausted'].includes(submitted.terminal)) {
387
+ await store.update({
388
+ status: 'blocked',
389
+ updatedAt: now(),
390
+ runtimeRefs: run.runtimeRefs,
391
+ evidenceRefs: run.evidenceRefs,
392
+ }, { expectedVersion: run.version });
393
+ }
394
+ return immutableJson({
395
+ status: submitted.nodeStatus === 'completed' ? 'accepted' : 'blocked',
396
+ runtimeVersion: submitted.version,
397
+ nodeId: submitted.nodeId,
398
+ nodeStatus: submitted.nodeStatus,
399
+ terminal: submitted.terminal,
400
+ reconciliation: submitted.report,
401
+ });
402
+ } finally { await hostLock.release(); }
403
+ }
404
+
405
+ async function verify(inputValue) {
406
+ const value = capture(inputValue, new Set([
407
+ 'project', 'runId', 'expectedRunVersion', 'expectedRuntimeVersion',
408
+ ]));
409
+ request({
410
+ project: value.project,
411
+ runId: value.runId,
412
+ expectedRunVersion: value.expectedRunVersion,
413
+ }, 'expectedRunVersion');
414
+ if (!Number.isSafeInteger(value.expectedRuntimeVersion) || value.expectedRuntimeVersion < 1) fail('invalid-input');
415
+ if (typeof resolveCommandExecutable !== 'function') fail('state-conflict');
416
+ const hostPaths = await resolveFeatureRunPaths(value.project, value.runId);
417
+ const hostLock = await acquireHostRunLock(hostPaths);
418
+ try {
419
+ const { paths, store, run } = await runRecord(value.project, value.runId);
420
+ if (run.status !== 'running' || run.version !== value.expectedRunVersion) fail('state-conflict');
421
+ const { instance, integration, config } = await runtimeContext(value.project, run);
422
+ const state = await inspectFeatureRuntime(instance);
423
+ if (state.version !== value.expectedRuntimeVersion) fail('state-conflict');
424
+ const accepted = await createAcceptedIntegrationStore(await acceptedIntegrationPaths(paths)).readOnly();
425
+ if (accepted === null || accepted.runId !== run.runId
426
+ || accepted.baselineCommit !== run.featurePlan.baselineCommit
427
+ || accepted.runtimeVersion > state.version
428
+ || accepted.path !== integration.path || accepted.branch !== integration.branch) fail('repository');
429
+ const finalNode = state.graph.nodes.find(node => node.approvalGate === 'final-delivery');
430
+ const nonHumanComplete = state.graph.nodes.filter(node => !node.approvalGate)
431
+ .every(node => ['completed', 'archived'].includes(node.status));
432
+ if (!nonHumanComplete || finalNode?.status !== 'ready') fail('state-conflict');
433
+ const integrated = await gitClient.inspectRepository(integration.path);
434
+ if (integrated.dirty || integrated.branch !== integration.branch
435
+ || integrated.headSha !== accepted.commitSha
436
+ || integrated.headSha === run.featurePlan.baselineCommit
437
+ || !(await gitClient.isAncestor(integration.path, run.featurePlan.baselineCommit, integrated.headSha))) {
438
+ fail('repository');
439
+ }
440
+ const changedPaths = await gitClient.changedPaths(integration.path, run.featurePlan.baselineCommit, integrated.headSha);
441
+ if (changedPaths.length === 0) fail('repository');
442
+ const reports = createVerificationReportStore(await verificationReportPaths(paths));
443
+ let quality;
444
+ let failure;
445
+ try {
446
+ quality = await runQualityGates({
447
+ projectRoot: integration.path,
448
+ commitSha: integrated.headSha,
449
+ authority: featureQualityAuthority(config),
450
+ gates: await configuredFeatureGates(config, resolveCommandExecutable, integration.path),
451
+ environment,
452
+ }, { gitClient, now: () => featureNowMilliseconds(now) });
453
+ } catch (error) {
454
+ failure = typeof error?.safeMessage === 'string' ? error.safeMessage : 'Verification could not complete safely.';
455
+ }
456
+ const afterChecks = await gitClient.inspectRepository(integration.path);
457
+ if (afterChecks.dirty || afterChecks.branch !== integration.branch
458
+ || afterChecks.headSha !== accepted.commitSha) fail('repository');
459
+ const currentRun = await store.readOnly();
460
+ if (currentRun?.status !== 'running' || currentRun.version !== run.version) fail('state-conflict');
461
+ await reports.write(verificationReport({
462
+ run, state, integration, commitSha: integrated.headSha, changedPaths,
463
+ quality, failure, checkedAt: now(),
464
+ }));
465
+ if (quality?.status !== 'pass') fail('verification-failed');
466
+ const beforeFinal = await gitClient.inspectRepository(integration.path);
467
+ if (beforeFinal.dirty || beforeFinal.branch !== integration.branch
468
+ || beforeFinal.headSha !== accepted.commitSha) fail('repository');
469
+ return await store.update({
470
+ status: 'awaiting-final-approval',
471
+ updatedAt: now(),
472
+ runtimeRefs: [...new Set([...run.runtimeRefs, `worktree:${run.runId}`])],
473
+ evidenceRefs: [...new Set([
474
+ ...run.evidenceRefs,
475
+ `commit:${integrated.headSha}`,
476
+ ...quality.gates.map(gate => `test:${gate.id}`),
477
+ ...state.evidence.map(item => `evidence:${item.id}`),
478
+ ])],
479
+ }, { expectedVersion: run.version });
480
+ } finally { await hostLock.release(); }
481
+ }
482
+
483
+ async function workerCheckoutStatus(project, run, statePaths, snapshot) {
484
+ if (statePaths === null) return [];
485
+ const { reservations } = await createReservationStore(statePaths).readOnly();
486
+ if (reservations.length === 0) return [];
487
+ if (reservations.length > 64 || new Set(reservations.map(item => item.nodeId)).size !== reservations.length) fail('state-conflict');
488
+ if (snapshot === null) fail('state-conflict');
489
+ const repository = await gitClient.inspectRepository(project);
490
+ if (repository.root !== project || repository.gitCommonDir !== statePaths.gitCommonDir) fail('state-conflict');
491
+ const parent = join(dirname(project), '.rivet-worktrees', repository.repositoryId, basename(statePaths.instanceDir), 'workers');
492
+ // Validate every association before inspecting any reservation-selected checkout.
493
+ const associated = reservations.map(reservation => {
494
+ const node = snapshot.data.graph.nodes.find(item => item.id === reservation.nodeId);
495
+ const planned = run.featurePlan.nodes.find(item => item.id === reservation.nodeId);
496
+ if (!node || planned?.role !== 'worker' || node.owner.role !== 'worker'
497
+ || node.owner.id !== reservation.ownerId || reservation.repositoryId !== repository.repositoryId
498
+ || reservation.branch !== `worker/${run.runId}/${node.id}`
499
+ || reservation.worktreePath !== join(parent, node.id)) fail('state-conflict');
500
+ return { reservation, node };
501
+ });
502
+ let topology = null;
503
+ try { topology = await gitClient.listWorktrees(project); } catch {}
504
+ const nowMs = featureNowMilliseconds(now);
505
+ const results = [];
506
+ for (const { reservation, node } of associated) {
507
+ const registered = topology?.filter(item => item.path === reservation.worktreePath) ?? [];
508
+ const branchLocations = topology?.filter(item => item.branch === reservation.branch && item.path !== reservation.worktreePath)
509
+ .map(item => item.path) ?? [];
510
+ let status = 'unavailable';
511
+ let observed = null;
512
+ if (topology !== null) {
513
+ let exists = false;
514
+ try { const metadata = await lstat(reservation.worktreePath); exists = metadata.isDirectory() && !metadata.isSymbolicLink()
515
+ && await realpath(reservation.worktreePath) === reservation.worktreePath; }
516
+ catch (error) { if (error.code === 'ENOENT') status = 'missing'; }
517
+ if (exists) {
518
+ if (registered.length !== 1 || registered[0].branch !== reservation.branch) status = 'mismatched';
519
+ else {
520
+ try {
521
+ const candidate = await gitClient.inspectRepository(reservation.worktreePath);
522
+ if (candidate.root !== reservation.worktreePath || candidate.repositoryId !== repository.repositoryId
523
+ || candidate.gitCommonDir !== repository.gitCommonDir || candidate.branch !== reservation.branch) status = 'mismatched';
524
+ else { observed = candidate; status = candidate.dirty ? 'dirty' : 'clean'; }
525
+ } catch {}
526
+ }
527
+ }
528
+ }
529
+ if ((observed?.dirtyPaths.length ?? 0) > 256 || branchLocations.length > 64) fail('state-conflict');
530
+ const leaseExpired = Date.parse(reservation.expiresAt) <= nowMs;
531
+ const nextAction = status === 'dirty'
532
+ ? 'Inspect and preserve the Worker edits in the owning harness before continuing. Status does not commit or discard them.'
533
+ : status !== 'clean'
534
+ ? 'Inspect the registered Worker checkout and branch locations before recovery. Do not launch a duplicate Worker.'
535
+ : ['completed', 'archived'].includes(node.status)
536
+ ? 'Review the completed Worker evidence and integration checkout; do not restart this Worker.'
537
+ : leaseExpired
538
+ ? 'Inspect the expired Worker lease and reconcile it explicitly before continuing. Status does not renew leases.'
539
+ : reservation.status === 'reserved'
540
+ ? 'Inspect the interrupted checkout preparation before continuing in the owning harness.'
541
+ : 'Continue the pending Worker action in the owning harness; a clean checkout alone is not completion evidence.';
542
+ results.push({ nodeId: node.id, nodeStatus: node.status, path: reservation.worktreePath,
543
+ expectedBranch: reservation.branch, observedBranch: observed?.branch ?? registered[0]?.branch ?? null,
544
+ reservationStatus: reservation.status, leaseExpired, status, headCommit: observed?.headSha ?? null,
545
+ dirtyPaths: observed?.dirtyPaths ?? [], branchLocations, nextAction });
546
+ }
547
+ if (Buffer.byteLength(JSON.stringify(results)) > 32 * 1024) fail('state-conflict');
548
+ return results;
549
+ }
550
+
551
+ async function status(inputValue) {
552
+ const value = request(inputValue);
553
+ const paths = await resolveExistingFeatureRunPaths(value.project, value.runId);
554
+ if (paths === null) fail('run-missing');
555
+ const run = await createFeatureRunStore(paths).readOnly();
556
+ if (run === null) fail('run-missing');
557
+ const reports = createVerificationReportStore(await verificationReportPaths(paths));
558
+ const verification = await reports.readOnly();
559
+ if (verification !== null && (verification.runId !== run.runId
560
+ || verification.baselineCommit !== run.featurePlan.baselineCommit)) fail('state-conflict');
561
+ const accepted = await createAcceptedIntegrationStore(await acceptedIntegrationPaths(paths)).readOnly();
562
+ if (accepted !== null && (accepted.runId !== run.runId
563
+ || accepted.baselineCommit !== run.featurePlan.baselineCommit)) fail('state-conflict');
564
+ let checkout = null;
565
+ if (accepted !== null) {
566
+ let observed = null;
567
+ try {
568
+ const worktrees = await gitClient.listWorktrees(value.project);
569
+ const registered = worktrees.filter(item => item.path === accepted.path && item.branch === accepted.branch);
570
+ if (registered.length === 1) observed = await gitClient.inspectRepository(accepted.path);
571
+ } catch {}
572
+ checkout = {
573
+ status: observed !== null && observed.branch === accepted.branch
574
+ && observed.headSha === accepted.commitSha && !observed.dirty ? 'clean' : 'stale',
575
+ path: accepted.path,
576
+ branch: accepted.branch,
577
+ acceptedCommit: accepted.commitSha,
578
+ observedCommit: observed?.headSha ?? null,
579
+ dirty: observed?.dirty ?? null,
580
+ };
581
+ }
582
+ const statePaths = await resolveExistingStatePaths(value.project, value.runId);
583
+ const snapshot = statePaths === null ? null : await readSnapshotWithoutLock(statePaths);
584
+ const runtime = snapshot === null ? null : runtimeSummary(snapshot.data);
585
+ const workerCheckouts = await workerCheckoutStatus(value.project, run, statePaths, snapshot);
586
+ const blockedNodes = run.status === 'blocked'
587
+ ? (runtime?.nodes ?? []).filter(node => ['blocked', 'failed'].includes(node.status)).map(node => node.id)
588
+ : [];
589
+ const readyForVerification = (runtime?.nodes ?? [])
590
+ .some(node => node.id === 'final-delivery' && node.status === 'ready');
591
+ const hostRun = run.featurePlan.client === 'host';
592
+ const verificationStale = verification !== null && accepted !== null
593
+ && (verification.commitSha !== accepted.commitSha || checkout?.status !== 'clean');
594
+ const protocols = selectedProtocolStatus(value.project, run.workRequest.contextRefs);
595
+ const deliverable = protocols.status === 'current' && run.status === 'awaiting-final-approval'
596
+ && runtime !== null && readyForVerification
597
+ && accepted !== null && checkout?.status === 'clean'
598
+ && verification?.status === 'pass'
599
+ && verification.runVersion === run.version - 1
600
+ && verification.runtimeVersion === runtime.version
601
+ && verification.commitSha === accepted.commitSha
602
+ && verification.integration.path === accepted.path
603
+ && verification.integration.branch === accepted.branch;
604
+ const nextAction = protocols.status === 'changed' ? protocols.message : checkout?.status === 'stale' || verificationStale
605
+ ? 'The integration checkout no longer matches the accepted tested commit. Do not deliver it; create a new reviewed proposal for source changes.'
606
+ : run.status === 'awaiting-final-approval' && !deliverable
607
+ ? 'Final approval evidence is missing or inconsistent. Do not deliver this run; inspect private state and create a new reviewed proposal if it cannot be restored.'
608
+ : deliverable
609
+ ? 'Review the integration diff and executed checks, then make the separate final delivery decision.'
610
+ : run.status === 'blocked'
611
+ ? hostRun
612
+ ? 'Inspect the blocked nodes and last submission response. Source correction requires a new reviewed proposal.'
613
+ : verification?.status === 'fail'
614
+ ? 'For missing locked dependencies, run rivet task deps and approve the shown install, then use rivet task resume. Source changes require a new reviewed proposal.'
615
+ : 'Inspect the blocked spawned run. Use rivet task resume only after its cause is corrected.'
616
+ : readyForVerification && accepted === null
617
+ ? 'Accepted integration identity is missing. Do not verify this run; create a new reviewed proposal.'
618
+ : verification?.status === 'fail' && hostRun
619
+ ? verification.nextAction
620
+ : run.status === 'running' && runtime !== null
621
+ ? hostRun
622
+ ? 'Use work next with runtime.version to continue or recover the pending action.'
623
+ : 'A spawned run is marked running. Inspect its process and private state before recovery; do not launch a duplicate worker.'
624
+ : run.status === 'approved'
625
+ ? hostRun
626
+ ? 'Use work prepare with run.version to create the isolated execution state.'
627
+ : 'Use rivet task resume to continue the approved spawned task.'
628
+ : 'Review the proposal and current run state before proceeding.';
629
+ return immutableJson({ run, runtime, protocols, verification, checkout, workerCheckouts, deliveryReady: deliverable, blockedNodes, nextAction });
630
+ }
631
+
632
+ return Object.freeze({ prepare, nextAction, submitResult, verify, status, recover });
633
+ }