@moretea-labs/forge 1.7.0 → 1.7.2

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 +26 -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 +125 -101
  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 +69 -29
  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 +150 -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 +815 -1015
  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 +3 -2
  166. package/src/runtime/root/package-runtime-service.ts +48 -3
  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
@@ -3,8 +3,8 @@ import { spawnSync } from 'child_process';
3
3
  import { getIssue, listIssues, recordTaskVerification, acceptVerifiedTask, projectTaskFromWork } from '../../../cli/controller/issue-store';
4
4
  import { resolveCompletionTargetBranch } from '../../../cli/controller/completion-target';
5
5
  import type { CompletionReceipt, ControllerIssue } from '../../../cli/controller/types';
6
- import { completeRequirementFromWork } from '../persistence/requirement-store';
7
- import { appendWorkEvidence, getWorkContract, recordWorkCompletionReceipt, updateWorkContract } from '../facade/work-contract-store';
6
+ import { getWorkContract, updateWorkContract } from '../facade/work-contract-store';
7
+ import { completeWorkWithReceipt } from './work-completion-authority';
8
8
  import { isRepositoryCompletionReceipt, WORK_RECONCILIATION_METHODS, WORK_RECONCILIATION_OUTCOMES } from '../facade/types';
9
9
  import type {
10
10
  WorkReconciliationMethod,
@@ -289,32 +289,13 @@ export function acceptVerifiedTaskFromReviewedWorkReconciliation(input: Controll
289
289
  recordedAt,
290
290
  };
291
291
  const completionOutcome = noChange ? 'completed_no_change' : contract.workKind === 'remote_effect' ? 'completed_remote' : 'completed_changed';
292
- const recorded = recordWorkCompletionReceipt(
292
+ const recorded = completeWorkWithReceipt(
293
293
  { controllerHome: input.controllerHome, repoId: input.repoId },
294
294
  input.workId,
295
295
  receipt,
296
296
  completionOutcome,
297
297
  contract.workKind,
298
298
  );
299
- if (recorded.requirementId) {
300
- try {
301
- completeRequirementFromWork(
302
- { controllerHome: input.controllerHome },
303
- { requirementId: recorded.requirementId, work: recorded },
304
- );
305
- } catch (error) {
306
- try {
307
- appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
308
- title: 'requirement completion projection pending',
309
- summary: `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000),
310
- detailLevel: 'summary',
311
- });
312
- } catch {
313
- // The completion receipt was already persisted; legacy Task projection
314
- // remains retryable even if this diagnostic cannot be stored.
315
- }
316
- }
317
- }
318
299
  const recordedReceipt = recorded.completionReceipt;
319
300
  const projectedReceipt = recordedReceipt && isRepositoryCompletionReceipt(recordedReceipt) ? recordedReceipt : receipt;
320
301
  const projectedVerification = { ...task.verification!, completionReceipt: projectedReceipt };
@@ -438,32 +419,12 @@ export function acceptVerifiedTaskFromControllerWork(input: ControllerWorkTaskRe
438
419
  // historical Task write is interrupted, the Work authority is still
439
420
  // durable and a retry can safely rebuild the projection.
440
421
  const completionOutcome = noChange ? 'completed_no_change' : contract.workKind === 'remote_effect' ? 'completed_remote' : 'completed_changed';
441
- const recorded = recordWorkCompletionReceipt(
422
+ const recorded = completeWorkWithReceipt(
442
423
  { controllerHome: input.controllerHome, repoId: input.repoId },
443
424
  input.workId,
444
425
  receipt,
445
426
  completionOutcome,
446
427
  );
447
- if (recorded.requirementId) {
448
- try {
449
- completeRequirementFromWork(
450
- { controllerHome: input.controllerHome },
451
- { requirementId: recorded.requirementId, work: recorded },
452
- );
453
- } catch (error) {
454
- try {
455
- appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
456
- title: 'requirement completion projection pending',
457
- summary: `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000),
458
- detailLevel: 'summary',
459
- });
460
- } catch {
461
- // The Work receipt is canonical and already durable. Keep historical
462
- // reconciliation/task projection retryable instead of surfacing a false
463
- // completion failure from this downstream diagnostic path.
464
- }
465
- }
466
- }
467
428
  const recordedReceipt = recorded.completionReceipt;
