@moretea-labs/forge 1.5.0-rc.1 → 1.5.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 (259) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +19 -0
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +50 -55
  7. package/SKILL.md +8 -11
  8. package/THIRD_PARTY_NOTICES.md +9 -3
  9. package/assets/branding/README.md +4 -2
  10. package/assets/hooks/lib/workflow-state.sh +19 -18
  11. package/assets/hooks/post-edit-guard.sh +6 -75
  12. package/assets/hooks/prompt-guard.sh +24 -12
  13. package/assets/hooks/session-start-context.sh +17 -17
  14. package/assets/hooks/stop-orchestrator.sh +2 -2
  15. package/assets/partials/04-project-structure.partial.md +6 -6
  16. package/assets/partials/05-workflow.partial.md +6 -6
  17. package/assets/partials/07-footer.partial.md +1 -1
  18. package/assets/partials/08-orchestration.partial.md +2 -2
  19. package/assets/partials-agents/02-operating-mode.partial.md +5 -5
  20. package/assets/partials-agents/03-orchestration.partial.md +3 -3
  21. package/assets/partials-agents/04-task-protocol.partial.md +3 -3
  22. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  23. package/assets/reference-configs/agentic-development-flow.md +49 -86
  24. package/assets/reference-configs/external-tooling.md +21 -61
  25. package/assets/reference-configs/handoff-protocol.md +25 -20
  26. package/assets/reference-configs/harness-overview.md +8 -8
  27. package/assets/templates/helpers/archive-workflow.sh +0 -1
  28. package/assets/templates/helpers/capture-plan.sh +3 -3
  29. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  30. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  31. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  34. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  35. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  36. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  37. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  38. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  39. package/assets/templates/helpers/verify-sprint.sh +7 -7
  40. package/assets/templates/helpers/workstream-sync.sh +1 -1
  41. package/assets/templates/plan.template.md +1 -1
  42. package/assets/workflow-contract.v1.json +6 -3
  43. package/docs/architecture/current/README.md +10 -9
  44. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  45. package/docs/architecture/current/architecture-invariants.md +2 -2
  46. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  47. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  48. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  49. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  50. package/docs/architecture/current/entity-model.md +0 -27
  51. package/docs/architecture/current/failure-recovery.md +0 -24
  52. package/docs/architecture/current/governance.md +1 -1
  53. package/docs/architecture/current/implementation-status.md +1 -1
  54. package/docs/architecture/current/legacy-governance.md +58 -0
  55. package/docs/architecture/current/multi-repository-execution.md +1 -1
  56. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  57. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  58. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  59. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  60. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  61. package/docs/architecture/current/thin-harness-v1.md +9 -11
  62. package/docs/architecture/domains/runtime-harness.md +1 -1
  63. package/docs/forge-autonomous-goal-loop.md +3 -1
  64. package/docs/forge-chatgpt-mcp-setup.md +51 -184
  65. package/docs/images/forge-banner-cn.svg +26 -1
  66. package/docs/images/forge-banner.svg +26 -1
  67. package/docs/operations/features.md +1 -1
  68. package/docs/operations/homebrew.md +3 -3
  69. package/docs/operations/mcp-tool-exposure.md +5 -5
  70. package/docs/operations/platform-support.md +45 -37
  71. package/docs/operations/platform-support.zh-CN.md +40 -38
  72. package/docs/operations/releasing.md +3 -3
  73. package/docs/operations/releasing.zh-CN.md +3 -3
  74. package/docs/operations/troubleshooting.md +11 -6
  75. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  76. package/docs/public-usage-guide.md +28 -21
  77. package/docs/public-usage-guide.zh-CN.md +28 -21
  78. package/docs/tutorials/01-install-and-start.md +56 -35
  79. package/docs/tutorials/01-install-and-start.zh-CN.md +56 -35
  80. package/docs/tutorials/02-connect-chatgpt.md +112 -46
  81. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +113 -45
  82. package/docs/wiki/Architecture.md +1 -1
  83. package/docs/wiki/Installation.md +2 -2
  84. package/docs/wiki/Quick-Start.md +1 -1
  85. package/docs/wiki/Work-Lifecycle.md +1 -1
  86. package/install.ps1 +4 -5
  87. package/install.sh +4 -6
  88. package/package.json +15 -5
  89. package/scripts/archive-workflow.sh +0 -1
  90. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  91. package/scripts/build-controller-ui.ts +28 -0
  92. package/scripts/capture-plan.sh +4 -4
  93. package/scripts/check-agent-tooling.sh +12 -1
  94. package/scripts/check-platform-support.mjs +2 -2
  95. package/scripts/check-public-docs.sh +16 -5
  96. package/scripts/check-release-readiness.sh +5 -11
  97. package/scripts/check-runtime-architecture.mjs +14 -22
  98. package/scripts/check-tarball-install-smoke.sh +3 -2
  99. package/scripts/check-task-workflow.sh +2 -2
  100. package/scripts/codex-handoff-resume.sh +5 -5
  101. package/scripts/create-project-dirs.sh +3 -3
  102. package/scripts/ensure-task-workflow.sh +45 -35
  103. package/scripts/init-project.sh +4 -4
  104. package/scripts/install-desktop-operator-registration.ts +2 -2
  105. package/scripts/lib/project-init-lib.sh +46 -44
  106. package/scripts/migrate-project-template.sh +7 -4
  107. package/scripts/new-plan.sh +1 -1
  108. package/scripts/plan-to-todo.sh +2 -2
  109. package/scripts/prepare-codex-handoff.sh +2 -2
  110. package/scripts/prepare-handoff.sh +9 -9
  111. package/scripts/refresh-current-status.sh +2 -2
  112. package/scripts/setup-plugins.sh +3 -3
  113. package/scripts/ship-worktrees.sh +1 -1
  114. package/scripts/sprint-backlog.sh +5 -5
  115. package/scripts/sync-codex-installed-copies.sh +2 -3
  116. package/scripts/verify-sprint.sh +7 -7
  117. package/scripts/workstream-sync.sh +1 -1
  118. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  119. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  120. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  121. package/src/cli/commands/chatgpt.ts +51 -0
  122. package/src/cli/commands/doctor.ts +1 -1
  123. package/src/cli/commands/init-hook.ts +211 -70
  124. package/src/cli/commands/init.ts +5 -6
  125. package/src/cli/commands/mcp.ts +29 -5
  126. package/src/cli/commands/runtime.ts +60 -8
  127. package/src/cli/commands/setup-profile.ts +441 -0
  128. package/src/cli/controller/check-runner.ts +20 -3
  129. package/src/cli/controller/codex-continuation.ts +4 -7
  130. package/src/cli/controller/context-pack.ts +118 -14
  131. package/src/cli/controller/operational-plan.ts +4 -4
  132. package/src/cli/controller/task-ledger.ts +10 -10
  133. package/src/cli/controller/work-mode.ts +6 -13
  134. package/src/cli/index.ts +14 -35
  135. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  136. package/src/cli/local-bridge/console-automations.ts +160 -0
  137. package/src/cli/local-bridge/dashboard.ts +8 -1303
  138. package/src/cli/local-bridge/facade-api.ts +16 -7
  139. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  140. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  141. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  142. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  144. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  146. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  147. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  148. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  149. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  150. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  152. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  153. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  155. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  156. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  157. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  158. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  159. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  160. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  161. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  162. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  163. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  164. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  165. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  166. package/src/cli/local-bridge/server.ts +44 -0
  167. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  168. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  169. package/src/cli/mcp/instructions.ts +3 -3
  170. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  171. package/src/cli/mcp/multi-repository.ts +2 -0
  172. package/src/cli/mcp/policy.ts +7 -5
  173. package/src/cli/mcp/repository-tools.ts +53 -14
  174. package/src/cli/mcp/server.ts +5 -10
  175. package/src/cli/mcp/setup.ts +211 -275
  176. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  177. package/src/cli/mcp/toolset-names.ts +1 -1
  178. package/src/cli/mcp/toolset.ts +0 -1
  179. package/src/cli/mcp/transports/http.ts +24 -4
  180. package/src/cli/repositories/branch-name-policy.ts +0 -7
  181. package/src/cli/repositories/controller-home.ts +110 -3
  182. package/src/cli/repositories/safe-patch.ts +1 -1
  183. package/src/cli/repositories/selected-path-actions.ts +26 -24
  184. package/src/core/adoption/gitignore-plan.ts +2 -3
  185. package/src/core/adoption/plan.ts +4 -1
  186. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  187. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  188. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  189. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  190. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  191. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  192. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  193. package/src/runtime/control-plane/facade/types.ts +0 -1
  194. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  195. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  196. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  197. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  198. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  199. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  200. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  201. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  202. package/src/runtime/evidence/event-ledger.ts +1 -1
  203. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  204. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  205. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  206. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  207. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  208. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  209. package/src/runtime/execution/process-runtime/index.ts +3 -0
  210. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  211. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  212. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  213. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  214. package/src/runtime/execution/process-runtime/store.ts +1 -19
  215. package/src/runtime/execution/process-runtime/types.ts +4 -0
  216. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  217. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  218. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  219. package/src/runtime/execution/thin-harness/types.ts +0 -1
  220. package/src/runtime/execution/workers/executor.ts +1 -1
  221. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  222. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  223. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  224. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  225. package/src/runtime/gateway/mcp/router.ts +117 -182
  226. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  227. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  228. package/src/runtime/plugins/browser-adapter.ts +5 -0
  229. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  230. package/src/runtime/plugins/store.ts +14 -6
  231. package/src/runtime/plugins/types.ts +1 -1
  232. package/src/runtime/projections/materialized-view.ts +1 -23
  233. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  234. package/src/runtime/resources/ownership.ts +0 -1
  235. package/src/runtime/root/gateway-adapter.ts +2 -0
  236. package/src/runtime/root/mcp-transport.ts +10 -2
  237. package/src/runtime/root/package-runtime-release.ts +161 -0
  238. package/src/runtime/root/package-runtime-service.ts +172 -0
  239. package/src/runtime/root/runtime.ts +2 -1
  240. package/src/runtime/root/scheduler.ts +5 -0
  241. package/src/runtime/root/service.ts +18 -5
  242. package/src/runtime/standalone-recovery/core.ts +65 -16
  243. package/src/runtime/workflow/findings/store.ts +145 -106
  244. package/src/runtime/workflow/findings/types.ts +22 -0
  245. package/src/runtime/workflow/schedules/engine.ts +132 -2
  246. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  247. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  248. package/docs/CHANGELOG.md +0 -978
  249. package/docs/README.zh-CN.md +0 -58
  250. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  251. package/src/cli/commands/controller.ts +0 -680
  252. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  253. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  254. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  255. package/src/runtime/workflow/campaigns/review.ts +0 -315
  256. package/src/runtime/workflow/campaigns/store.ts +0 -526
  257. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  258. package/src/runtime/workflow/campaigns/types.ts +0 -290
  259. /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
