@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.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +26 -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 +125 -101
- 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 +69 -29
- 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 +150 -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 +815 -1015
- 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 +3 -2
- package/src/runtime/root/package-runtime-service.ts +48 -3
- 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
|
@@ -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 {
|
|
7
|
-
import {
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
972
|
-
|
|
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;
|