468
429
  const projectedReceipt = recordedReceipt && isRepositoryCompletionReceipt(recordedReceipt) ? recordedReceipt : receipt;
469
430
  const projectedVerification = {
@@ -19,7 +19,7 @@ import { managedPathInside, managedWorktreeStorageRoot } from '../../../cli/repo
19
19
  import { markRepositoryProjectionDirty } from '../../projections/invalidation';
20
20
  import { listControlPlaneRecords } from '../persistence/sqlite-store';
21
21
  import { getWorkContract } from '../facade/work-contract-store';
22
- import { getControllerSession, releaseControllerSession } from '../facade/controller-session-store';
22
+ import { getControllerSession, releaseObservedControllerSession } from '../facade/controller-session-store';
23
23
  import { isRepositoryCompletionReceipt, isTerminalWorkContractStatus, type WorkContract } from '../facade/types';
24
24
  import {
25
25
  cancelProcess,
@@ -968,8 +968,18 @@ export async function reconcileTerminalWorkCleanups(
968
968
  const workId = cleaned.handle.workContractId ?? cleaned.handle.workId;
969
969
  const controllerSession = getControllerSession({ controllerHome, repoId: repository.repoId }, workId);
970
970
  if (controllerSession) {
971
- releaseControllerSession({ controllerHome, repoId: repository.repoId }, workId, controllerSession.controllerId);
972
- cleaned.receipt.ownership.controllerLease = 'released';
971
+ const released = releaseObservedControllerSession(
972
+ { controllerHome, repoId: repository.repoId },
973
+ { workId, actor: `terminal-cleanup:${originalHandle.workId}`, owner: controllerSession },
974
+ );
975
+ if (released.allowed) {
976
+ cleaned.receipt.ownership.controllerLease = 'released';
977
+ } else {
978
+ cleaned.receipt.complete = false;
979
+ cleaned.receipt.partial = true;
980
+ cleaned.receipt.completedAt = undefined;
981
+ cleaned.receipt.blockers = appendUnique(cleaned.receipt.blockers, `controller lease release fenced: ${released.reason}`);
982
+ }
973
983
  } else {
974
984
  cleaned.receipt.ownership.controllerLease = 'already_released';
975
985
  }
@@ -0,0 +1,56 @@
1
+ import { listControllerChecks, type ControllerCheck } from '../../../cli/controller/check-runner';
2
+ import { selectRepositoryCheckout } from '../../../cli/repositories/registry';
3
+ import type { RepositoryRecord } from '../../../cli/repositories/types';
4
+ import { getWorkContract } from '../facade/work-contract-store';
5
+ import type { WorkContract } from '../facade/types';
6
+
7
+ export interface WorkVerificationContext {
8
+ store: { controllerHome: string; repoId: string };
9
+ repository: RepositoryRecord;
10
+ workContract?: WorkContract;
11
+ checks: ControllerCheck[];
12
+ }
13
+
14
+ export type WorkVerificationContextResolution =
15
+ | { ok: true; context: WorkVerificationContext }
16
+ | { ok: false; code: 'WORK_VERIFICATION_CHECKOUT_UNAVAILABLE'; detail: string; workContract?: WorkContract };
17
+
18
+ /**
19
+ * Resolve the only repository/check-registry context that may be used to verify
20
+ * a Work. A Work-bound checkout is semantic verification authority: if it can no
21
+ * longer be resolved, verification must fail closed instead of silently reading
22
+ * the canonical/main registry.
23
+ */
24
+ export function resolveWorkVerificationContext(input: {
25
+ controllerHome: string;
26
+ repository: RepositoryRecord;
27
+ workId?: string;
28
+ }): WorkVerificationContextResolution {
29
+ const store = { controllerHome: input.controllerHome, repoId: input.repository.repoId };
30
+ const workId = input.workId?.trim();
31
+ const workContract = workId ? getWorkContract(store, workId) : undefined;
32
+ let repository = input.repository;
33
+
34
+ if (workContract?.checkoutId) {
35
+ try {
36
+ repository = selectRepositoryCheckout(input.repository, workContract.checkoutId, { allowArchived: true });
37
+ } catch (error) {
38
+ return {
39
+ ok: false,
40
+ code: 'WORK_VERIFICATION_CHECKOUT_UNAVAILABLE',
41
+ detail: error instanceof Error ? error.message : 'Work checkout could not be resolved',
42
+ workContract,
43
+ };
44
+ }
45
+ }
46
+
47
+ return {
48
+ ok: true,
49
+ context: {
50
+ store,
51
+ repository,
52
+ workContract,
53
+ checks: listControllerChecks(repository.canonicalRoot),
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,388 @@
1
+ import { readLatestControllerCheckEvidence } from '../../../cli/controller/check-runner';
2
+ import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
3
+ import type { RepositoryRecord } from '../../../cli/repositories/types';
4
+ import {
5
+ DEFAULT_WORK_CHECK_LEASE_WAIT_MS,
6
+ checkRequiresDurableWorkflow,
7
+ getProcessRecord,
8
+ processCheckCompletionReceipt,
9
+ readPersistedCheckResultReceipt,
10
+ runPersistedCheckViaProcessRuntime,
11
+ } from '../../execution/process-runtime';
12
+ import { classifyPersistedCheckTerminalEvidence } from '../../execution/process-runtime/check-result';
13
+ import { buildFacadeResult } from '../facade/facade-result';
14
+ import { classifyVerificationOutcome, normalizeCheckIds } from '../facade/check-normalization';
15
+ import { verifyGoalWorkloop } from '../facade/goal-workloop';
16
+ import type { FacadeResult, WorkContract } from '../facade/types';
17
+ import { executionIdentityForRepository } from './execution-identity';
18
+ import { commandFingerprint, verificationInputFingerprint, workspaceValidationFingerprint } from './verification-evidence';
19
+ import { resolveWorkVerificationContext } from './work-verification-context';
20
+
21
+ export interface ExecuteWorkVerificationInput {
22
+ controllerHome: string;
23
+ repository: RepositoryRecord;
24
+ workId?: string;
25
+ checkId?: string;
26
+ requestId?: string;
27
+ timeoutMs?: number;
28
+ interactiveWaitMs?: number;
29
+ leaseWaitMs?: number;
30
+ simulate?: {
31
+ infrastructureFailed?: boolean;
32
+ checkFailed?: boolean;
33
+ skipped?: boolean;
34
+ };
35
+ /** Caller-supplied exact ownership proof for durable-class checks. */
36
+ allowDurableCheckExecution?: (input: { work: WorkContract; checkId: string }) => boolean;
37
+ }
38
+
39
+ export interface ExecuteWorkVerificationResult {
40
+ facade: FacadeResult;
41
+ isError: boolean;
42
+ }
43
+
44
+ function result(facade: FacadeResult, isError = false): ExecuteWorkVerificationResult {
45
+ return { facade, isError };
46
+ }
47
+
48
+ /**
49
+ * Canonical Work verification application service.
50
+ *
51
+ * Transports supply authenticated ownership proof when they want to execute a
52
+ * durable-class check, but they never choose the Work checkout, check registry,
53
+ * Process evidence identity, or acceptance/infrastructure classification.
54
+ */
55
+ export async function executeWorkVerification(input: ExecuteWorkVerificationInput): Promise<ExecuteWorkVerificationResult> {
56
+ const workId = input.workId?.trim() ?? '';
57
+ const checkId = input.checkId?.trim() ?? '';
58
+ const resolvedVerification = resolveWorkVerificationContext({
59
+ controllerHome: input.controllerHome,
60
+ repository: input.repository,
61
+ workId,
62
+ });
63
+ if (!resolvedVerification.ok) {
64
+ return result(buildFacadeResult({
65
+ status: 'blocked',
66
+ summary: `${resolvedVerification.code}: ${resolvedVerification.detail}`,
67
+ data: {
68
+ verification: {
69
+ checkId: checkId || undefined,
70
+ outcome: 'infrastructure_failure',
71
+ isAcceptanceFailure: false,
72
+ isInfrastructureIssue: true,
73
+ doesNotRequestTaskChanges: true,
74
+ },
75
+ },
76
+ warnings: ['Work verification never falls back to the canonical/main check registry when the Work-bound checkout is unavailable.'],
77
+ }), true);
78
+ }
79
+
80
+ const { store, workContract, repository: verificationRepository, checks } = resolvedVerification.context;
81
+ if (!checkId) {
82
+ return result(buildFacadeResult({
83
+ status: 'blocked',
84
+ summary: 'Work verification requires a registered check_id.',
85
+ data: {
86
+ verification: {
87
+ outcome: 'check_id_required',
88
+ isAcceptanceFailure: false,
89
+ isInfrastructureIssue: true,
90
+ doesNotRequestTaskChanges: true,
91
+ },
92
+ registeredCheckCount: checks.length,
93
+ },
94
+ warnings: ['CHECK_ID_REQUIRED: pass check_id for one registered repository check.'],
95
+ suggestedNextActions: normalizeCheckIds(checks.slice(0, 3).map((check) => check.id), checks).suggestedNextActions,
96
+ }), true);
97
+ }
98
+
99
+ const workloopCtx = {
100
+ workStore: store,
101
+ handoffStore: store,
102
+ repoId: input.repository.repoId,
103
+ availableChecks: checks,
104
+ };
105
+ if (workId && (!workContract || workContract.status === 'completed' || workContract.status === 'cancelled' || workContract.status === 'failed')) {
106
+ const facade = verifyGoalWorkloop(workloopCtx, { workId, checkId });
107
+ return result(facade, facade.status === 'failed');
108
+ }
109
+
110
+ const classified = classifyVerificationOutcome({ checkId, available: checks });
111
+ if (classified.outcome === 'invalid_check_id') {
112
+ if (workId) return result(verifyGoalWorkloop(workloopCtx, { workId, checkId }));
113
+ return result(buildFacadeResult({
114
+ status: 'ok',
115
+ summary: classified.summary,
116
+ data: {
117
+ verification: {
118
+ checkId,
119
+ outcome: 'invalid_check_id',
120
+ isAcceptanceFailure: false,
121
+ isInfrastructureIssue: true,
122
+ doesNotRequestTaskChanges: true,
123
+ },
124
+ registeredCheckCount: checks.length,
125
+ },
126
+ warnings: classified.warnings,
127
+ suggestedNextActions: normalizeCheckIds(checks.slice(0, 3).map((check) => check.id), checks).suggestedNextActions,
128
+ }));
129
+ }
130
+
131
+ if (input.simulate) {
132
+ if (!workId) {
133
+ return result(buildFacadeResult({
134
+ status: input.simulate.checkFailed ? 'failed' : 'ok',
135
+ summary: 'Simulated verification without WorkContract.',
136
+ data: {
137
+ verification: {
138
+ checkId: classified.normalizedCheckId,
139
+ outcome: input.simulate.skipped ? 'skipped' : input.simulate.infrastructureFailed ? 'infrastructure_failure' : input.simulate.checkFailed ? 'valid_fail' : 'valid_pass',
140
+ isAcceptanceFailure: input.simulate.checkFailed === true,
141
+ simulated: true,
142
+ },
143
+ },
144
+ }), input.simulate.checkFailed === true);
145
+ }
146
+ const facade = verifyGoalWorkloop(workloopCtx, {
147
+ workId,
148
+ checkId: classified.normalizedCheckId ?? checkId,
149
+ infrastructureFailed: input.simulate.infrastructureFailed === true,
150
+ checkFailed: input.simulate.checkFailed === true,
151
+ skipped: input.simulate.skipped === true,
152
+ });
153
+ return result(facade, facade.status === 'failed');
154
+ }
155
+
156
+ try {
157
+ const normalizedCheckId = classified.normalizedCheckId!;
158
+ const verificationStatus = repositoryGitStatus(verificationRepository);
159
+ const observedGitHead = verificationStatus.head;
160
+ const workspaceFingerprint = workspaceValidationFingerprint(verificationRepository.canonicalRoot, verificationStatus);
161
+ const requestedChecks = workContract?.checks.length ? workContract.checks : [normalizedCheckId];
162
+ const verificationRequestFingerprint = observedGitHead ? verificationInputFingerprint({
163
+ sourceRevision: observedGitHead,
164
+ workspaceFingerprint,
165
+ checkId: normalizedCheckId,
166
+ requestedChecks,
167
+ }) : undefined;
168
+ const registeredCheck = checks.find((entry) => entry.id === normalizedCheckId);
169
+ const durableClassCheck = checkRequiresDurableWorkflow(normalizedCheckId, registeredCheck);
170
+ const allowDurableCheckExecution = Boolean(
171
+ durableClassCheck
172
+ && workContract
173
+ && workContract.checks.includes(normalizedCheckId)
174
+ && !workContract.completionReceipt
175
+ && workContract.status === 'running'
176
+ && input.allowDurableCheckExecution?.({ work: workContract, checkId: normalizedCheckId }),
177
+ );
178
+
179
+ const executed = await runPersistedCheckViaProcessRuntime({
180
+ controllerHome: input.controllerHome,
181
+ repoId: verificationRepository.repoId,
182
+ checkoutId: verificationRepository.activeCheckoutId,
183
+ repoRoot: verificationRepository.canonicalRoot,
184
+ executionIdentity: executionIdentityForRepository(verificationRepository, workId ? { workId } : {}),
185
+ checkId: normalizedCheckId,
186
+ timeoutMs: input.timeoutMs,
187
+ interactiveWaitMs: input.interactiveWaitMs ?? 0,
188
+ leaseWaitMs: input.leaseWaitMs ?? (workId ? DEFAULT_WORK_CHECK_LEASE_WAIT_MS : undefined),
189
+ requestId: input.requestId,
190
+ requestSemanticFingerprint: verificationRequestFingerprint,
191
+ workId: workId || undefined,
192
+ commandId: input.requestId,
193
+ verificationSnapshot: workContract ? {
194
+ workId: workContract.workId,
195
+ allowedPaths: workContract.allowedPaths,
196
+ forbiddenPaths: workContract.forbiddenPaths,
197
+ } : undefined,
198
+ allowDurableCheckExecution,
199
+ });
200
+
201
+ if (executed.mode === 'durable') {
202
+ return result(buildFacadeResult({
203
+ status: 'blocked',
204
+ summary: `Check ${normalizedCheckId} requires an explicit durable workflow; no acceptance result was recorded.`,
205
+ data: {
206
+ verification: {
207
+ checkId: normalizedCheckId,
208
+ outcome: 'deferred',
209
+ isAcceptanceFailure: false,
210
+ isInfrastructureIssue: false,
211
+ durable: executed.durable,
212
+ observedGitHead,
213
+ },
214
+ },
215
+ suggestedNextActions: workId ? [{
216
+ label: 'Continue Work with the durable check requirement',
217
+ tool: 'rh_work',
218
+ operation: 'continue',
219
+ payload: { work_id: workId },
220
+ risk: 'workspace_write',
221
+ confidence: 'high',
222
+ }] : [],
223
+ }), true);
224
+ }
225
+
226
+ const handle = executed.process;
227
+ if (!handle) throw new Error(`PROCESS_CHECK_HANDLE_MISSING: ${normalizedCheckId}`);
228
+ const record = getProcessRecord(input.controllerHome, verificationRepository.repoId, handle.processId);
229
+ const checkContentRevision = record?.checkExecution?.revision;
230
+ if (!handle.completed) {
231
+ return result(buildFacadeResult({
232
+ status: 'ok',
233
+ summary: `Check ${normalizedCheckId} is running through Process Runtime; continue other work and reattach to ${handle.processId}.`,
234
+ data: {
235
+ verification: {
236
+ checkId: normalizedCheckId,
237
+ outcome: 'running',
238
+ isAcceptanceFailure: false,
239
+ isInfrastructureIssue: false,
240
+ executed: true,
241
+ completed: false,
242
+ processId: handle.processId,
243
+ processStatus: handle.status,
244
+ deduplicated: handle.deduplicated === true,
245
+ semanticDeduplicated: handle.semanticDeduplicated === true,
246
+ checkContentRevision,
247
+ observedGitHead,
248
+ verificationIsolation: workContract ? 'work_snapshot' : 'shared_checkout',
249
+ revisionSemantics: 'checkContentRevision is a content-bound Check identity; observedGitHead is Git HEAD and is not interchangeable.',
250
+ },
251
+ },
252
+ rawAvailable: false,
253
+ }));
254
+ }
255
+
256
+ if (!record) throw new Error(`PROCESS_CHECK_RECORD_MISSING: ${handle.processId}`);
257
+ const receipt = processCheckCompletionReceipt(record, {
258
+ repoId: verificationRepository.repoId,
259
+ checkId: normalizedCheckId,
260
+ processId: handle.processId,
261
+ ...(record.checkExecution ? {
262
+ checkoutId: verificationRepository.activeCheckoutId,
263
+ workId: workId || undefined,
264
+ requestId: input.requestId,
265
+ checkExecution: {
266
+ cacheKey: record.checkExecution.cacheKey,
267
+ revision: record.checkExecution.revision,
268
+ definitionDigest: record.checkExecution.definitionDigest,
269
+ environmentFingerprint: record.checkExecution.environmentFingerprint,
270
+ timeoutMs: record.checkExecution.timeoutMs,
271
+ scopeKey: record.checkExecution.scopeKey,
272
+ },
273
+ } : {}),
274
+ });
275
+ const structuredCheckResult = readPersistedCheckResultReceipt(record.origin?.checkResultReceiptPath);
276
+ const legacyEvidence = record.origin?.checkResultReceiptPath
277
+ ? undefined
278
+ : readLatestControllerCheckEvidence(verificationRepository.canonicalRoot, normalizedCheckId);
279
+ const evidenceState = classifyPersistedCheckTerminalEvidence(record, normalizedCheckId, { legacyEvidence });
280
+ const failureClass = evidenceState.failureClass;
281
+ const infrastructureFailed = receipt.timedOut
282
+ || receipt.cancelled
283
+ || evidenceState.state !== 'matched'
284
+ || (!receipt.ok && failureClass !== 'acceptance_failure');
285
+ const checkFailed = !receipt.ok && !infrastructureFailed;
286
+ const commonVerification = {
287
+ checkId: normalizedCheckId,
288
+ outcome: infrastructureFailed ? 'infrastructure_failure' : receipt.ok ? 'valid_pass' : 'valid_fail',
289
+ isAcceptanceFailure: checkFailed,
290
+ isInfrastructureIssue: infrastructureFailed,
291
+ executed: true,
292
+ completed: true,
293
+ processId: receipt.processId,
294
+ processStatus: receipt.runtimeStatus,
295
+ ok: receipt.ok,
296
+ timedOut: receipt.timedOut,
297
+ cancelled: receipt.cancelled,
298
+ failureClass: infrastructureFailed ? 'infrastructure_failure' : failureClass,
299
+ deduplicated: handle.deduplicated === true,
300
+ semanticDeduplicated: handle.semanticDeduplicated === true,
301
+ checkContentRevision: receipt.checkRevision,
302
+ observedGitHead,
303
+ revisionSemantics: 'checkContentRevision is a content-bound Check identity; observedGitHead is Git HEAD and is not interchangeable.',
304
+ evidenceArtifactPath: record.origin?.workVerificationSnapshot ? undefined : receipt.artifactPath,
305
+ evidenceReceiptId: receipt.receiptId,
306
+ checkResultReceiptId: structuredCheckResult?.receiptId,
307
+ verificationIsolation: record.origin?.workVerificationSnapshot ? 'work_snapshot' : 'shared_checkout',
308
+ boundedStatus: receipt.ok ? 'pass' : infrastructureFailed ? 'infrastructure_failure' : 'fail',
309
+ evidenceState: evidenceState.state,
310
+ ...(evidenceState.infrastructureReason ? { infrastructureReason: evidenceState.infrastructureReason } : {}),
311
+ ...(record.error?.code ? { processErrorCode: record.error.code } : {}),
312
+ };
313
+
314
+ if (workId) {
315
+ const sourceRevision = observedGitHead ?? undefined;
316
+ const facade = verifyGoalWorkloop(workloopCtx, {
317
+ workId,
318
+ checkId: normalizedCheckId,
319
+ sourceRevision,
320
+ workspaceFingerprint,
321
+ verificationInputFingerprint: sourceRevision ? verificationRequestFingerprint : undefined,
322
+ commandFingerprint: commandFingerprint(normalizedCheckId, receipt.commandId),
323
+ receipt,
324
+ infrastructureFailed,
325
+ checkFailed,
326
+ });
327
+ return result({
328
+ ...facade,
329
+ data: {
330
+ ...(facade.data as Record<string, unknown>),
331
+ verification: {
332
+ ...(typeof facade.data.verification === 'object' && facade.data.verification ? facade.data.verification as Record<string, unknown> : {}),
333
+ ...commonVerification,
334
+ },
335
+ },
336
+ warnings: infrastructureFailed
337
+ ? [...facade.warnings, evidenceState.warning ?? 'infrastructure_failure is distinct from acceptance failure']
338
+ : facade.warnings,
339
+ }, facade.status === 'failed');
340
+ }
341
+
342
+ return result(buildFacadeResult({
343
+ status: checkFailed ? 'failed' : 'ok',
344
+ summary: infrastructureFailed
345
+ ? `Infrastructure failure while running ${normalizedCheckId}; not an acceptance failure.`
346
+ : receipt.ok
347
+ ? `Check ${normalizedCheckId} passed with persisted Process evidence.`
348
+ : `Check ${normalizedCheckId} failed acceptance.`,
349
+ data: { verification: commonVerification },
350
+ warnings: infrastructureFailed ? [evidenceState.warning ?? 'infrastructure_failure is distinct from acceptance failure'] : [],
351
+ rawAvailable: false,
352
+ }), checkFailed);
353
+ } catch (error) {
354
+ const message = error instanceof Error ? error.message : String(error);
355
+ if (workId) {
356
+ const facade = verifyGoalWorkloop(workloopCtx, {
357
+ workId,
358
+ checkId: classified.normalizedCheckId ?? checkId,
359
+ infrastructureFailed: true,
360
+ });
361
+ return result({
362
+ ...facade,
363
+ warnings: [...facade.warnings, `check_runner_error: ${message.slice(0, 200)}`],
364
+ data: { ...(facade.data as Record<string, unknown>), isAcceptanceFailure: false },
365
+ });
366
+ }
367
+ return result(buildFacadeResult({
368
+ status: 'ok',
369
+ summary: `Infrastructure failure invoking Process Runtime for ${classified.normalizedCheckId}; not acceptance failure.`,
370
+ data: {
371
+ verification: {
372
+ checkId: classified.normalizedCheckId,
373
+ outcome: 'infrastructure_failure',
374
+ isAcceptanceFailure: false,
375
+ isInfrastructureIssue: true,
376
+ },
377
+ },
378
+ warnings: [`check_runner_error: ${message.slice(0, 200)}`],
379
+ suggestedNextActions: [{
380
+ label: 'Diagnose runtime (dry-run)',
381
+ tool: 'rh_work',
382
+ operation: 'repair',
383
+ payload: { repair_operation: 'diagnose', dry_run: true },
384
+ risk: 'readonly',
385
+ }],
386
+ }));
387
+ }
388
+ }
@@ -30,6 +30,8 @@ export interface WorkVerificationSnapshotScope {
30
30
 
31
31
  export interface WorkVerificationSnapshot {
32
32
  root: string;
33
+ /** Ephemeral Controller Home visible only to Candidate verification children. */
34
+ isolatedControllerHome: string;
33
35
  sourceHead: string;
34
36
  includedPaths: string[];
35
37
  excludedPaths: string[];
@@ -233,9 +235,11 @@ export function materializeWorkVerificationSnapshot(input: {
233
235
  allowedPaths: [...input.scope.allowedPaths].sort(),
234
236
  forbiddenPaths: [...input.scope.forbiddenPaths].sort(),
235
237
  })).digest('hex');
238
+ const isolatedControllerHome = join(root, '.git', 'forge-candidate-controller');
239
+ mkdirSync(isolatedControllerHome, { recursive: true, mode: 0o700 });
236
240
  mkdirSync(dirname(join(root, SNAPSHOT_MARKER)), { recursive: true });
237
241
  writeFileSync(join(root, SNAPSHOT_MARKER), `${JSON.stringify({ schemaVersion: 1, ...input.scope, sourceHead, includedPaths, excludedPaths, ownershipDigest }, null, 2)}\n`);
238
- return { root, sourceHead, includedPaths, excludedPaths, ownershipDigest };
242
+ return { root, isolatedControllerHome, sourceHead, includedPaths, excludedPaths, ownershipDigest };
239
243
  } catch (error) {
240
244
  rmSync(root, { recursive: true, force: true });
241
245
  throw error;