@@ -1,219 +1 @@
1
- import {
2
- getWorkContract,
3
- transitionWorkContractPhase,
4
- updateWorkContract,
5
- } from '../../control-plane/facade/work-contract-store';
6
- import {
7
- transitionWorkHandle,
8
- type WorkHandleState,
9
- } from '../../control-plane/execution/work-handle-store';
10
- import { processCheckCompletionReceipt } from '../../execution/process-runtime/check-receipt';
11
- import { processCheckSemanticScopeKey } from '../../execution/process-runtime/check-facade';
12
- import { getProcessRecord } from '../../execution/process-runtime/store';
13
- import { controllerCheckExecutionIdentity } from '../../../cli/controller/check-runner';
14
-
15
- export type WorkValidationOutcome =
16
- | 'not_validating'
17
- | 'running'
18
- | 'passed'
19
- | 'failed'
20
- | 'infrastructure_failure';
21
-
22
- export interface WorkValidationReconciliation {
23
- handle: WorkHandleState;
24
- changed: boolean;
25
- outcome: WorkValidationOutcome;
26
- summary?: string;
27
- }
28
-
29
- export function hasCurrentWorkValidationAuthority(input: {
30
- finalizationValidation: WorkHandleState['finalization']['validation'];
31
- validatedInputFingerprint?: string;
32
- evidenceState: string;
33
- expectedFingerprint: string;
34
- }): boolean {
35
- return input.finalizationValidation === 'done'
36
- && input.evidenceState === 'valid'
37
- && input.validatedInputFingerprint === input.expectedFingerprint;
38
- }
39
-
40
- /**
41
- * A new validation run invalidates delivery authority without deleting or
42
- * rewriting the prior receipts. `stale` is the only valid transition from an
43
- * already-valid evidence set; all other retryable states converge through
44
- * `partial`.
45
- */
46
- export function markWorkValidationPending(controllerHome: string, handle: WorkHandleState): void {
47
- const contractId = handle.workContractId;
48
- if (!contractId) return;
49
- const options = { controllerHome, repoId: handle.repositoryId };
50
- const contract = getWorkContract(options, contractId);
51
- if (!contract || contract.completionReceipt) return;
52
- const evidenceState = contract.evidenceState === 'valid' || contract.evidenceState === 'stale'
53
- ? 'stale'
54
- : 'partial';
55
- if (contract.evidenceState !== evidenceState) updateWorkContract(options, contractId, { evidenceState });
56
- }
57
-
58
- /**
59
- * Project durable validation evidence into the Work-owned contract lifecycle.
60
- * Process and Check records contribute evidence only; they never become a
61
- * second completion or lifecycle authority.
62
- */
63
- export function projectWorkValidationOutcome(
64
- controllerHome: string,
65
- handle: WorkHandleState,
66
- outcome: Exclude<WorkValidationOutcome, 'not_validating' | 'running'>,
67
- summary?: string,
68
- ): void {
69
- const contractId = handle.workContractId;
70
- if (!contractId) return;
71
- const options = { controllerHome, repoId: handle.repositoryId };
72
- const contract = getWorkContract(options, contractId);
73
- if (!contract || contract.completionReceipt) return;
74
-
75
- if (outcome === 'passed') {
76
- transitionWorkContractPhase(options, contractId, {
77
- phase: 'delivery',
78
- status: 'running',
79
- state: 'active',
80
- summary: summary ?? 'All requested validation receipts passed; delivery is next.',
81
- evidenceRefs: contract.evidenceRefs,
82
- });
83
- updateWorkContract(options, contractId, { evidenceState: 'valid' });
84
- return;
85
- }
86
-
87
- if (outcome === 'failed') {
88
- transitionWorkContractPhase(options, contractId, {
89
- phase: 'cleanup',
90
- status: 'failed',
91
- state: 'failed',
92
- summary: summary ?? 'A requested validation check failed; terminal cleanup is next.',
93
- evidenceRefs: contract.evidenceRefs,
94
- });
95
- updateWorkContract(options, contractId, { evidenceState: 'failed' });
96
- return;
97
- }
98
-
99
- transitionWorkContractPhase(options, contractId, {
100
- phase: 'cleanup',
101
- status: 'failed',
102
- state: 'failed',
103
- summary: summary ?? 'Validation infrastructure failed; terminal cleanup is required without treating this as an acceptance failure.',
104
- evidenceRefs: contract.evidenceRefs,
105
- });
106
- updateWorkContract(options, contractId, {
107
- evidenceState: contract.evidenceState === 'valid' || contract.evidenceState === 'stale' ? 'stale' : 'partial',
108
- });
109
- }
110
-
111
- function settleInfrastructureFailure(
112
- controllerHome: string,
113
- handle: WorkHandleState,
114
- summary: string,
115
- ): WorkValidationReconciliation {
116
- const next = transitionWorkHandle(controllerHome, handle, 'failed', {
117
- finalization: { ...handle.finalization, validation: 'failed', lastError: summary },
118
- validationRun: undefined,
119
- failureReason: summary,
120
- });
121
- projectWorkValidationOutcome(controllerHome, next, 'infrastructure_failure', summary);
122
- return { handle: next, changed: true, outcome: 'infrastructure_failure', summary };
123
- }
124
-
125
- /**
126
- * Converge a persisted Work validation run from durable Process receipts.
127
- *
128
- * No command is launched here. Missing/running bindings stay pending; timeout,
129
- * cancellation, missing process state, or malformed receipt makes the Work
130
- * terminally failed for cleanup without classifying it as an acceptance failure.
131
- */
132
- export function reconcileWorkValidation(
133
- controllerHome: string,
134
- handle: WorkHandleState,
135
- ): WorkValidationReconciliation {
136
- const run = handle.validationRun;
137
- if (handle.state !== 'validating' || !run) {
138
- return { handle, changed: false, outcome: 'not_validating' };
139
- }
140
-
141
- if (run.requestedChecks.length === 0) {
142
- const next = transitionWorkHandle(controllerHome, handle, run.resumeState, {
143
- finalization: { ...handle.finalization, validation: 'done', lastError: undefined },
144
- validationRun: undefined,
145
- validatedInputFingerprint: run.fingerprint,
146
- failureReason: undefined,
147
- });
148
- projectWorkValidationOutcome(controllerHome, next, 'passed', 'No validation checks were required.');
149
- return { handle: next, changed: true, outcome: 'passed' };
150
- }
151
-
152
- for (const checkId of run.requestedChecks) {
153
- const binding = run.processes[checkId];
154
- if (!binding) return { handle, changed: false, outcome: 'running' };
155
- const record = getProcessRecord(controllerHome, handle.repositoryId, binding.processId);
156
- if (!record) {
157
- return settleInfrastructureFailure(
158
- controllerHome,
159
- handle,
160
- `Validation process record is unavailable: ${binding.processId}`,
161
- );
162
- }
163
- if (record.status === 'running' || record.status === 'starting' || record.status === 'running_recovered') {
164
- return { handle, changed: false, outcome: 'running' };
165
- }
166
-
167
- try {
168
- const currentIdentity = record.checkExecution
169
- ? controllerCheckExecutionIdentity(handle.worktreePath, checkId, record.checkExecution.timeoutMs)
170
- : undefined;
171
- const receipt = processCheckCompletionReceipt(record, {
172
- repoId: handle.repositoryId,
173
- checkoutId: handle.checkoutId,
174
- workId: handle.workId,
175
- executionSessionId: handle.sessionId,
176
- checkId,
177
- processId: binding.processId,
178
- ...(currentIdentity ? {
179
- checkExecution: {
180
- cacheKey: currentIdentity.cacheKey,
181
- revision: currentIdentity.revision,
182
- definitionDigest: currentIdentity.definitionDigest,
183
- environmentFingerprint: currentIdentity.environmentFingerprint,
184
- timeoutMs: currentIdentity.timeoutMs,
185
- scopeKey: processCheckSemanticScopeKey({
186
- checkoutId: handle.checkoutId,
187
- workId: handle.workId,
188
- verificationBinding: { executionSessionId: handle.sessionId },
189
- }, currentIdentity.reuseScope),
190
- },
191
- } : {}),
192
- });
193
- if (!receipt.ok) {
194
- if (receipt.status === 'timed_out' || receipt.status === 'cancelled') {
195
- return settleInfrastructureFailure(controllerHome, handle, receipt.summary);
196
- }
197
- const next = transitionWorkHandle(controllerHome, handle, 'failed', {
198
- finalization: { ...handle.finalization, validation: 'failed', lastError: receipt.summary },
199
- validationRun: undefined,
200
- failureReason: receipt.summary,
201
- });
202
- projectWorkValidationOutcome(controllerHome, next, 'failed', receipt.summary);
203
- return { handle: next, changed: true, outcome: 'failed', summary: receipt.summary };
204
- }
205
- } catch (error) {
206
- const summary = error instanceof Error ? error.message : String(error);
207
- return settleInfrastructureFailure(controllerHome, handle, summary);
208
- }
209
- }
210
-
211
- const next = transitionWorkHandle(controllerHome, handle, run.resumeState, {
212
- finalization: { ...handle.finalization, validation: 'done', lastError: undefined },
213
- validationRun: undefined,
214
- validatedInputFingerprint: run.fingerprint,
215
- failureReason: undefined,
216
- });
217
- projectWorkValidationOutcome(controllerHome, next, 'passed', 'All requested validation receipts passed.');
218
- return { handle: next, changed: true, outcome: 'passed' };
219
- }
1
+ export * from '../../control-plane/execution/work-validation-reconciler';
@@ -383,6 +383,11 @@ function loadConfig(repoRoot: string): BrowserPluginConfig {
383
383
  return normalizeConfig(readJson<Partial<BrowserPluginConfig>>(configPath(repoRoot)) ?? {});
384
384
  }
