@moretea-labs/forge 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +13 -0
  3. package/CLAUDE.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/agents/openai.yaml +1 -1
  6. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  7. package/assets/plugin-registry.v1.json +3 -3
  8. package/assets/skill-version.json +2 -2
  9. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  10. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  11. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  12. package/assets/templates/helpers/workflow-contract.ts +5 -1
  13. package/docs/architecture/CURRENT.md +12 -4
  14. package/docs/forge-plugin-management.md +1 -1
  15. package/docs/operations/controller-performance-and-502.md +19 -0
  16. package/docs/operations/homebrew.md +2 -2
  17. package/package.json +5 -2
  18. package/scripts/architecture-event.ts +2 -820
  19. package/scripts/architecture-queue.sh +2 -645
  20. package/scripts/archive-architecture-request.sh +2 -242
  21. package/scripts/archive-workflow.sh +1 -291
  22. package/scripts/benchmark-work-efficiency.ts +0 -0
  23. package/scripts/capability-config.ts +2 -392
  24. package/scripts/capability-resolver.ts +2 -503
  25. package/scripts/capture-plan.sh +1 -332
  26. package/scripts/check-agent-tooling.sh +2 -1531
  27. package/scripts/check-architecture-sync.sh +2 -302
  28. package/scripts/check-brain-manifest.sh +2 -253
  29. package/scripts/check-context-files.sh +2 -47
  30. package/scripts/check-deploy-sql-order.sh +2 -115
  31. package/scripts/check-runtime-architecture.mjs +243 -16
  32. package/scripts/check-skill-version.ts +3 -191
  33. package/scripts/check-source-duplication.mjs +61 -0
  34. package/scripts/check-stable-baseline.ts +0 -0
  35. package/scripts/check-task-sync.sh +2 -63
  36. package/scripts/check-task-workflow.sh +2 -1000
  37. package/scripts/codex-handoff-resume.sh +2 -267
  38. package/scripts/context-contract-sync.sh +2 -514
  39. package/scripts/contract-run.ts +2 -463
  40. package/scripts/contract-worktree.sh +1 -702
  41. package/scripts/ensure-task-workflow.sh +1 -1192
  42. package/scripts/inspect-project-state.ts +4 -279
  43. package/scripts/lib/project-init-lib.sh +7 -2
  44. package/scripts/lib/run-forge-helper.sh +19 -0
  45. package/scripts/lib/run-forge-helper.ts +26 -0
  46. package/scripts/migrate-project-template.sh +1 -33
  47. package/scripts/migrate-workflow-docs.ts +4 -412
  48. package/scripts/new-plan.sh +1 -180
  49. package/scripts/new-spec.sh +1 -47
  50. package/scripts/new-sprint.sh +1 -46
  51. package/scripts/plan-to-todo.sh +1 -771
  52. package/scripts/prepare-codex-handoff.sh +2 -201
  53. package/scripts/prepare-handoff.sh +1 -78
  54. package/scripts/refresh-current-status.sh +2 -467
  55. package/scripts/run-governed-gate.ts +3 -0
  56. package/scripts/select-agent-context-blocks.sh +2 -64
  57. package/scripts/ship-worktrees.sh +1 -531
  58. package/scripts/sprint-backlog.sh +1 -834
  59. package/scripts/stage-runtime-release.ts +2 -1
  60. package/scripts/summarize-failures.sh +2 -123
  61. package/scripts/switch-plan.sh +1 -150
  62. package/scripts/sync-brain-docs.sh +2 -379
  63. package/scripts/sync-generated-authority.mjs +175 -0
  64. package/scripts/verify-contract.sh +2 -593
  65. package/scripts/verify-sprint.sh +1 -500
  66. package/scripts/workflow-contract.ts +4 -201
  67. package/scripts/workstream-sync.sh +2 -261
  68. package/src/cli/agent-jobs/executor-health.ts +1 -1
  69. package/src/cli/commands/plugin.ts +12 -3
  70. package/src/cli/controller/check-runner.ts +7 -2
  71. package/src/cli/controller/context-pack.ts +46 -21
  72. package/src/cli/github/contracts.ts +16 -0
  73. package/src/cli/github/github.ts +2 -16
  74. package/src/cli/github/session.ts +2 -1
  75. package/src/cli/local-bridge/facade-api.ts +37 -47
  76. package/src/cli/local-bridge/server.ts +2 -2
  77. package/src/cli/mcp/legacy-tool-service.ts +10 -33
  78. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  79. package/src/cli/mcp/repository-tools.ts +85 -89
  80. package/src/cli/mcp/server.ts +57 -13
  81. package/src/cli/mcp/tool-contract.ts +14 -0
  82. package/src/cli/mcp/toolset.ts +2 -2
  83. package/src/cli/mcp/transports/http.ts +23 -0
  84. package/src/cli/repositories/command-classifier.ts +2 -2
  85. package/src/cli/repositories/command-executor.ts +24 -6
  86. package/src/cli/repositories/repository-snapshot.ts +5 -2
  87. package/src/cli/repositories/structured-git.ts +57 -3
  88. package/src/cli/repository/inspector.ts +11 -3
  89. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  90. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  91. package/src/runtime/context/semantic-navigation.ts +9 -61
  92. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
  93. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  94. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  95. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  96. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  97. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  98. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  99. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  100. package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
  101. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  102. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  103. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  104. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  105. package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
  106. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
  107. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  108. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  109. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  110. package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
  111. package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
  112. package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
  113. package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
  114. package/src/runtime/control-plane/facade/index.ts +2 -0
  115. package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
  116. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  117. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  118. package/src/runtime/control-plane/facade/types.ts +68 -5
  119. package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
  120. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  121. package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
  122. package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
  123. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
  124. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
  125. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
  126. package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
  127. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  128. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  129. package/src/runtime/control-plane/routing/route-policy.ts +18 -6
  130. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  131. package/src/runtime/evidence/process-check-execution.ts +18 -0
  132. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  133. package/src/runtime/execution/process-runtime/check-result.ts +53 -18
  134. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  135. package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
  136. package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
  137. package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
  138. package/src/runtime/execution/process-runtime/runtime.ts +10 -0
  139. package/src/runtime/execution/process-runtime/types.ts +2 -12
  140. package/src/runtime/execution/work-activity.ts +21 -0
  141. package/src/runtime/execution/workers/executor.ts +18 -13
  142. package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
  143. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  144. package/src/runtime/gateway/mcp/router.ts +2 -2
  145. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
  146. package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
  147. package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
  148. package/src/runtime/plugins/browser-adapter.ts +256 -1083
  149. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  150. package/src/runtime/plugins/browser-handoff.ts +18 -3
  151. package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
  152. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  153. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  154. package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
  155. package/src/runtime/plugins/browser-runtime.ts +10 -0
  156. package/src/runtime/plugins/browser-session-store.ts +121 -0
  157. package/src/runtime/plugins/browser-session-types.ts +98 -0
  158. package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
  159. package/src/runtime/plugins/external-adapter.ts +92 -58
  160. package/src/runtime/plugins/macos-capability-broker.ts +0 -8
  161. package/src/runtime/plugins/store.ts +66 -9
  162. package/src/runtime/plugins/types.ts +3 -1
  163. package/src/runtime/recovery/maintenance-executor.ts +61 -25
  164. package/src/runtime/root/package-connector-service.ts +46 -4
  165. package/src/runtime/root/package-runtime-release.ts +1 -1
  166. package/src/runtime/root/package-runtime-service.ts +33 -2
  167. package/src/runtime/root/release-manifest.ts +137 -16
  168. package/src/runtime/root/release-materialize.ts +109 -35
  169. package/src/runtime/root/types.ts +10 -0
  170. package/src/runtime/standalone-recovery/core.ts +425 -194
  171. package/src/runtime/standalone-recovery/entry.ts +1 -1
  172. package/src/runtime/standalone-recovery/installer.ts +1 -1
  173. package/src/runtime/workflow/schedules/engine.ts +113 -87
  174. package/src/runtime/workflow/schedules/settlement.ts +26 -23
  175. package/src/runtime/workflow/schedules/store.ts +79 -10
