@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.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +85 -89
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +1 -1
- package/src/runtime/root/package-runtime-service.ts +33 -2
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- 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
|
+
}
|