@moretea-labs/forge 1.8.1 → 1.8.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 +5 -2
- package/CHANGELOG.md +20 -0
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
- package/adapters/chatgpt/provider-delivery.ts +1 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
- package/adapters/computer/chatgpt-extension-target.ts +295 -50
- package/adapters/computer/chrome-extension/background.js +227 -71
- package/adapters/computer/chrome-extension/content.js +181 -157
- package/adapters/computer/chrome-extension/manifest.json +3 -1
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +39 -9
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -6
- package/adapters/mcp/instructions.ts +2 -2
- package/adapters/mcp/multi-repository.ts +7 -28
- package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
- package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
- package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
- package/adapters/mcp/runtime-gateway/router.ts +3 -5
- package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
- package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
- package/adapters/mcp/server.ts +24 -3
- package/adapters/mcp/setup.ts +4 -10
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
- package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
- package/adapters/mcp/toolset-names.ts +11 -42
- package/adapters/mcp/toolset.ts +16 -90
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +18 -6
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +54 -14
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +1 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +114 -30
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +5 -5
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +14 -2
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -54
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +24 -12
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +1 -12
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
- package/packages/kernel/work/application/work-service.ts +1 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +9 -2
- package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
- package/scripts/assemble-template.ts +11 -72
- package/scripts/check-mcp-compatibility.ts +55 -74
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +305 -28
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +3 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +0 -1
- package/scripts/run-governed-gate.ts +2 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +7 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
- package/scripts/sync-generated-authority.mjs +0 -1
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/agent-jobs/integration.ts +2 -2
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +2 -14
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/controller/completion-orchestrator.ts +1 -1
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +2 -2
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +97 -76
- package/src/cli/controller/operational-plan.ts +1 -1
- package/src/cli/controller/systemd-user.ts +4 -10
- package/src/cli/editing/edit-session.ts +17 -5
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +1 -1
- package/src/cli/local-bridge/console-view-models.ts +1 -1
- package/src/cli/local-bridge/facade-api.ts +0 -2
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-scope.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +2 -1
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +39 -3
- package/src/cli/repository/inspector.ts +185 -343
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +7 -0
- package/src/runtime/context/semantic-navigation.ts +1 -1
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
- package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
- package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
- package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
- package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
- package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
- package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
- package/src/runtime/execution/process-runtime/runtime.ts +20 -16
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +1 -2
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/plugins/action-application.ts +12 -1
- package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
- package/src/runtime/plugins/browser-adapter.ts +381 -313
- package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
- package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
- package/src/runtime/plugins/browser-registration.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +26 -35
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
- package/src/runtime/plugins/computer-registration.ts +76 -9
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +166 -1
- package/src/runtime/plugins/google-shared.ts +10 -5
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +45 -40
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/local-system-adapter.ts +1 -99
- package/src/runtime/plugins/store.ts +80 -7
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/recovery/actions.ts +1 -1
- package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/release/release-coordinator.ts +20 -4
- package/src/runtime/root/controller-progression-composition.ts +67 -5
- package/src/runtime/root/package-connector-service.ts +123 -11
- package/src/runtime/root/package-runtime-release.ts +6 -18
- package/src/runtime/root/package-runtime-service.ts +7 -13
- package/src/runtime/root/release-execution-canary.ts +18 -5
- package/src/runtime/root/release-manifest.ts +0 -34
- package/src/runtime/root/release-materialize.ts +0 -78
- package/src/runtime/root/runtime.ts +1 -2
- package/src/runtime/root/scheduled-controller-composition.ts +2 -1
- package/src/runtime/root/service.ts +90 -11
- package/src/runtime/root/types.ts +0 -6
- package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
- package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
- package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/standalone-recovery/core.ts +189 -174
- package/src/runtime/standalone-recovery/entry.ts +62 -30
- package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
- package/src/runtime/workflow/schedules/engine.ts +36 -14
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
- package/supervisor/client.ts +31 -14
- package/supervisor/control-plane.ts +210 -115
- package/supervisor/forge-validators.ts +88 -21
- package/supervisor/native-browser-adapter.ts +393 -28
- package/supervisor/native-messaging/host.ts +21 -2
- package/supervisor/protocol.ts +74 -26
- package/supervisor/server.ts +16 -8
- package/supervisor/store.ts +476 -57
- package/supervisor/types.ts +19 -13
- package/SKILL.md +0 -427
- package/adapters/computer/chatgpt-target-router.ts +0 -103
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
|
@@ -25,6 +25,8 @@ export interface SemanticNavigationResult {
|
|
|
25
25
|
target: { path: string; line: number; column: number };
|
|
26
26
|
locations: SemanticNavigationLocation[];
|
|
27
27
|
policyDeniedReads?: number;
|
|
28
|
+
/** Denied requested/material reads, excluding incidental dependency probes. */
|
|
29
|
+
materialPolicyDeniedReads?: number;
|
|
28
30
|
details?: Record<string, unknown>;
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -68,3 +70,8 @@ export interface IndexedSemanticNavigationOutcome {
|
|
|
68
70
|
index: number;
|
|
69
71
|
outcome: SemanticNavigationOutcome;
|
|
70
72
|
}
|
|
73
|
+
|
|
74
|
+
/** Dependency probes rejected by policy cannot certify missing requested source relationships. */
|
|
75
|
+
export function isIncidentalSemanticDependencyProbe(path: string): boolean {
|
|
76
|
+
return /(?:^|\/)node_modules\/[^/]+/.test(path.replace(/\\/g, '/'));
|
|
77
|
+
}
|
|
@@ -45,7 +45,7 @@ const swiftProvider: SemanticNavigationProvider = {
|
|
|
45
45
|
if (request.tsconfigPath) {
|
|
46
46
|
invalid.set(index, { ok: false, code: 'SWIFT_SEMANTIC_TSCONFIG_UNSUPPORTED', message: 'tsconfig_path applies only to TypeScript semantic navigation.' });
|
|
47
47
|
} else if (access.profile !== 'controller') {
|
|
48
|
-
invalid.set(index, { ok: false, code: 'SWIFT_SEMANTIC_READ_SCOPE_UNSUPPORTED', message: 'External SourceKit-LSP navigation is restricted to the controller read profile; use lexical/
|
|
48
|
+
invalid.set(index, { ok: false, code: 'SWIFT_SEMANTIC_READ_SCOPE_UNSUPPORTED', message: 'External SourceKit-LSP navigation is restricted to the controller read profile; use bounded lexical/raw source evidence under narrower read policies.' });
|
|
49
49
|
} else {
|
|
50
50
|
eligible.push({ index, request });
|
|
51
51
|
}
|
|
@@ -107,14 +107,14 @@ function treeEntry(root: string, ref: string, path: string): string | undefined
|
|
|
107
107
|
|
|
108
108
|
export function proveWorkPreservationContained(
|
|
109
109
|
repositoryRoot: string,
|
|
110
|
-
handle: Pick<WorkHandleState, 'baseCommit' | 'deliveryBaseCommit'
|
|
110
|
+
handle: Pick<WorkHandleState, 'baseCommit' | 'deliveryBaseCommit'>,
|
|
111
111
|
targetBranch: string,
|
|
112
|
+
protectedCandidate: string | undefined,
|
|
112
113
|
): WorkPreservationContainmentProof {
|
|
113
114
|
const base = workDeliveryBaseRevision(handle);
|
|
114
115
|
if (!base || !revision(repositoryRoot, base)) {
|
|
115
116
|
return { contained: false, reason: 'base_revision_unavailable', comparedPaths: [] };
|
|
116
117
|
}
|
|
117
|
-
const protectedCandidate = handle.cleanupReceipt?.preservation.checkpointCommit?.trim() || handle.expectedHead?.trim();
|
|
118
118
|
if (!protectedCandidate) {
|
|
119
119
|
return { contained: false, reason: 'protected_revision_unavailable', comparedPaths: [] };
|
|
120
120
|
}
|
|
@@ -336,7 +336,7 @@ export function cleanupWorkPreservationArtifacts(
|
|
|
336
336
|
skip(report, 'retention_grace');
|
|
337
337
|
continue;
|
|
338
338
|
}
|
|
339
|
-
const proof = proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch);
|
|
339
|
+
const proof = proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch, bundleProtectedRevision(repository.canonicalRoot, bundlePath));
|
|
340
340
|
if (!proof.contained) {
|
|
341
341
|
report.retained += 1;
|
|
342
342
|
skip(report, `containment_${proof.reason}`);
|
|
@@ -468,7 +468,7 @@ export function cleanupWorkPreservationArtifacts(
|
|
|
468
468
|
|
|
469
469
|
const receipt = handle?.cleanupReceipt;
|
|
470
470
|
const handleProof = handle && receipt?.complete
|
|
471
|
-
? proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch)
|
|
471
|
+
? proveWorkPreservationContained(repository.canonicalRoot, handle, receipt.targetBranch, bundleProtectedRevision(repository.canonicalRoot, bundlePath))
|
|
472
472
|
: undefined;
|
|
473
473
|
const orphanProof = !handle && repository.defaultBranch
|
|
474
474
|
? proveOrphanBundleContained(repository.canonicalRoot, bundlePath, repository.defaultBranch)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { spawnSync } from 'child_process';
|
|
3
3
|
import type { McpExecutionContext } from '../../../../packages/protocols/mcp/execution-context';
|
|
4
4
|
import {
|
|
5
5
|
getWorkContract,
|
|
6
6
|
isRepositoryCompletionReceipt,
|
|
7
|
+
reviseWorkSemanticContext,
|
|
7
8
|
semanticWorkState,
|
|
9
|
+
workSemanticView,
|
|
8
10
|
type CompletionMaintenanceWarning,
|
|
9
11
|
type RepositoryCompletionReceipt,
|
|
10
12
|
} from '../../../../packages/kernel/work/api/index';
|
|
@@ -15,6 +17,8 @@ import {
|
|
|
15
17
|
} from '../../../cli/repositories/registry';
|
|
16
18
|
import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
|
|
17
19
|
import { listActiveLeases } from '../../resources/leases/store';
|
|
20
|
+
import { withCheckoutMutationGate } from '../../execution/thin-harness/mutation-gate';
|
|
21
|
+
import { runBoundedGit } from '../../execution/thin-harness/async-process';
|
|
18
22
|
import { assertRuntimeMayWriteOrThrow, isRuntimeWriteFenceError } from '../../root/write-fence';
|
|
19
23
|
import { cleanupTerminalWork } from './work-terminal-cleanup';
|
|
20
24
|
import { recordWorkDeliveryReceipt } from './work-completion-authority';
|
|
@@ -141,9 +145,11 @@ function assertTargetMutationAvailable(
|
|
|
141
145
|
targetBranch: string,
|
|
142
146
|
expectedTargetHead: string,
|
|
143
147
|
targetRoot: string,
|
|
148
|
+
ownedLeaseJobId?: string,
|
|
144
149
|
): void {
|
|
145
150
|
const blockingLease = listActiveLeases(controllerHome, handle.repositoryId)
|
|
146
|
-
.find((lease) => lease.checkoutId === targetCheckoutId
|
|
151
|
+
.find((lease) => lease.checkoutId === targetCheckoutId
|
|
152
|
+
&& lease.ownerJobId !== ownedLeaseJobId && lease.workId !== handle.workId);
|
|
147
153
|
if (blockingLease) {
|
|
148
154
|
throw new Error(`WORK_DELIVERY_TARGET_LEASE_ACTIVE: ${blockingLease.leaseId}`);
|
|
149
155
|
}
|
|
@@ -239,6 +245,33 @@ function cleanupEvidence(receipt: WorkCleanupReceipt, recordedAt: string): Repos
|
|
|
239
245
|
return { status: 'maintenance_warning', warnings, blockers: [], recordedAt };
|
|
240
246
|
}
|
|
241
247
|
|
|
248
|
+
/** Explicit model-selected semantic completion; physical Git delivery alone never closes Work. */
|
|
249
|
+
function semanticCompletionAfterDelivery(
|
|
250
|
+
ctx: McpExecutionContext,
|
|
251
|
+
handle: WorkHandleState,
|
|
252
|
+
receipt: RepositoryCompletionReceipt,
|
|
253
|
+
completeWork: boolean,
|
|
254
|
+
expectedSemanticRevision: number,
|
|
255
|
+
): Record<string, unknown> {
|
|
256
|
+
if (!completeWork) return { semanticCompletionRequired: true };
|
|
257
|
+
if (receipt.cleanup.status !== 'complete' || receipt.cleanup.blockers.length > 0) {
|
|
258
|
+
// Git can already be integrated. Leave the semantic Work open for recovery.
|
|
259
|
+
return { semanticCompletionRequired: true, semanticCompletionBlocked: 'WORK_DELIVERY_CLEANUP_INCOMPLETE' };
|
|
260
|
+
}
|
|
261
|
+
const store = { controllerHome: ctx.controllerHome, repoId: handle.repositoryId };
|
|
262
|
+
const workId = handle.workContractId ?? handle.workId;
|
|
263
|
+
const current = getWorkContract(store, workId);
|
|
264
|
+
if (!current || semanticWorkState(current) !== 'open') {
|
|
265
|
+
throw new Error(`WORK_DELIVERY_SEMANTIC_WORK_NOT_OPEN: ${current ? semanticWorkState(current) : 'missing'}`);
|
|
266
|
+
}
|
|
267
|
+
const completed = reviseWorkSemanticContext(store, workId, {
|
|
268
|
+
// Do not close a Work whose objective changed while Git delivery was running.
|
|
269
|
+
expectedRevision: expectedSemanticRevision,
|
|
270
|
+
state: 'completed',
|
|
271
|
+
});
|
|
272
|
+
return { semanticCompletionRequired: false, semanticWork: workSemanticView(completed) };
|
|
273
|
+
}
|
|
274
|
+
|
|
242
275
|
function recordDelivery(
|
|
243
276
|
ctx: McpExecutionContext,
|
|
244
277
|
handle: WorkHandleState,
|
|
@@ -249,7 +282,8 @@ function recordDelivery(
|
|
|
249
282
|
strategy: RepositoryCompletionReceipt['delivery']['strategy'],
|
|
250
283
|
cleanupReceipt: WorkCleanupReceipt,
|
|
251
284
|
): RepositoryCompletionReceipt {
|
|
252
|
-
|
|
285
|
+
// Receipt scope is the immutable Work origin, not a movable integration baseline.
|
|
286
|
+
const baseRevision = handle.baseCommit;
|
|
253
287
|
const paths = baseRevision ? changedPaths(targetRoot, baseRevision, sourceRevision) : [];
|
|
254
288
|
const recordedAt = new Date().toISOString();
|
|
255
289
|
const receipt: RepositoryCompletionReceipt = {
|
|
@@ -292,6 +326,8 @@ async function reconcileDeliveredWork(
|
|
|
292
326
|
targetBranch: string,
|
|
293
327
|
targetRoot: string,
|
|
294
328
|
sourceRevision: string,
|
|
329
|
+
completeWork: boolean,
|
|
330
|
+
expectedSemanticRevision: number,
|
|
295
331
|
): Promise<Record<string, unknown>> {
|
|
296
332
|
const targetRevision = exactRevision(targetRoot, targetBranch, 'TARGET_HEAD');
|
|
297
333
|
if (!git(targetRoot, ['merge-base', '--is-ancestor', sourceRevision, targetRevision]).ok) {
|
|
@@ -328,7 +364,7 @@ async function reconcileDeliveredWork(
|
|
|
328
364
|
delivery: receipt,
|
|
329
365
|
cleanup: cleanup.receipt,
|
|
330
366
|
recoveredAfterDelivery: true,
|
|
331
|
-
|
|
367
|
+
...semanticCompletionAfterDelivery(ctx, handle, receipt, completeWork, expectedSemanticRevision),
|
|
332
368
|
});
|
|
333
369
|
}
|
|
334
370
|
|
|
@@ -347,8 +383,12 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
|
|
|
347
383
|
handle.workContractId ?? handle.workId,
|
|
348
384
|
);
|
|
349
385
|
if (!contract) throw new Error(`WORK_CONTRACT_MISSING: ${handle.workContractId ?? handle.workId}`);
|
|
350
|
-
|
|
351
|
-
|
|
386
|
+
const completeWork = args.complete_work === true;
|
|
387
|
+
const allowMergeCommit = args.allow_merge_commit === true;
|
|
388
|
+
const semanticState = semanticWorkState(contract);
|
|
389
|
+
const expectedSemanticRevision = workSemanticView(contract).revision;
|
|
390
|
+
if (semanticState !== 'open' && !(completeWork && semanticState === 'completed')) {
|
|
391
|
+
throw new Error(`WORK_DELIVERY_SEMANTIC_WORK_NOT_OPEN: ${semanticState}`);
|
|
352
392
|
}
|
|
353
393
|
if (contract.workKind !== 'repository_change') {
|
|
354
394
|
throw new Error(`WORK_DELIVERY_WORK_KIND_INVALID: ${contract.workKind}`);
|
|
@@ -360,6 +400,22 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
|
|
|
360
400
|
const targetRoot = target.canonicalRoot;
|
|
361
401
|
const targetHead = exactRevision(targetRoot, targetStatus.head ?? 'HEAD', 'TARGET_HEAD');
|
|
362
402
|
|
|
403
|
+
// Repeated explicit delivery after a successful semantic close is read-only.
|
|
404
|
+
// Never rewrite a terminal Work receipt or reopen its managed worktree.
|
|
405
|
+
if (semanticState === 'completed') {
|
|
406
|
+
const receipt = contract.completionReceipt;
|
|
407
|
+
if (!receipt || !isRepositoryCompletionReceipt(receipt) || !receipt.sourceRevision
|
|
408
|
+
|| receipt.delivery.status !== 'integrated' || receipt.cleanup.status !== 'complete'
|
|
409
|
+
|| receipt.targetBranch !== targetBranch || receipt.sourceRevision !== handle.expectedHead
|
|
410
|
+
|| !git(targetRoot, ['merge-base', '--is-ancestor', receipt.sourceRevision, targetHead]).ok) {
|
|
411
|
+
throw new Error('WORK_DELIVERY_TERMINAL_RECEIPT_UNPROVEN');
|
|
412
|
+
}
|
|
413
|
+
return makeBoundedWorkResult(ctx, session, handle.repositoryId, handle.workId, 'finalization', {
|
|
414
|
+
work: compactHandle(handle), delivery: receipt, cleanup: handle.cleanupReceipt,
|
|
415
|
+
recoveredAfterDelivery: true, semanticCompletionRequired: false, semanticWork: workSemanticView(contract),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
363
419
|
if (
|
|
364
420
|
(handle.state === 'merged' || handle.state === 'failed_terminal_cleanup' || handle.state === 'cleaned')
|
|
365
421
|
&& handle.expectedHead
|
|
@@ -371,6 +427,8 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
|
|
|
371
427
|
targetBranch,
|
|
372
428
|
targetRoot,
|
|
373
429
|
exactRevision(targetRoot, handle.expectedHead, 'SOURCE_REVISION'),
|
|
430
|
+
completeWork,
|
|
431
|
+
expectedSemanticRevision,
|
|
374
432
|
);
|
|
375
433
|
}
|
|
376
434
|
|
|
@@ -416,33 +474,94 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
|
|
|
416
474
|
if (git(targetRoot, ['merge-base', '--is-ancestor', sourceRevision, targetHead]).ok) {
|
|
417
475
|
strategy = 'already_integrated';
|
|
418
476
|
} else {
|
|
419
|
-
|
|
477
|
+
const targetIsAncestor = git(targetRoot, ['merge-base', '--is-ancestor', targetHead, sourceRevision]).ok;
|
|
478
|
+
if (!targetIsAncestor && !allowMergeCommit) {
|
|
420
479
|
throw new Error(`WORK_DELIVERY_NON_FAST_FORWARD: target ${targetHead} and source ${sourceRevision} diverged`);
|
|
421
480
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
targetRoot,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
481
|
+
// Only the actual Git write owns the shared target. An unrelated source Work
|
|
482
|
+
// never holds this lease throughout planning, review or build validation.
|
|
483
|
+
const gated = await withCheckoutMutationGate({
|
|
484
|
+
controllerHome: ctx.controllerHome,
|
|
485
|
+
repoId: handle.repositoryId,
|
|
486
|
+
checkoutId: target.activeCheckoutId,
|
|
487
|
+
repoRoot: targetRoot,
|
|
488
|
+
owner: `deliver-work:${handle.workId}:${randomUUID()}`,
|
|
489
|
+
ttlMs: 120_000,
|
|
490
|
+
ownership: { mutatesGitIndex: true, mutatesGitRefs: true },
|
|
491
|
+
ownerIdentity: {
|
|
492
|
+
repositoryId: handle.repositoryId,
|
|
493
|
+
checkoutId: target.activeCheckoutId,
|
|
494
|
+
worktreeId: targetRoot,
|
|
495
|
+
branch: targetBranch,
|
|
496
|
+
principalId: session.principalId,
|
|
497
|
+
},
|
|
498
|
+
}, async (gate, ownership) => {
|
|
499
|
+
assertTargetMutationAvailable(ctx.controllerHome, handle, target.activeCheckoutId,
|
|
500
|
+
targetBranch, targetHead, targetRoot, gate.ownerJobId);
|
|
501
|
+
ownership.assert();
|
|
502
|
+
const preview = !targetIsAncestor
|
|
503
|
+
? await runBoundedGit(targetRoot, ['merge-tree', '--write-tree', targetHead, sourceRevision], {
|
|
504
|
+
timeoutMs: 30_000, maxOutputBytes: 128 * 1024, signal: ownership.signal,
|
|
505
|
+
})
|
|
506
|
+
: undefined;
|
|
507
|
+
if (preview && !preview.ok) {
|
|
508
|
+
throw new Error(`WORK_DELIVERY_MERGE_CONFLICT: ${preview.stderr || preview.stdout || 'merge-tree failed'}`);
|
|
509
|
+
}
|
|
510
|
+
if (handle.state !== 'committed') {
|
|
511
|
+
handle = transitionWorkHandle(ctx.controllerHome, handle, 'committed', {
|
|
512
|
+
expectedHead: sourceRevision,
|
|
513
|
+
failureReason: undefined,
|
|
514
|
+
finalization: { ...handle.finalization, commit: 'done', lastError: undefined },
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
ownership.assert();
|
|
518
|
+
const merged = await runBoundedGit(targetRoot,
|
|
519
|
+
targetIsAncestor
|
|
520
|
+
? ['merge', '--ff-only', sourceRevision]
|
|
521
|
+
: ['merge', '--no-ff', '--no-edit', sourceRevision],
|
|
522
|
+
{ timeoutMs: 60_000, maxOutputBytes: 128 * 1024, signal: ownership.signal });
|
|
523
|
+
if (!merged.ok) {
|
|
524
|
+
if (ownership.ownershipLost()) throw new Error('WORK_DELIVERY_MUTATION_OWNERSHIP_LOST');
|
|
525
|
+
// A conflicted merge must never strand the shared integration checkout.
|
|
526
|
+
// Abort only our own in-progress merge while retaining the same lease.
|
|
527
|
+
const mergeHead = git(targetRoot, ['rev-parse', '--verify', 'MERGE_HEAD']);
|
|
528
|
+
if (mergeHead.ok) {
|
|
529
|
+
const aborted = await runBoundedGit(targetRoot, ['merge', '--abort'], {
|
|
530
|
+
timeoutMs: 30_000, maxOutputBytes: 128 * 1024, signal: ownership.signal,
|
|
531
|
+
});
|
|
532
|
+
if (!aborted.ok) {
|
|
533
|
+
throw new Error(`WORK_DELIVERY_MERGE_ABORT_FAILED: ${aborted.stderr || aborted.stdout}; merge error: ${merged.stderr || merged.stdout}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
throw new Error(`WORK_DELIVERY_GIT_MERGE_FAILED: ${merged.stderr || merged.stdout || 'git merge failed'}`);
|
|
537
|
+
}
|
|
538
|
+
ownership.assert();
|
|
539
|
+
const afterMergeStatus = git(targetRoot, ['status', '--porcelain=v1']);
|
|
540
|
+
if (!afterMergeStatus.ok || afterMergeStatus.stdout) {
|
|
541
|
+
throw new Error('WORK_DELIVERY_TARGET_DIRTY_AFTER_MERGE');
|
|
542
|
+
}
|
|
543
|
+
const newHead = exactRevision(targetRoot, 'HEAD', 'TARGET_HEAD');
|
|
544
|
+
if (targetIsAncestor && newHead !== sourceRevision) {
|
|
545
|
+
throw new Error(`WORK_DELIVERY_TARGET_HEAD_UNEXPECTED: expected ${sourceRevision}, found ${newHead}`);
|
|
546
|
+
}
|
|
547
|
+
if (!targetIsAncestor) {
|
|
548
|
+
const ancestry = git(targetRoot, ['rev-list', '--parents', '-n', '1', newHead]);
|
|
549
|
+
const [, firstParent, secondParent, ...extraParents] = ancestry.stdout.split(/\s+/);
|
|
550
|
+
if (!ancestry.ok || firstParent !== targetHead || secondParent !== sourceRevision || extraParents.length > 0) {
|
|
551
|
+
throw new Error('WORK_DELIVERY_MERGE_PARENT_MISMATCH');
|
|
552
|
+
}
|
|
553
|
+
const actualTree = git(targetRoot, ['rev-parse', `${newHead}^{tree}`]);
|
|
554
|
+
if (!actualTree.ok || actualTree.stdout !== preview!.stdout.split(/\s+/)[0]) {
|
|
555
|
+
throw new Error('WORK_DELIVERY_MERGE_TREE_MISMATCH');
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return { newHead, mergeStrategy: (targetIsAncestor ? 'work_fast_forward' : 'work_merge_commit') as RepositoryCompletionReceipt['delivery']['strategy'] };
|
|
559
|
+
});
|
|
560
|
+
if (!gated.ok) {
|
|
561
|
+
throw new Error(`WORK_DELIVERY_TARGET_LEASE_ACTIVE: ${gated.busy.blockers.map((blocker) => blocker.owner).join(', ')}`);
|
|
444
562
|
}
|
|
445
|
-
|
|
563
|
+
integratedTargetHead = gated.value.newHead;
|
|
564
|
+
strategy = gated.value.mergeStrategy;
|
|
446
565
|
}
|
|
447
566
|
|
|
448
567
|
if (!git(targetRoot, ['merge-base', '--is-ancestor', sourceRevision, integratedTargetHead]).ok) {
|
|
@@ -492,6 +611,6 @@ export async function deliverWork(ctx: McpExecutionContext, args: Record<string,
|
|
|
492
611
|
work: compactHandle(refreshed),
|
|
493
612
|
delivery: receipt,
|
|
494
613
|
cleanup: cleanup.receipt,
|
|
495
|
-
|
|
614
|
+
...semanticCompletionAfterDelivery(ctx, handle, receipt, completeWork, expectedSemanticRevision),
|
|
496
615
|
});
|
|
497
616
|
}
|
|
@@ -2,7 +2,7 @@ import { resolve } from 'path';
|
|
|
2
2
|
import type { RepositoryRecord } from '../../../cli/repositories/types';
|
|
3
3
|
import { getRepository, resolveRepositorySelection, selectRepositoryCheckout } from '../../../cli/repositories/registry';
|
|
4
4
|
import { repositoryGitStatus } from '../../../cli/repositories/structured-git';
|
|
5
|
-
import { appendWorkEvidence, getWorkContract, recordWorkEvidenceState, semanticWorkState } from '../../../../packages/kernel/work/api/index';
|
|
5
|
+
import { appendWorkEvidence, getWorkContract, promoteWorkToRepositoryChange, recordWorkEvidenceState, semanticWorkState } from '../../../../packages/kernel/work/api/index';
|
|
6
6
|
import { currentPermissionSnapshotVersion } from './validation';
|
|
7
7
|
import { listWorkHandles, readWorkHandle, transitionWorkHandle, writeWorkHandle, type WorkHandleState } from './work-handle-store';
|
|
8
8
|
import { gitIsAncestor } from './work-execution-support';
|
|
@@ -371,7 +371,7 @@ export function ensureRepositoryMutationWorkHandle(input: {
|
|
|
371
371
|
principalId: string;
|
|
372
372
|
sessionId?: string;
|
|
373
373
|
deferEffectPromotion?: boolean;
|
|
374
|
-
}): { handle: WorkHandleState; promotedFrom?: 'local_effect' | 'remote_effect' } {
|
|
374
|
+
}): { handle: WorkHandleState; promotedFrom?: 'investigation' | 'local_effect' | 'remote_effect' } {
|
|
375
375
|
const store = { controllerHome: input.controllerHome, repoId: input.repository.repoId };
|
|
376
376
|
const contract = getWorkContract(store, input.workId);
|
|
377
377
|
if (!contract) throw new Error(`WORK_NOT_FOUND: ${input.workId}`);
|
|
@@ -421,6 +421,14 @@ export function ensureRepositoryMutationWorkHandle(input: {
|
|
|
421
421
|
handle,
|
|
422
422
|
});
|
|
423
423
|
assertManagedRepositoryMutationAuthority({ repository: input.repository, handle });
|
|
424
|
-
|
|
424
|
+
// This is the first point at which concrete repository mutation has passed
|
|
425
|
+
// the exact WorkHandle checkout, ownership, and managed-worktree fences.
|
|
426
|
+
// WorkContract remains the sole writer for its semantic classification.
|
|
427
|
+
const promotedFrom = contract.workKind === 'investigation'
|
|
428
|
+
|| contract.workKind === 'local_effect'
|
|
429
|
+
|| contract.workKind === 'remote_effect'
|
|
430
|
+
? contract.workKind
|
|
431
|
+
: undefined;
|
|
432
|
+
promoteWorkToRepositoryChange(store, input.workId);
|
|
433
|
+
return promotedFrom ? { handle, promotedFrom } : { handle };
|
|
425
434
|
}
|
|
426
|
-
|
|
@@ -4,7 +4,8 @@ import { join } from 'path';
|
|
|
4
4
|
import { scopedOperationRoot } from '../../../cli/repositories/controller-home';
|
|
5
5
|
import { readJsonFile, sanitizeFileComponent } from '../../shared/json-files';
|
|
6
6
|
import type { ProcessCheckExecutionIdentity } from '../../evidence/process-check-execution';
|
|
7
|
-
import { listControlPlaneRecords, mutateControlPlaneRecord, readOrImportControlPlaneRecord, writeControlPlaneRecord } from '../persistence/sqlite-store';
|
|
7
|
+
import { listControlPlaneRecords, listControlPlaneRecordsWithinTransaction, mutateControlPlaneRecord, readControlPlaneRecordWithinTransaction, readOrImportControlPlaneRecord, writeControlPlaneRecord, writeControlPlaneRecordWithinTransaction } from '../persistence/sqlite-store';
|
|
8
|
+
import { updateWorkContractPlacementAtomically } from '../../../../packages/kernel/work/api/index';
|
|
8
9
|
|
|
9
10
|
export const WORK_HANDLE_STATES = [
|
|
10
11
|
'prepared',
|
|
@@ -304,6 +305,9 @@ function updateValidationIndexMembership(
|
|
|
304
305
|
validating: boolean,
|
|
305
306
|
): void {
|
|
306
307
|
const existing = readValidationIndex(controllerHome, repositoryId);
|
|
308
|
+
// The index describes pending validation membership, not WorkHandle activity.
|
|
309
|
+
// Other lifecycle writes do not change its membership or need an index CAS.
|
|
310
|
+
if (existing && existing.workIds.includes(workId) === validating) return;
|
|
307
311
|
if (!existing) {
|
|
308
312
|
// The WorkHandle write is already durable. Rebuild once from authoritative
|
|
309
313
|
// handles so legacy repositories establish an exact index without a scan
|
|
@@ -376,6 +380,54 @@ export function writeWorkHandle(controllerHome: string, handle: WorkHandleState)
|
|
|
376
380
|
return { ...record.value, recordRevision: record.revision };
|
|
377
381
|
}
|
|
378
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Atomically rebind the one existing execution handle and its Kernel Work
|
|
385
|
+
* placement. The application service already proved exact Git ancestry and
|
|
386
|
+
* clean exclusive target; this store owns only the two-record CAS mutation.
|
|
387
|
+
*/
|
|
388
|
+
export function relocateWorkHandlePlacementAtomically(
|
|
389
|
+
controllerHome: string,
|
|
390
|
+
prior: WorkHandleState,
|
|
391
|
+
target: { checkoutId: string; root: string; branch: string; head: string },
|
|
392
|
+
): WorkHandleState {
|
|
393
|
+
const moved: WorkHandleState = {
|
|
394
|
+
...prior, checkoutId: target.checkoutId, worktreePath: target.root, branch: target.branch,
|
|
395
|
+
expectedHead: target.head, managedWorktree: true,
|
|
396
|
+
validationRun: undefined, validatedInputFingerprint: undefined,
|
|
397
|
+
finalization: { validation: 'pending', commit: 'pending', merge: 'pending', branchCleanup: 'pending', worktreeCleanup: 'pending' },
|
|
398
|
+
};
|
|
399
|
+
updateWorkContractPlacementAtomically({ controllerHome, repoId: prior.repositoryId }, prior.workId, {
|
|
400
|
+
checkoutId: target.checkoutId,
|
|
401
|
+
worktreeRef: target.root,
|
|
402
|
+
executionPlacement: { schemaVersion: 1, repositoryId: prior.repositoryId, checkoutId: target.checkoutId },
|
|
403
|
+
worktreePolicy: { required: true, reason: 'Explicit identity-preserving managed checkout relocation', ref: target.root },
|
|
404
|
+
}, (database, updatedContract) => {
|
|
405
|
+
if (updatedContract.checkoutId !== moved.checkoutId) throw new Error('WORK_RELOCATION_TRANSACTION_CONTRACT_MISMATCH');
|
|
406
|
+
const row = readControlPlaneRecordWithinTransaction<WorkHandleState>(
|
|
407
|
+
database, 'execution_work_handle', prior.repositoryId, prior.workId,
|
|
408
|
+
);
|
|
409
|
+
if (!row || row.revision !== prior.recordRevision || row.value.checkoutId !== prior.checkoutId
|
|
410
|
+
|| row.value.expectedHead !== prior.expectedHead) {
|
|
411
|
+
throw new Error('WORK_RELOCATION_HANDLE_CAS_CONFLICT');
|
|
412
|
+
}
|
|
413
|
+
const competing = listControlPlaneRecordsWithinTransaction<WorkHandleState>(database, {
|
|
414
|
+
namespace: 'execution_work_handle', scope: prior.repositoryId, limit: 5000,
|
|
415
|
+
}).some((entry) => entry.value.workId !== prior.workId && entry.value.checkoutId === target.checkoutId
|
|
416
|
+
&& entry.value.state !== 'cleaned');
|
|
417
|
+
if (competing) throw new Error('WORK_RELOCATION_TARGET_OWNED');
|
|
418
|
+
const { recordRevision: _revision, ...persisted } = moved;
|
|
419
|
+
writeControlPlaneRecordWithinTransaction(database, {
|
|
420
|
+
namespace: 'execution_work_handle', scope: prior.repositoryId, key: prior.workId,
|
|
421
|
+
schemaVersion: 1, expectedRevision: row.revision,
|
|
422
|
+
value: { ...persisted, updatedAt: new Date().toISOString() },
|
|
423
|
+
action: 'work_handle_checkout_relocated',
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
const refreshed = readWorkHandle(controllerHome, prior.repositoryId, prior.workId);
|
|
427
|
+
if (!refreshed || refreshed.checkoutId !== target.checkoutId) throw new Error('WORK_RELOCATION_COMMIT_NOT_VERIFIED');
|
|
428
|
+
return refreshed;
|
|
429
|
+
}
|
|
430
|
+
|
|
379
431
|
/**
|
|
380
432
|
* Refresh legacy controller identity fields as provenance only. Repository
|
|
381
433
|
* mutation authority remains WorkHandle CAS plus concrete checkout/resource fences.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { isAbsolute, relative, resolve } from 'path';
|
|
3
3
|
import type { McpExecutionContext } from '../../../../packages/protocols/mcp/execution-context';
|
|
4
|
-
import { repositoryGitStatus,
|
|
4
|
+
import { repositoryGitStatus, repositoryGitWorkspaceChanges } from '../../../cli/repositories/structured-git';
|
|
5
5
|
import { classifyRepositoryCommand } from '../../../cli/repositories/command-classifier';
|
|
6
6
|
import { listControllerChecks, readLatestControllerCheckEvidence } from '../../../cli/controller/check-runner';
|
|
7
7
|
import { readRepositoryAccessPolicy } from '../governance/access-policy';
|
|
@@ -21,7 +21,7 @@ import { projectTerminalCheckVerification } from '../../execution/process-runtim
|
|
|
21
21
|
import { claimProcessInvocation, getProcessRecord } from '../../execution/process-runtime/store';
|
|
22
22
|
import { runPersistedCheckViaProcessRuntime } from '../../execution/process-runtime/persisted-check';
|
|
23
23
|
import { markWorkValidationCurrentFromReusedEvidence, markWorkValidationPending, projectWorkValidationOutcome } from './work-validation-reconciler';
|
|
24
|
-
import { compactHandle, contractFor,
|
|
24
|
+
import { compactHandle, contractFor, identityFor, makeBoundedWorkResult, reconcileTerminalCleanup, requireSession, terminalCleanupOutcome, workForSession } from './work-execution-support';
|
|
25
25
|
|
|
26
26
|
function commandInputs(args: Record<string, unknown>): Array<Record<string, unknown>> {
|
|
27
27
|
if (Array.isArray(args.commands)) return args.commands.filter((value): value is Record<string, unknown> => Boolean(value && typeof value === 'object' && !Array.isArray(value)));
|
|
@@ -173,11 +173,18 @@ export async function executeWork(ctx: McpExecutionContext, args: Record<string,
|
|
|
173
173
|
}
|
|
174
174
|
return ordered;
|
|
175
175
|
})();
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// Reuse the exact post-command Git snapshot for both branch and HEAD.
|
|
177
|
+
// A second rev-parse can observe a different instant during concurrent writes.
|
|
178
|
+
const postCommandStatus = repositoryGitStatus(cheap.worktreeRepository);
|
|
179
|
+
const branch = postCommandStatus.branch;
|
|
180
|
+
const head = postCommandStatus.head ?? undefined;
|
|
178
181
|
let nextHandle = handle;
|
|
179
182
|
if (branch !== handle.branch) nextHandle = markWorkHandleFailed(ctx.controllerHome, handle, `command changed the bound branch to ${branch ?? 'detached'}`);
|
|
180
|
-
else
|
|
183
|
+
else if (handle.state !== 'editing' || handle.expectedHead !== head || handle.failureReason !== undefined) {
|
|
184
|
+
// A read-only command on an already-current Work must not consume a CAS
|
|
185
|
+
// revision (or rewrite the validation index) just to reaffirm the same HEAD.
|
|
186
|
+
nextHandle = transitionWorkHandle(ctx.controllerHome, handle, 'editing', { expectedHead: head, failureReason: undefined });
|
|
187
|
+
}
|
|
181
188
|
const value = {
|
|
182
189
|
work: compactHandle(nextHandle),
|
|
183
190
|
commands: executions,
|
|
@@ -237,9 +244,9 @@ export async function validateWork(ctx: McpExecutionContext, args: Record<string
|
|
|
237
244
|
}
|
|
238
245
|
const validated = validateWorkHandle(ctx.controllerHome, handle, identityFor(ctx, args), 'full', 'validate');
|
|
239
246
|
const contract = contractFor(ctx, handle);
|
|
240
|
-
const
|
|
241
|
-
const workspaceChangedPaths =
|
|
242
|
-
const validationStatus =
|
|
247
|
+
const workspaceChanges = repositoryGitWorkspaceChanges(validated.worktreeRepository);
|
|
248
|
+
const workspaceChangedPaths = workspaceChanges.changedPaths;
|
|
249
|
+
const validationStatus = workspaceChanges.status;
|
|
243
250
|
const validationHead = validationStatus.head
|
|
244
251
|
?? handle.expectedHead
|
|
245
252
|
?? handle.baseCommit
|
|
@@ -469,9 +476,12 @@ export async function validateWork(ctx: McpExecutionContext, args: Record<string
|
|
|
469
476
|
: acceptedFailure
|
|
470
477
|
? String(acceptedFailure.summary ?? 'targeted validation failed')
|
|
471
478
|
: undefined;
|
|
479
|
+
// A missing/stale Check receipt is retryable validation evidence, not a
|
|
480
|
+
// failed Work execution. Keep the original Work state so repair commands
|
|
481
|
+
// remain authorized; accepted test failures retain the existing failed path.
|
|
472
482
|
const nextState = !completed
|
|
473
483
|
? 'validating'
|
|
474
|
-
: passed
|
|
484
|
+
: passed || infrastructureFailure
|
|
475
485
|
? validationRun.resumeState
|
|
476
486
|
: 'failed';
|
|
477
487
|
const validation = !completed ? 'pending' : passed ? 'done' : 'failed';
|
|
@@ -482,7 +492,7 @@ export async function validateWork(ctx: McpExecutionContext, args: Record<string
|
|
|
482
492
|
lastError: failureSummary,
|
|
483
493
|
},
|
|
484
494
|
validationRun: completed ? undefined : validationRun,
|
|
485
|
-
|
|
495
|
+
validatedInputFingerprint: passed ? validationFingerprint : undefined,
|
|
486
496
|
...(failureSummary ? { failureReason: failureSummary } : { failureReason: undefined }),
|
|
487
497
|
});
|
|
488
498
|
if (completed) {
|