@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,840 @@
1
+ import {assertSelectedProtocolRefs, selectedProtocolStatus} from '../protocols/project.js';
2
+ import {createProtocolPresentation} from '../protocols/presentation.js';
3
+ import { createHash } from 'node:crypto';
4
+ import { lstat, mkdir } from 'node:fs/promises';
5
+ import { basename, dirname, join } from 'node:path';
6
+
7
+ import { immutableJson } from '../clients/contract.js';
8
+ import { loadProjectConfig } from '../config/load.js';
9
+ import { compileQualitySteps } from '../config/commands.js';
10
+ import { validateProjectConfiguration } from '../config/validate.js';
11
+ import { prepareIntegrationWorktree } from '../git/integration-worktree.js';
12
+ import { reconcileWorktree } from '../git/reconcile.js';
13
+ import { createAcceptedIntegrationStore } from './accepted-integration.js';
14
+ import { createVerificationReportStore, verificationReport } from './verification-report.js';
15
+ import { acceptedIntegrationPaths, resolveFeatureRunPaths, resolveStatePaths, verificationReportPaths } from '../state/paths.js';
16
+ import { createReservationStore } from '../git/reservations.js';
17
+ import { createReservedWorktree, verifyReservedWorktree } from '../git/worktrees.js';
18
+ import { validatedGraphSnapshot } from '../graph/validate.js';
19
+ import { createApprovalReceipt, createApprovalRegistry } from '../policy/approvals.js';
20
+ import { createAuthorityEnvelope } from '../policy/authority.js';
21
+ import { buildLaunchContract, validateLaunchPayload } from '../prompts/launch-contract.js';
22
+ import { runQualityGates } from '../quality/runner.js';
23
+ import { createRuntimeInstance } from '../runtime/instance-store.js';
24
+ import { createOrchestrator } from '../runtime/orchestrator.js';
25
+ import { bootstrapWorktreeDependencies, inspectWorktreeDependencies, WorktreeBootstrapError } from '../runtime/worktree-bootstrap.js';
26
+ import { validateWorkRequest } from '../work-request/contract.js';
27
+ import { createFeaturePlan, featurePlanDigest } from './plan-contract.js';
28
+ import { executionForNode } from './client-profile.js';
29
+
30
+ const ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
31
+
32
+ export class FeatureRuntimeBridgeError extends Error {
33
+ constructor() {
34
+ super('Feature runtime bridge input is invalid.');
35
+ this.name = 'FeatureRuntimeBridgeError';
36
+ this.code = 'ERR_FEATURE_RUNTIME_BRIDGE';
37
+ this.safeMessage = this.message;
38
+ }
39
+ }
40
+
41
+ function fail() { throw new FeatureRuntimeBridgeError(); }
42
+
43
+ function captureInput(input) {
44
+ try {
45
+ if (!input || typeof input !== 'object' || Array.isArray(input)
46
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(input))) fail();
47
+ const keys = Reflect.ownKeys(input);
48
+ if (keys.length !== 2 || !keys.includes('config') || !keys.includes('run')) fail();
49
+ const result = Object.create(null);
50
+ for (const key of keys) {
51
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
52
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) fail();
53
+ result[key] = descriptor.value;
54
+ }
55
+ return result;
56
+ } catch (error) {
57
+ if (error instanceof FeatureRuntimeBridgeError) throw error;
58
+ fail();
59
+ }
60
+ }
61
+
62
+ function captureRun(input) {
63
+ try {
64
+ if (!input || typeof input !== 'object' || Array.isArray(input)
65
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(input))) fail();
66
+ const allowed = new Set([
67
+ 'schemaVersion',
68
+ 'runId', 'status', 'workRequest', 'featurePlan', 'proposalDigest', 'tracker', 'activation',
69
+ 'runtimeRefs', 'evidenceRefs', 'createdAt', 'updatedAt', 'version',
70
+ ]);
71
+ const keys = Reflect.ownKeys(input);
72
+ if (keys.some(key => typeof key !== 'string' || !allowed.has(key))
73
+ || !['runId', 'workRequest', 'featurePlan', 'proposalDigest', 'activation'].every(key => keys.includes(key))) fail();
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();
78
+ result[key] = descriptor.value;
79
+ }
80
+ return result;
81
+ } catch (error) {
82
+ if (error instanceof FeatureRuntimeBridgeError) throw error;
83
+ fail();
84
+ }
85
+ }
86
+
87
+ function validRun(config, input) {
88
+ const run = captureRun(input);
89
+ let featurePlan;
90
+ try {
91
+ validateProjectConfiguration(config);
92
+ validateWorkRequest(run.workRequest);
93
+ featurePlan = createFeaturePlan({
94
+ proposal: run.featurePlan,
95
+ config,
96
+ workRequest: run.workRequest,
97
+ baselineCommit: run.featurePlan?.baselineCommit,
98
+ client: run.featurePlan?.client,
99
+ });
100
+ } catch { fail(); }
101
+ const validated = Object.freeze({ ...run, featurePlan });
102
+ if ((validated.schemaVersion !== undefined && validated.schemaVersion !== 1)
103
+ || typeof validated.runId !== 'string' || validated.runId.length > 64 || !ID.test(validated.runId)
104
+ || !['claude', 'codex', 'host'].includes(validated.featurePlan.client)
105
+ || validated.featurePlan.workRequestDigest !== validated.workRequest.digest
106
+ || typeof validated.proposalDigest !== 'string' || !/^[a-f0-9]{64}$/.test(validated.proposalDigest)
107
+ || featurePlanDigest(validated.featurePlan) !== validated.proposalDigest
108
+ || !validated.activation || typeof validated.activation !== 'object' || Array.isArray(validated.activation)
109
+ || validated.activation.requestDigest !== validated.workRequest.digest
110
+ || validated.activation.proposalDigest !== validated.proposalDigest) fail();
111
+ const roles = new Map(config.orchestration.roles.map(role => [role.id, role]));
112
+ const commands = new Set(Object.keys(config.project.commands));
113
+ for (const node of validated.featurePlan.nodes) {
114
+ const role = roles.get(node.roleId);
115
+ if (!role || role.kind !== node.role
116
+ || node.authorityScopes.some(scope => !role.authorityScopes.includes(scope))
117
+ || node.commandIds.some(command => !commands.has(command))
118
+ || node.budget.timeMinutes > role.budget.timeMinutes
119
+ || node.budget.tokenLimit > role.budget.tokenLimit
120
+ || node.budget.costUsd > role.budget.costUsd
121
+ || node.budget.taskLimit > role.budget.taskLimit) fail();
122
+ }
123
+ return validated;
124
+ }
125
+
126
+ function evidenceRefs(node) {
127
+ const digest = createHash('sha256').update(node.id).digest('hex').slice(0, 40);
128
+ return node.requiredEvidenceTypes.map((type, index) => {
129
+ const direct = `${node.id}-${type}-${index + 1}`;
130
+ return direct.length <= 64 ? direct : `e-${digest}-${type}-${index + 1}`;
131
+ });
132
+ }
133
+
134
+ function runtimeNode(node, role) {
135
+ let status = 'ready';
136
+ if (node.approvalGate === 'activation' || node.role === 'manager') status = 'completed';
137
+ return {
138
+ id: node.id,
139
+ ...(node.parentId === undefined ? {} : { parentId: node.parentId }),
140
+ objective: node.objective,
141
+ owner: { role: node.role, id: node.roleId },
142
+ dependencies: node.dependencies,
143
+ authorityScopes: node.authorityScopes,
144
+ budget: node.budget,
145
+ completionProfile: role.completionProfile,
146
+ requiredEvidenceTypes: node.requiredEvidenceTypes,
147
+ evidenceRefs: evidenceRefs(node),
148
+ status,
149
+ ...(node.approvalGate === undefined ? {} : { approvalGate: node.approvalGate }),
150
+ };
151
+ }
152
+
153
+ export function createFeatureRuntimeState(input) {
154
+ const value = captureInput(input);
155
+ const run = validRun(value.config, value.run);
156
+ const roles = new Map(value.config.orchestration.roles.map(role => [role.id, role]));
157
+ const graph = validatedGraphSnapshot({
158
+ schemaVersion: 1,
159
+ id: run.featurePlan.id,
160
+ goal: run.workRequest.title,
161
+ providerRefs: run.featurePlan.providerRefs,
162
+ maxDelegationDepth: value.config.orchestration.maxDelegationDepth,
163
+ status: 'approved',
164
+ nodes: run.featurePlan.nodes.map(node => runtimeNode(node, roles.get(node.roleId))),
165
+ });
166
+ const rootBudget = run.featurePlan.nodes.find(node => node.approvalGate === 'activation')?.budget;
167
+ if (!rootBudget) fail();
168
+ return immutableJson({
169
+ schemaVersion: 1,
170
+ version: 0,
171
+ activated: false,
172
+ terminal: null,
173
+ graph,
174
+ events: [],
175
+ attempts: {},
176
+ launchIntents: {},
177
+ results: {},
178
+ heartbeats: {},
179
+ evidence: [],
180
+ usage: { tokens: 0, costUsd: '0', retries: 0, timeMinutes: 0, taskLimit: 0 },
181
+ limits: { ...rootBudget, retries: 2 },
182
+ }, 'invalid-contract');
183
+ }
184
+
185
+ export function createFeatureRuntimeControls({ run: runInput, nowMs }) {
186
+ if (!Number.isSafeInteger(nowMs) || nowMs < 0) fail();
187
+ const run = captureRun(runInput);
188
+ if (!run.activation || typeof run.activation.approverId !== 'string'
189
+ || !ID.test(run.activation.approverId) || run.activation.proposalDigest !== run.proposalDigest) fail();
190
+ const activation = run.featurePlan?.nodes?.find(node => node.approvalGate === 'activation');
191
+ if (!activation || activation.role !== 'boss') fail();
192
+ const expectedApproverId = run.activation.approverId;
193
+ const registry = createApprovalRegistry({ approvers: [{ id: expectedApproverId, principal: 'human' }] });
194
+ const receipt = createApprovalReceipt({
195
+ id: `activation-${run.runId}`.slice(0, 64).replace(/-+$/, ''),
196
+ approverId: expectedApproverId,
197
+ approverPrincipal: 'human',
198
+ subjectId: activation.roleId,
199
+ action: 'activation',
200
+ resource: run.runId,
201
+ policyId: 'authority.human-gate.activation',
202
+ decision: 'approved',
203
+ expiresAt: new Date(nowMs + 24 * 60 * 60 * 1000).toISOString(),
204
+ singleUse: true,
205
+ });
206
+ const authority = createAuthorityEnvelope({
207
+ actorId: activation.roleId,
208
+ principal: 'agent',
209
+ role: 'boss',
210
+ actions: ['activation'],
211
+ ownedPaths: [],
212
+ providers: [],
213
+ commands: [],
214
+ });
215
+ return Object.freeze({ receipt, registry, authority, expectedApproverId });
216
+ }
217
+
218
+ function captureEnvironment(value) {
219
+ if (value === undefined) return Object.freeze(Object.create(null));
220
+ if (!value || typeof value !== 'object' || Array.isArray(value)
221
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail();
222
+ let keys;
223
+ try { keys = Reflect.ownKeys(value); } catch { fail(); }
224
+ if (keys.length > 512 || keys.some(key => typeof key !== 'string')) fail();
225
+ const output = Object.create(null);
226
+ for (const key of keys) {
227
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
228
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')
229
+ || typeof descriptor.value !== 'string' || descriptor.value.includes('\0')) fail();
230
+ output[key] = descriptor.value;
231
+ }
232
+ return Object.freeze(output);
233
+ }
234
+
235
+ function captureExecutor(input) {
236
+ try {
237
+ if (!input || typeof input !== 'object' || Array.isArray(input)) fail();
238
+ const allowed = new Set(['gitClient', 'clientFor', 'resolveCommandExecutable', 'now', 'environment']);
239
+ const keys = Reflect.ownKeys(input);
240
+ if (keys.length > allowed.size || keys.some(key => typeof key !== 'string' || !allowed.has(key))) fail();
241
+ const result = Object.create(null);
242
+ for (const key of keys) {
243
+ const descriptor = Object.getOwnPropertyDescriptor(input, key);
244
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, 'value')) fail();
245
+ result[key] = descriptor.value;
246
+ }
247
+ if (!result.gitClient || typeof result.gitClient.inspectRepository !== 'function'
248
+ || typeof result.clientFor !== 'function' || typeof result.resolveCommandExecutable !== 'function'
249
+ || typeof result.now !== 'function') fail();
250
+ result.environment = captureEnvironment(result.environment);
251
+ return Object.freeze(result);
252
+ } catch (error) {
253
+ if (error instanceof FeatureRuntimeBridgeError) throw error;
254
+ fail();
255
+ }
256
+ }
257
+
258
+ export function featureBranchFor(config, plan, runId) {
259
+ const pattern = config.project.repository.branchPattern;
260
+ if (typeof pattern !== 'string' || pattern.split('{slug}').length !== 2) fail();
261
+ if (typeof runId !== 'string' || !ID.test(runId)) fail();
262
+ return pattern.replace('{slug}', `${plan.id}-${runId}`);
263
+ }
264
+
265
+ export function featureNowMilliseconds(now) {
266
+ let value;
267
+ try { value = now(); } catch { fail(); }
268
+ const milliseconds = Date.parse(value);
269
+ if (typeof value !== 'string' || !Number.isSafeInteger(milliseconds) || new Date(milliseconds).toISOString() !== value) fail();
270
+ return milliseconds;
271
+ }
272
+
273
+ export async function inspectFeatureRuntime(instance) {
274
+ const lock = await instance.acquire();
275
+ try { return await instance.read(); } finally { await lock.release(); }
276
+ }
277
+
278
+ export async function prepareFeatureWorkerParent(worktreeRunRoot) {
279
+ const path = join(worktreeRunRoot, 'workers');
280
+ try { await mkdir(path, { mode: 0o700 }); } catch (error) { if (error?.code !== 'EEXIST') throw error; }
281
+ const metadata = await lstat(path);
282
+ if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 0o777) !== 0o700) fail();
283
+ return path;
284
+ }
285
+
286
+ function exactStrings(left, right) {
287
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
288
+ const first = [...left].sort();
289
+ const second = [...right].sort();
290
+ return first.every((value, index) => value === second[index]);
291
+ }
292
+
293
+ function responsibilityStrings(responsibilities) {
294
+ if (!Array.isArray(responsibilities)) fail();
295
+ return responsibilities.map(scope => {
296
+ if (!scope || typeof scope !== 'object' || Array.isArray(scope)
297
+ || typeof scope.path !== 'string' || typeof scope.directory !== 'boolean') fail();
298
+ return `${scope.path}${scope.directory ? '/' : ''}`;
299
+ });
300
+ }
301
+
302
+ function pathWithinResponsibilities(responsibilities, path) {
303
+ return responsibilities.some(scope => (
304
+ scope.path === path || (scope.directory && path.startsWith(`${scope.path}/`))
305
+ ));
306
+ }
307
+
308
+ export async function reusableWorkerCheckout(input) {
309
+ const {
310
+ gitClient, projectRoot, statePaths, node, planNode, branch, worktreePath,
311
+ baseSha, nowMs, expectedWorktree,
312
+ } = input;
313
+ const reservations = await createReservationStore(statePaths).list();
314
+ const matches = reservations.reservations.filter(item => (
315
+ item.nodeId === node.id && item.ownerId === node.owner.id && item.status === 'active'
316
+ ));
317
+ if (matches.length !== 1) fail();
318
+ const reservation = matches[0];
319
+ if (reservation.branch !== branch || reservation.worktreePath !== worktreePath
320
+ || reservation.baseSha !== baseSha || Date.parse(reservation.expiresAt) <= nowMs
321
+ || !exactStrings(responsibilityStrings(reservation.responsibilities), planNode.ownedPaths)
322
+ || !exactStrings(reservation.intendedPaths, planNode.ownedPaths)) fail();
323
+ const verified = await verifyReservedWorktree({
324
+ projectRoot,
325
+ statePaths,
326
+ nodeId: node.id,
327
+ ownerId: node.owner.id,
328
+ leaseId: reservation.leaseId,
329
+ }, { gitClient, nowMs });
330
+ if (expectedWorktree && (verified.worker.root !== expectedWorktree.path
331
+ || verified.worker.rootIdentity.dev !== expectedWorktree.dev
332
+ || verified.worker.rootIdentity.ino !== expectedWorktree.ino)) fail();
333
+ if (!(await gitClient.isAncestor(
334
+ verified.worker.root,
335
+ reservation.baseSha,
336
+ verified.worker.headSha,
337
+ ))) fail();
338
+ const committedPaths = await gitClient.changedPaths(
339
+ verified.worker.root,
340
+ reservation.baseSha,
341
+ verified.worker.headSha,
342
+ );
343
+ const uncommittedPaths = await gitClient.statusPaths(verified.worker.root);
344
+ if ([...committedPaths, ...uncommittedPaths]
345
+ .some(path => !pathWithinResponsibilities(reservation.responsibilities, path))) fail();
346
+ return Object.freeze({ reservation, worker: verified.worker });
347
+ }
348
+
349
+ export async function finalizeFeatureWorkerCommit(input) {
350
+ const {
351
+ gitClient, statePaths, node, planNode, leaseId, nowMs,
352
+ } = input;
353
+ const verified = await verifyReservedWorktree({
354
+ projectRoot: input.projectRoot,
355
+ statePaths,
356
+ nodeId: node.id,
357
+ ownerId: node.owner.id,
358
+ leaseId,
359
+ }, { gitClient, nowMs });
360
+ const committedPaths = await gitClient.changedPaths(
361
+ verified.worker.root,
362
+ verified.reservation.baseSha,
363
+ verified.worker.headSha,
364
+ );
365
+ const uncommittedPaths = await gitClient.statusPaths(verified.worker.root);
366
+ const allChangedPaths = [...new Set([...committedPaths, ...uncommittedPaths])];
367
+ if (allChangedPaths.some(path => !pathWithinResponsibilities(verified.reservation.responsibilities, path))) return;
368
+ if (uncommittedPaths.length === 0) return;
369
+ await gitClient.commitPaths(verified.worker.root, {
370
+ paths: planNode.ownedPaths,
371
+ expectedHeadSha: verified.worker.headSha,
372
+ branch: verified.reservation.branch,
373
+ message: `rivet: complete ${node.id}`,
374
+ });
375
+ }
376
+
377
+ async function recoverBlockedWorker(input) {
378
+ const {
379
+ state, runtime, instance, gitClient, integration, statePaths, planNodes,
380
+ parent, run, nowMs, recoveryWorktrees,
381
+ } = input;
382
+ if (state.terminal !== 'blocked' || state.graph.status !== 'blocked') fail();
383
+ const candidates = state.graph.nodes.filter(node => node.owner.role === 'worker'
384
+ && !node.approvalGate && node.status === 'blocked');
385
+ if (candidates.length !== 1) fail();
386
+ const node = candidates[0];
387
+ const intent = state.launchIntents[node.id];
388
+ const planNode = planNodes.get(node.id);
389
+ const manager = state.graph.nodes.find(item => item.id === node.parentId);
390
+ if (!intent || intent.status !== 'complete'
391
+ || (!intent.worktree && Object.hasOwn(intent, 'startedAtMs')) || !planNode
392
+ || planNode.role !== 'worker' || !manager || manager.owner.role !== 'manager'
393
+ || state.evidence.some(item => item.nodeId === node.id)) fail();
394
+ const current = await gitClient.inspectRepository(integration.path);
395
+ if (current.branch !== integration.branch || current.dirty) fail();
396
+ const reused = await reusableWorkerCheckout({
397
+ gitClient,
398
+ projectRoot: integration.path,
399
+ statePaths,
400
+ node,
401
+ planNode,
402
+ branch: `worker/${run.runId}/${node.id}`,
403
+ worktreePath: join(parent, node.id),
404
+ baseSha: current.headSha,
405
+ nowMs,
406
+ expectedWorktree: intent.worktree,
407
+ });
408
+ if (intent.worktree) recoveryWorktrees.set(node.id, intent.worktree);
409
+ const authority = createAuthorityEnvelope({
410
+ actorId: manager.owner.id,
411
+ principal: 'agent',
412
+ role: 'manager',
413
+ actions: ['orchestration.retry'],
414
+ ownedPaths: [],
415
+ providers: [],
416
+ commands: [],
417
+ });
418
+ const recovered = await runtime.retryNode(instance, {
419
+ expectedVersion: state.version,
420
+ nodeId: node.id,
421
+ authority,
422
+ reason: 'explicit-feature-resume',
423
+ nowMs,
424
+ });
425
+ return Object.freeze({ state: recovered, leaseId: reused.reservation.leaseId, nodeId: node.id });
426
+ }
427
+
428
+ export function createFeatureLaunchInput(node, intent, planNode, run) {
429
+ const approvedCost = Number(intent.allocation.costUsd);
430
+ const { clientProfile } = executionForNode(run.featurePlan, planNode);
431
+ const maxCostUsd = clientProfile === undefined
432
+ ? approvedCost
433
+ : Math.min(approvedCost, clientProfile.execution.maxCostUsd);
434
+ return {
435
+ nodeId: node.id,
436
+ parentId: node.parentId ?? null,
437
+ objective: `${planNode.objective}\n\nExecution protocol: implement the objective only within the sealed owned paths. The trusted host will validate scope and create the local commit after a successful result, so do not block or return retry merely because you cannot create a Git commit yourself. Return status success only after the implementation and required checks are complete, with exactly the declared evidence references.`,
438
+ ownedPaths: planNode.ownedPaths,
439
+ authority: { actions: planNode.authorityScopes, providers: run.featurePlan.providerRefs },
440
+ commands: planNode.commandIds,
441
+ evidence: node.evidenceRefs,
442
+ budget: {
443
+ maxTokens: intent.allocation.tokenLimit,
444
+ maxRuntimeMs: intent.allocation.timeMinutes * 60_000,
445
+ maxCostUsd,
446
+ },
447
+ worktree: intent.worktree,
448
+ contextRefs: [
449
+ `request:${run.workRequest.digest}`,
450
+ ...run.workRequest.contextRefs.filter(ref => ref.startsWith('protocol:')),
451
+ ],
452
+ heartbeatInterval: 60_000,
453
+ stopConditions: ['objective-complete', 'commit-created', 'evidence-ready'],
454
+ };
455
+ }
456
+
457
+ export async function configuredFeatureGates(config, resolveCommandExecutable, worktree) {
458
+ const gates = [];
459
+ for (const configured of compileQualitySteps(config)) {
460
+ gates.push(Object.freeze({
461
+ id: configured.id,
462
+ executable: await resolveCommandExecutable(configured.argv[0], configured.execution === 'argv'
463
+ ? { execution: 'argv', ...(worktree === undefined ? {} : { worktree, cwd: configured.cwd }) } : {}),
464
+ args: Object.freeze(configured.argv.slice(1)),
465
+ cwd: configured.cwd,
466
+ ...(configured.execution === 'argv' ? { execution: 'argv' }
467
+ : { packageScript: Object.freeze({ runner: configured.argv[0], script: configured.argv[2] }) }),
468
+ required: configured.required,
469
+ artifactPaths: Object.freeze([]),
470
+ tests: Object.freeze([]),
471
+ }));
472
+ }
473
+ return Object.freeze(gates);
474
+ }
475
+
476
+ export function featureQualityAuthority(config) {
477
+ const commandIds = compileQualitySteps(config).map(step => step.id);
478
+ return createAuthorityEnvelope({
479
+ actorId: 'quality-worker',
480
+ principal: 'agent',
481
+ role: 'worker',
482
+ actions: commandIds.map(id => `command.${id}`),
483
+ ownedPaths: [],
484
+ providers: [],
485
+ commands: commandIds,
486
+ });
487
+ }
488
+
489
+ function blocked(run, state, summary = 'Feature execution is blocked and can be resumed after the reported condition is corrected.') {
490
+ return immutableJson({
491
+ status: 'blocked',
492
+ summary,
493
+ runtimeRefs: [`runtime:${run.runId}`],
494
+ evidenceRefs: (state?.evidence ?? []).map(item => `evidence:${item.id}`),
495
+ });
496
+ }
497
+
498
+ export function createFeatureExecutor(input) {
499
+ const configured = captureExecutor(input);
500
+ return async function executeFeature(request, options = {}) {
501
+ if (!options || typeof options !== 'object' || Array.isArray(options)
502
+ || Reflect.ownKeys(options).some(key => !['signal', 'confirmDependencyInstall'].includes(key))
503
+ || (options.signal !== undefined && !(options.signal instanceof AbortSignal))
504
+ || (options.confirmDependencyInstall !== undefined && typeof options.confirmDependencyInstall !== 'function')) fail();
505
+ const signal = options.signal;
506
+ if (signal?.aborted) fail();
507
+ if (!request || typeof request !== 'object' || Array.isArray(request)
508
+ || Reflect.ownKeys(request).length !== 2 || !Object.hasOwn(request, 'project') || !Object.hasOwn(request, 'run')) fail();
509
+ const project = request.project;
510
+ if (typeof project !== 'string') fail();
511
+ const repository = await configured.gitClient.inspectRepository(project).catch(() => fail());
512
+ const config = await loadProjectConfig(project).catch(() => fail());
513
+ const run = validRun(config, request.run);
514
+ assertSelectedProtocolRefs(project, run.workRequest.contextRefs);
515
+ const protocolRecovery = () => selectedProtocolStatus(project, run.workRequest.contextRefs).message;
516
+ if (run.featurePlan.client === 'host') fail();
517
+ if (repository.root !== project || repository.detached || repository.dirty
518
+ || repository.branch !== config.project.repository.defaultBranch
519
+ || repository.headSha !== run.featurePlan.baselineCommit) fail();
520
+ // Only select adapters after the saved approval and current role configuration
521
+ // are validated. Probe before creating integration or worker checkouts.
522
+ const selectedClients = new Map();
523
+ for (const planNode of run.featurePlan.nodes.filter(node => node.role === 'worker')) {
524
+ const execution = executionForNode(run.featurePlan, planNode);
525
+ if (!selectedClients.has(execution.client)) {
526
+ const selected = await configured.clientFor(execution.client, execution.clientProfile, { project, signal });
527
+ if (!selected || selected.provider !== execution.client || typeof selected.launch !== 'function') fail();
528
+ selectedClients.set(execution.client, selected);
529
+ }
530
+ }
531
+
532
+ async function prepareDependencies(worker, branch, expectedCommit) {
533
+ if (config.project.schemaVersion === 3 && !config.project.dependencies
534
+ && !['npm', 'pnpm', 'yarn', 'bun'].includes(config.project.stack.packageManager)) return;
535
+ const input = {
536
+ projectRoot: project, worktreePath: worker.root, expectedCommit,
537
+ expectedBranch: branch, ...(config.project.dependencies ? {dependencies:config.project.dependencies} : {manager: config.project.stack.packageManager}),
538
+ };
539
+ try {
540
+ await inspectWorktreeDependencies(input, { gitClient: configured.gitClient });
541
+ } catch (error) {
542
+ if (error instanceof WorktreeBootstrapError && error.details.reason === 'missing-lockfile') return;
543
+ throw error;
544
+ }
545
+ if (!options.confirmDependencyInstall) throw new WorktreeBootstrapError('approval-required');
546
+ const installed = await bootstrapWorktreeDependencies(input, {
547
+ gitClient: configured.gitClient,
548
+ resolveCommandExecutable: configured.resolveCommandExecutable,
549
+ confirm: options.confirmDependencyInstall,
550
+ signal,
551
+ });
552
+ if (installed.status === 'declined') throw new WorktreeBootstrapError('approval-declined');
553
+ if (installed.status !== 'ready') throw new WorktreeBootstrapError('install-failed');
554
+ }
555
+
556
+ const statePaths = await resolveStatePaths(project, run.runId);
557
+ const integrationBranch = featureBranchFor(config, run.featurePlan, run.runId);
558
+ const worktreeRunRoot = join(dirname(project), '.rivet-worktrees', repository.repositoryId, basename(statePaths.instanceDir));
559
+ const integration = await prepareIntegrationWorktree({
560
+ projectRoot: project,
561
+ statePaths,
562
+ branch: integrationBranch,
563
+ worktreePath: join(worktreeRunRoot, 'integration'),
564
+ baseSha: run.featurePlan.baselineCommit,
565
+ }, { gitClient: configured.gitClient });
566
+ const instance = createRuntimeInstance({
567
+ id: run.runId,
568
+ paths: statePaths,
569
+ initialState: createFeatureRuntimeState({ config, run }),
570
+ });
571
+ const planNodes = new Map(run.featurePlan.nodes.map(node => [node.id, node]));
572
+ const parent = await prepareFeatureWorkerParent(worktreeRunRoot);
573
+ const nowMs = () => featureNowMilliseconds(configured.now);
574
+ let preparationReason = null;
575
+ let executionReason = null;
576
+ let reconciliationReason = null;
577
+ let reconciledTip = null;
578
+ const leases = new Map();
579
+ const recoveryWorktrees = new Map();
580
+ const client = Object.freeze({
581
+ provider: run.featurePlan.client,
582
+ async launch(contract, options) {
583
+ try {
584
+ const payload = validateLaunchPayload(buildLaunchContract(contract));
585
+ const planNode = planNodes.get(payload.nodeId);
586
+ if (!planNode || planNode.role !== 'worker') fail();
587
+ const execution = executionForNode(run.featurePlan, planNode);
588
+ const selected = selectedClients.get(execution.client);
589
+ if (!selected) fail();
590
+ const { version, ...sealedInput } = payload;
591
+ const protocolContext = run.workRequest.contextRefs.some(ref => ref.startsWith('protocol:'))
592
+ ? createProtocolPresentation({sourceRoot: project, refs: run.workRequest.contextRefs}) : undefined;
593
+ return await selected.launch(sealedInput, {...options, ...(protocolContext ? {protocolContext} : {})});
594
+ }
595
+ catch (error) {
596
+ const detail = typeof error?.details?.reason === 'string' ? `:${error.details.reason}` : '';
597
+ executionReason = typeof error?.code === 'string' ? `${error.code}${detail}` : 'worker-execution-error';
598
+ throw error;
599
+ }
600
+ },
601
+ });
602
+ const runtime = createOrchestrator({
603
+ client,
604
+ now: nowMs,
605
+ async prepareWorktree(node, intent) {
606
+ try {
607
+ const planNode = planNodes.get(node.id);
608
+ if (!planNode || planNode.role !== 'worker') fail();
609
+ const current = await configured.gitClient.inspectRepository(integration.path);
610
+ if (current.branch !== integration.branch || current.dirty) fail();
611
+ const branch = `worker/${run.runId}/${node.id}`;
612
+ const worktreePath = join(parent, node.id);
613
+ if (intent.attempt > 1) {
614
+ const reused = await reusableWorkerCheckout({
615
+ gitClient: configured.gitClient,
616
+ projectRoot: integration.path,
617
+ statePaths,
618
+ node,
619
+ planNode,
620
+ branch,
621
+ worktreePath,
622
+ baseSha: current.headSha,
623
+ nowMs: nowMs(),
624
+ expectedWorktree: recoveryWorktrees.get(node.id),
625
+ });
626
+ leases.set(node.id, reused.reservation.leaseId);
627
+ if (!recoveryWorktrees.has(node.id)) await prepareDependencies(reused.worker, branch, current.headSha);
628
+ preparationReason = null;
629
+ return Object.freeze({
630
+ path: reused.worker.root,
631
+ dev: reused.worker.rootIdentity.dev,
632
+ ino: reused.worker.rootIdentity.ino,
633
+ reservationId: node.id === intent.nodeId ? intent.reservationId : fail(),
634
+ });
635
+ }
636
+ const created = await createReservedWorktree({
637
+ projectRoot: integration.path,
638
+ statePaths,
639
+ nodeId: node.id,
640
+ branch,
641
+ worktreePath,
642
+ ownerId: node.owner.id,
643
+ baseSha: current.headSha,
644
+ responsibilities: planNode.ownedPaths,
645
+ intendedPaths: planNode.ownedPaths,
646
+ expiresAt: new Date(nowMs() + 24 * 60 * 60 * 1000).toISOString(),
647
+ }, { gitClient: configured.gitClient, nowMs: nowMs() });
648
+ const worker = await configured.gitClient.inspectRepository(created.reservation.worktreePath);
649
+ leases.set(node.id, created.reservation.leaseId);
650
+ await prepareDependencies(worker, branch, current.headSha);
651
+ preparationReason = null;
652
+ return Object.freeze({
653
+ path: worker.root,
654
+ dev: worker.rootIdentity.dev,
655
+ ino: worker.rootIdentity.ino,
656
+ reservationId: node.id === intent.nodeId ? intent.reservationId : fail(),
657
+ });
658
+ } catch (error) {
659
+ preparationReason = typeof error?.code === 'string' ? error.code : 'worktree-preparation-error';
660
+ throw error;
661
+ }
662
+ },
663
+ launchFor(node, intent) {
664
+ try {
665
+ const source = createFeatureLaunchInput(node, intent, planNodes.get(node.id), run);
666
+ buildLaunchContract(source);
667
+ return source;
668
+ } catch (error) {
669
+ executionReason = typeof error?.code === 'string' ? error.code : 'launch-contract-error';
670
+ throw error;
671
+ }
672
+ },
673
+ async reconcile(node, intent, result) {
674
+ try {
675
+ assertSelectedProtocolRefs(project, run.workRequest.contextRefs);
676
+ let leaseId = leases.get(node.id);
677
+ if (leaseId === undefined) {
678
+ const reservations = await createReservationStore(statePaths).list();
679
+ const matches = reservations.reservations.filter(item => item.nodeId === node.id
680
+ && item.ownerId === node.owner.id && item.status === 'active');
681
+ if (matches.length !== 1) fail();
682
+ leaseId = matches[0].leaseId;
683
+ }
684
+ const evidenceMatches = result.status !== 'success' || exactStrings(result.output?.evidence, node.evidenceRefs);
685
+ if (result.status === 'success') {
686
+ await finalizeFeatureWorkerCommit({
687
+ gitClient: configured.gitClient,
688
+ projectRoot: integration.path,
689
+ statePaths,
690
+ node,
691
+ planNode: planNodes.get(node.id),
692
+ leaseId,
693
+ nowMs: nowMs(),
694
+ });
695
+ }
696
+ const report = await reconcileWorktree({
697
+ projectRoot: integration.path,
698
+ statePaths,
699
+ nodeId: node.id,
700
+ ownerId: node.owner.id,
701
+ leaseId,
702
+ integrationBranch: integration.branch,
703
+ integrate: evidenceMatches,
704
+ }, { gitClient: configured.gitClient, nowMs: nowMs() });
705
+ if (!evidenceMatches) {
706
+ reconciliationReason = 'result-evidence-mismatch';
707
+ return Object.freeze({ ...report, status: 'blocked', reason: 'result-evidence-mismatch' });
708
+ }
709
+ reconciliationReason = report.status === 'integrated' ? null : report.reason ?? 'reconciliation-blocked';
710
+ if (report.status === 'integrated') reconciledTip = report.integrationTip;
711
+ return report;
712
+ } catch (error) {
713
+ reconciliationReason = typeof error?.code === 'string' ? error.code : 'reconciliation-error';
714
+ throw error;
715
+ }
716
+ },
717
+ });
718
+
719
+ let state = await inspectFeatureRuntime(instance);
720
+ try {
721
+ if (!state.activated) {
722
+ state = await runtime.activate(instance, {
723
+ expectedVersion: state.version,
724
+ ...createFeatureRuntimeControls({ run, nowMs: nowMs() }),
725
+ });
726
+ }
727
+ if (state.terminal === 'blocked') {
728
+ const recovered = await recoverBlockedWorker({
729
+ state,
730
+ runtime,
731
+ instance,
732
+ gitClient: configured.gitClient,
733
+ integration,
734
+ statePaths,
735
+ planNodes,
736
+ parent,
737
+ run,
738
+ nowMs: nowMs(),
739
+ recoveryWorktrees,
740
+ });
741
+ leases.set(recovered.nodeId, recovered.leaseId);
742
+ state = recovered.state;
743
+ }
744
+ const maximumTicks = run.featurePlan.nodes.length * 4 + 8;
745
+ for (let tick = 0; tick < maximumTicks; tick += 1) {
746
+ const outcome = await runtime.tick(instance, { expectedVersion: state.version, maxActiveNodes: 1, signal });
747
+ state = await inspectFeatureRuntime(instance);
748
+ if (['blocked', 'failed', 'budget-exhausted', 'cancelled'].includes(outcome.terminal)) {
749
+ const stopped = state.graph.nodes.filter(node => ['blocked', 'failed', 'cancelled'].includes(node.status)).map(node => node.id);
750
+ const preparation = preparationReason ? ` Preparation reason: ${preparationReason}.` : '';
751
+ const execution = executionReason ? ` Execution reason: ${executionReason}.` : '';
752
+ const reconciliation = reconciliationReason ? ` Reconciliation reason: ${reconciliationReason}.` : '';
753
+ return blocked(run, state, protocolRecovery() ?? `Runtime stopped at ${outcome.terminal}${stopped.length ? ` (${stopped.join(', ')})` : ''}.${preparation}${execution}${reconciliation} Correct the reported condition and resume the feature run.`);
754
+ }
755
+ const finalNode = state.graph.nodes.find(node => node.approvalGate === 'final-delivery');
756
+ const nonHumanComplete = state.graph.nodes
757
+ .filter(node => !node.approvalGate)
758
+ .every(node => ['completed', 'archived'].includes(node.status));
759
+ if (outcome.launched.length === 0 && nonHumanComplete && finalNode?.status === 'ready') break;
760
+ if (outcome.launched.length === 0) return blocked(run, state);
761
+ }
762
+ } catch (error) {
763
+ state = await inspectFeatureRuntime(instance).catch(() => state);
764
+ const reason = typeof error?.code === 'string' ? ` Runtime reason: ${error.code}.` : '';
765
+ return blocked(run, state, protocolRecovery() ?? `Feature execution stopped safely.${reason} Correct the reported condition and resume the feature run.`);
766
+ }
767
+
768
+ if (signal?.aborted) return blocked(run, state, 'Feature execution was interrupted. Inspect the run before resuming.');
769
+
770
+ const integrated = await configured.gitClient.inspectRepository(integration.path);
771
+ if (integrated.dirty || integrated.branch !== integration.branch) return blocked(run, state);
772
+ const featurePaths = await resolveFeatureRunPaths(project, run.runId);
773
+ const accepted = createAcceptedIntegrationStore(await acceptedIntegrationPaths(featurePaths));
774
+ let identity = await accepted.readOnly();
775
+ if (reconciledTip !== null) {
776
+ if (integrated.headSha !== reconciledTip) return blocked(run, state, 'Integration changed after Worker reconciliation. A new reviewed proposal is required.');
777
+ identity = await accepted.write({
778
+ schemaVersion: 1, runId: run.runId,
779
+ baselineCommit: run.featurePlan.baselineCommit,
780
+ commitSha: reconciledTip,
781
+ runtimeVersion: state.version,
782
+ path: integration.path, branch: integration.branch,
783
+ });
784
+ }
785
+ if (identity === null || identity.runId !== run.runId
786
+ || identity.baselineCommit !== run.featurePlan.baselineCommit
787
+ || identity.path !== integration.path || identity.branch !== integration.branch
788
+ || identity.runtimeVersion > state.version || identity.commitSha !== integrated.headSha
789
+ || identity.commitSha === run.featurePlan.baselineCommit
790
+ || !(await configured.gitClient.isAncestor(integration.path, run.featurePlan.baselineCommit, identity.commitSha))) {
791
+ return blocked(run, state, 'Integration does not match the accepted Worker commit. A new reviewed proposal is required.');
792
+ }
793
+ const changedPaths = await configured.gitClient.changedPaths(integration.path, run.featurePlan.baselineCommit, identity.commitSha);
794
+ if (changedPaths.length === 0) return blocked(run, state, 'Accepted Worker commit has no changed paths.');
795
+ let quality;
796
+ let failure;
797
+ try {
798
+ if (config.project.dependencies) await prepareDependencies({root:integration.path}, integration.branch, identity.commitSha);
799
+ quality = await runQualityGates({
800
+ projectRoot: integration.path,
801
+ commitSha: identity.commitSha,
802
+ authority: featureQualityAuthority(config),
803
+ gates: await configuredFeatureGates(config, configured.resolveCommandExecutable, integration.path),
804
+ environment: configured.environment,
805
+ }, { gitClient: configured.gitClient, now: nowMs, signal });
806
+ } catch (error) {
807
+ failure = typeof error?.safeMessage === 'string' ? error.safeMessage : 'Configured quality gates could not complete safely.';
808
+ }
809
+ const afterChecks = await configured.gitClient.inspectRepository(integration.path);
810
+ if (afterChecks.dirty || afterChecks.branch !== integration.branch || afterChecks.headSha !== identity.commitSha) {
811
+ return blocked(run, state, 'Integration changed during verification. A new reviewed proposal is required.');
812
+ }
813
+ const reports = createVerificationReportStore(await verificationReportPaths(featurePaths));
814
+ await reports.write(verificationReport({
815
+ run, state, integration, commitSha: identity.commitSha, changedPaths,
816
+ quality, failure, checkedAt: configured.now(),
817
+ }));
818
+ if (failure) {
819
+ return blocked(run, state, 'Configured quality gates could not complete safely. Correct the gate failure and resume the feature run.');
820
+ }
821
+ if (signal?.aborted) return blocked(run, state, 'Feature verification was interrupted. Inspect the run before resuming.');
822
+ if (quality.status !== 'pass') {
823
+ return blocked(run, state, 'One or more required quality gates failed. Correct the failure and resume the feature run.');
824
+ }
825
+ const beforeFinal = await configured.gitClient.inspectRepository(integration.path);
826
+ if (beforeFinal.dirty || beforeFinal.branch !== integration.branch || beforeFinal.headSha !== identity.commitSha) {
827
+ return blocked(run, state, 'Integration changed before final approval. A new reviewed proposal is required.');
828
+ }
829
+ return immutableJson({
830
+ status: 'awaiting-final-approval',
831
+ summary: 'All approved Workers completed on the isolated local integration branch and required quality gates passed. Final human approval is still required.',
832
+ runtimeRefs: [`runtime:${run.runId}`, `worktree:${run.runId}`],
833
+ evidenceRefs: [
834
+ `commit:${identity.commitSha}`,
835
+ ...quality.gates.map(gate => `test:${gate.id}`),
836
+ ...state.evidence.map(item => `evidence:${item.id}`),
837
+ ],
838
+ });
839
+ };
840
+ }