385
385
 
386
+ export function readBrowserPluginConfiguration(repoRoot: string): { enabled: boolean; allowedDomains: string[] } {
387
+ const config = loadConfig(repoRoot);
388
+ return { enabled: config.enabled, allowedDomains: [...(config.allowedDomains ?? [])] };
389
+ }
390
+
386
391
  function saveConfig(repoRoot: string, patch: Partial<BrowserPluginConfig>): BrowserPluginConfig {
387
392
  const next = normalizeConfig({ ...loadConfig(repoRoot), ...patch });
388
393
  writeJson(configPath(repoRoot), next);
@@ -384,7 +384,7 @@ export function createDesktopOperatorRegistrationInput(
384
384
  providerPluginId: 'desktop_operator',
385
385
  displayName: 'Forge Desktop Operator',
386
386
  provider: 'local-macos',
387
- pluginVersion: options.pluginVersion ?? '0.1.0',
387
+ pluginVersion: options.pluginVersion ?? '0.2.1',
388
388
  protocolVersion: options.protocolVersion ?? '1.0',
389
389
  scope: 'controller',
390
390
  enabled: options.enabled !== false,
@@ -265,13 +265,21 @@ function writeRegistry(controllerHome: string, repoId: string, manifests: Assist
265
265
  return index;
266
266
  }
267
267
 
268
- function mapResourceClaims(action: AssistantPluginActionDescriptor, repository: RepositoryRecord): ResourceClaimSpec[] {
268
+ export function claimsForAssistantPluginAction(
269
+ action: AssistantPluginActionDescriptor,
270
+ repository: RepositoryRecord,
271
+ trustedPluginId: string,
272
+ ): ResourceClaimSpec[] {
269
273
  const controllerScoped = repository.repoId === CONTROLLER_SCOPE_REPO_ID;
274
+ const pluginId = trustedPluginId.trim().replace(/[^a-zA-Z0-9._-]+/g, '-');
275
+ if (!pluginId) throw new Error('PLUGIN_ID_REQUIRED_FOR_RESOURCE_CLAIMS');
270
276
  return action.resourceClaims.map((claim) => ({
271
- resourceKey: controllerScoped
272
- ? `controller-system:${claim.resource}`
273
- : claim.resource === 'remote'
274
- ? `remote:${repository.repoId}`
277
+ resourceKey: claim.resource === 'remote' || claim.resource === 'provider-state'
278
+ // The manifest/plugin id was resolved from the trusted registration; a
279
+ // provider never supplies arbitrary resource-key text.
280
+ ? `provider-state:${pluginId}`
281
+ : controllerScoped
282
+ ? `controller-system:${claim.resource}`
275
283
  : claim.resource === 'workspace'
276
284
  ? `workspace:${repository.activeCheckoutId}`
277
285
  : claim.resource === 'git-refs'
@@ -764,7 +772,7 @@ export async function submitAssistantPluginAction(
764
772
  mode: 'mutating',
765
773
  idempotent: action.idempotent,
766
774
  replayable: action.idempotent,
767
- resourceClaims: mapResourceClaims(action, repository),
775
+ resourceClaims: claimsForAssistantPluginAction(action, repository, manifest.pluginId),
768
776
  },
769
777
  objective: `Execute bounded controller-local effect ${request.pluginId}/${request.actionId}`,
770
778
  mode: 'direct_control',
@@ -4,7 +4,7 @@ export type AssistantPluginLifecycleState = 'enabled' | 'disabled' | 'degraded'
4
4
  export type AssistantPluginHealthState = 'ready' | 'disabled' | 'degraded' | 'error';
5
5
  export type AssistantPluginActionRisk = 'readonly' | 'workspace_write' | 'remote_write' | 'destructive';
6
6
  export type AssistantPluginActionConfirmation = 'none' | 'authorization' | 'strong_confirmation';
7
- export type AssistantPluginResource = 'repo-state' | 'workspace' | 'remote' | 'git-refs';
7
+ export type AssistantPluginResource = 'repo-state' | 'workspace' | 'remote' | 'git-refs' | 'provider-state';
8
8
 
9
9
  export interface AssistantPluginPermissionScope {
10
10
  scope: string;
@@ -22,7 +22,6 @@ import {
22
22
  type ProjectionRefreshOwner,
23
23
  } from './invalidation';
24
24
  import { readRepositoryGitStatusSample } from './git-status-sampler';
25
- import { listCampaigns } from '../workflow/campaigns/store';
26
25
  import { listAssistantPluginManifests } from '../plugins/store';
27
26
  import type { ProjectionObservation, ProjectionSourceReconciliation } from '../health';
28
27
  import { RUNTIME_HEALTH_THRESHOLDS } from '../health/evaluator';
@@ -60,12 +59,6 @@ export interface RepositoryRuntimeProjection {
60
59
  degraded: number;
61
60
  error: number;
62
61
  };
63
- campaigns?: {
64
- active: number;
65
- waitingForSupervisor: number;
66
- pendingReviews: number;
67
- readyForHumanAcceptance: number;
68
- };
69
62
  }
70
63
 
71
64
  interface DirtyProjectionReadCacheEntry {
@@ -112,12 +105,6 @@ function emptyProjection(repoId: string, reason?: string): RepositoryRuntimeProj
112
105
  degraded: 0,
113
106
  error: 0,
114
107
  },
115
- campaigns: {
116
- active: 0,
117
- waitingForSupervisor: 0,
118
- pendingReviews: 0,
119
- readyForHumanAcceptance: 0,
120
- },
121
108
  };
122
109
  }
123
110
 
@@ -191,7 +178,7 @@ function projectionWithExecutionIndexOverlay(
191
178
  }
192
179
 
193
180
  function dirtyReasonImpliesActiveRisk(reason: string | undefined): boolean {
194
- return Boolean(reason && /^(job:|leases-|campaign:|schedule:|worker:|process:|cleanup:)/.test(reason));
181
+ return Boolean(reason && /^(job:|leases-|schedule:|worker:|process:|cleanup:)/.test(reason));
195
182
  }
196
183
 
197
184
  function buildRepositoryProjection(
@@ -210,8 +197,6 @@ function buildRepositoryProjection(
210
197
  // Terminal attention records remain in history for diagnosis and audit, but only
211
198
  // active/unresolved records should influence "current readiness" decisions.
212
199
  const currentAttentionJobs = attentionJobs.filter((job) => !job.finishedAt || activeJobIds.has(job.jobId));
213
- const campaigns = listCampaigns(controllerHome, repoId, 1_000);
214
- const reviewableCampaigns = campaigns.filter((campaign) => ['active', 'waiting_for_supervisor'].includes(campaign.status));
215
200
  const repository = listRepositories(controllerHome).find((entry) => entry.repoId === repoId);
216
201
  const plugins = repository ? listAssistantPluginManifests(controllerHome, repository, {
217
202
  preferStored: true,
@@ -251,12 +236,6 @@ function buildRepositoryProjection(
251
236
  degraded: plugins.filter((plugin) => plugin.health.state === 'degraded').length,
252
237
  error: plugins.filter((plugin) => plugin.health.state === 'error').length,
253
238
  },
254
- campaigns: {
255
- active: campaigns.filter((campaign) => campaign.status === 'active').length,
256
- waitingForSupervisor: campaigns.filter((campaign) => campaign.status === 'waiting_for_supervisor').length,
257
- pendingReviews: reviewableCampaigns.reduce((count, campaign) => count + campaign.checkpoints.filter((checkpoint) => checkpoint.status === 'open').length, 0),
258
- readyForHumanAcceptance: campaigns.filter((campaign) => campaign.status === 'ready_for_human_acceptance').length,
259
- },
260
239
  };
261
240
  projection.metadata = {
262
241
  ...projection.metadata!,
@@ -270,7 +249,6 @@ function buildRepositoryProjection(
270
249
  currentAttention: projection.currentAttention,
271
250
  attention: projection.attention,
272
251
  plugins: projection.plugins,
273
- campaigns: projection.campaigns,
274
252
  })).digest('hex'),
275
253
  };
276
254
  return projection;
@@ -19,6 +19,7 @@ import {
19
19
  type RuntimeStorageRepairApplyResult,
20
20
  type RuntimeStorageRepairPreview,
21
21
  } from './local-jobs-repair';
22
+ import { gcTerminalProcesses, type ProcessGcResult } from '../execution/process-runtime/gc';
22
23
 
23
24
  export type RuntimeMaintenanceActionId =
24
25
  | 'local_jobs_reconcile'
@@ -118,6 +119,8 @@ export interface RuntimeMaintenanceApplyResult extends Omit<RuntimeMaintenanceSt
118
119
  actionId: RuntimeMaintenanceActionId;
119
120
  applied: Array<RuntimeMaintenanceCandidate & { applied: boolean; result?: string; error?: string }>;
120
121
  runtimeStorageRepairApply?: RuntimeStorageRepairApplyResult;
122
+ /** Existing bounded terminal-process retention, run only during explicit full maintenance. */
123
+ processGc?: ProcessGcResult;
121
124
  projection?: unknown;
122
125
  }
123
126
 
@@ -801,6 +804,11 @@ export function applyRuntimeMaintenance(
801
804
  return { ...candidate, applied: false, error: error instanceof Error ? error.message : String(error) };
802
805
  }
803
806
  });
807
+ // Reuse the existing Process GC within the explicit full-maintenance cadence.
808
+ // No scheduler, per-tool-call cleanup, or database compaction is introduced.
809
+ const processGc = options.actionId === 'full_maintenance_pass'
810
+ ? gcTerminalProcesses({ controllerHome, repoId: repository.repoId })
811
+ : undefined;
804
812
 
805
813
  if (options.actionId === 'runtime_storage_finalize_relocation' || options.actionId === 'full_maintenance_pass' || applied.some((candidate) => candidate.applied) || runtimeStorageRepairApply) {
806
814
  rebuildActiveIndex(repository.canonicalRoot);
@@ -817,6 +825,7 @@ export function applyRuntimeMaintenance(
817
825
  runtimeStorageError: after.runtimeStorageError ?? storage.error,
818
826
  applied,
819
827
  runtimeStorageRepairApply,
828
+ processGc,
820
829
  projection,
821
830
  };
822
831
  }
@@ -1,7 +1,6 @@
1
1
  export type ResourceOwnerKind =
2
2
  | 'execution_job'
3
3
  | 'agent_run'
4
- | 'campaign'
5
4
  | 'work_contract'
6
5
  | 'edit_session'
7
6
  | 'controller_generation'
@@ -8,6 +8,7 @@ export interface RuntimeGatewayServerOptions {
8
8
  runtimeInstanceId: string;
9
9
  runtimeSourceRoot: string;
10
10
  sessionId?: string;
11
+ controllerType?: 'chatgpt' | 'codex' | 'claude' | 'grok';
11
12
  }
12
13
 
13
14
  /**
@@ -28,6 +29,7 @@ export function createRuntimeGatewayServer(
28
29
  profile: 'controller',
29
30
  principalId,
30
31
  sessionId: options.sessionId,
32
+ controllerType: options.controllerType,
31
33
  controllerInstanceId: options.runtimeInstanceId,
32
34
  runtimeSourceRoot: options.runtimeSourceRoot,
33
35
  });
@@ -17,12 +17,14 @@ export interface RuntimeMcpTransportHandle {
17
17
  close(): Promise<void>;
18
18
  }
19
19
 
20
+ export type ForwardedControllerType = 'chatgpt' | 'codex' | 'claude' | 'grok';
21
+
20
22
  export interface StartRuntimeMcpTransportOptions {
21
23
  host: string;
22
24
  port: number;
23
25
  authToken: string;
24
26
  readiness: () => RuntimeReadiness;
25
- createServer: (principalId: string, sessionId?: string) => Server;
27
+ createServer: (principalId: string, sessionId?: string, controllerType?: ForwardedControllerType) => Server;
26
28
  onFatal?: (error: Error) => void;
27
29
  }
28
30
 
@@ -99,8 +101,14 @@ export async function startRuntimeMcpTransport(
99
101
  const forwardedSessionId = typeof req.headers['x-forge-forwarded-session-id'] === 'string'
100
102
  ? req.headers['x-forge-forwarded-session-id'].trim().slice(0, 512)
101
103
  : '';
104
+ const forwardedControllerTypeRaw = typeof req.headers['x-forge-forwarded-controller-type'] === 'string'
105
+ ? req.headers['x-forge-forwarded-controller-type'].trim().toLowerCase()
106
+ : '';
107
+ const forwardedControllerType = ['chatgpt', 'codex', 'claude', 'grok'].includes(forwardedControllerTypeRaw)
108
+ ? forwardedControllerTypeRaw as ForwardedControllerType
109
+ : undefined;
102
110
  const principal = forwardedPrincipalId || principalId(options.authToken);
103
- const server = options.createServer(principal, forwardedSessionId || undefined);
111
+ const server = options.createServer(principal, forwardedSessionId || undefined, forwardedControllerType);
104
112
  transport = new StreamableHTTPServerTransport({
105
113
  sessionIdGenerator: () => randomUUID(),
106
114
  onsessioninitialized: (createdSessionId) => {
@@ -0,0 +1,161 @@
1
+ import { createHash, randomUUID } from 'crypto';
2
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'fs';
3
+ import { dirname, join, relative, resolve } from 'path';
4
+ import { ensureControllerHome } from '../../cli/repositories/controller-home';
5
+ import { CONTROL_PLANE_SCHEMA_VERSION } from '../control-plane/persistence/sqlite-store';
6
+ import { packageRuntimeSourceRoot } from '../control-plane/runtime-generation';
7
+ import { FORGE_VERSION } from '../../version';
8
+ import { publishRuntimeRelease, type RuntimeReleaseAuthority } from './release-store';
9
+ import type { RuntimeReleaseManifest } from './types';
10
+
11
+ export interface PackageRuntimeFileRecord {
12
+ path: string;
13
+ sha256: string;
14
+ bytes: number;
15
+ }
16
+
17
+ export interface PackageRuntimeRelease {
18
+ releaseId: string;
19
+ releaseRoot: string;
20
+ packageRoot: string;
21
+ packageVersion: string;
22
+ packageFingerprint: string;
23
+ artifactIdentity: string;
24
+ entrypointPath: string;
25
+ manifestPath: string;
26
+ indexPath: string;
27
+ fileCount: number;
28
+ authority: RuntimeReleaseAuthority;
29
+ }
30
+
31
+ const PACKAGE_RUNTIME_ROOTS = ['src', 'bin', 'assets', 'scripts'] as const;
32
+ const PACKAGE_RUNTIME_FILES = ['package.json'] as const;
33
+
34
+ function sha256(value: Buffer | string): string {
35
+ return createHash('sha256').update(value).digest('hex');
36
+ }
37
+
38
+ function atomicWrite(path: string, content: string, mode = 0o600): void {
39
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
40
+ const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
41
+ writeFileSync(temporary, content, { encoding: 'utf8', mode });
42
+ renameSync(temporary, path);
43
+ }
44
+
45
+ function walkRegularFiles(root: string, current: string, output: string[]): void {
46
+ if (!existsSync(current)) return;
47
+ const stat = lstatSync(current);
48
+ if (stat.isSymbolicLink()) return;
49
+ if (stat.isFile()) {
50
+ output.push(relative(root, current).split('\\').join('/'));
51
+ return;
52
+ }
53
+ if (!stat.isDirectory()) return;
54
+ for (const entry of readdirSync(current).sort()) walkRegularFiles(root, join(current, entry), output);
55
+ }
56
+
57
+ export function packageRuntimeFileIndex(packageRoot = packageRuntimeSourceRoot()): PackageRuntimeFileRecord[] {
58
+ const root = resolve(packageRoot);
59
+ const paths: string[] = [];
60
+ for (const directory of PACKAGE_RUNTIME_ROOTS) walkRegularFiles(root, join(root, directory), paths);
61
+ for (const file of PACKAGE_RUNTIME_FILES) walkRegularFiles(root, join(root, file), paths);
62
+ const unique = [...new Set(paths)].sort();
63
+ if (!unique.includes('package.json') || !unique.some((path) => path === 'bin/forge-runtime.mjs')) {
64
+ throw new Error(`PACKAGE_RUNTIME_SURFACE_INCOMPLETE: ${root}`);
65
+ }
66
+ return unique.map((path) => {
67
+ const bytes = readFileSync(join(root, path));
68
+ return { path, sha256: sha256(bytes), bytes: bytes.length };
69
+ });
70
+ }
71
+
72
+ export function packageRuntimeFingerprint(records: PackageRuntimeFileRecord[]): string {
73
+ const hash = createHash('sha256');
74
+ for (const record of records) hash.update(record.path).update('\0').update(record.sha256).update('\0').update(String(record.bytes)).update('\n');
75
+ return hash.digest('hex');
76
+ }
77
+
78
+ function packageVersion(root: string): string {
79
+ try {
80
+ const value = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { name?: unknown; version?: unknown };
81
+ if (value.name !== '@moretea-labs/forge') throw new Error('package name mismatch');
82
+ if (typeof value.version !== 'string' || !value.version.trim()) throw new Error('package version missing');
83
+ return value.version.trim();
84
+ } catch (error) {
85
+ throw new Error(`PACKAGE_RUNTIME_IDENTITY_INVALID: ${error instanceof Error ? error.message : String(error)}`);
86
+ }
87
+ }
88
+
89
+ function launcherSource(input: { packageRoot: string; indexPath: string; indexSha256: string }): string {
90
+ return `#!/usr/bin/env node\n`
91
+ + `import { createHash } from 'node:crypto';\n`
92
+ + `import { existsSync, readFileSync } from 'node:fs';\n`
93
+ + `import { join } from 'node:path';\n`
94
+ + `import { spawn } from 'node:child_process';\n`
95
+ + `const packageRoot=${JSON.stringify(input.packageRoot)};\n`
96
+ + `const indexPath=${JSON.stringify(input.indexPath)};\n`
97
+ + `const expectedIndex=${JSON.stringify(input.indexSha256)};\n`
98
+ + `const digest=(v)=>createHash('sha256').update(v).digest('hex');\n`
99
+ + `let raw; try { raw=readFileSync(indexPath); } catch (error) { console.error('FORGE_PACKAGE_RUNTIME_INDEX_MISSING: '+error.message); process.exit(78); }\n`
100
+ + `if(digest(raw)!==expectedIndex){console.error('FORGE_PACKAGE_RUNTIME_INDEX_CHANGED');process.exit(78);}\n`
101
+ + `let records; try { records=JSON.parse(raw.toString('utf8')).files; } catch { console.error('FORGE_PACKAGE_RUNTIME_INDEX_INVALID'); process.exit(78); }\n`
102
+ + `for(const record of records){const path=join(packageRoot,record.path);if(!existsSync(path)||digest(readFileSync(path))!==record.sha256){console.error('FORGE_PACKAGE_RUNTIME_SOURCE_CHANGED: '+record.path);process.exit(78);}}\n`
103
+ + `const runtime=join(packageRoot,'bin','forge-runtime.mjs');\n`
104
+ + `const child=spawn(process.execPath,[runtime,...process.argv.slice(2)],{stdio:'inherit',env:{...process.env,FORGE_FORCE_NODE:'1'}});\n`
105
+ + `for(const signal of ['SIGINT','SIGTERM','SIGHUP']){try{process.on(signal,()=>child.kill(signal));}catch{}}\n`
106
+ + `child.on('error',(error)=>{console.error('FORGE_PACKAGE_RUNTIME_LAUNCH_FAILED: '+error.message);process.exit(1);});\n`
107
+ + `child.on('exit',(code)=>process.exit(code??1));\n`;
108
+ }
109
+
110
+ export function materializePackageRuntimeRelease(input: {
111
+ controllerHome?: string;
112
+ packageRoot?: string;
113
+ operationId?: string;
114
+ } = {}): PackageRuntimeRelease {
115
+ const controllerHome = ensureControllerHome(input.controllerHome);
116
+ const packageRoot = resolve(input.packageRoot ?? packageRuntimeSourceRoot());
117
+ const version = packageVersion(packageRoot);
118
+ const records = packageRuntimeFileIndex(packageRoot);
119
+ const fingerprint = packageRuntimeFingerprint(records);
120
+ const safeVersion = version.replace(/[^A-Za-z0-9._-]+/g, '-');
121
+ const releaseId = `package-${safeVersion}-${fingerprint.slice(0, 16)}`;
122
+ const releaseRoot = join(controllerHome, 'runtime', 'releases', releaseId);
123
+ const indexPath = join(releaseRoot, 'package-files.json');
124
+ const indexJson = `${JSON.stringify({ schemaVersion: 1, package: '@moretea-labs/forge', version, fingerprint, files: records }, null, 2)}\n`;
125
+ atomicWrite(indexPath, indexJson);
126
+ const entrypointPath = join(releaseRoot, 'forge-runtime');
127
+ const launcher = launcherSource({ packageRoot, indexPath, indexSha256: sha256(Buffer.from(indexJson)) });
128
+ atomicWrite(entrypointPath, launcher, 0o700);
129
+ chmodSync(entrypointPath, 0o700);
130
+ const artifactIdentity = `sha256:${sha256(Buffer.from(launcher))}`;
131
+ const manifest: RuntimeReleaseManifest = {
132
+ schemaVersion: 1,
133
+ releaseId,
134
+ artifactIdentity,
135
+ entrypoint: 'forge-runtime',
136
+ arguments: [],
137
+ configurationSchemaVersion: 1,
138
+ controllerHome,
139
+ databaseSchemaCompatibility: { minimum: CONTROL_PLANE_SCHEMA_VERSION, maximum: CONTROL_PLANE_SCHEMA_VERSION },
140
+ workerProtocolVersion: 1,
141
+ releaseRevision: `package:${version}:${fingerprint}`,
142
+ cleanWorkspace: true,
143
+ createdAt: new Date().toISOString(),
144
+ };
145
+ const manifestPath = join(releaseRoot, 'manifest.json');
146
+ atomicWrite(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
147
+ const authority = publishRuntimeRelease(controllerHome, manifestPath, input.operationId ?? `package-runtime-${FORGE_VERSION}-${Date.now()}`);
148
+ return {
149
+ releaseId,
150
+ releaseRoot,
151
+ packageRoot,
152
+ packageVersion: version,
153
+ packageFingerprint: fingerprint,
154
+ artifactIdentity,
155
+ entrypointPath,
156
+ manifestPath,
157
+ indexPath,
158
+ fileCount: records.length,
159
+ authority,
160
+ };
161
+ }