@@ -107,7 +107,7 @@ function assertPackageRuntimeSnapshot(snapshotRoot: string, records: PackageRunt
107
107
  }
108
108
  }
109
109
 
110
- function stagePackageRuntimeSnapshot(sourceRoot: string, snapshotRoot: string, records: PackageRuntimeFileRecord[]): void {
110
+ export function stagePackageRuntimeSnapshot(sourceRoot: string, snapshotRoot: string, records: PackageRuntimeFileRecord[]): void {
111
111
  mkdirSync(snapshotRoot, { recursive: false, mode: 0o700 });
112
112
  for (const record of records) {
113
113
  const source = join(sourceRoot, record.path);
@@ -357,6 +357,10 @@ async function cleanupActivationHelper(request: PackageRuntimeActivationRequest)
357
357
  async function rollbackPackageRuntimeActivation(
358
358
  request: PackageRuntimeActivationRequest,
359
359
  installDarwinService: typeof installForgeRuntimeService,
360
+ options: {
361
+ restoreConnector?: boolean;
362
+ ensureConnectorService?: typeof ensurePackageConnectorService;
363
+ } = {},
360
364
  ): Promise<string[]> {
361
365
  const errors: string[] = [];
362
366
  const servicePaths = forgeRuntimeServicePaths(request.controllerHome);
@@ -391,6 +395,29 @@ async function rollbackPackageRuntimeActivation(
391
395
  errors.push(error instanceof Error ? error.message : String(error));
392
396
  }
393
397
  }
398
+ if (options.restoreConnector && request.connectorEndpoint && request.priorAuthorityPresent) {
399
+ try {
400
+ const restored = readRuntimeReleaseAuthority(request.controllerHome);
401
+ if (!restored) throw new Error('FORGE_PACKAGE_CONNECTOR_ROLLBACK_AUTHORITY_MISSING');
402
+ const releaseRoot = dirname(resolve(restored.active.manifestPath));
403
+ const packageRoot = join(releaseRoot, 'package');
404
+ const ensureConnectorService = options.ensureConnectorService ?? ensurePackageConnectorService;
405
+ await ensureConnectorService({
406
+ release: {
407
+ releaseId: restored.active.releaseId,
408
+ releaseRoot,
409
+ packageRoot,
410
+ },
411
+ controllerHome: request.controllerHome,
412
+ endpoint: request.connectorEndpoint,
413
+ executable: request.nodeExecutable,
414
+ platform: 'darwin',
415
+ refresh: true,
416
+ });
417
+ } catch (error) {
418
+ errors.push(`primary Connector rollback failed: ${error instanceof Error ? error.message : String(error)}`);
419
+ }
420
+ }
394
421
  return errors;
395
422
  }
396
423
 
@@ -421,6 +448,7 @@ export async function activateScheduledPackageRuntimeService(
421
448
  release: request.release,
422
449
  controllerHome: request.controllerHome,
423
450
  endpoint: request.connectorEndpoint,
451
+ executable: request.nodeExecutable,
424
452
  platform: 'darwin',
425
453
  refresh: request.refreshConnector === true,
426
454
  });
@@ -428,7 +456,10 @@ export async function activateScheduledPackageRuntimeService(
428
456
  return writeActivationReceipt(request, 'activated', { servicePath: paths.installedPlistPath });
429
457
  } catch (error) {
430
458
  const detail = error instanceof Error ? error.message : String(error);
431
- const rollbackErrors = await rollbackPackageRuntimeActivation(request, installDarwinService);
459
+ const rollbackErrors = await rollbackPackageRuntimeActivation(request, installDarwinService, {
460
+ restoreConnector: Boolean(request.connectorEndpoint),
461
+ ensureConnectorService,
462
+ });
432
463
  writeActivationReceipt(request, 'failed+rollback', {
433
464
  error: detail,
434
465
  rollbackSucceeded: rollbackErrors.length === 0,
@@ -587,7 +618,7 @@ export async function installPackageRuntimeService(
587
618
  if (!connectorEndpoint || base.status === 'activation_scheduled') return base;
588
619
  const ensureConnectorService = dependencies.ensureConnectorService ?? ensurePackageConnectorService;
589
620
  const connector = await ensureConnectorService({
590
- release, controllerHome, endpoint: connectorEndpoint, platform, env, forcePortable: options.forcePortable === true, refresh: options.refreshConnector === true,
621
+ release, controllerHome, endpoint: connectorEndpoint, executable: process.execPath, platform, env, forcePortable: options.forcePortable === true, refresh: options.refreshConnector === true,
591
622
  });
592
623
  return { ...base, connector };
593
624
  }
@@ -13,6 +13,77 @@ function canonicalExistingPathIdentity(path: string): string {
13
13
  return existsSync(resolved) ? realpathSync(resolved) : resolved;
14
14
  }
15
15
 
16
+ function requireSha256Identity(value: unknown, field: string): string {
17
+ const identity = requireString(value, field);
18
+ if (!/^sha256:[a-f0-9]{64}$/i.test(identity)) {
19
+ throw new Error(`RELEASE_MANIFEST_INVALID: ${field} must be sha256:<64 hex>`);
20
+ }
21
+ return identity;
22
+ }
23
+
24
+ type RuntimeComponentPair<KEntry extends keyof RuntimeReleaseManifest, KIdentity extends keyof RuntimeReleaseManifest> = Pick<
25
+ RuntimeReleaseManifest,
26
+ KEntry | KIdentity
27
+ >;
28
+
29
+ function optionalRuntimeComponent<
30
+ KEntry extends keyof RuntimeReleaseManifest,
31
+ KIdentity extends keyof RuntimeReleaseManifest,
32
+ >(input: {
33
+ value: Record<string, unknown>;
34
+ entryField: KEntry;
35
+ identityField: KIdentity;
36
+ canonicalEntry: RuntimeReleaseManifest[KEntry];
37
+ }): RuntimeComponentPair<KEntry, KIdentity> | undefined {
38
+ const rawEntry = input.value[input.entryField as string];
39
+ const rawIdentity = input.value[input.identityField as string];
40
+ if (rawEntry === undefined && rawIdentity === undefined) return undefined;
41
+ const entry = requireString(rawEntry, input.entryField as string);
42
+ if (entry !== input.canonicalEntry) {
43
+ throw new Error(`RELEASE_MANIFEST_INVALID: ${String(input.entryField)} must be ${String(input.canonicalEntry)}, got ${entry}`);
44
+ }
45
+ const identity = requireSha256Identity(rawIdentity, input.identityField as string);
46
+ return {
47
+ [input.entryField]: input.canonicalEntry,
48
+ [input.identityField]: identity,
49
+ } as RuntimeComponentPair<KEntry, KIdentity>;
50
+ }
51
+
52
+ export const COMPILED_RUNTIME_RELEASE_COMPONENT_FIELDS = [
53
+ 'diagnosticEntrypoint', 'diagnosticArtifactIdentity',
54
+ 'browserNodeBridgeEntrypoint', 'browserNodeBridgeArtifactIdentity',
55
+ 'browserHandoffEntrypoint', 'browserHandoffArtifactIdentity',
56
+ 'processRunnerEntrypoint', 'processRunnerArtifactIdentity',
57
+ 'checkRunnerEntrypoint', 'checkRunnerArtifactIdentity',
58
+ 'pluginActionSidecarEntrypoint', 'pluginActionSidecarArtifactIdentity',
59
+ 'externalPluginProbeEntrypoint', 'externalPluginProbeArtifactIdentity',
60
+ 'codeGraphNodeEntrypoint', 'codeGraphNodeArtifactIdentity',
61
+ 'codeGraphSidecarEntrypoint', 'codeGraphSidecarArtifactIdentity',
62
+ 'codeGraphLibraryRoot', 'codeGraphLibraryArtifactIdentity',
63
+ 'packageRoot', 'packageArtifactIdentity',
64
+ 'controllerUiRoot', 'controllerUiArtifactIdentity',
65
+ ] as const satisfies readonly (keyof RuntimeReleaseManifest)[];
66
+
67
+ /**
68
+ * Compiled self-host candidates have a closed execution surface. Package
69
+ * launcher releases are a separate source-backed release form and therefore
70
+ * intentionally do not satisfy this contract.
71
+ */
72
+ export type CompleteCompiledRuntimeReleaseManifest = RuntimeReleaseManifest & Required<Pick<
73
+ RuntimeReleaseManifest,
74
+ typeof COMPILED_RUNTIME_RELEASE_COMPONENT_FIELDS[number]
75
+ >>;
76
+
77
+ export function requireCompleteCompiledRuntimeReleaseManifest(
78
+ manifest: RuntimeReleaseManifest,
79
+ ): asserts manifest is CompleteCompiledRuntimeReleaseManifest {
80
+ for (const field of COMPILED_RUNTIME_RELEASE_COMPONENT_FIELDS) {
81
+ if (manifest[field] === undefined) {
82
+ throw new Error(`RUNTIME_RELEASE_COMPILED_COMPONENT_MISSING: ${field}`);
83
+ }
84
+ }
85
+ }
86
+
16
87
  export function loadRuntimeReleaseManifest(
17
88
  path: string,
18
89
  expectedControllerHome: string,
@@ -50,18 +121,60 @@ export function loadRuntimeReleaseManifest(
50
121
  `RELEASE_DATABASE_SCHEMA_INCOMPATIBLE: runtime=${CONTROL_PLANE_SCHEMA_VERSION} supported=${minimum}-${maximum}`,
51
122
  );
52
123
  }
53
- let diagnosticEntrypoint: 'forge-cli' | undefined;
54
- let diagnosticArtifactIdentity: string | undefined;
55
- if (value.diagnosticEntrypoint !== undefined) {
56
- const diagnosticEntry = requireString(value.diagnosticEntrypoint, 'diagnosticEntrypoint');
57
- if (diagnosticEntry !== 'forge-cli') {
58
- throw new Error(`RELEASE_MANIFEST_INVALID: diagnosticEntrypoint must be forge-cli, got ${diagnosticEntry}`);
59
- }
60
- diagnosticEntrypoint = 'forge-cli';
61
- diagnosticArtifactIdentity = requireString(value.diagnosticArtifactIdentity, 'diagnosticArtifactIdentity');
62
- } else if (value.diagnosticArtifactIdentity !== undefined) {
63
- throw new Error('RELEASE_MANIFEST_INVALID: diagnosticArtifactIdentity requires diagnosticEntrypoint');
64
- }
124
+ const diagnostic = optionalRuntimeComponent({
125
+ value,
126
+ entryField: 'diagnosticEntrypoint',
127
+ identityField: 'diagnosticArtifactIdentity',
128
+ canonicalEntry: 'forge-cli',
129
+ });
130
+ const browserNodeBridge = optionalRuntimeComponent({
131
+ value,
132
+ entryField: 'browserNodeBridgeEntrypoint',
133
+ identityField: 'browserNodeBridgeArtifactIdentity',
134
+ canonicalEntry: 'browser-node-bridge-host.js',
135
+ });
136
+ const browserHandoff = optionalRuntimeComponent({
137
+ value,
138
+ entryField: 'browserHandoffEntrypoint',
139
+ identityField: 'browserHandoffArtifactIdentity',
140
+ canonicalEntry: 'browser-handoff-host.js',
141
+ });
142
+ const processRunner = optionalRuntimeComponent({
143
+ value,
144
+ entryField: 'processRunnerEntrypoint',
145
+ identityField: 'processRunnerArtifactIdentity',
146
+ canonicalEntry: 'process-runner.js',
147
+ });
148
+ const checkRunner = optionalRuntimeComponent({
149
+ value,
150
+ entryField: 'checkRunnerEntrypoint',
151
+ identityField: 'checkRunnerArtifactIdentity',
152
+ canonicalEntry: 'forge-check-runner',
153
+ });
154
+ const pluginActionSidecar = optionalRuntimeComponent({
155
+ value,
156
+ entryField: 'pluginActionSidecarEntrypoint',
157
+ identityField: 'pluginActionSidecarArtifactIdentity',
158
+ canonicalEntry: 'forge-plugin-action-sidecar',
159
+ });
160
+ const externalPluginProbe = optionalRuntimeComponent({
161
+ value,
162
+ entryField: 'externalPluginProbeEntrypoint',
163
+ identityField: 'externalPluginProbeArtifactIdentity',
164
+ canonicalEntry: 'external-unix-socket-probe.cjs',
165
+ });
166
+ const packageSnapshot = optionalRuntimeComponent({
167
+ value,
168
+ entryField: 'packageRoot',
169
+ identityField: 'packageArtifactIdentity',
170
+ canonicalEntry: 'package',
171
+ });
172
+ const controllerUi = optionalRuntimeComponent({
173
+ value,
174
+ entryField: 'controllerUiRoot',
175
+ identityField: 'controllerUiArtifactIdentity',
176
+ canonicalEntry: 'ui-dist',
177
+ });
65
178
  const browserAutomationHelperFields = [
66
179
  value.browserAutomationHelperEntrypoint,
67
180
  value.browserAutomationHelperArtifactIdentity,
@@ -112,11 +225,11 @@ export function loadRuntimeReleaseManifest(
112
225
  }
113
226
  codeGraphRuntime = {
114
227
  codeGraphNodeEntrypoint: 'codegraph-node',
115
- codeGraphNodeArtifactIdentity: requireString(value.codeGraphNodeArtifactIdentity, 'codeGraphNodeArtifactIdentity'),
228
+ codeGraphNodeArtifactIdentity: requireSha256Identity(value.codeGraphNodeArtifactIdentity, 'codeGraphNodeArtifactIdentity'),
116
229
  codeGraphSidecarEntrypoint: 'codegraph-sidecar.cjs',
117
- codeGraphSidecarArtifactIdentity: requireString(value.codeGraphSidecarArtifactIdentity, 'codeGraphSidecarArtifactIdentity'),
230
+ codeGraphSidecarArtifactIdentity: requireSha256Identity(value.codeGraphSidecarArtifactIdentity, 'codeGraphSidecarArtifactIdentity'),
118
231
  codeGraphLibraryRoot: 'codegraph-lib',
119
- codeGraphLibraryArtifactIdentity: requireString(value.codeGraphLibraryArtifactIdentity, 'codeGraphLibraryArtifactIdentity'),
232
+ codeGraphLibraryArtifactIdentity: requireSha256Identity(value.codeGraphLibraryArtifactIdentity, 'codeGraphLibraryArtifactIdentity'),
120
233
  };
121
234
  }
122
235
 
@@ -136,9 +249,17 @@ export function loadRuntimeReleaseManifest(
136
249
  releaseId: requireString(value.releaseId, 'releaseId'),
137
250
  artifactIdentity: requireString(value.artifactIdentity, 'artifactIdentity'),
138
251
  entrypoint: 'forge-runtime',
139
- ...(diagnosticEntrypoint ? { diagnosticEntrypoint, diagnosticArtifactIdentity } : {}),
252
+ ...(diagnostic ?? {}),
253
+ ...(browserNodeBridge ?? {}),
254
+ ...(browserHandoff ?? {}),
255
+ ...(processRunner ?? {}),
256
+ ...(checkRunner ?? {}),
257
+ ...(pluginActionSidecar ?? {}),
258
+ ...(externalPluginProbe ?? {}),
140
259
  ...(browserAutomationHelper ?? {}),
141
260
  ...(codeGraphRuntime ?? {}),
261
+ ...(packageSnapshot ?? {}),
262
+ ...(controllerUi ?? {}),
142
263
  arguments: argumentsValue as string[],
143
264
  configurationSchemaVersion: 1,
144
265
  controllerHome,
@@ -5,6 +5,8 @@ import { dirname, join, relative, resolve } from 'path';
5
5
  import { runProcess } from '../../effects/process-runner';
6
6
  import { resolveBunExecutable } from '../shared/process-environment';
7
7
  import { CONTROL_PLANE_SCHEMA_VERSION } from '../control-plane/persistence/sqlite-store';
8
+ import { loadRuntimeReleaseManifest, requireCompleteCompiledRuntimeReleaseManifest } from './release-manifest';
9
+ import { packageRuntimeFileIndex, stagePackageRuntimeSnapshot } from './package-runtime-release';
8
10
 
9
11
  /**
10
12
  * Stage one immutable Forge Runtime release below Controller Home. The staged
@@ -24,6 +26,7 @@ export interface MacOSRuntimeCodeSigning {
24
26
  }
25
27
 
26
28
  export interface StagedRuntimeRelease {
29
+ controllerHome: string;
27
30
  releasePath: string;
28
31
  manifestPath: string;
29
32
  releaseId: string;
@@ -38,6 +41,7 @@ export interface StagedRuntimeRelease {
38
41
  codeGraphNodeArtifactIdentity?: string;
39
42
  codeGraphSidecarArtifactIdentity?: string;
40
43
  codeGraphLibraryArtifactIdentity?: string;
44
+ packageArtifactIdentity?: string;
41
45
  controllerUiArtifactIdentity?: string;
42
46
  macosCodeSigning?: MacOSRuntimeCodeSigning;
43
47
  manifestSha256: string;
@@ -309,6 +313,13 @@ export function stageRuntimeReleaseFromCandidateSource(input: {
309
313
  let rawManifest: Record<string, unknown>;
310
314
  try { rawManifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as Record<string, unknown>; }
311
315
  catch (error) { throw new Error(`RUNTIME_RELEASE_CANDIDATE_MANIFEST_INVALID: ${error instanceof Error ? error.message : String(error)}`); }
316
+ const manifest = loadRuntimeReleaseManifest(manifestPath, controllerHome);
317
+ requireCompleteCompiledRuntimeReleaseManifest(manifest);
318
+ if (manifest.releaseId !== receipt.releaseId
319
+ || manifest.artifactIdentity !== receipt.artifactIdentity
320
+ || manifest.sourceCommit !== receipt.sourceCommit) {
321
+ throw new Error('RUNTIME_RELEASE_CANDIDATE_MANIFEST_RECEIPT_MISMATCH');
322
+ }
312
323
  const platform = dependencies.platform ?? process.platform;
313
324
  const macosCodeSigning = parseMacOSRuntimeCodeSigning(rawManifest.macosCodeSigning, 'RUNTIME_RELEASE_CANDIDATE_SIGNING_INVALID');
314
325
  if (platform === 'darwin') {
@@ -322,15 +333,30 @@ export function stageRuntimeReleaseFromCandidateSource(input: {
322
333
  }
323
334
  }
324
335
 
325
- return {
336
+ const staged: StagedRuntimeRelease = {
337
+ controllerHome,
326
338
  releasePath,
327
339
  manifestPath,
328
340
  releaseId: receipt.releaseId,
329
341
  artifactIdentity: receipt.artifactIdentity,
342
+ diagnosticArtifactIdentity: manifest.diagnosticArtifactIdentity,
343
+ browserNodeBridgeArtifactIdentity: manifest.browserNodeBridgeArtifactIdentity,
344
+ browserHandoffArtifactIdentity: manifest.browserHandoffArtifactIdentity,
345
+ processRunnerArtifactIdentity: manifest.processRunnerArtifactIdentity,
346
+ checkRunnerArtifactIdentity: manifest.checkRunnerArtifactIdentity,
347
+ pluginActionSidecarArtifactIdentity: manifest.pluginActionSidecarArtifactIdentity,
348
+ externalPluginProbeArtifactIdentity: manifest.externalPluginProbeArtifactIdentity,
349
+ codeGraphNodeArtifactIdentity: manifest.codeGraphNodeArtifactIdentity,
350
+ codeGraphSidecarArtifactIdentity: manifest.codeGraphSidecarArtifactIdentity,
351
+ codeGraphLibraryArtifactIdentity: manifest.codeGraphLibraryArtifactIdentity,
352
+ packageArtifactIdentity: manifest.packageArtifactIdentity,
353
+ controllerUiArtifactIdentity: manifest.controllerUiArtifactIdentity,
330
354
  ...(macosCodeSigning ? { macosCodeSigning } : {}),
331
355
  manifestSha256: receipt.manifestSha256,
332
356
  sourceCommit: receipt.sourceCommit,
333
357
  };
358
+ assertRuntimeReleaseFiles(staged, dependencies);
359
+ return staged;
334
360
  }
335
361
 
336
362
  function defaultCompileBinary(input: { sourceRoot: string; outputPath: string; entryPath?: string }): { ok: boolean; stderr?: string; stdout?: string; error?: string } {
@@ -547,6 +573,16 @@ export function stageRuntimeRelease(input: {
547
573
  cpSync(sourceControllerUiPath, controllerUiPath, { recursive: true, force: false });
548
574
  const controllerUiArtifactIdentity = `sha256:${sha256Directory(controllerUiPath)}`;
549
575
 
576
+ // The persistent OAuth/Connector is source-backed even when the primary
577
+ // Runtime itself is compiled. Co-locate one immutable package snapshot in
578
+ // the same release so standalone Recovery can bind the Connector to the
579
+ // exact active release instead of retaining an older package release.
580
+ const packageRoot = 'package' as const;
581
+ const packagePath = join(staging, packageRoot);
582
+ const packageRecords = packageRuntimeFileIndex(sourceRoot);
583
+ stagePackageRuntimeSnapshot(sourceRoot, packagePath, packageRecords);
584
+ const packageArtifactIdentity = `sha256:${sha256Directory(packagePath)}`;
585
+
550
586
  const manifest = {
551
587
  schemaVersion: 1,
552
588
  releaseId,
@@ -573,6 +609,8 @@ export function stageRuntimeRelease(input: {
573
609
  codeGraphSidecarArtifactIdentity,
574
610
  codeGraphLibraryRoot,
575
611
  codeGraphLibraryArtifactIdentity,
612
+ packageRoot,
613
+ packageArtifactIdentity,
576
614
  controllerUiRoot,
577
615
  controllerUiArtifactIdentity,
578
616
  arguments: [],
@@ -592,6 +630,7 @@ export function stageRuntimeRelease(input: {
592
630
  writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
593
631
  renameSync(staging, releasePath);
594
632
  return {
633
+ controllerHome: resolve(input.controllerHome),
595
634
  releasePath,
596
635
  manifestPath: join(releasePath, 'manifest.json'),
597
636
  releaseId,
@@ -602,10 +641,12 @@ export function stageRuntimeRelease(input: {
602
641
  browserHandoffArtifactIdentity,
603
642
  processRunnerArtifactIdentity,
604
643
  checkRunnerArtifactIdentity,
644
+ pluginActionSidecarArtifactIdentity,
605
645
  externalPluginProbeArtifactIdentity,
606
646
  codeGraphNodeArtifactIdentity,
607
647
  codeGraphSidecarArtifactIdentity,
608
648
  codeGraphLibraryArtifactIdentity,
649
+ packageArtifactIdentity,
609
650
  controllerUiArtifactIdentity,
610
651
  manifestSha256: createHash('sha256').update(`${JSON.stringify(manifest, null, 2)}\n`).digest('hex'),
611
652
  sourceCommit,
@@ -617,13 +658,53 @@ export function stageRuntimeRelease(input: {
617
658
  }
618
659
 
619
660
  export function assertRuntimeReleaseFiles(release: StagedRuntimeRelease, dependencies: { platform?: NodeJS.Platform; inspectMacOSRuntime?: (executable: string) => MacOSRuntimeCodeSigning } = {}): void {
661
+ const assertRegularFile = (path: string, missingCode: string): void => {
662
+ if (!existsSync(path)) throw new Error(`${missingCode}: ${path}`);
663
+ const status = lstatSync(path);
664
+ if (status.isSymbolicLink() || !status.isFile()) throw new Error(`RUNTIME_RELEASE_PATH_NOT_REGULAR_FILE: ${path}`);
665
+ };
666
+ const assertExecutable = (path: string): void => {
667
+ if ((statSync(path).mode & 0o111) === 0) throw new Error(`RUNTIME_RELEASE_ENTRYPOINT_NOT_EXECUTABLE: ${path}`);
668
+ };
669
+ const assertFileIdentity = (path: string, expected: string): void => {
670
+ const actual = `sha256:${sha256(path)}`;
671
+ if (actual !== expected) throw new Error(`RUNTIME_RELEASE_ARTIFACT_IDENTITY_MISMATCH: ${path}`);
672
+ };
673
+ const assertDirectoryIdentity = (path: string, expected: string): void => {
674
+ if (!existsSync(path)) throw new Error(`RUNTIME_RELEASE_DIRECTORY_MISSING: ${path}`);
675
+ const status = lstatSync(path);
676
+ if (status.isSymbolicLink() || !status.isDirectory()) throw new Error(`RUNTIME_RELEASE_PATH_NOT_DIRECTORY: ${path}`);
677
+ const actual = `sha256:${sha256Directory(path)}`;
678
+ if (actual !== expected) throw new Error(`RUNTIME_RELEASE_ARTIFACT_IDENTITY_MISMATCH: ${path}`);
679
+ };
680
+ const assertComponentFile = (input: { path: string; identity?: string; missingCode: string; executable?: boolean }): void => {
681
+ if (!input.identity) return;
682
+ assertRegularFile(input.path, input.missingCode);
683
+ assertFileIdentity(input.path, input.identity);
684
+ if (input.executable) assertExecutable(input.path);
685
+ };
686
+
620
687
  if (!existsSync(release.releasePath) || !existsSync(release.manifestPath)) {
621
688
  throw new Error(`RUNTIME_RELEASE_FILES_MISSING: ${release.releasePath}`);
622
689
  }
623
- const runtimePath = join(release.releasePath, 'forge-runtime');
624
- if (!existsSync(runtimePath)) {
625
- throw new Error(`RUNTIME_RELEASE_ENTRYPOINT_MISSING: ${runtimePath}`);
690
+ const releasesRoot = join(resolve(release.controllerHome), 'runtime', 'releases');
691
+ if (resolve(release.releasePath) !== join(releasesRoot, release.releaseId)
692
+ || resolve(release.manifestPath) !== join(release.releasePath, 'manifest.json')) {
693
+ throw new Error('RUNTIME_RELEASE_PATH_OUTSIDE_CONTROLLER_HOME');
694
+ }
695
+ const releaseStatus = lstatSync(release.releasePath);
696
+ const manifestStatus = lstatSync(release.manifestPath);
697
+ if (releaseStatus.isSymbolicLink() || !releaseStatus.isDirectory()
698
+ || manifestStatus.isSymbolicLink() || !manifestStatus.isFile()) {
699
+ throw new Error(`RUNTIME_RELEASE_PATH_NOT_PHYSICAL: ${release.releasePath}`);
626
700
  }
701
+ if (sha256(release.manifestPath) !== release.manifestSha256) {
702
+ throw new Error('RUNTIME_RELEASE_MANIFEST_IDENTITY_MISMATCH');
703
+ }
704
+ const runtimePath = join(release.releasePath, 'forge-runtime');
705
+ assertRegularFile(runtimePath, 'RUNTIME_RELEASE_ENTRYPOINT_MISSING');
706
+ assertExecutable(runtimePath);
707
+ assertFileIdentity(runtimePath, release.artifactIdentity);
627
708
  const platform = dependencies.platform ?? process.platform;
628
709
  if (platform === 'darwin' && release.macosCodeSigning) {
629
710
  const actual = (dependencies.inspectMacOSRuntime ?? inspectMacOSRuntimeCodeSigning)(runtimePath);
@@ -634,38 +715,31 @@ export function assertRuntimeReleaseFiles(release: StagedRuntimeRelease, depende
634
715
  throw new Error('RUNTIME_RELEASE_MACOS_SIGNING_MISMATCH');
635
716
  }
636
717
  }
637
- if (release.diagnosticArtifactIdentity && !existsSync(join(release.releasePath, 'forge-cli'))) {
638
- throw new Error(`RUNTIME_RELEASE_DIAGNOSTIC_ENTRYPOINT_MISSING: ${join(release.releasePath, 'forge-cli')}`);
639
- }
640
- if (release.browserNodeBridgeArtifactIdentity && !existsSync(join(release.releasePath, 'browser-node-bridge-host.js'))) {
641
- throw new Error(`RUNTIME_RELEASE_BROWSER_NODE_HOST_MISSING: ${join(release.releasePath, 'browser-node-bridge-host.js')}`);
642
- }
643
- if (release.browserHandoffArtifactIdentity && !existsSync(join(release.releasePath, 'browser-handoff-host.js'))) {
644
- throw new Error(`RUNTIME_RELEASE_BROWSER_HANDOFF_HOST_MISSING: ${join(release.releasePath, 'browser-handoff-host.js')}`);
645
- }
646
- if (release.processRunnerArtifactIdentity && !existsSync(join(release.releasePath, 'process-runner.js'))) {
647
- throw new Error(`RUNTIME_RELEASE_PROCESS_RUNNER_MISSING: ${join(release.releasePath, 'process-runner.js')}`);
648
- }
649
- if (release.checkRunnerArtifactIdentity && !existsSync(join(release.releasePath, 'forge-check-runner'))) {
650
- throw new Error(`RUNTIME_RELEASE_CHECK_RUNNER_MISSING: ${join(release.releasePath, 'forge-check-runner')}`);
651
- }
652
- if (release.pluginActionSidecarArtifactIdentity && !existsSync(join(release.releasePath, 'forge-plugin-action-sidecar'))) {
653
- throw new Error(`RUNTIME_RELEASE_PLUGIN_ACTION_SIDECAR_MISSING: ${join(release.releasePath, 'forge-plugin-action-sidecar')}`);
654
- }
655
- if (release.externalPluginProbeArtifactIdentity && !existsSync(join(release.releasePath, 'external-unix-socket-probe.cjs'))) {
656
- throw new Error(`RUNTIME_RELEASE_EXTERNAL_PLUGIN_PROBE_MISSING: ${join(release.releasePath, 'external-unix-socket-probe.cjs')}`);
657
- }
658
- if (release.codeGraphNodeArtifactIdentity && !existsSync(join(release.releasePath, 'codegraph-node'))) {
659
- throw new Error(`RUNTIME_RELEASE_CODEGRAPH_NODE_MISSING: ${join(release.releasePath, 'codegraph-node')}`);
660
- }
661
- if (release.codeGraphSidecarArtifactIdentity && !existsSync(join(release.releasePath, 'codegraph-sidecar.cjs'))) {
662
- throw new Error(`RUNTIME_RELEASE_CODEGRAPH_SIDECAR_MISSING: ${join(release.releasePath, 'codegraph-sidecar.cjs')}`);
718
+ assertComponentFile({ path: join(release.releasePath, 'forge-cli'), identity: release.diagnosticArtifactIdentity, missingCode: 'RUNTIME_RELEASE_DIAGNOSTIC_ENTRYPOINT_MISSING', executable: true });
719
+ assertComponentFile({ path: join(release.releasePath, 'browser-node-bridge-host.js'), identity: release.browserNodeBridgeArtifactIdentity, missingCode: 'RUNTIME_RELEASE_BROWSER_NODE_HOST_MISSING', executable: true });
720
+ assertComponentFile({ path: join(release.releasePath, 'browser-handoff-host.js'), identity: release.browserHandoffArtifactIdentity, missingCode: 'RUNTIME_RELEASE_BROWSER_HANDOFF_HOST_MISSING', executable: true });
721
+ assertComponentFile({ path: join(release.releasePath, 'process-runner.js'), identity: release.processRunnerArtifactIdentity, missingCode: 'RUNTIME_RELEASE_PROCESS_RUNNER_MISSING', executable: true });
722
+ assertComponentFile({ path: join(release.releasePath, 'forge-check-runner'), identity: release.checkRunnerArtifactIdentity, missingCode: 'RUNTIME_RELEASE_CHECK_RUNNER_MISSING', executable: true });
723
+ assertComponentFile({ path: join(release.releasePath, 'forge-plugin-action-sidecar'), identity: release.pluginActionSidecarArtifactIdentity, missingCode: 'RUNTIME_RELEASE_PLUGIN_ACTION_SIDECAR_MISSING', executable: true });
724
+ assertComponentFile({ path: join(release.releasePath, 'external-unix-socket-probe.cjs'), identity: release.externalPluginProbeArtifactIdentity, missingCode: 'RUNTIME_RELEASE_EXTERNAL_PLUGIN_PROBE_MISSING', executable: true });
725
+ assertComponentFile({ path: join(release.releasePath, 'codegraph-node'), identity: release.codeGraphNodeArtifactIdentity, missingCode: 'RUNTIME_RELEASE_CODEGRAPH_NODE_MISSING', executable: true });
726
+ assertComponentFile({ path: join(release.releasePath, 'codegraph-sidecar.cjs'), identity: release.codeGraphSidecarArtifactIdentity, missingCode: 'RUNTIME_RELEASE_CODEGRAPH_SIDECAR_MISSING', executable: true });
727
+ if (release.codeGraphLibraryArtifactIdentity) {
728
+ const libraryRoot = join(release.releasePath, 'codegraph-lib');
729
+ if (!existsSync(join(libraryRoot, 'dist', 'index.js'))) {
730
+ throw new Error(`RUNTIME_RELEASE_CODEGRAPH_LIBRARY_MISSING: ${join(libraryRoot, 'dist', 'index.js')}`);
731
+ }
732
+ assertDirectoryIdentity(libraryRoot, release.codeGraphLibraryArtifactIdentity);
663
733
  }
664
- if (release.codeGraphLibraryArtifactIdentity && !existsSync(join(release.releasePath, 'codegraph-lib', 'dist', 'index.js'))) {
665
- throw new Error(`RUNTIME_RELEASE_CODEGRAPH_LIBRARY_MISSING: ${join(release.releasePath, 'codegraph-lib', 'dist', 'index.js')}`);
734
+ if (release.packageArtifactIdentity) {
735
+ const packageRoot = join(release.releasePath, 'package');
736
+ assertDirectoryIdentity(packageRoot, release.packageArtifactIdentity);
666
737
  }
667
- if (release.controllerUiArtifactIdentity
668
- && (!existsSync(join(release.releasePath, 'ui-dist', 'app.js')) || !existsSync(join(release.releasePath, 'ui-dist', 'app.css')))) {
669
- throw new Error(`RUNTIME_RELEASE_CONTROLLER_UI_MISSING: ${join(release.releasePath, 'ui-dist')}`);
738
+ if (release.controllerUiArtifactIdentity) {
739
+ const uiRoot = join(release.releasePath, 'ui-dist');
740
+ if (!existsSync(join(uiRoot, 'app.js')) || !existsSync(join(uiRoot, 'app.css'))) {
741
+ throw new Error(`RUNTIME_RELEASE_CONTROLLER_UI_MISSING: ${uiRoot}`);
742
+ }
743
+ assertDirectoryIdentity(uiRoot, release.controllerUiArtifactIdentity);
670
744
  }
671
745
  }
@@ -39,8 +39,14 @@ export interface RuntimeReleaseManifest {
39
39
  browserNodeBridgeArtifactIdentity?: string;
40
40
  browserHandoffEntrypoint?: 'browser-handoff-host.js';
41
41
  browserHandoffArtifactIdentity?: string;
42
+ processRunnerEntrypoint?: 'process-runner.js';
43
+ processRunnerArtifactIdentity?: string;
44
+ checkRunnerEntrypoint?: 'forge-check-runner';
45
+ checkRunnerArtifactIdentity?: string;
42
46
  pluginActionSidecarEntrypoint?: 'forge-plugin-action-sidecar';
43
47
  pluginActionSidecarArtifactIdentity?: string;
48
+ externalPluginProbeEntrypoint?: 'external-unix-socket-probe.cjs';
49
+ externalPluginProbeArtifactIdentity?: string;
44
50
  browserAutomationHelperEntrypoint?: 'browser-automation-helper';
45
51
  browserAutomationHelperArtifactIdentity?: string;
46
52
  browserAutomationHelperContractIdentity?: string;
@@ -50,6 +56,10 @@ export interface RuntimeReleaseManifest {
50
56
  codeGraphSidecarArtifactIdentity?: string;
51
57
  codeGraphLibraryRoot?: 'codegraph-lib';
52
58
  codeGraphLibraryArtifactIdentity?: string;
59
+ packageRoot?: 'package';
60
+ packageArtifactIdentity?: string;
61
+ controllerUiRoot?: 'ui-dist';
62
+ controllerUiArtifactIdentity?: string;
53
63
  arguments: string[];
54
64
  configurationSchemaVersion: 1;
55
65
  controllerHome: string;