@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
@@ -0,0 +1,203 @@
1
+ import { existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import type { RepositoryRecord } from '../../../cli/repositories/types';
4
+ import { listRepositories, selectRepositoryCheckout } from '../../../cli/repositories/registry';
5
+ import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
6
+ import { ensureManagedWorkspace } from '../../execution/managed-workspace';
7
+ import { getControllerSession } from '../facade/controller-session-store';
8
+ import { getWorkContract, resumeRetainedCancelledWorkContract, updateWorkContract } from '../facade/work-contract-store';
9
+ import { gitCommitAtRef, gitWorktreeSnapshot } from './work-lifecycle-audit';
10
+ import { readWorkHandle, writeWorkHandle } from './work-handle-store';
11
+
12
+ export interface RetainedWorkResumeIdentity {
13
+ principalId: string;
14
+ sessionId: string;
15
+ controllerInstanceId: string;
16
+ }
17
+
18
+ export interface ReauthorizeRetainedCancelledWorkInput {
19
+ controllerHome: string;
20
+ repository: RepositoryRecord;
21
+ workId: string;
22
+ identity: RetainedWorkResumeIdentity;
23
+ requestedBy?: string;
24
+ approvalConfirmed?: boolean;
25
+ prepareDependencies?: boolean;
26
+ }
27
+
28
+ /**
29
+ * Canonical orchestration authority for explicitly re-authorizing the exact
30
+ * retained cancelled repository Work. Transport adapters provide authenticated
31
+ * identity and typed user intent only; they must not duplicate these lifecycle,
32
+ * ownership, zero-delta, or reconstruction rules.
33
+ */
34
+ export function reauthorizeRetainedCancelledRepositoryWork(
35
+ input: ReauthorizeRetainedCancelledWorkInput,
36
+ ): { reconstructedCheckout: boolean } {
37
+ const { controllerHome, repository, workId, identity } = input;
38
+ const store = { controllerHome, repoId: repository.repoId };
39
+ let work = getWorkContract(store, workId);
40
+ if (!work || work.status !== 'cancelled') return { reconstructedCheckout: false };
41
+ if (input.approvalConfirmed !== true || input.requestedBy !== 'user') {
42
+ throw new Error(`WORK_CANCELLED_RESUME_REAUTHORIZATION_REQUIRED: ${workId}; use exact work_id with requested_by=user and approval_confirmed=true`);
43
+ }
44
+ if (!work.principalId?.trim() || work.principalId.trim() !== identity.principalId) {
45
+ throw new Error(`WORK_CANCELLED_RESUME_PRINCIPAL_MISMATCH: ${workId}`);
46
+ }
47
+ const owner = getControllerSession(store, workId);
48
+ if (owner && (owner.principalId?.trim() || owner.controllerId) !== identity.principalId) {
49
+ throw new Error(`WORK_CANCELLED_RESUME_CONTROLLER_OWNERSHIP_MISMATCH: ${workId}`);
50
+ }
51
+ if (work.completionReceipt || work.completionOutcome) throw new Error(`WORK_CANCELLED_RESUME_COMPLETION_CONFLICT: ${workId}`);
52
+ if (work.reconciliations.length > 0) throw new Error(`WORK_CANCELLED_RESUME_DELIVERY_HISTORY_AMBIGUOUS: ${workId}`);
53
+ if (work.phase !== 'cleanup' || work.phaseEvidence.cleanup.state !== 'skipped' || work.phaseEvidence.cleanup.source !== 'recorded') {
54
+ throw new Error(`WORK_CANCELLED_RESUME_HISTORY_AMBIGUOUS: ${workId}`);
55
+ }
56
+ if (work.workKind !== 'repository_change' || work.worktreePolicy.required !== true) {
57
+ throw new Error(`WORK_CANCELLED_RESUME_ISOLATED_REPOSITORY_WORK_REQUIRED: ${workId}`);
58
+ }
59
+
60
+ let handle = readWorkHandle(controllerHome, repository.repoId, workId);
61
+ if (handle) {
62
+ if (handle.repositoryId !== repository.repoId || (handle.workContractId && handle.workContractId !== workId)) {
63
+ throw new Error(`WORK_CANCELLED_RESUME_HANDLE_IDENTITY_MISMATCH: ${workId}`);
64
+ }
65
+ if (handle.principalId !== identity.principalId) throw new Error(`WORK_CANCELLED_RESUME_HANDLE_PRINCIPAL_MISMATCH: ${workId}`);
66
+ if (['committed', 'merged', 'cleaned', 'failed_terminal_cleanup'].includes(handle.state)
67
+ || handle.finalization.commit === 'done'
68
+ || handle.finalization.merge === 'done'
69
+ || handle.cleanupReceipt?.complete === true) {
70
+ throw new Error(`WORK_CANCELLED_RESUME_DELIVERY_OR_CLEANUP_CONFLICT: ${workId}`);
71
+ }
72
+ }
73
+
74
+ const registryRepository = listRepositories(controllerHome, { includeRemoved: true })
75
+ .find((candidate) => candidate.repoId === repository.repoId);
76
+ if (!registryRepository) throw new Error(`WORK_CANCELLED_RESUME_REPOSITORY_MISSING: ${workId}`);
77
+ const recordedCheckoutId = work.checkoutId?.trim();
78
+ const recordedWorktree = work.worktreeRef?.trim();
79
+ const checkoutRecord = recordedCheckoutId
80
+ ? registryRepository.checkouts.find((candidate) => candidate.checkoutId === recordedCheckoutId)
81
+ : undefined;
82
+ const checkoutActive = checkoutRecord?.lifecycle === 'active';
83
+ const worktreePresent = Boolean(recordedWorktree && existsSync(recordedWorktree));
84
+
85
+ if (checkoutActive && worktreePresent && recordedCheckoutId && recordedWorktree) {
86
+ const retained = selectRepositoryCheckout(registryRepository, recordedCheckoutId);
87
+ if (resolve(retained.canonicalRoot) !== resolve(recordedWorktree)) {
88
+ throw new Error(`WORK_CANCELLED_RESUME_CHECKOUT_REUSED: ${workId}`);
89
+ }
90
+ if (!handle) throw new Error(`WORK_CANCELLED_RESUME_HANDLE_REQUIRED: ${workId}`);
91
+ if (handle.checkoutId !== recordedCheckoutId || resolve(handle.worktreePath) !== resolve(recordedWorktree)) {
92
+ throw new Error(`WORK_CANCELLED_RESUME_CHECKOUT_OWNERSHIP_MISMATCH: ${workId}`);
93
+ }
94
+ const status = repositoryGitStatus(retained);
95
+ if (!status.branch || status.branch !== handle.branch) throw new Error(`WORK_CANCELLED_RESUME_BRANCH_OWNERSHIP_MISMATCH: ${workId}`);
96
+ writeWorkHandle(controllerHome, {
97
+ ...handle,
98
+ principalId: identity.principalId,
99
+ sessionId: identity.sessionId,
100
+ updatedAt: new Date().toISOString(),
101
+ });
102
+ resumeRetainedCancelledWorkContract(store, workId, {
103
+ principalId: identity.principalId,
104
+ controllerInstanceId: identity.controllerInstanceId,
105
+ summary: 'Explicit current user re-authorized the same retained cancelled Work; exact managed checkout ownership was revalidated.',
106
+ });
107
+ return { reconstructedCheckout: false };
108
+ }
109
+
110
+ if (checkoutActive) throw new Error(`WORK_CANCELLED_RESUME_PRESERVATION_AMBIGUOUS: ${workId}`);
111
+ if (!recordedCheckoutId || !recordedWorktree || !handle) throw new Error(`WORK_CANCELLED_RESUME_HANDLE_REQUIRED: ${workId}`);
112
+
113
+ const actualChangedPaths = work.scopeEvidence?.actualChangedPaths;
114
+ if (!actualChangedPaths || actualChangedPaths.length !== 0
115
+ || work.checks.length !== 0
116
+ || work.checkRefs.length !== 0
117
+ || work.evidenceState !== 'none') {
118
+ throw new Error(`WORK_CANCELLED_RESUME_ZERO_DELTA_PROOF_REQUIRED: ${workId}`);
119
+ }
120
+ const recordedBase = work.baseRevision?.trim();
121
+ if (!recordedBase) throw new Error(`WORK_CANCELLED_RESUME_BASE_REVISION_REQUIRED: ${workId}`);
122
+ const baseRevision = gitCommitAtRef(repository.canonicalRoot, recordedBase);
123
+ if (!baseRevision) throw new Error(`WORK_CANCELLED_RESUME_BASE_REVISION_MISSING: ${workId}`);
124
+ const handleBase = handle.baseCommit ? gitCommitAtRef(repository.canonicalRoot, handle.baseCommit) : undefined;
125
+ const handleHead = handle.expectedHead ? gitCommitAtRef(repository.canonicalRoot, handle.expectedHead) : undefined;
126
+ if ((handle.baseCommit && handleBase !== baseRevision) || (handle.expectedHead && handleHead !== baseRevision)) {
127
+ throw new Error(`WORK_CANCELLED_RESUME_REVISION_AMBIGUOUS: ${workId}`);
128
+ }
129
+ if (handle.checkoutId !== recordedCheckoutId || resolve(handle.worktreePath) !== resolve(recordedWorktree)) {
130
+ throw new Error(`WORK_CANCELLED_RESUME_CHECKOUT_OWNERSHIP_MISMATCH: ${workId}`);
131
+ }
132
+ if (handle.branch) {
133
+ const historicalBranchHead = gitCommitAtRef(repository.canonicalRoot, `refs/heads/${handle.branch}`);
134
+ if (historicalBranchHead && historicalBranchHead !== baseRevision) {
135
+ throw new Error(`WORK_CANCELLED_RESUME_UNIQUE_COMMIT_CONFLICT: ${workId}`);
136
+ }
137
+ }
138
+ if (worktreePresent) {
139
+ const retainedSnapshot = gitWorktreeSnapshot(recordedWorktree);
140
+ if (!retainedSnapshot || !retainedSnapshot.clean || retainedSnapshot.head !== baseRevision || (handle.branch && retainedSnapshot.branch !== handle.branch)) {
141
+ throw new Error(`WORK_CANCELLED_RESUME_ZERO_DELTA_PHYSICAL_CONFLICT: ${workId}`);
142
+ }
143
+ }
144
+
145
+ const branchName = `work/resume-${workId.replace(/[^A-Za-z0-9._-]+/g, '-').slice(0, 42)}-${baseRevision.slice(0, 12)}`;
146
+ const existingResumeBranch = gitCommitAtRef(repository.canonicalRoot, `refs/heads/${branchName}`);
147
+ if (existingResumeBranch && existingResumeBranch !== baseRevision) {
148
+ throw new Error(`WORK_CANCELLED_RESUME_RECONSTRUCTION_BRANCH_CONFLICT: ${workId}`);
149
+ }
150
+ const workspace = ensureManagedWorkspace(controllerHome, repository, {
151
+ requestId: `${workId}:explicit-user-resume:${baseRevision}`,
152
+ title: `${work.objective} explicit user resume`,
153
+ branchName,
154
+ baseRef: baseRevision,
155
+ prepareDependencies: input.prepareDependencies === true,
156
+ });
157
+ if (!workspace.managed || !workspace.checkoutId || !workspace.root || !workspace.branch) {
158
+ throw new Error(`WORK_CANCELLED_RESUME_RECONSTRUCTION_FAILED: ${workId}`);
159
+ }
160
+ const refreshedRepository = listRepositories(controllerHome, { includeRemoved: true })
161
+ .find((candidate) => candidate.repoId === repository.repoId);
162
+ if (!refreshedRepository) throw new Error(`WORK_CANCELLED_RESUME_REPOSITORY_MISSING: ${workId}`);
163
+ const reconstructed = selectRepositoryCheckout(refreshedRepository, workspace.checkoutId);
164
+ const reconstructedStatus = repositoryGitStatus(reconstructed);
165
+ if (!reconstructedStatus.clean || reconstructedStatus.head !== baseRevision || reconstructedStatus.branch !== workspace.branch) {
166
+ throw new Error(`WORK_CANCELLED_RESUME_RECONSTRUCTION_REVISION_MISMATCH: ${workId}`);
167
+ }
168
+
169
+ work = updateWorkContract(store, workId, {
170
+ checkoutId: workspace.checkoutId,
171
+ worktreeRef: workspace.root,
172
+ baseRevision,
173
+ controllerInstanceId: identity.controllerInstanceId,
174
+ });
175
+ const now = new Date().toISOString();
176
+ handle = writeWorkHandle(controllerHome, {
177
+ ...handle,
178
+ principalId: identity.principalId,
179
+ sessionId: identity.sessionId,
180
+ checkoutId: workspace.checkoutId,
181
+ worktreePath: workspace.root,
182
+ branch: workspace.branch,
183
+ sourceCheckoutId: repository.activeCheckoutId,
184
+ managedWorktree: true,
185
+ baseCommit: baseRevision,
186
+ deliveryBaseCommit: baseRevision,
187
+ expectedHead: baseRevision,
188
+ state: 'prepared',
189
+ validatedInputFingerprint: undefined,
190
+ failureReason: undefined,
191
+ cleanupReceipt: undefined,
192
+ finalization: { validation: 'pending', commit: 'pending', merge: 'pending', branchCleanup: 'pending', worktreeCleanup: 'pending' },
193
+ updatedAt: now,
194
+ });
195
+ resumeRetainedCancelledWorkContract(store, workId, {
196
+ principalId: identity.principalId,
197
+ controllerInstanceId: identity.controllerInstanceId,
198
+ summary: 'Explicit current user re-authorized the same cancelled Work; prior managed checkout was absent and a fresh isolated checkout was reconstructed at the exact zero-delta base revision.',
199
+ checkoutId: workspace.checkoutId,
200
+ worktreeRef: workspace.root,
201
+ });
202
+ return { reconstructedCheckout: true };
203
+ }
@@ -0,0 +1,58 @@
1
+ import { completeRequirementFromWork } from '../persistence/requirement-store';
2
+ import {
3
+ appendWorkEvidence,
4
+ recordWorkCompletionReceipt,
5
+ type WorkContractStoreOptions,
6
+ } from '../facade/work-contract-store';
7
+ import type { WorkContract, WorkKind } from '../facade/types';
8
+
9
+ export interface WorkRequirementProjectionResult {
10
+ attempted: boolean;
11
+ ok: boolean;
12
+ warning?: string;
13
+ }
14
+
15
+ /**
16
+ * Project one already-terminal Work receipt into Requirement semantic review.
17
+ * Work completion remains authoritative; Requirement projection is downstream
18
+ * and must never turn a durable completion receipt back into a failed Work.
19
+ */
20
+ export function projectRequirementDeliveryFromCompletedWork(
21
+ options: WorkContractStoreOptions,
22
+ work: WorkContract,
23
+ ): WorkRequirementProjectionResult {
24
+ if (!work.requirementId || !options.controllerHome) return { attempted: false, ok: true };
25
+ try {
26
+ completeRequirementFromWork(
27
+ { controllerHome: options.controllerHome },
28
+ { requirementId: work.requirementId, work },
29
+ );
30
+ return { attempted: true, ok: true };
31
+ } catch (error) {
32
+ const warning = `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000);
33
+ try {
34
+ appendWorkEvidence(options, work.workId, {
35
+ title: 'requirement completion projection pending',
36
+ summary: warning,
37
+ detailLevel: 'summary',
38
+ });
39
+ } catch {
40
+ // The completion receipt is already canonical and durable. Diagnostic
41
+ // persistence is downstream and cannot invalidate terminal Work state.
42
+ }
43
+ return { attempted: true, ok: false, warning };
44
+ }
45
+ }
46
+
47
+ /** Record the canonical Work completion fact, then update downstream Requirement review state. */
48
+ export function completeWorkWithReceipt(
49
+ options: WorkContractStoreOptions,
50
+ workId: string,
51
+ receipt: NonNullable<WorkContract['completionReceipt']>,
52
+ completionOutcome: NonNullable<WorkContract['completionOutcome']>,
53
+ completionWorkKind?: WorkKind,
54
+ ): WorkContract {
55
+ const recorded = recordWorkCompletionReceipt(options, workId, receipt, completionOutcome, completionWorkKind);
56
+ projectRequirementDeliveryFromCompletedWork(options, recorded);
57
+ return recorded;
58
+ }
@@ -0,0 +1,200 @@
1
+ import { reconcileVerificationHistory } from '../facade/check-normalization';
2
+ import type { VerificationRecord, WorkContract } from '../facade/types';
3
+
4
+ type ReconciledVerificationHistory = ReturnType<typeof reconcileVerificationHistory>;
5
+
6
+ export interface WorkCompletionEvidenceEvaluation {
7
+ status: 'complete' | 'failed' | 'incomplete';
8
+ history: ReconciledVerificationHistory;
9
+ missingChecks: string[];
10
+ durableResultEvidence: boolean;
11
+ semanticAcceptanceComplete: boolean;
12
+ missingSemanticAcceptanceCriteria: string[];
13
+ reasons: string[];
14
+ }
15
+
16
+ export function verificationRecordAppliesToCurrentWorkspace(
17
+ record: VerificationRecord,
18
+ currentRevision?: string,
19
+ currentWorkspaceFingerprint?: string,
20
+ ): boolean {
21
+ if (currentRevision && record.sourceRevision && record.sourceRevision !== currentRevision) return false;
22
+ if (
23
+ currentWorkspaceFingerprint
24
+ && record.workspaceFingerprint
25
+ && record.workspaceFingerprint !== currentWorkspaceFingerprint
26
+ ) return false;
27
+ return true;
28
+ }
29
+
30
+ function isAuthoritativeCurrentWorkVerification(
31
+ work: WorkContract,
32
+ record: VerificationRecord,
33
+ currentRevision?: string,
34
+ ): boolean {
35
+ const receipt = record.receipt;
36
+ return Boolean(
37
+ currentRevision
38
+ && record.outcome === 'valid_pass'
39
+ && record.sourceRevision === currentRevision
40
+ && receipt
41
+ && receipt.repoId === work.repoId
42
+ && receipt.workId === work.workId
43
+ && receipt.checkId === record.checkId
44
+ && receipt.ok === true
45
+ && receipt.timedOut === false
46
+ && receipt.cancelled === false,
47
+ );
48
+ }
49
+
50
+ export interface WorkImplementationEvidenceEvaluation {
51
+ status: 'complete' | 'incomplete';
52
+ changedPaths: string[];
53
+ reasons: string[];
54
+ }
55
+
56
+ export function evaluateWorkImplementationEvidence(
57
+ work: WorkContract,
58
+ currentWorkspaceChangedPaths: readonly string[] = [],
59
+ ): WorkImplementationEvidenceEvaluation {
60
+ if (work.workKind !== 'repository_change') {
61
+ return { status: 'complete', changedPaths: [], reasons: [] };
62
+ }
63
+
64
+ const changedPaths = [...new Set(currentWorkspaceChangedPaths.map((path) => path.trim()).filter(Boolean))].sort();
65
+ if (changedPaths.length > 0) {
66
+ return { status: 'complete', changedPaths, reasons: [] };
67
+ }
68
+
69
+ return {
70
+ status: 'incomplete',
71
+ changedPaths: [],
72
+ reasons: ['Repository-change Work has no current net source changes relative to its base revision. Verification evidence cannot substitute for implementation evidence.'],
73
+ };
74
+ }
75
+
76
+ export function evaluateReadOnlyReviewSourceIdentity(
77
+ work: WorkContract,
78
+ currentRevision?: string,
79
+ currentWorkspaceChangedPaths?: readonly string[],
80
+ ): { status: 'complete' | 'incomplete'; reasons: string[] } {
81
+ if (work.workKind !== 'read_only_review') return { status: 'complete', reasons: [] };
82
+ const reasons: string[] = [];
83
+ if (!work.baseRevision?.trim()) reasons.push('Read-only review has no frozen base revision.');
84
+ if (!currentRevision?.trim()) reasons.push('Current source revision is unavailable, so unchanged-source proof is incomplete.');
85
+ if (work.baseRevision && currentRevision && work.baseRevision !== currentRevision) {
86
+ reasons.push(`Read-only review source drifted from frozen base ${work.baseRevision} to ${currentRevision}.`);
87
+ }
88
+ if (currentWorkspaceChangedPaths === undefined) {
89
+ reasons.push('Current workspace changed-path proof is unavailable for read-only review.');
90
+ } else if (currentWorkspaceChangedPaths.length > 0) {
91
+ reasons.push(`Read-only review workspace is not unchanged: ${[...new Set(currentWorkspaceChangedPaths)].slice(0, 12).join(', ')}.`);
92
+ }
93
+ return { status: reasons.length === 0 ? 'complete' : 'incomplete', reasons };
94
+ }
95
+
96
+ export function evaluateWorkCompletionEvidence(
97
+ work: WorkContract,
98
+ currentRevision?: string,
99
+ currentWorkspaceFingerprint?: string,
100
+ workBoundProcessEvidenceIds: readonly string[] = [],
101
+ currentWorkspaceChangedPaths?: readonly string[],
102
+ ): WorkCompletionEvidenceEvaluation {
103
+ const applicableCheckRefs = work.checkRefs.filter((record) =>
104
+ verificationRecordAppliesToCurrentWorkspace(record, currentRevision, currentWorkspaceFingerprint));
105
+ const history = reconcileVerificationHistory(
106
+ applicableCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
107
+ );
108
+ const missingChecks = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
109
+ const readOnlySourceIdentity = evaluateReadOnlyReviewSourceIdentity(work, currentRevision, currentWorkspaceChangedPaths);
110
+ const readOnlyEvidence = work.readOnlyReviewEvidence;
111
+ const readOnlyEvidenceMatchesCurrentSource = Boolean(
112
+ work.workKind === 'read_only_review'
113
+ && readOnlyEvidence
114
+ && currentRevision
115
+ && readOnlyEvidence.sourceRevision === currentRevision
116
+ && (readOnlyEvidence.workspaceFingerprint === undefined
117
+ || currentWorkspaceFingerprint === undefined
118
+ || readOnlyEvidence.workspaceFingerprint === currentWorkspaceFingerprint),
119
+ );
120
+ const cleanReadOnlyReviewEvidence = Boolean(
121
+ readOnlyEvidenceMatchesCurrentSource
122
+ && readOnlySourceIdentity.status === 'complete'
123
+ && (readOnlyEvidence?.inspectedPaths.length ?? 0) > 0
124
+ && (readOnlyEvidence?.findings.length ?? 0) === 0,
125
+ );
126
+ const workEvidenceIds = work.evidenceRefs.flatMap((evidence) => [evidence.evidenceId, evidence.artifactId])
127
+ .map((value) => value?.trim())
128
+ .filter((value): value is string => Boolean(value));
129
+ const availableDurableEvidenceIds = new Set([
130
+ ...workEvidenceIds,
131
+ ...workBoundProcessEvidenceIds.map((value) => value.trim()).filter(Boolean),
132
+ ]);
133
+ const durableResultEvidence = availableDurableEvidenceIds.size > 0
134
+ || applicableCheckRefs.some((record) => isAuthoritativeCurrentWorkVerification(work, record, currentRevision))
135
+ || cleanReadOnlyReviewEvidence;
136
+ const requiresSemanticAcceptance = work.workKind === 'local_effect' && work.checks.length === 0;
137
+ const acceptedCriteria = new Set((work.semanticAcceptanceEvidence ?? [])
138
+ .filter((review) => review.evidenceIds.length > 0 && review.evidenceIds.every((evidenceId) => availableDurableEvidenceIds.has(evidenceId)))
139
+ .map((review) => review.criterion));
140
+ const missingSemanticAcceptanceCriteria = requiresSemanticAcceptance
141
+ ? work.acceptanceCriteria.filter((criterion) => !acceptedCriteria.has(criterion))
142
+ : [];
143
+ const semanticAcceptanceComplete = !requiresSemanticAcceptance
144
+ || (work.acceptanceCriteria.length > 0 && missingSemanticAcceptanceCriteria.length === 0);
145
+ const reasons: string[] = [];
146
+
147
+ if (history.acceptanceFailures.length > 0) {
148
+ reasons.push(`Acceptance checks failed: ${history.acceptanceFailures.join(', ')}.`);
149
+ return { status: 'failed', history, missingChecks, durableResultEvidence, semanticAcceptanceComplete, missingSemanticAcceptanceCriteria, reasons };
150
+ }
151
+ if (history.infrastructureIssues.length > 0) {
152
+ reasons.push(`Infrastructure issues remain: ${history.infrastructureIssues.join(', ')}.`);
153
+ }
154
+ if (history.invalidCheckIds.length > 0) {
155
+ reasons.push(`Invalid check ids remain: ${history.invalidCheckIds.join(', ')}.`);
156
+ }
157
+ if (missingChecks.length > 0) {
158
+ reasons.push(`Declared checks are missing valid_pass evidence: ${missingChecks.join(', ')}.`);
159
+ }
160
+ if (work.workKind === 'read_only_review') {
161
+ reasons.push(...readOnlySourceIdentity.reasons);
162
+ if (!readOnlyEvidence || readOnlyEvidence.inspectedPaths.length === 0) {
163
+ reasons.push('Read-only review has no persisted inspected-path evidence.');
164
+ } else if (!readOnlyEvidenceMatchesCurrentSource) {
165
+ reasons.push('Persisted read-only review evidence is stale for the current source/workspace identity.');
166
+ }
167
+ if ((readOnlyEvidence?.findings.length ?? 0) > 0) {
168
+ reasons.push(`Read-only review has unresolved semantic findings (${readOnlyEvidence!.findings.length}); clean no-change completion is not allowed.`);
169
+ }
170
+ }
171
+ if (work.checks.length === 0 && !durableResultEvidence) {
172
+ const staleWorkVerification = work.checkRefs.some((record) =>
173
+ Boolean(record.receipt)
174
+ && !verificationRecordAppliesToCurrentWorkspace(record, currentRevision, currentWorkspaceFingerprint));
175
+ reasons.push(staleWorkVerification
176
+ ? 'Work-bound verification evidence is stale for the current source/workspace identity.'
177
+ : 'No durable result evidence (evidenceId, artifactId, or current Work-bound verification receipt) was recorded for this no-check WorkContract.');
178
+ }
179
+ if (requiresSemanticAcceptance && !semanticAcceptanceComplete) {
180
+ reasons.push(work.acceptanceCriteria.length === 0
181
+ ? 'No-check local-effect Work has no declared acceptance criteria to review.'
182
+ : `Controller-reviewed semantic acceptance evidence is incomplete; missing criteria: ${missingSemanticAcceptanceCriteria.join(' | ')}.`);
183
+ }
184
+
185
+ const complete = history.infrastructureIssues.length === 0
186
+ && history.invalidCheckIds.length === 0
187
+ && missingChecks.length === 0
188
+ && (work.checks.length > 0 || durableResultEvidence)
189
+ && semanticAcceptanceComplete
190
+ && (work.workKind !== 'read_only_review' || cleanReadOnlyReviewEvidence);
191
+ return {
192
+ status: complete ? 'complete' : 'incomplete',
193
+ history,
194
+ missingChecks,
195
+ durableResultEvidence,
196
+ semanticAcceptanceComplete,
197
+ missingSemanticAcceptanceCriteria,
198
+ reasons,
199
+ };
200
+ }