@moretea-labs/forge 1.6.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 +4 -0
- package/CHANGELOG.md +39 -0
- package/CLAUDE.md +4 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/prompt-guard.sh +5 -81
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +178 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-plugin-management.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +26 -6
- package/docs/operations/controller-performance-and-502.md +143 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +33 -11
- package/scripts/activate-source-baseline.ts +17 -13
- 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-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -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 -1558
- package/scripts/check-architecture-sync.sh +2 -371
- 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-mcp-compatibility.ts +14 -0
- package/scripts/check-runtime-architecture.mjs +349 -79
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +92 -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 -722
- package/scripts/ensure-task-workflow.sh +1 -1225
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +22 -46
- 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 -1189
- 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 -796
- 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 +5 -1
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -558
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- 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 -703
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +1 -677
- package/scripts/workflow-contract.ts +4 -205
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +113 -5
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +828 -487
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +135 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +37 -264
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +2 -515
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -222
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +282 -146
- package/src/cli/mcp/server.ts +586 -116
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +44 -7
- package/src/cli/mcp/transports/http.ts +124 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +103 -21
- package/src/cli/repositories/command-executor.ts +78 -526
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +124 -0
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +118 -6
- package/src/cli/repository/inspector.ts +563 -65
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +223 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- 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/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- 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 +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- 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-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- 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 +90 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
- package/src/runtime/control-plane/facade/index.ts +3 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
- 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/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +90 -10
- package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +24 -21
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +13 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +88 -0
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +53 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -0
- package/src/runtime/execution/process-runtime/types.ts +4 -12
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -61
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +186 -84
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
- package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
- package/src/runtime/plugins/browser-adapter.ts +2662 -829
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +110 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
- 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-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
- package/src/runtime/plugins/browser-runtime.ts +193 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -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/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
- package/src/runtime/plugins/external-adapter.ts +358 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +123 -6
- package/src/runtime/plugins/macos-capability-broker.ts +47 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +330 -33
- package/src/runtime/plugins/types.ts +38 -3
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/maintenance-executor.ts +426 -25
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +230 -10
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +461 -22
- package/src/runtime/root/release-manifest.ts +144 -18
- package/src/runtime/root/release-materialize.ts +125 -32
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +12 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +1079 -240
- package/src/runtime/standalone-recovery/entry.ts +100 -24
- package/src/runtime/standalone-recovery/installer.ts +20 -4
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
- package/src/runtime/workflow/schedules/engine.ts +364 -202
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +64 -17
- package/src/runtime/workflow/schedules/store.ts +209 -19
- package/src/runtime/workflow/schedules/types.ts +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
- package/docs/architecture/current/entity-model.md +0 -612
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -169
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -321
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import {
|
|
3
3
|
createHandoffItem,
|
|
4
|
+
listHandoffItems,
|
|
4
5
|
type HandoffInboxStoreOptions,
|
|
5
6
|
} from './handoff-inbox-store';
|
|
7
|
+
import { getControllerSession } from './controller-session-store';
|
|
6
8
|
import {
|
|
7
9
|
appendVerificationRecord,
|
|
8
10
|
appendWorkEvidence,
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
createWorkContract,
|
|
11
13
|
getWorkContract,
|
|
12
14
|
listWorkContracts,
|
|
15
|
+
recordWorkScopeEvidence,
|
|
13
16
|
summarizeWorkContract,
|
|
14
17
|
transitionWorkContractPhase,
|
|
15
18
|
updateWorkContract,
|
|
@@ -21,6 +24,10 @@ import {
|
|
|
21
24
|
getPlanContract,
|
|
22
25
|
type PlanContractStoreOptions,
|
|
23
26
|
} from './plan-contract-store';
|
|
27
|
+
import { withPrimaryWorkAdmissionLock } from './semantic-admission';
|
|
28
|
+
import { completeWorkWithReceipt } from '../execution/work-completion-authority';
|
|
29
|
+
import { evaluateReadOnlyReviewSourceIdentity, evaluateWorkCompletionEvidence, evaluateWorkImplementationEvidence, verificationRecordAppliesToCurrentWorkspace } from '../execution/work-evidence-policy';
|
|
30
|
+
import { readRequirement } from '../persistence/requirement-store';
|
|
24
31
|
import {
|
|
25
32
|
classifyVerificationOutcome,
|
|
26
33
|
normalizeCheckIds,
|
|
@@ -41,9 +48,11 @@ import type {
|
|
|
41
48
|
SuggestedNextAction,
|
|
42
49
|
VerificationRecord,
|
|
43
50
|
WorkContract,
|
|
51
|
+
WorkKind,
|
|
44
52
|
WorkRisk,
|
|
45
53
|
} from './types';
|
|
46
54
|
import { selectExecutionMode } from './types';
|
|
55
|
+
import { resolveWorkspaceAdmissionConstraint } from '../routing/workspace-admission';
|
|
47
56
|
|
|
48
57
|
export type GoalWorkloopOperation = 'start' | 'continue' | 'verify' | 'finalize' | 'stop';
|
|
49
58
|
|
|
@@ -58,8 +67,14 @@ export interface GoalWorkloopContext {
|
|
|
58
67
|
principalId?: string;
|
|
59
68
|
controllerInstanceId?: string;
|
|
60
69
|
workspaceFingerprint?: string;
|
|
70
|
+
/** Current net repository paths changed from the Work base plus dirty checkout paths. */
|
|
71
|
+
workspaceChangedPaths?: readonly string[];
|
|
72
|
+
/** Durable terminal repository Process ids explicitly bound to this Work + checkout. */
|
|
73
|
+
workBoundProcessEvidenceIds?: readonly string[];
|
|
61
74
|
now?: () => string;
|
|
62
|
-
|
|
75
|
+
/** True only while a Gateway caller holds the cross-process primary Work admission lock. */
|
|
76
|
+
semanticAdmissionLocked?: boolean;
|
|
77
|
+
materializeIsolatedWorkspace?: (input: { workId: string; title: string; baseRef?: string; needsDependencies?: boolean }) => { checkoutId: string; root: string; baseRevision?: string | null; managed: true };
|
|
63
78
|
}
|
|
64
79
|
|
|
65
80
|
export type WorkAdmissionRelation = 'continue' | 'extend' | 'parallel' | 'new_goal';
|
|
@@ -68,6 +83,8 @@ export interface GoalWorkloopStartInput {
|
|
|
68
83
|
objective: string;
|
|
69
84
|
acceptanceCriteria?: string[];
|
|
70
85
|
allowedPaths?: string[];
|
|
86
|
+
/** Non-authoritative first-pass discovery candidates. */
|
|
87
|
+
initialLikelyPaths?: string[];
|
|
71
88
|
forbiddenPaths?: string[];
|
|
72
89
|
checks?: string[];
|
|
73
90
|
constraints?: WorkContract['constraints'];
|
|
@@ -83,16 +100,45 @@ export interface GoalWorkloopStartInput {
|
|
|
83
100
|
forceMode?: WorkContract['mode'];
|
|
84
101
|
planId?: string;
|
|
85
102
|
planStepId?: string;
|
|
103
|
+
/** Explicit technical evidence shape chosen by the semantic Controller. Never inferred from objective/check text. */
|
|
104
|
+
workKind?: Extract<WorkKind, 'repository_change' | 'completed_no_change' | 'read_only_review' | 'investigation' | 'local_effect' | 'remote_effect' | 'reconciliation'>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface GoalWorkloopAcceptanceEvidenceBinding {
|
|
108
|
+
criterion: string;
|
|
109
|
+
evidenceIds: string[];
|
|
110
|
+
rationale: string;
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
export interface GoalWorkloopContinueInput {
|
|
89
114
|
workId: string;
|
|
90
115
|
note?: string;
|
|
116
|
+
allowedPaths?: string[];
|
|
117
|
+
forbiddenPaths?: string[];
|
|
118
|
+
checks?: string[];
|
|
119
|
+
additionalLikelyPaths?: string[];
|
|
120
|
+
inspectedPaths?: string[];
|
|
121
|
+
/** Concise semantic findings discovered by a first-class read-only review. */
|
|
122
|
+
reviewFindings?: string[];
|
|
123
|
+
/** Explicit Controller-reviewed bindings from exact declared criteria to durable Work evidence. */
|
|
124
|
+
acceptanceEvidence?: GoalWorkloopAcceptanceEvidenceBinding[];
|
|
125
|
+
/** Explicit Controller decision after deterministic acceptance failure; never inferred from note text. */
|
|
126
|
+
acceptanceFailureDecision?: 'repair' | 'rescope';
|
|
91
127
|
}
|
|
92
128
|
|
|
93
129
|
export interface GoalWorkloopVerifyInput {
|
|
94
130
|
workId: string;
|
|
95
131
|
checkId: string;
|
|
132
|
+
/** Exact Git revision observed by the authoritative Work-bound verification Process. */
|
|
133
|
+
sourceRevision?: string;
|
|
134
|
+
/** Exact dirty-workspace content identity observed by the authoritative check. */
|
|
135
|
+
workspaceFingerprint?: string;
|
|
136
|
+
/** Stable semantic check-input identity used for exact evidence reuse. */
|
|
137
|
+
verificationInputFingerprint?: string;
|
|
138
|
+
/** Bounded command/invocation audit identity; not part of reusable semantic inputs. */
|
|
139
|
+
commandFingerprint?: string;
|
|
140
|
+
/** Persistence-safe Process receipt. Accepted only when its Work/repo/check identity matches this verification. */
|
|
141
|
+
receipt?: VerificationRecord['receipt'];
|
|
96
142
|
/** When true, simulate infrastructure failure rather than acceptance fail. */
|
|
97
143
|
infrastructureFailed?: boolean;
|
|
98
144
|
/** When true and check is valid, record acceptance failure. */
|
|
@@ -131,6 +177,7 @@ function handoffIdFor(prefix: string): string {
|
|
|
131
177
|
}
|
|
132
178
|
|
|
133
179
|
function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
|
|
180
|
+
if (input.workKind === 'read_only_review') return 'readonly';
|
|
134
181
|
const risk = input.modeInput.risk;
|
|
135
182
|
if (input.modeInput.secretAccess === true || input.modeInput.destructive === true) return 'destructive';
|
|
136
183
|
if (input.modeInput.remoteWrite === true) return 'high';
|
|
@@ -141,13 +188,13 @@ function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
|
|
|
141
188
|
return 'medium';
|
|
142
189
|
}
|
|
143
190
|
|
|
144
|
-
function
|
|
191
|
+
function suggestedForWorkIdentity(workId: string, checks: string[], extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
145
192
|
const base: SuggestedNextAction[] = [
|
|
146
193
|
{
|
|
147
194
|
label: 'Continue workloop',
|
|
148
195
|
tool: 'rh_work',
|
|
149
196
|
operation: 'continue',
|
|
150
|
-
payload: { work_id:
|
|
197
|
+
payload: { work_id: workId },
|
|
151
198
|
risk: 'readonly',
|
|
152
199
|
confidence: 'high',
|
|
153
200
|
},
|
|
@@ -155,24 +202,28 @@ function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []
|
|
|
155
202
|
label: 'Verify registered checks',
|
|
156
203
|
tool: 'rh_work',
|
|
157
204
|
operation: 'verify',
|
|
158
|
-
payload: { work_id:
|
|
205
|
+
payload: { work_id: workId, check_id: checks[0] },
|
|
159
206
|
risk: 'workspace_write',
|
|
160
|
-
confidence:
|
|
207
|
+
confidence: checks[0] ? 'high' : 'low',
|
|
161
208
|
},
|
|
162
209
|
{
|
|
163
210
|
label: 'Finalize when ready',
|
|
164
211
|
tool: 'rh_work',
|
|
165
212
|
operation: 'finalize',
|
|
166
|
-
payload: { work_id:
|
|
213
|
+
payload: { work_id: workId },
|
|
167
214
|
risk: 'readonly',
|
|
168
215
|
confidence: 'medium',
|
|
169
216
|
},
|
|
170
217
|
];
|
|
171
218
|
return validateSuggestedNextActions([...extras, ...base], {
|
|
172
|
-
validCheckIds:
|
|
219
|
+
validCheckIds: checks,
|
|
173
220
|
}).actions;
|
|
174
221
|
}
|
|
175
222
|
|
|
223
|
+
function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
224
|
+
return suggestedForWorkIdentity(work.workId, work.checks, extras);
|
|
225
|
+
}
|
|
226
|
+
|
|
176
227
|
function initialEvidence(objective: string): EvidenceRef {
|
|
177
228
|
return {
|
|
178
229
|
title: 'work contract created',
|
|
@@ -181,56 +232,6 @@ function initialEvidence(objective: string): EvidenceRef {
|
|
|
181
232
|
};
|
|
182
233
|
}
|
|
183
234
|
|
|
184
|
-
type ReconciledVerificationHistory = ReturnType<typeof reconcileVerificationHistory>;
|
|
185
|
-
|
|
186
|
-
interface WorkCompletionEvidenceEvaluation {
|
|
187
|
-
status: 'complete' | 'failed' | 'incomplete';
|
|
188
|
-
history: ReconciledVerificationHistory;
|
|
189
|
-
missingChecks: string[];
|
|
190
|
-
durableResultEvidence: boolean;
|
|
191
|
-
reasons: string[];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function evaluateWorkCompletionEvidence(
|
|
195
|
-
work: WorkContract,
|
|
196
|
-
history: ReconciledVerificationHistory = reconcileVerificationHistory(
|
|
197
|
-
work.checkRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
|
|
198
|
-
),
|
|
199
|
-
): WorkCompletionEvidenceEvaluation {
|
|
200
|
-
const missingChecks = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
201
|
-
const durableResultEvidence = work.evidenceRefs.some((evidence) => Boolean(evidence.evidenceId || evidence.artifactId));
|
|
202
|
-
const reasons: string[] = [];
|
|
203
|
-
|
|
204
|
-
if (history.acceptanceFailures.length > 0) {
|
|
205
|
-
reasons.push(`Acceptance checks failed: ${history.acceptanceFailures.join(', ')}.`);
|
|
206
|
-
return { status: 'failed', history, missingChecks, durableResultEvidence, reasons };
|
|
207
|
-
}
|
|
208
|
-
if (history.infrastructureIssues.length > 0) {
|
|
209
|
-
reasons.push(`Infrastructure issues remain: ${history.infrastructureIssues.join(', ')}.`);
|
|
210
|
-
}
|
|
211
|
-
if (history.invalidCheckIds.length > 0) {
|
|
212
|
-
reasons.push(`Invalid check ids remain: ${history.invalidCheckIds.join(', ')}.`);
|
|
213
|
-
}
|
|
214
|
-
if (missingChecks.length > 0) {
|
|
215
|
-
reasons.push(`Declared checks are missing valid_pass evidence: ${missingChecks.join(', ')}.`);
|
|
216
|
-
}
|
|
217
|
-
if (work.checks.length === 0 && !durableResultEvidence) {
|
|
218
|
-
reasons.push('No durable result evidence (evidenceId or artifactId) was recorded for this no-check WorkContract.');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const complete = history.infrastructureIssues.length === 0
|
|
222
|
-
&& history.invalidCheckIds.length === 0
|
|
223
|
-
&& missingChecks.length === 0
|
|
224
|
-
&& (work.checks.length > 0 || durableResultEvidence);
|
|
225
|
-
return {
|
|
226
|
-
status: complete ? 'complete' : 'incomplete',
|
|
227
|
-
history,
|
|
228
|
-
missingChecks,
|
|
229
|
-
durableResultEvidence,
|
|
230
|
-
reasons,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
235
|
/**
|
|
235
236
|
* Mode selection for facade routing. Direct control never creates a WorkContract.
|
|
236
237
|
* Goal workloop creates one. Handoff-only creates a handoff and stops.
|
|
@@ -239,19 +240,86 @@ export function routeWorkStart(
|
|
|
239
240
|
ctx: GoalWorkloopContext,
|
|
240
241
|
input: GoalWorkloopStartInput,
|
|
241
242
|
): FacadeResult {
|
|
243
|
+
const placementResolution = resolveWorkspaceAdmissionConstraint(input.constraints);
|
|
244
|
+
if (placementResolution.ok === false) {
|
|
245
|
+
return buildFacadeResult({
|
|
246
|
+
status: 'blocked',
|
|
247
|
+
summary: `${placementResolution.code}: ${placementResolution.message}`,
|
|
248
|
+
data: { executionStarted: false, workContractCreated: false, placementConstraintConflict: true },
|
|
249
|
+
rawAvailable: false,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const placementConstraint = placementResolution.constraint;
|
|
253
|
+
const canonicalConstraints: WorkContract['constraints'] = {
|
|
254
|
+
...(input.constraints ?? {}),
|
|
255
|
+
workspaceMode: placementConstraint.workspaceMode,
|
|
256
|
+
requireWorktree: placementConstraint.requireWorktree,
|
|
257
|
+
directMainProhibited: placementConstraint.directMainProhibited,
|
|
258
|
+
};
|
|
242
259
|
const strategyConflictRequiresApproval = input.constraints?.architectureStrategyChange === true
|
|
243
260
|
|| input.constraints?.conflictsWithThinHarnessPolicy === true
|
|
244
261
|
|| input.constraints?.changesDefaultExecutionStrategy === true;
|
|
262
|
+
const relatedLifecycleSource = input.relatedWorkId ? getWorkContract(ctx.workStore, input.relatedWorkId) : undefined;
|
|
263
|
+
const inheritedReadOnlyReview = Boolean(
|
|
264
|
+
input.workKind === undefined
|
|
265
|
+
&& input.workRelation === 'new_goal'
|
|
266
|
+
&& relatedLifecycleSource?.workKind === 'read_only_review'
|
|
267
|
+
&& (relatedLifecycleSource.status === 'cancelled' || relatedLifecycleSource.status === 'completed' || relatedLifecycleSource.status === 'failed'),
|
|
268
|
+
);
|
|
269
|
+
if (
|
|
270
|
+
inheritedReadOnlyReview
|
|
271
|
+
&& relatedLifecycleSource?.baseRevision
|
|
272
|
+
&& ctx.sourceRevision
|
|
273
|
+
&& relatedLifecycleSource.baseRevision !== ctx.sourceRevision
|
|
274
|
+
) {
|
|
275
|
+
return buildFacadeResult({
|
|
276
|
+
status: 'blocked',
|
|
277
|
+
summary: `READ_ONLY_REVIEW_REPLACEMENT_SOURCE_DRIFT: replacement source ${ctx.sourceRevision} does not match frozen review base ${relatedLifecycleSource.baseRevision}.`,
|
|
278
|
+
data: { executionStarted: false, workContractCreated: false, relatedWorkId: relatedLifecycleSource.workId, sourceDrift: true },
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const requestedWorkKind = input.workKind ?? (inheritedReadOnlyReview ? 'read_only_review' : undefined);
|
|
282
|
+
const typedReadOnlyReviewRequested = requestedWorkKind === undefined
|
|
283
|
+
&& (input.modeInput.mutation === false || (input.modeInput.mutation === undefined && input.modeInput.risk === 'readonly'))
|
|
284
|
+
&& input.modeInput.requiresInvestigation === true
|
|
285
|
+
&& input.modeInput.requiresRecovery === true;
|
|
286
|
+
const effectiveWorkKind = requestedWorkKind ?? (typedReadOnlyReviewRequested ? 'read_only_review' : undefined);
|
|
287
|
+
const readOnlyReviewRequested = effectiveWorkKind === 'read_only_review';
|
|
288
|
+
const readOnlyMutationConflict = readOnlyReviewRequested && (
|
|
289
|
+
input.modeInput.mutation === true
|
|
290
|
+
|| (input.modeInput.expectedFiles ?? 0) > 0
|
|
291
|
+
|| (input.modeInput.expectedChangedLines ?? 0) > 0
|
|
292
|
+
|| input.modeInput.requiresExternalEffect === true
|
|
293
|
+
|| input.modeInput.remoteWrite === true
|
|
294
|
+
|| input.modeInput.destructive === true
|
|
295
|
+
);
|
|
296
|
+
if (readOnlyMutationConflict) {
|
|
297
|
+
return buildFacadeResult({
|
|
298
|
+
status: 'blocked',
|
|
299
|
+
summary: 'READ_ONLY_REVIEW_MUTATION_CONFLICT: read_only_review cannot request repository mutation, external effects, remote writes, or destructive execution.',
|
|
300
|
+
data: { executionStarted: false, workContractCreated: false, workKind: effectiveWorkKind },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
245
303
|
const effectiveModeInput: ExecutionModeSelectionInput = {
|
|
246
304
|
...input.modeInput,
|
|
247
305
|
objective: input.objective,
|
|
248
306
|
knownPaths: input.allowedPaths,
|
|
249
|
-
|
|
307
|
+
workspacePlacement: placementConstraint.workspaceMode,
|
|
308
|
+
directMainProhibited: placementConstraint.directMainProhibited,
|
|
309
|
+
mutation: readOnlyReviewRequested ? false : input.modeInput.mutation ?? input.modeInput.risk !== 'readonly',
|
|
310
|
+
risk: readOnlyReviewRequested ? 'readonly' : input.modeInput.risk,
|
|
250
311
|
approvalConfirmed: input.approvalConfirmed === true,
|
|
251
312
|
requiresUserApproval: input.approvalConfirmed === true
|
|
252
313
|
? false
|
|
253
314
|
: input.modeInput.requiresUserApproval === true || strategyConflictRequiresApproval,
|
|
254
315
|
};
|
|
316
|
+
if (effectiveModeInput.explicitMode === 'scale' && (!input.planId || !input.planStepId || !ctx.planStore)) {
|
|
317
|
+
return buildFacadeResult({
|
|
318
|
+
status: 'blocked',
|
|
319
|
+
summary: 'SCALE_PLAN_REQUIRED: explicit Scale execution requires a bound approved PlanContract step.',
|
|
320
|
+
data: { executionStarted: false, workContractCreated: false, planRequired: true, explicitMode: 'scale' },
|
|
321
|
+
});
|
|
322
|
+
}
|
|
255
323
|
const applyForcedMode = (selected: ReturnType<typeof selectExecutionMode>) => input.forceMode
|
|
256
324
|
? {
|
|
257
325
|
...selected,
|
|
@@ -263,7 +331,7 @@ export function routeWorkStart(
|
|
|
263
331
|
: selected;
|
|
264
332
|
const evaluateAccessPolicy = (selected: ReturnType<typeof selectExecutionMode>) => evaluatePolicyGate({
|
|
265
333
|
capabilityId: selected.mode === 'direct_control' ? 'repository.direct_edit' : selected.mode === 'goal_workloop' ? 'controller.goal_workloop' : 'controller.handoff_inbox',
|
|
266
|
-
risk:
|
|
334
|
+
risk: effectiveModeInput.risk
|
|
267
335
|
?? (input.modeInput.secretAccess === true ? 'raw_secret_config'
|
|
268
336
|
: input.modeInput.destructive === true ? 'destructive'
|
|
269
337
|
: input.modeInput.remoteWrite === true ? 'remote_write'
|
|
@@ -283,12 +351,24 @@ export function routeWorkStart(
|
|
|
283
351
|
});
|
|
284
352
|
|
|
285
353
|
let selectedMode = selectExecutionMode(effectiveModeInput);
|
|
354
|
+
if (input.forceMode === 'direct_control' && selectedMode.routeDecision.requiresIsolation) {
|
|
355
|
+
return buildFacadeResult({
|
|
356
|
+
status: 'blocked',
|
|
357
|
+
summary: 'WORKSPACE_PLACEMENT_DIRECT_CONTROL_FORBIDDEN: typed placement requires isolation, so force_mode=direct_control cannot override admission policy.',
|
|
358
|
+
data: { executionStarted: false, workContractCreated: false, routeDecision: selectedMode.routeDecision },
|
|
359
|
+
rawAvailable: false,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
286
362
|
let policy = evaluateAccessPolicy(selectedMode);
|
|
287
363
|
if (policy.decision === 'allowed' && effectiveModeInput.requiresUserApproval !== true) {
|
|
288
364
|
selectedMode = selectExecutionMode({ ...effectiveModeInput, approvalConfirmed: true });
|
|
289
365
|
policy = evaluateAccessPolicy(selectedMode);
|
|
290
366
|
}
|
|
291
367
|
let mode = applyForcedMode(selectedMode);
|
|
368
|
+
const routeApprovalRequired = mode.routeDecision.requiresApproval
|
|
369
|
+
&& mode.routeDecision.waitForUser
|
|
370
|
+
&& mode.routeDecision.approvalState !== 'blocked_by_policy';
|
|
371
|
+
const approvalRequired = policy.decision === 'approval_required' || routeApprovalRequired;
|
|
292
372
|
|
|
293
373
|
// Route Policy is the sole execution-depth authority. Existing Work is
|
|
294
374
|
// ownership context, not a reason to upgrade an otherwise Direct operation
|
|
@@ -375,7 +455,7 @@ export function routeWorkStart(
|
|
|
375
455
|
severity: policy.decision === 'denied' ? 'blocked' : 'needs_review',
|
|
376
456
|
creationReason: !input.modeInput.scopeClear
|
|
377
457
|
? 'invalid_objective'
|
|
378
|
-
:
|
|
458
|
+
: approvalRequired
|
|
379
459
|
? (input.modeInput.destructive ? 'destructive_action_requires_confirmation' : 'policy_approval_required')
|
|
380
460
|
: 'missing_authorization',
|
|
381
461
|
reason: mode.reason,
|
|
@@ -388,13 +468,13 @@ export function routeWorkStart(
|
|
|
388
468
|
},
|
|
389
469
|
attemptedActions: ['route_execution_mode'],
|
|
390
470
|
evidenceRefs: [],
|
|
391
|
-
blockingDecision:
|
|
471
|
+
blockingDecision: approvalRequired
|
|
392
472
|
? 'Approve side effects or restate a safer objective.'
|
|
393
473
|
: 'Clarify objective, scope, and acceptance criteria.',
|
|
394
474
|
recommendedDecision: 'Provide a clear objective and authorization, or cancel the request.',
|
|
395
475
|
recommendedPrompt: `Resolve handoff and restate work for repo ${ctx.repoId}.`,
|
|
396
476
|
recommendedContinuationPrompt: `After approval, start the approved work for ${ctx.repoId}.`,
|
|
397
|
-
approvalAction:
|
|
477
|
+
approvalAction: approvalRequired
|
|
398
478
|
? {
|
|
399
479
|
operation: 'start',
|
|
400
480
|
label: 'Approve and start work',
|
|
@@ -415,6 +495,9 @@ export function routeWorkStart(
|
|
|
415
495
|
requiresApproval: input.modeInput.requiresApproval === true || input.modeInput.requiresUserApproval === true,
|
|
416
496
|
destructive: input.modeInput.destructive === true,
|
|
417
497
|
accessMode: input.constraints?.accessMode,
|
|
498
|
+
workspaceMode: placementConstraint.workspaceMode,
|
|
499
|
+
requireWorktree: placementConstraint.requireWorktree,
|
|
500
|
+
directMainProhibited: placementConstraint.directMainProhibited,
|
|
418
501
|
approvalConfirmed: true,
|
|
419
502
|
forceMode: 'goal_workloop',
|
|
420
503
|
},
|
|
@@ -432,7 +515,7 @@ export function routeWorkStart(
|
|
|
432
515
|
});
|
|
433
516
|
|
|
434
517
|
return buildFacadeResult({
|
|
435
|
-
status: policy.decision === 'denied' ? 'blocked' :
|
|
518
|
+
status: policy.decision === 'denied' ? 'blocked' : approvalRequired ? 'approval_required' : 'blocked',
|
|
436
519
|
summary: `Handoff-only: no WorkContract created and no execution started. ${mode.reason}`,
|
|
437
520
|
data: {
|
|
438
521
|
mode,
|
|
@@ -462,7 +545,12 @@ export function routeWorkStart(
|
|
|
462
545
|
});
|
|
463
546
|
}
|
|
464
547
|
|
|
465
|
-
return startGoalWorkloop(ctx,
|
|
548
|
+
return startGoalWorkloop(ctx, {
|
|
549
|
+
...input,
|
|
550
|
+
constraints: canonicalConstraints,
|
|
551
|
+
modeInput: effectiveModeInput,
|
|
552
|
+
workKind: effectiveWorkKind,
|
|
553
|
+
}, policy, 'goal_workloop', mode.routeDecision);
|
|
466
554
|
}
|
|
467
555
|
|
|
468
556
|
export function startGoalWorkloop(
|
|
@@ -472,9 +560,35 @@ export function startGoalWorkloop(
|
|
|
472
560
|
executionMode: 'direct_control' | 'goal_workloop' = 'goal_workloop',
|
|
473
561
|
routeDecision = selectExecutionMode({ ...input.modeInput, objective: input.objective, knownPaths: input.allowedPaths }).routeDecision,
|
|
474
562
|
): FacadeResult {
|
|
563
|
+
const placementResolution = resolveWorkspaceAdmissionConstraint(input.constraints);
|
|
564
|
+
if (placementResolution.ok === false) {
|
|
565
|
+
return buildFacadeResult({
|
|
566
|
+
status: 'blocked',
|
|
567
|
+
summary: `${placementResolution.code}: ${placementResolution.message}`,
|
|
568
|
+
data: { executionStarted: false, workContractCreated: false, placementConstraintConflict: true },
|
|
569
|
+
rawAvailable: false,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
const placementConstraint = placementResolution.constraint;
|
|
573
|
+
const canonicalConstraints: WorkContract['constraints'] = {
|
|
574
|
+
...(input.constraints ?? {}),
|
|
575
|
+
workspaceMode: placementConstraint.workspaceMode,
|
|
576
|
+
requireWorktree: placementConstraint.requireWorktree,
|
|
577
|
+
directMainProhibited: placementConstraint.directMainProhibited,
|
|
578
|
+
};
|
|
579
|
+
const hasStrongAdmissionBinding = Boolean(input.planId || input.planStepId || input.requirementId || input.relatedWorkId || input.workRelation);
|
|
580
|
+
if (hasStrongAdmissionBinding && !ctx.semanticAdmissionLocked) {
|
|
581
|
+
return withPrimaryWorkAdmissionLock(ctx.workStore, () => startGoalWorkloop(
|
|
582
|
+
{ ...ctx, semanticAdmissionLocked: true },
|
|
583
|
+
input,
|
|
584
|
+
policy,
|
|
585
|
+
executionMode,
|
|
586
|
+
routeDecision,
|
|
587
|
+
));
|
|
588
|
+
}
|
|
475
589
|
const at = nowIso(ctx);
|
|
476
590
|
const available = ctx.availableChecks ?? [];
|
|
477
|
-
const workspaceMode =
|
|
591
|
+
const workspaceMode = placementConstraint.workspaceMode;
|
|
478
592
|
const activeWorks = listWorkContracts({ ...ctx.workStore, status: 'active', limit: 100 })
|
|
479
593
|
.filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary');
|
|
480
594
|
const workspaceOwner = ctx.checkoutId
|
|
@@ -487,6 +601,26 @@ export function startGoalWorkloop(
|
|
|
487
601
|
: undefined;
|
|
488
602
|
const plan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
|
|
489
603
|
const planStep = plan && input.planStepId ? plan.steps.find((step) => step.id === input.planStepId) : undefined;
|
|
604
|
+
const requestedRequirementId = input.requirementId?.trim() || undefined;
|
|
605
|
+
if (plan && requestedRequirementId && requestedRequirementId !== plan.requirementId) {
|
|
606
|
+
return buildFacadeResult({
|
|
607
|
+
status: 'blocked',
|
|
608
|
+
summary: `PLAN_REQUIREMENT_MISMATCH: ${plan.planId} is bound to ${plan.requirementId ?? 'no Requirement'}, not ${requestedRequirementId}. Use the Plan relationship authority or replan explicitly.`,
|
|
609
|
+
data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planRequirementId: plan.requirementId, requestedRequirementId },
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
const effectiveRequirementId = requestedRequirementId || plan?.requirementId;
|
|
613
|
+
if (
|
|
614
|
+
effectiveRequirementId
|
|
615
|
+
&& ctx.workStore.controllerHome
|
|
616
|
+
&& !readRequirement({ controllerHome: ctx.workStore.controllerHome }, effectiveRequirementId)
|
|
617
|
+
) {
|
|
618
|
+
return buildFacadeResult({
|
|
619
|
+
status: 'blocked',
|
|
620
|
+
summary: `${plan ? 'PLAN' : 'WORK'}_REQUIREMENT_NOT_FOUND: ${effectiveRequirementId}. Create or reconcile the Requirement authority before admitting Work.`,
|
|
621
|
+
data: { executionStarted: false, workContractCreated: false, requirementId: effectiveRequirementId, planId: plan?.planId },
|
|
622
|
+
});
|
|
623
|
+
}
|
|
490
624
|
const sameStringSet = (provided: string[] | undefined, frozen: string[]): boolean => {
|
|
491
625
|
if (provided === undefined) return true;
|
|
492
626
|
const actual = [...new Set(provided)].sort();
|
|
@@ -513,17 +647,23 @@ export function startGoalWorkloop(
|
|
|
513
647
|
const effectiveForbiddenPaths = planStep?.forbiddenPaths ?? input.forbiddenPaths ?? [];
|
|
514
648
|
const effectiveChecks = planStep?.checks ?? input.checks ?? [];
|
|
515
649
|
const normalized = normalizeCheckIds(effectiveChecks, available);
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
650
|
+
const boundPlanStepWorks = input.planId && input.planStepId
|
|
651
|
+
? activeWorks.filter((candidate) => candidate.planId === input.planId && candidate.planStepId === input.planStepId)
|
|
652
|
+
: [];
|
|
653
|
+
const explicitPlanStepWork = planStep?.workId ? activeWorks.find((candidate) => candidate.workId === planStep.workId) : undefined;
|
|
654
|
+
const planStepWork = explicitPlanStepWork ?? (boundPlanStepWorks.length === 1 ? boundPlanStepWorks[0] : undefined);
|
|
655
|
+
const requirementWorks = effectiveRequirementId
|
|
656
|
+
? activeWorks.filter((candidate) => candidate.requirementId === effectiveRequirementId)
|
|
519
657
|
: [];
|
|
520
|
-
const deterministicTarget =
|
|
658
|
+
const deterministicTarget = input.relatedWorkId
|
|
659
|
+
? explicitRelatedWork
|
|
660
|
+
: planStepWork ?? (requirementWorks.length === 1 ? requirementWorks[0] : undefined);
|
|
521
661
|
const requestedRelation = input.workRelation ?? (input.modeInput.requiresParallelism === true ? 'parallel' : undefined);
|
|
522
662
|
// Only strong semantic bindings participate in ownership resolution. An
|
|
523
663
|
// unrelated active Work or a checkout writer is a placement fact, not a
|
|
524
664
|
// semantic candidate for continue/extend/parallel/new_goal.
|
|
525
665
|
const candidateWorks = [...new Map(
|
|
526
|
-
[explicitRelatedWork, planStepWork, ...requirementWorks]
|
|
666
|
+
[explicitRelatedWork, planStepWork, ...boundPlanStepWorks, ...requirementWorks]
|
|
527
667
|
.filter((candidate): candidate is WorkContract => Boolean(candidate))
|
|
528
668
|
.map((candidate) => [candidate.workId, candidate]),
|
|
529
669
|
).values()].slice(0, 8);
|
|
@@ -556,6 +696,27 @@ export function startGoalWorkloop(
|
|
|
556
696
|
rawAvailable: false,
|
|
557
697
|
});
|
|
558
698
|
|
|
699
|
+
if (boundPlanStepWorks.length > 1) {
|
|
700
|
+
return resolutionRequired(`PLAN_STEP_MULTIPLE_PRIMARY_WORKS: ${input.planId}/${input.planStepId} has ${boundPlanStepWorks.length} active primary Work records and requires repair before execution.`);
|
|
701
|
+
}
|
|
702
|
+
if (planStep?.workId && !explicitPlanStepWork) {
|
|
703
|
+
return buildFacadeResult({
|
|
704
|
+
status: 'blocked',
|
|
705
|
+
summary: `PLAN_STEP_BOUND_WORK_MISSING: ${input.planId}/${input.planStepId} is bound to ${planStep.workId}, but that Work is not active. Repair the exact binding; do not create a replacement Work.`,
|
|
706
|
+
data: { executionStarted: false, workContractCreated: false, planId: input.planId, planStepId: input.planStepId, boundWorkId: planStep.workId, repairRequired: true },
|
|
707
|
+
suggestedNextActions: [{
|
|
708
|
+
label: 'Diagnose exact Plan step binding',
|
|
709
|
+
tool: 'rh_work',
|
|
710
|
+
operation: 'repair',
|
|
711
|
+
payload: { plan_id: input.planId, plan_step_id: input.planStepId, repair_operation: 'diagnose', dry_run: true },
|
|
712
|
+
risk: 'readonly',
|
|
713
|
+
confidence: 'high',
|
|
714
|
+
reason: 'The Plan step has an exact Work identity but the active Work view cannot resolve it. Diagnose that binding before any replacement Work can be admitted.',
|
|
715
|
+
}],
|
|
716
|
+
rawAvailable: false,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
559
720
|
if ((input.requestedBy ?? 'chatgpt') === 'scheduler') {
|
|
560
721
|
return resolutionRequired(
|
|
561
722
|
deterministicTarget
|
|
@@ -587,7 +748,7 @@ export function startGoalWorkloop(
|
|
|
587
748
|
}
|
|
588
749
|
if (requestedRelation === 'extend' && deterministicTarget.planId) {
|
|
589
750
|
return resolutionRequired(
|
|
590
|
-
`PLAN_EXTENSION_REQUIRES_REPLAN: Work ${deterministicTarget.workId} is governed by Plan ${deterministicTarget.planId};
|
|
751
|
+
`PLAN_EXTENSION_REQUIRES_REPLAN: Work ${deterministicTarget.workId} is governed by Plan ${deterministicTarget.planId}; record additional discovery in scopeEvidence, but update the authoritative Plan before widening policy or acceptance scope.`,
|
|
591
752
|
deterministicTarget,
|
|
592
753
|
);
|
|
593
754
|
}
|
|
@@ -597,6 +758,15 @@ export function startGoalWorkloop(
|
|
|
597
758
|
allowedPaths: [...new Set([...deterministicTarget.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
598
759
|
forbiddenPaths: [...new Set([...deterministicTarget.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
599
760
|
checks: [...new Set([...deterministicTarget.checks, ...normalized.validCheckIds])].slice(0, 30),
|
|
761
|
+
scopeEvidence: {
|
|
762
|
+
initialLikelyPaths: [...new Set([
|
|
763
|
+
...(deterministicTarget.scopeEvidence?.initialLikelyPaths ?? deterministicTarget.allowedPaths),
|
|
764
|
+
...(input.initialLikelyPaths ?? input.allowedPaths ?? []),
|
|
765
|
+
])].slice(0, 100),
|
|
766
|
+
inspectedPaths: deterministicTarget.scopeEvidence?.inspectedPaths ?? [],
|
|
767
|
+
actualChangedPaths: deterministicTarget.scopeEvidence?.actualChangedPaths ?? [],
|
|
768
|
+
recordedAt: at,
|
|
769
|
+
},
|
|
600
770
|
scopeSummary: `Extended serially: ${input.objective}`.slice(0, 500),
|
|
601
771
|
continuationPrompt: `Continue work ${ctx.repoId}: ${deterministicTarget.objective.slice(0, 160)}. Additional requested scope: ${input.objective.slice(0, 500)}`,
|
|
602
772
|
})
|
|
@@ -624,10 +794,10 @@ export function startGoalWorkloop(
|
|
|
624
794
|
}
|
|
625
795
|
|
|
626
796
|
const placementConflict = Boolean(workspaceOwner);
|
|
627
|
-
const needsWorktree = executionMode === 'goal_workloop' && (
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
797
|
+
const needsWorktree = executionMode === 'goal_workloop' && (placementConstraint.requireWorktree
|
|
798
|
+
|| placementConflict
|
|
799
|
+
|| input.modeInput.requiresParallelism === true
|
|
800
|
+
|| routeDecision.requiresIsolation === true);
|
|
631
801
|
if (!needsWorktree && workspaceOwner) {
|
|
632
802
|
return buildFacadeResult({
|
|
633
803
|
status: 'blocked',
|
|
@@ -643,74 +813,98 @@ export function startGoalWorkloop(
|
|
|
643
813
|
});
|
|
644
814
|
}
|
|
645
815
|
const generatedWorkId = workIdFor(input.objective);
|
|
646
|
-
let isolatedWorkspace: ReturnType<NonNullable<GoalWorkloopContext['materializeIsolatedWorkspace']>> | undefined;
|
|
647
|
-
if (needsWorktree) {
|
|
648
|
-
if (!ctx.materializeIsolatedWorkspace) {
|
|
649
|
-
return buildFacadeResult({ status: 'blocked', summary: 'ISOLATED_WORKSPACE_MATERIALIZER_REQUIRED: isolated Work cannot enter running state without a concrete managed checkout.', data: { executionStarted: false, workContractCreated: false, worktreeRequired: true } });
|
|
650
|
-
}
|
|
651
|
-
try {
|
|
652
|
-
isolatedWorkspace = ctx.materializeIsolatedWorkspace({ workId: generatedWorkId, title: input.objective, baseRef: ctx.sourceRevision });
|
|
653
|
-
if (!isolatedWorkspace.checkoutId || !isolatedWorkspace.root || isolatedWorkspace.checkoutId === ctx.checkoutId) {
|
|
654
|
-
throw new Error('ISOLATED_WORKSPACE_NOT_DISTINCT');
|
|
655
|
-
}
|
|
656
|
-
} catch (error) {
|
|
657
|
-
return buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? `ISOLATED_WORKSPACE_MATERIALIZATION_FAILED: ${error.message}` : 'ISOLATED_WORKSPACE_MATERIALIZATION_FAILED', data: { executionStarted: false, workContractCreated: false, worktreeRequired: true } });
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
816
|
if (input.planId || input.planStepId) {
|
|
661
|
-
if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision) {
|
|
662
|
-
return buildFacadeResult({ status: 'blocked', summary: 'PLAN_CONTEXT_REQUIRED: plan_id, plan_step_id and a current source revision are required.', data: { executionStarted: false } });
|
|
817
|
+
if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision || !plan || !planStep) {
|
|
818
|
+
return buildFacadeResult({ status: 'blocked', summary: 'PLAN_CONTEXT_REQUIRED: plan_id, plan_step_id, an executable Plan step and a current source revision are required.', data: { executionStarted: false } });
|
|
663
819
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
820
|
+
if (plan.status !== 'approved' && plan.status !== 'executing') {
|
|
821
|
+
return buildFacadeResult({ status: 'blocked', summary: `PLAN_NOT_EXECUTABLE: ${plan.planId} is ${plan.status}`, data: { executionStarted: false, planId: plan.planId } });
|
|
822
|
+
}
|
|
823
|
+
if (plan.sourceRevision !== ctx.sourceRevision) {
|
|
824
|
+
const invalidated = claimPlanStepForWork(ctx.planStore, {
|
|
667
825
|
planId: input.planId,
|
|
668
826
|
stepId: input.planStepId,
|
|
669
827
|
workId: generatedWorkId,
|
|
670
828
|
sourceRevision: ctx.sourceRevision,
|
|
671
829
|
});
|
|
672
|
-
} catch (error) {
|
|
673
830
|
return buildFacadeResult({
|
|
674
831
|
status: 'blocked',
|
|
675
|
-
summary:
|
|
676
|
-
data: { planId:
|
|
832
|
+
summary: `PLAN_SOURCE_DRIFT: ${invalidated.planId} was invalidated because its source revision no longer matches. Replan before execution.`,
|
|
833
|
+
data: { planId: invalidated.planId, executionStarted: false, workContractCreated: false, replanRequired: true },
|
|
677
834
|
});
|
|
678
835
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
});
|
|
836
|
+
const unresolved = planStep.dependencies.filter((dependency) => plan.steps.find((candidate) => candidate.id === dependency)?.status !== 'completed');
|
|
837
|
+
if (unresolved.length > 0) {
|
|
838
|
+
return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_DEPENDENCIES_PENDING: ${unresolved.join(', ')}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
|
|
839
|
+
}
|
|
840
|
+
if (planStep.status === 'completed') {
|
|
841
|
+
return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_ALREADY_COMPLETED: ${planStep.id}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
|
|
685
842
|
}
|
|
686
843
|
}
|
|
844
|
+
const initialSuggestedNextActions = suggestedForWorkIdentity(
|
|
845
|
+
generatedWorkId,
|
|
846
|
+
normalized.validCheckIds,
|
|
847
|
+
normalized.suggestedNextActions,
|
|
848
|
+
);
|
|
849
|
+
// allowed_paths and initial_likely_paths are policy/discovery fences, not proof
|
|
850
|
+
// that this Work owns a repository mutation. External-effect Work therefore
|
|
851
|
+
// needs either an explicit repository_change semantic choice or concrete
|
|
852
|
+
// expected source-change evidence before Forge treats it as repository_change.
|
|
853
|
+
const repositoryChangeIntent = input.workKind === 'repository_change'
|
|
854
|
+
|| (input.modeInput.expectedFiles ?? 0) > 0
|
|
855
|
+
|| (input.modeInput.expectedChangedLines ?? 0) > 0;
|
|
856
|
+
const typedRecoverableReadOnlyReview = input.workKind === undefined
|
|
857
|
+
&& input.modeInput.mutation === false
|
|
858
|
+
&& input.modeInput.requiresInvestigation === true
|
|
859
|
+
&& input.modeInput.requiresRecovery === true;
|
|
860
|
+
const resolvedWorkKind = input.workKind ?? (
|
|
861
|
+
typedRecoverableReadOnlyReview
|
|
862
|
+
? 'read_only_review'
|
|
863
|
+
: input.modeInput.requiresExternalEffect === true
|
|
864
|
+
&& !repositoryChangeIntent
|
|
865
|
+
? (input.modeInput.remoteWrite === true ? 'remote_effect' : 'local_effect')
|
|
866
|
+
: 'repository_change'
|
|
867
|
+
);
|
|
868
|
+
const remoteDeliveryRequired = resolvedWorkKind === 'repository_change'
|
|
869
|
+
&& input.modeInput.requiresExternalEffect === true
|
|
870
|
+
&& input.modeInput.remoteWrite === true;
|
|
687
871
|
const work = createWorkContract(ctx.workStore, {
|
|
688
872
|
workId: generatedWorkId,
|
|
689
873
|
repoId: ctx.repoId,
|
|
690
|
-
checkoutId:
|
|
874
|
+
checkoutId: needsWorktree ? undefined : ctx.checkoutId,
|
|
691
875
|
principalId: ctx.principalId,
|
|
692
876
|
controllerInstanceId: ctx.controllerInstanceId,
|
|
693
|
-
baseRevision:
|
|
694
|
-
workspaceFingerprint:
|
|
877
|
+
baseRevision: ctx.sourceRevision,
|
|
878
|
+
workspaceFingerprint: needsWorktree ? undefined : ctx.workspaceFingerprint,
|
|
695
879
|
routeDecisionFingerprint: routeDecision.inputFingerprint,
|
|
696
880
|
routeDecision,
|
|
697
881
|
mode: executionMode,
|
|
698
882
|
objective: input.objective,
|
|
699
883
|
acceptanceCriteria: effectiveAcceptanceCriteria,
|
|
700
|
-
constraints:
|
|
884
|
+
constraints: {
|
|
885
|
+
...canonicalConstraints,
|
|
886
|
+
...(remoteDeliveryRequired ? { remoteDeliveryRequired: true } : {}),
|
|
887
|
+
},
|
|
701
888
|
risk: workRiskFor(input),
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
889
|
+
// Remote delivery is a risk/effect dimension, not a reason to erase a
|
|
890
|
+
// repository-change Work's semantic identity. Pure external actions with no
|
|
891
|
+
// repository-change signal remain remote_effect and keep plugin receipt semantics.
|
|
892
|
+
workKind: resolvedWorkKind,
|
|
705
893
|
status: 'running',
|
|
706
894
|
phase: 'implementation',
|
|
707
895
|
issueId: input.issueId,
|
|
708
896
|
taskId: input.taskId,
|
|
709
|
-
requirementId:
|
|
897
|
+
requirementId: effectiveRequirementId,
|
|
710
898
|
planId: input.planId,
|
|
711
899
|
planStepId: input.planStepId,
|
|
712
900
|
planSourceRevision: input.planId ? ctx.sourceRevision : undefined,
|
|
713
901
|
scopeSummary: input.modeInput.scopeClear ? 'scope declared at start' : 'scope incomplete',
|
|
902
|
+
scopeEvidence: {
|
|
903
|
+
initialLikelyPaths: [...new Set(input.initialLikelyPaths ?? effectiveAllowedPaths)].slice(0, 100),
|
|
904
|
+
inspectedPaths: [],
|
|
905
|
+
actualChangedPaths: [],
|
|
906
|
+
recordedAt: at,
|
|
907
|
+
},
|
|
714
908
|
allowedPaths: effectiveAllowedPaths,
|
|
715
909
|
forbiddenPaths: effectiveForbiddenPaths,
|
|
716
910
|
checks: normalized.validCheckIds,
|
|
@@ -727,7 +921,7 @@ export function startGoalWorkloop(
|
|
|
727
921
|
? 'Isolation was explicitly requested or required for parallel execution.'
|
|
728
922
|
: 'Current workspace is the stability-first default; isolation remains opt-in.',
|
|
729
923
|
},
|
|
730
|
-
worktreeRef:
|
|
924
|
+
worktreeRef: undefined,
|
|
731
925
|
evidencePolicy: {
|
|
732
926
|
defaultDetailLevel: 'summary',
|
|
733
927
|
allowRawOptIn: true,
|
|
@@ -746,21 +940,39 @@ export function startGoalWorkloop(
|
|
|
746
940
|
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
747
941
|
evidenceRefs: [initialEvidence(input.objective)],
|
|
748
942
|
policyDecisions: policy ? [policy] : [],
|
|
749
|
-
suggestedNextActions:
|
|
943
|
+
suggestedNextActions: initialSuggestedNextActions,
|
|
750
944
|
continuationPrompt: `Continue work ${ctx.repoId}: ${input.objective.slice(0, 200)}`,
|
|
751
945
|
});
|
|
752
946
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
947
|
+
if (input.planId && input.planStepId && ctx.planStore && ctx.sourceRevision) {
|
|
948
|
+
try {
|
|
949
|
+
const claimed = claimPlanStepForWork(ctx.planStore, {
|
|
950
|
+
planId: input.planId,
|
|
951
|
+
stepId: input.planStepId,
|
|
952
|
+
workId: work.workId,
|
|
953
|
+
sourceRevision: ctx.sourceRevision,
|
|
954
|
+
});
|
|
955
|
+
if (claimed.status === 'invalidated_by_drift') {
|
|
956
|
+
return buildFacadeResult({
|
|
957
|
+
status: 'blocked',
|
|
958
|
+
summary: `PLAN_SOURCE_DRIFT: ${claimed.planId} was invalidated because its source revision no longer matches. Repair or replan before execution.`,
|
|
959
|
+
data: { executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), planId: claimed.planId, canonicalWorkRetained: true, replanRequired: true },
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
} catch (error) {
|
|
963
|
+
return buildFacadeResult({
|
|
964
|
+
status: 'blocked',
|
|
965
|
+
summary: error instanceof Error ? error.message : 'PLAN_STEP_CLAIM_FAILED',
|
|
966
|
+
data: { planId: input.planId, planStepId: input.planStepId, executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), canonicalWorkRetained: true },
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
}
|
|
758
970
|
|
|
759
971
|
return buildFacadeResult({
|
|
760
972
|
status: 'ok',
|
|
761
973
|
summary: executionMode === 'direct_control'
|
|
762
|
-
? `Direct-control Work lineage started as ${
|
|
763
|
-
: `Goal workloop started as ${
|
|
974
|
+
? `Direct-control Work lineage started as ${work.workId}.`
|
|
975
|
+
: `Goal workloop started as ${work.workId}.`,
|
|
764
976
|
data: {
|
|
765
977
|
mode: {
|
|
766
978
|
mode: executionMode,
|
|
@@ -774,20 +986,20 @@ export function startGoalWorkloop(
|
|
|
774
986
|
routeDecision,
|
|
775
987
|
},
|
|
776
988
|
workContractCreated: true,
|
|
777
|
-
work: summarizeWorkContract(
|
|
778
|
-
worktreeRequired:
|
|
989
|
+
work: summarizeWorkContract(work),
|
|
990
|
+
worktreeRequired: work.worktreePolicy.required,
|
|
779
991
|
normalizedChecks: normalized,
|
|
780
992
|
policy,
|
|
781
993
|
},
|
|
782
|
-
evidenceRefs:
|
|
994
|
+
evidenceRefs: work.evidenceRefs,
|
|
783
995
|
warnings: normalized.warnings,
|
|
784
|
-
suggestedNextActions:
|
|
996
|
+
suggestedNextActions: initialSuggestedNextActions,
|
|
785
997
|
rawAvailable: false,
|
|
786
998
|
});
|
|
787
999
|
}
|
|
788
1000
|
|
|
789
1001
|
export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopContinueInput): FacadeResult {
|
|
790
|
-
|
|
1002
|
+
let work = getWorkContract(ctx.workStore, input.workId);
|
|
791
1003
|
if (!work) {
|
|
792
1004
|
return buildFacadeResult({
|
|
793
1005
|
status: 'not_found',
|
|
@@ -814,78 +1026,339 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
814
1026
|
});
|
|
815
1027
|
}
|
|
816
1028
|
|
|
1029
|
+
if ((input.reviewFindings?.length ?? 0) > 0 && work.workKind !== 'read_only_review') {
|
|
1030
|
+
return buildFacadeResult({
|
|
1031
|
+
status: 'blocked',
|
|
1032
|
+
summary: `READ_ONLY_REVIEW_FINDINGS_KIND_REQUIRED: ${work.workId} is ${work.workKind}, not read_only_review.`,
|
|
1033
|
+
data: { work: summarizeWorkContract(work), reviewFindingsRecorded: false },
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (work.workKind === 'read_only_review') {
|
|
1037
|
+
const sourceIdentity = evaluateReadOnlyReviewSourceIdentity(work, ctx.sourceRevision, ctx.workspaceChangedPaths);
|
|
1038
|
+
if (sourceIdentity.status !== 'complete') {
|
|
1039
|
+
return buildFacadeResult({
|
|
1040
|
+
status: 'blocked',
|
|
1041
|
+
summary: `READ_ONLY_REVIEW_SOURCE_IDENTITY_REQUIRED: ${sourceIdentity.reasons.join(' ')}`,
|
|
1042
|
+
data: { work: summarizeWorkContract(work), sourceIdentityProven: false },
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const explicitPolicyScope = input.allowedPaths !== undefined
|
|
1048
|
+
|| input.forbiddenPaths !== undefined
|
|
1049
|
+
|| input.checks !== undefined;
|
|
1050
|
+
if (explicitPolicyScope) {
|
|
1051
|
+
if (work.planId) {
|
|
1052
|
+
return buildFacadeResult({
|
|
1053
|
+
status: 'blocked',
|
|
1054
|
+
summary: `PLAN_EXTENSION_REQUIRES_REPLAN: Work ${work.workId} is governed by Plan ${work.planId}; continue cannot widen frozen policy scope. Replan the authoritative Plan instead.`,
|
|
1055
|
+
data: { work: summarizeWorkContract(work), planId: work.planId, policyScopeUpdated: false },
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
const normalizedChecks = normalizeCheckIds(input.checks ?? [], ctx.availableChecks ?? []);
|
|
1059
|
+
if (normalizedChecks.invalidCheckIds.length > 0) {
|
|
1060
|
+
return buildFacadeResult({
|
|
1061
|
+
status: 'blocked',
|
|
1062
|
+
summary: `WORK_CHECKS_INVALID: ${normalizedChecks.invalidCheckIds.join(', ')}. Work scope was not updated.`,
|
|
1063
|
+
data: { work: summarizeWorkContract(work), policyScopeUpdated: false, normalizedChecks },
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
work = updateWorkContract(ctx.workStore, work.workId, {
|
|
1067
|
+
allowedPaths: [...new Set([...work.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
1068
|
+
forbiddenPaths: [...new Set([...work.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
1069
|
+
checks: [...new Set([...work.checks, ...normalizedChecks.validCheckIds])].slice(0, 30),
|
|
1070
|
+
scopeEvidence: {
|
|
1071
|
+
initialLikelyPaths: [...new Set([
|
|
1072
|
+
...(work.scopeEvidence?.initialLikelyPaths ?? work.allowedPaths),
|
|
1073
|
+
...(input.allowedPaths ?? []),
|
|
1074
|
+
])].slice(0, 100),
|
|
1075
|
+
inspectedPaths: work.scopeEvidence?.inspectedPaths ?? [],
|
|
1076
|
+
actualChangedPaths: work.scopeEvidence?.actualChangedPaths ?? [],
|
|
1077
|
+
recordedAt: nowIso(ctx),
|
|
1078
|
+
},
|
|
1079
|
+
scopeSummary: `Continue adopted explicit policy scope for ${work.workId}.`.slice(0, 500),
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if ((input.additionalLikelyPaths?.length ?? 0) > 0 || (input.inspectedPaths?.length ?? 0) > 0) {
|
|
1084
|
+
work = recordWorkScopeEvidence(ctx.workStore, work.workId, {
|
|
1085
|
+
initialLikelyPaths: input.additionalLikelyPaths,
|
|
1086
|
+
inspectedPaths: input.inspectedPaths,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
if ((input.acceptanceEvidence?.length ?? 0) > 0) {
|
|
1090
|
+
if (work.workKind !== 'local_effect') {
|
|
1091
|
+
return buildFacadeResult({
|
|
1092
|
+
status: 'blocked',
|
|
1093
|
+
summary: `WORK_SEMANTIC_ACCEPTANCE_LOCAL_EFFECT_REQUIRED: ${work.workId} is ${work.workKind}.`,
|
|
1094
|
+
data: { work: summarizeWorkContract(work), semanticAcceptanceRecorded: false },
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
const declaredCriteria = new Map(work.acceptanceCriteria.map((criterion) => [criterion.trim(), criterion]));
|
|
1098
|
+
const availableEvidenceIds = new Set([
|
|
1099
|
+
...work.evidenceRefs.flatMap((evidence) => [evidence.evidenceId, evidence.artifactId])
|
|
1100
|
+
.map((value) => value?.trim())
|
|
1101
|
+
.filter((value): value is string => Boolean(value)),
|
|
1102
|
+
...(ctx.workBoundProcessEvidenceIds ?? []).map((value) => value.trim()).filter(Boolean),
|
|
1103
|
+
]);
|
|
1104
|
+
const normalizedBindings: Array<{ criterion: string; evidenceIds: string[]; rationale: string }> = [];
|
|
1105
|
+
for (const binding of input.acceptanceEvidence ?? []) {
|
|
1106
|
+
const requestedCriterion = binding.criterion.trim();
|
|
1107
|
+
const criterion = declaredCriteria.get(requestedCriterion);
|
|
1108
|
+
if (!criterion) {
|
|
1109
|
+
return buildFacadeResult({
|
|
1110
|
+
status: 'blocked',
|
|
1111
|
+
summary: `WORK_SEMANTIC_ACCEPTANCE_CRITERION_UNKNOWN: ${requestedCriterion || '<empty>'}.`,
|
|
1112
|
+
data: { work: summarizeWorkContract(work), semanticAcceptanceRecorded: false },
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
const evidenceIds = [...new Set(binding.evidenceIds.map((value) => value.trim()).filter(Boolean))];
|
|
1116
|
+
if (evidenceIds.length === 0) {
|
|
1117
|
+
return buildFacadeResult({
|
|
1118
|
+
status: 'blocked',
|
|
1119
|
+
summary: `WORK_SEMANTIC_ACCEPTANCE_EVIDENCE_REQUIRED: ${criterion}.`,
|
|
1120
|
+
data: { work: summarizeWorkContract(work), semanticAcceptanceRecorded: false },
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
const unknownEvidenceIds = evidenceIds.filter((evidenceId) => !availableEvidenceIds.has(evidenceId));
|
|
1124
|
+
if (unknownEvidenceIds.length > 0) {
|
|
1125
|
+
return buildFacadeResult({
|
|
1126
|
+
status: 'blocked',
|
|
1127
|
+
summary: `WORK_SEMANTIC_ACCEPTANCE_EVIDENCE_UNKNOWN: ${unknownEvidenceIds.join(', ')}.`,
|
|
1128
|
+
data: { work: summarizeWorkContract(work), semanticAcceptanceRecorded: false },
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
const rationale = binding.rationale.trim();
|
|
1132
|
+
if (!rationale) {
|
|
1133
|
+
return buildFacadeResult({
|
|
1134
|
+
status: 'blocked',
|
|
1135
|
+
summary: `WORK_SEMANTIC_ACCEPTANCE_RATIONALE_REQUIRED: ${criterion}.`,
|
|
1136
|
+
data: { work: summarizeWorkContract(work), semanticAcceptanceRecorded: false },
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
normalizedBindings.push({ criterion, evidenceIds, rationale: rationale.slice(0, 1_000) });
|
|
1140
|
+
}
|
|
1141
|
+
const recordedAt = nowIso(ctx);
|
|
1142
|
+
const byCriterion = new Map((work.semanticAcceptanceEvidence ?? []).map((review) => [review.criterion, review]));
|
|
1143
|
+
for (const binding of normalizedBindings) {
|
|
1144
|
+
const previous = byCriterion.get(binding.criterion);
|
|
1145
|
+
byCriterion.set(binding.criterion, {
|
|
1146
|
+
criterion: binding.criterion,
|
|
1147
|
+
evidenceIds: [...new Set([...(previous?.evidenceIds ?? []), ...binding.evidenceIds])].slice(0, 50),
|
|
1148
|
+
rationale: binding.rationale,
|
|
1149
|
+
recordedAt,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
work = updateWorkContract(ctx.workStore, work.workId, {
|
|
1153
|
+
semanticAcceptanceEvidence: [...byCriterion.values()].slice(0, 20),
|
|
1154
|
+
});
|
|
1155
|
+
work = appendWorkEvidence(ctx.workStore, work.workId, {
|
|
1156
|
+
title: 'Controller semantic acceptance review',
|
|
1157
|
+
summary: `Controller reviewed ${normalizedBindings.length} exact acceptance criterion binding(s) against durable Work evidence.`,
|
|
1158
|
+
detailLevel: 'summary',
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (work.workKind === 'read_only_review' && ((input.inspectedPaths?.length ?? 0) > 0 || input.reviewFindings !== undefined)) {
|
|
1163
|
+
const findings = [...new Set([
|
|
1164
|
+
...(work.readOnlyReviewEvidence?.findings ?? []),
|
|
1165
|
+
...(input.reviewFindings ?? []).map((finding) => finding.trim()).filter(Boolean),
|
|
1166
|
+
])].slice(0, 50);
|
|
1167
|
+
const inspectedPaths = [...new Set(work.scopeEvidence?.inspectedPaths ?? [])].slice(0, 200);
|
|
1168
|
+
work = updateWorkContract(ctx.workStore, work.workId, {
|
|
1169
|
+
readOnlyReviewEvidence: {
|
|
1170
|
+
sourceRevision: ctx.sourceRevision!,
|
|
1171
|
+
...(ctx.workspaceFingerprint ? { workspaceFingerprint: ctx.workspaceFingerprint } : {}),
|
|
1172
|
+
inspectedPaths,
|
|
1173
|
+
findings,
|
|
1174
|
+
recordedAt: nowIso(ctx),
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
for (const finding of input.reviewFindings ?? []) {
|
|
1178
|
+
const summary = finding.trim();
|
|
1179
|
+
if (!summary) continue;
|
|
1180
|
+
work = appendWorkEvidence(ctx.workStore, work.workId, {
|
|
1181
|
+
title: 'read-only review finding',
|
|
1182
|
+
summary: summary.slice(0, 1_500),
|
|
1183
|
+
detailLevel: 'summary',
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const currentCheckRefs = work.checkRefs.filter((record) =>
|
|
1189
|
+
verificationRecordAppliesToCurrentWorkspace(record, ctx.sourceRevision, ctx.workspaceFingerprint));
|
|
817
1190
|
const history = reconcileVerificationHistory(
|
|
818
|
-
|
|
1191
|
+
currentCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
|
|
819
1192
|
);
|
|
820
1193
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
severity: 'needs_review',
|
|
830
|
-
creationReason: 'ambiguous_outcome',
|
|
831
|
-
reason: `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`,
|
|
832
|
-
summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
|
|
833
|
-
currentState: {
|
|
834
|
-
repoId: ctx.repoId,
|
|
835
|
-
workId: work.workId,
|
|
836
|
-
mode: work.mode,
|
|
837
|
-
statusSummary: 'waiting_for_review after acceptance failure',
|
|
838
|
-
checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
|
|
839
|
-
workSemantics: continuation.semantics,
|
|
840
|
-
reconciliationRequired: continuation.reconciliationRequired,
|
|
841
|
-
nextSafeAction: continuation.nextSafeAction,
|
|
842
|
-
},
|
|
843
|
-
attemptedActions: ['continue'],
|
|
844
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
845
|
-
blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
|
|
846
|
-
recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
|
|
847
|
-
recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
|
|
848
|
-
recommendedContinuationPrompt: continuation.continuationPrompt,
|
|
849
|
-
suggestedNextActions: [
|
|
850
|
-
{
|
|
851
|
-
label: 'Read work context',
|
|
852
|
-
tool: 'rh_context',
|
|
853
|
-
operation: 'get',
|
|
854
|
-
payload: { work_id: work.workId },
|
|
855
|
-
risk: 'readonly',
|
|
856
|
-
},
|
|
857
|
-
],
|
|
1194
|
+
const explicitAcceptanceRepair = history.acceptanceFailures.length > 0
|
|
1195
|
+
&& (input.acceptanceFailureDecision === 'repair' || input.acceptanceFailureDecision === 'rescope');
|
|
1196
|
+
if (explicitAcceptanceRepair) {
|
|
1197
|
+
const decision = input.acceptanceFailureDecision!;
|
|
1198
|
+
work = appendWorkEvidence(ctx.workStore, work.workId, {
|
|
1199
|
+
title: 'Controller acceptance-failure continuation decision',
|
|
1200
|
+
summary: `Controller explicitly chose bounded ${decision} after deterministic acceptance failure: ${history.acceptanceFailures.join(', ')}.`,
|
|
1201
|
+
detailLevel: 'summary',
|
|
858
1202
|
});
|
|
859
1203
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
860
|
-
status: '
|
|
861
|
-
phase: '
|
|
862
|
-
state: '
|
|
863
|
-
summary: `
|
|
1204
|
+
status: 'running',
|
|
1205
|
+
phase: 'implementation',
|
|
1206
|
+
state: 'active',
|
|
1207
|
+
summary: `Controller chose bounded ${decision} after acceptance failure; implementation may resume before re-verification.`,
|
|
864
1208
|
evidenceRefs: work.evidenceRefs,
|
|
865
1209
|
});
|
|
866
|
-
|
|
867
|
-
|
|
1210
|
+
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
868
1211
|
return buildFacadeResult({
|
|
869
|
-
status: '
|
|
870
|
-
summary: `Continue
|
|
1212
|
+
status: 'ok',
|
|
1213
|
+
summary: `Continue accepted explicit bounded ${decision} decision after acceptance failure; implementation may resume.`,
|
|
871
1214
|
data: {
|
|
872
|
-
work: summarizeWorkContract(
|
|
873
|
-
handoffId: handoff.id,
|
|
1215
|
+
work: summarizeWorkContract(work),
|
|
874
1216
|
acceptanceFailures: history.acceptanceFailures,
|
|
875
1217
|
infrastructureIssues: history.infrastructureIssues,
|
|
876
1218
|
backgroundCompleted: false,
|
|
1219
|
+
nextStep: work.workKind === 'read_only_review' ? 'review' : 'execute',
|
|
1220
|
+
acceptanceFailureDecision: decision,
|
|
877
1221
|
},
|
|
878
1222
|
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
879
|
-
suggestedNextActions:
|
|
880
|
-
|
|
1223
|
+
suggestedNextActions: suggestedForWork(work),
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Ambiguous acceptance failure without an explicit bounded Controller decision →
|
|
1228
|
+
// ask once per distinct failure evidence. Repeated continue calls must not
|
|
1229
|
+
// manufacture an unbounded handoff loop. A resolved handoff remains the decision
|
|
1230
|
+
// authority until newer valid-fail evidence; an unresolved matching handoff is reused.
|
|
1231
|
+
if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair && work.recoveryPolicy.handoffOnAmbiguity) {
|
|
1232
|
+
const failureReason = `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`;
|
|
1233
|
+
const latestFailureAt = work.checkRefs
|
|
1234
|
+
.filter((record) => record.outcome === 'valid_fail' && history.acceptanceFailures.includes(record.checkId))
|
|
1235
|
+
.map((record) => record.recordedAt)
|
|
1236
|
+
.sort((left, right) => right.localeCompare(left))[0];
|
|
1237
|
+
const matchingHandoffs = listHandoffItems({ ...ctx.handoffStore, status: 'all', limit: 100 })
|
|
1238
|
+
.filter((item) => (
|
|
1239
|
+
item.workId === input.workId
|
|
1240
|
+
&& item.creationReason === 'ambiguous_outcome'
|
|
1241
|
+
&& item.title === 'Acceptance failure needs review'
|
|
1242
|
+
&& item.reason === failureReason
|
|
1243
|
+
&& (!latestFailureAt || item.createdAt >= latestFailureAt)
|
|
1244
|
+
));
|
|
1245
|
+
const activeHandoff = matchingHandoffs.find((item) => item.status === 'pending' || item.status === 'acknowledged');
|
|
1246
|
+
if (activeHandoff) {
|
|
1247
|
+
return buildFacadeResult({
|
|
1248
|
+
status: 'blocked',
|
|
1249
|
+
summary: `Continue remains paused for ChatGPT review through existing handoff ${activeHandoff.id}.`,
|
|
1250
|
+
data: {
|
|
1251
|
+
work: summarizeWorkContract(work),
|
|
1252
|
+
handoffId: activeHandoff.id,
|
|
1253
|
+
acceptanceFailures: history.acceptanceFailures,
|
|
1254
|
+
infrastructureIssues: history.infrastructureIssues,
|
|
1255
|
+
backgroundCompleted: false,
|
|
1256
|
+
},
|
|
1257
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1258
|
+
suggestedNextActions: [{
|
|
881
1259
|
label: 'Get handoff',
|
|
882
1260
|
tool: 'rh_inbox',
|
|
883
1261
|
operation: 'get',
|
|
884
|
-
payload: { handoff_id:
|
|
1262
|
+
payload: { handoff_id: activeHandoff.id },
|
|
885
1263
|
risk: 'readonly',
|
|
886
1264
|
confidence: 'high',
|
|
1265
|
+
}],
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const resolvedHandoff = matchingHandoffs.find((item) => item.status === 'resolved');
|
|
1270
|
+
if (!resolvedHandoff) {
|
|
1271
|
+
const continuation = buildWorkContinuationSnapshot(work);
|
|
1272
|
+
const handoff = createHandoffItem(ctx.handoffStore, {
|
|
1273
|
+
id: handoffIdFor('continue'),
|
|
1274
|
+
repoId: ctx.repoId,
|
|
1275
|
+
workId: work.workId,
|
|
1276
|
+
title: 'Acceptance failure needs review',
|
|
1277
|
+
severity: 'needs_review',
|
|
1278
|
+
creationReason: 'ambiguous_outcome',
|
|
1279
|
+
reason: failureReason,
|
|
1280
|
+
summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
|
|
1281
|
+
currentState: {
|
|
1282
|
+
repoId: ctx.repoId,
|
|
1283
|
+
workId: work.workId,
|
|
1284
|
+
mode: work.mode,
|
|
1285
|
+
statusSummary: 'waiting_for_review after acceptance failure',
|
|
1286
|
+
checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
|
|
1287
|
+
workSemantics: continuation.semantics,
|
|
1288
|
+
reconciliationRequired: continuation.reconciliationRequired,
|
|
1289
|
+
nextSafeAction: continuation.nextSafeAction,
|
|
887
1290
|
},
|
|
888
|
-
|
|
1291
|
+
attemptedActions: ['continue'],
|
|
1292
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1293
|
+
blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
|
|
1294
|
+
recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
|
|
1295
|
+
recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
|
|
1296
|
+
recommendedContinuationPrompt: continuation.continuationPrompt,
|
|
1297
|
+
suggestedNextActions: [
|
|
1298
|
+
{
|
|
1299
|
+
label: 'Read work context',
|
|
1300
|
+
tool: 'rh_context',
|
|
1301
|
+
operation: 'get',
|
|
1302
|
+
payload: { work_id: work.workId },
|
|
1303
|
+
risk: 'readonly',
|
|
1304
|
+
},
|
|
1305
|
+
],
|
|
1306
|
+
});
|
|
1307
|
+
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1308
|
+
status: 'ready',
|
|
1309
|
+
phase: 'verification',
|
|
1310
|
+
state: 'blocked',
|
|
1311
|
+
summary: `Acceptance failure requires review through handoff ${handoff.id}.`,
|
|
1312
|
+
evidenceRefs: work.evidenceRefs,
|
|
1313
|
+
});
|
|
1314
|
+
const updated = appendWorkHandoffRef(ctx.workStore, work.workId, handoff.id);
|
|
1315
|
+
|
|
1316
|
+
return buildFacadeResult({
|
|
1317
|
+
status: 'blocked',
|
|
1318
|
+
summary: `Continue paused for ChatGPT review; handoff ${handoff.id} created. No background execution pretended.`,
|
|
1319
|
+
data: {
|
|
1320
|
+
work: summarizeWorkContract(updated),
|
|
1321
|
+
handoffId: handoff.id,
|
|
1322
|
+
acceptanceFailures: history.acceptanceFailures,
|
|
1323
|
+
infrastructureIssues: history.infrastructureIssues,
|
|
1324
|
+
backgroundCompleted: false,
|
|
1325
|
+
},
|
|
1326
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1327
|
+
suggestedNextActions: [
|
|
1328
|
+
{
|
|
1329
|
+
label: 'Get handoff',
|
|
1330
|
+
tool: 'rh_inbox',
|
|
1331
|
+
operation: 'get',
|
|
1332
|
+
payload: { handoff_id: handoff.id },
|
|
1333
|
+
risk: 'readonly',
|
|
1334
|
+
confidence: 'high',
|
|
1335
|
+
},
|
|
1336
|
+
],
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1341
|
+
status: 'running',
|
|
1342
|
+
phase: 'verification',
|
|
1343
|
+
state: 'active',
|
|
1344
|
+
summary: `Resolved acceptance-failure handoff ${resolvedHandoff.id} authorizes bounded continuation to re-verification.`,
|
|
1345
|
+
evidenceRefs: work.evidenceRefs,
|
|
1346
|
+
});
|
|
1347
|
+
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
1348
|
+
return buildFacadeResult({
|
|
1349
|
+
status: 'ok',
|
|
1350
|
+
summary: `Continue accepted resolved acceptance-failure handoff ${resolvedHandoff.id}; re-verification may resume.`,
|
|
1351
|
+
data: {
|
|
1352
|
+
work: summarizeWorkContract(work),
|
|
1353
|
+
handoffId: resolvedHandoff.id,
|
|
1354
|
+
acceptanceFailures: history.acceptanceFailures,
|
|
1355
|
+
infrastructureIssues: history.infrastructureIssues,
|
|
1356
|
+
backgroundCompleted: false,
|
|
1357
|
+
nextStep: 'verify',
|
|
1358
|
+
remainingChecks: history.acceptanceFailures,
|
|
1359
|
+
},
|
|
1360
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1361
|
+
suggestedNextActions: suggestedForWork(work),
|
|
889
1362
|
});
|
|
890
1363
|
}
|
|
891
1364
|
|
|
@@ -927,6 +1400,47 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
927
1400
|
});
|
|
928
1401
|
}
|
|
929
1402
|
|
|
1403
|
+
const implementationEvidence = evaluateWorkImplementationEvidence(work, ctx.workspaceChangedPaths);
|
|
1404
|
+
if (implementationEvidence.status !== 'complete') {
|
|
1405
|
+
const suggested = validateSuggestedNextActions([
|
|
1406
|
+
{
|
|
1407
|
+
label: 'Implement the repository change before verification',
|
|
1408
|
+
tool: 'rh_context',
|
|
1409
|
+
operation: 'get',
|
|
1410
|
+
payload: { work_id: work.workId },
|
|
1411
|
+
risk: 'readonly',
|
|
1412
|
+
confidence: 'high',
|
|
1413
|
+
reason: 'Verification proves behavior only after the repository-change Work has current source changes.',
|
|
1414
|
+
},
|
|
1415
|
+
]).actions;
|
|
1416
|
+
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1417
|
+
status: 'running',
|
|
1418
|
+
phase: 'implementation',
|
|
1419
|
+
state: 'active',
|
|
1420
|
+
summary: implementationEvidence.reasons.join(' '),
|
|
1421
|
+
evidenceRefs: work.evidenceRefs,
|
|
1422
|
+
});
|
|
1423
|
+
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
1424
|
+
return buildFacadeResult({
|
|
1425
|
+
status: 'blocked',
|
|
1426
|
+
summary: `Continue requires implementation before verification. ${implementationEvidence.reasons.join(' ')}`,
|
|
1427
|
+
data: {
|
|
1428
|
+
work: summarizeWorkContract(updated),
|
|
1429
|
+
backgroundCompleted: false,
|
|
1430
|
+
nextStep: 'execute',
|
|
1431
|
+
implementationEvidencePresent: false,
|
|
1432
|
+
workspaceChangedPaths: implementationEvidence.changedPaths,
|
|
1433
|
+
},
|
|
1434
|
+
suggestedNextActions: suggested,
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (implementationEvidence.changedPaths.length > 0) {
|
|
1439
|
+
recordWorkScopeEvidence(ctx.workStore, work.workId, {
|
|
1440
|
+
actualChangedPaths: implementationEvidence.changedPaths,
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
|
|
930
1444
|
if (work.checks.length > 0 && history.validPasses.length < work.checks.length) {
|
|
931
1445
|
const remaining = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
932
1446
|
const suggested = validateSuggestedNextActions(
|
|
@@ -966,7 +1480,13 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
966
1480
|
});
|
|
967
1481
|
}
|
|
968
1482
|
|
|
969
|
-
const completionEvidence = evaluateWorkCompletionEvidence(
|
|
1483
|
+
const completionEvidence = evaluateWorkCompletionEvidence(
|
|
1484
|
+
work,
|
|
1485
|
+
ctx.sourceRevision,
|
|
1486
|
+
ctx.workspaceFingerprint,
|
|
1487
|
+
ctx.workBoundProcessEvidenceIds,
|
|
1488
|
+
ctx.workspaceChangedPaths,
|
|
1489
|
+
);
|
|
970
1490
|
if (completionEvidence.status !== 'complete') {
|
|
971
1491
|
const suggested = validateSuggestedNextActions([
|
|
972
1492
|
{
|
|
@@ -981,9 +1501,11 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
981
1501
|
]).actions;
|
|
982
1502
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
983
1503
|
status: 'running',
|
|
984
|
-
phase: 'implementation',
|
|
1504
|
+
phase: work.workKind === 'read_only_review' ? 'verification' : 'implementation',
|
|
985
1505
|
state: 'active',
|
|
986
|
-
summary:
|
|
1506
|
+
summary: work.workKind === 'read_only_review'
|
|
1507
|
+
? `Read-only review completion evidence is incomplete: ${completionEvidence.reasons.join(' ')}`
|
|
1508
|
+
: `Meaningful implementation evidence is still missing: ${completionEvidence.reasons.join(' ')}`,
|
|
987
1509
|
evidenceRefs: work.evidenceRefs,
|
|
988
1510
|
});
|
|
989
1511
|
const updated = updateWorkContract(ctx.workStore, work.workId, {
|
|
@@ -996,7 +1518,7 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
996
1518
|
work: summarizeWorkContract(updated),
|
|
997
1519
|
backgroundCompleted: false,
|
|
998
1520
|
nextStep: 'execute',
|
|
999
|
-
executionEvidencePresent:
|
|
1521
|
+
executionEvidencePresent: Boolean(ctx.workBoundProcessEvidenceIds?.length),
|
|
1000
1522
|
missingChecks: completionEvidence.missingChecks,
|
|
1001
1523
|
durableResultEvidence: completionEvidence.durableResultEvidence,
|
|
1002
1524
|
ignoredWeakReferences: {
|
|
@@ -1022,7 +1544,9 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1022
1544
|
status: 'running',
|
|
1023
1545
|
phase: 'delivery',
|
|
1024
1546
|
state: 'active',
|
|
1025
|
-
summary:
|
|
1547
|
+
summary: work.workKind === 'read_only_review'
|
|
1548
|
+
? 'Read-only review evidence is source-bound and clean; semantic no-change finalization is next.'
|
|
1549
|
+
: 'Implementation and verification evidence are sufficient; exact delivery and cleanup receipt is next.',
|
|
1026
1550
|
evidenceRefs: work.evidenceRefs,
|
|
1027
1551
|
});
|
|
1028
1552
|
const updated = updateWorkContract(ctx.workStore, work.workId, {
|
|
@@ -1058,16 +1582,60 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
1058
1582
|
checkFailed: input.checkFailed,
|
|
1059
1583
|
skipped: input.skipped,
|
|
1060
1584
|
});
|
|
1585
|
+
if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
|
|
1586
|
+
const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
|
|
1587
|
+
const existing = [...work.checkRefs].reverse().find((record) => record.checkId === resolvedCheckId);
|
|
1588
|
+
const completed = work.status === 'completed';
|
|
1589
|
+
return buildFacadeResult({
|
|
1590
|
+
status: completed ? 'ok' : 'blocked',
|
|
1591
|
+
summary: `WorkContract ${work.workId} is terminal (${work.status}); verification was not re-executed.`,
|
|
1592
|
+
data: {
|
|
1593
|
+
work: summarizeWorkContract(work),
|
|
1594
|
+
verification: {
|
|
1595
|
+
checkId: resolvedCheckId,
|
|
1596
|
+
...(existing ? { outcome: existing.outcome } : {}),
|
|
1597
|
+
terminal: true,
|
|
1598
|
+
idempotent: true,
|
|
1599
|
+
reexecuted: false,
|
|
1600
|
+
isAcceptanceFailure: existing?.outcome === 'valid_fail',
|
|
1601
|
+
isInfrastructureIssue: existing?.outcome === 'invalid_check_id' || existing?.outcome === 'infrastructure_failure',
|
|
1602
|
+
doesNotRequestTaskChanges: existing?.outcome !== 'valid_fail',
|
|
1603
|
+
},
|
|
1604
|
+
backgroundCompleted: false,
|
|
1605
|
+
},
|
|
1606
|
+
evidenceRefs: existing?.evidenceRef ? [existing.evidenceRef] : [],
|
|
1607
|
+
warnings: classified.warnings,
|
|
1608
|
+
suggestedNextActions: [{ label: 'Inspect work via context', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }],
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1061
1611
|
|
|
1062
1612
|
const at = nowIso(ctx);
|
|
1613
|
+
const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
|
|
1614
|
+
const receipt = input.receipt
|
|
1615
|
+
&& input.receipt.repoId === work.repoId
|
|
1616
|
+
&& input.receipt.workId === work.workId
|
|
1617
|
+
&& input.receipt.checkId === resolvedCheckId
|
|
1618
|
+
? input.receipt
|
|
1619
|
+
: undefined;
|
|
1620
|
+
const sourceRevision = receipt && input.sourceRevision?.trim() ? input.sourceRevision.trim() : undefined;
|
|
1621
|
+
const verificationSummary = receipt
|
|
1622
|
+
? `${classified.summary} Durable Process receipt ${receipt.receiptId}.`
|
|
1623
|
+
: classified.summary;
|
|
1063
1624
|
const record: VerificationRecord = {
|
|
1064
|
-
checkId:
|
|
1625
|
+
checkId: resolvedCheckId,
|
|
1065
1626
|
outcome: classified.outcome,
|
|
1066
|
-
summary:
|
|
1627
|
+
summary: verificationSummary,
|
|
1067
1628
|
recordedAt: at,
|
|
1629
|
+
sourceRevision,
|
|
1630
|
+
workspaceFingerprint: receipt && sourceRevision ? input.workspaceFingerprint : undefined,
|
|
1631
|
+
verificationInputFingerprint: receipt && sourceRevision ? input.verificationInputFingerprint : undefined,
|
|
1632
|
+
commandFingerprint: receipt && sourceRevision ? input.commandFingerprint : undefined,
|
|
1633
|
+
startedAt: receipt?.startedAt,
|
|
1634
|
+
completedAt: receipt?.finishedAt,
|
|
1635
|
+
receipt,
|
|
1068
1636
|
evidenceRef: {
|
|
1069
1637
|
title: `verification:${classified.outcome}`,
|
|
1070
|
-
summary:
|
|
1638
|
+
summary: verificationSummary,
|
|
1071
1639
|
detailLevel: 'summary',
|
|
1072
1640
|
},
|
|
1073
1641
|
};
|
|
@@ -1097,12 +1665,16 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
1097
1665
|
? 'failed'
|
|
1098
1666
|
: 'ok';
|
|
1099
1667
|
|
|
1668
|
+
const completionEvidence = classified.outcome === 'valid_pass'
|
|
1669
|
+
? evaluateWorkCompletionEvidence(updated, sourceRevision, input.workspaceFingerprint)
|
|
1670
|
+
: undefined;
|
|
1671
|
+
const validPassReadyToFinalize = completionEvidence?.status === 'complete';
|
|
1100
1672
|
const suggested = validateSuggestedNextActions(
|
|
1101
1673
|
classified.outcome === 'valid_pass'
|
|
1102
1674
|
? [{
|
|
1103
|
-
label: 'Continue workloop',
|
|
1675
|
+
label: validPassReadyToFinalize ? 'Finalize work' : 'Continue workloop',
|
|
1104
1676
|
tool: 'rh_work',
|
|
1105
|
-
operation: 'continue',
|
|
1677
|
+
operation: validPassReadyToFinalize ? 'finalize' : 'continue',
|
|
1106
1678
|
payload: { work_id: work.workId },
|
|
1107
1679
|
risk: 'readonly',
|
|
1108
1680
|
confidence: 'high',
|
|
@@ -1141,6 +1713,9 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
1141
1713
|
doesNotRequestTaskChanges: !classified.isAcceptanceFailure,
|
|
1142
1714
|
},
|
|
1143
1715
|
backgroundCompleted: false,
|
|
1716
|
+
...(classified.outcome === 'valid_pass'
|
|
1717
|
+
? { nextStep: validPassReadyToFinalize ? 'finalize' : 'continue' }
|
|
1718
|
+
: {}),
|
|
1144
1719
|
},
|
|
1145
1720
|
warnings: classified.warnings,
|
|
1146
1721
|
evidenceRefs: record.evidenceRef ? [record.evidenceRef] : [],
|
|
@@ -1189,7 +1764,13 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1189
1764
|
});
|
|
1190
1765
|
}
|
|
1191
1766
|
|
|
1192
|
-
const completionEvidence = evaluateWorkCompletionEvidence(
|
|
1767
|
+
const completionEvidence = evaluateWorkCompletionEvidence(
|
|
1768
|
+
work,
|
|
1769
|
+
ctx.sourceRevision,
|
|
1770
|
+
ctx.workspaceFingerprint,
|
|
1771
|
+
ctx.workBoundProcessEvidenceIds,
|
|
1772
|
+
ctx.workspaceChangedPaths,
|
|
1773
|
+
);
|
|
1193
1774
|
const history = completionEvidence.history;
|
|
1194
1775
|
|
|
1195
1776
|
if (input.forceFailed || completionEvidence.status === 'failed') {
|
|
@@ -1264,6 +1845,91 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1264
1845
|
});
|
|
1265
1846
|
}
|
|
1266
1847
|
|
|
1848
|
+
// Read-only review completion is semantic no-change authority, not Git delivery.
|
|
1849
|
+
// The exact source/workspace fence is re-evaluated above at finalize time; only
|
|
1850
|
+
// a clean review with persisted inspected paths and zero findings can close.
|
|
1851
|
+
if (work.workKind === 'read_only_review' && !work.completionReceipt) {
|
|
1852
|
+
const reviewEvidence = work.readOnlyReviewEvidence!;
|
|
1853
|
+
const recordedAt = nowIso(ctx);
|
|
1854
|
+
const completed = completeWorkWithReceipt(
|
|
1855
|
+
ctx.workStore,
|
|
1856
|
+
work.workId,
|
|
1857
|
+
{
|
|
1858
|
+
schemaVersion: 1,
|
|
1859
|
+
receiptId: `ROR-WORK-${randomUUID()}`,
|
|
1860
|
+
source: 'read_only_review',
|
|
1861
|
+
workId: work.workId,
|
|
1862
|
+
baseRevision: work.baseRevision!,
|
|
1863
|
+
sourceRevision: ctx.sourceRevision!,
|
|
1864
|
+
...(ctx.workspaceFingerprint ? { workspaceFingerprint: ctx.workspaceFingerprint } : {}),
|
|
1865
|
+
workspaceChangedPaths: [],
|
|
1866
|
+
inspectedPaths: reviewEvidence.inspectedPaths,
|
|
1867
|
+
findingCount: 0,
|
|
1868
|
+
recordedAt,
|
|
1869
|
+
},
|
|
1870
|
+
'completed_no_change',
|
|
1871
|
+
'read_only_review',
|
|
1872
|
+
);
|
|
1873
|
+
if (completed.planId && completed.planStepId && ctx.planStore) {
|
|
1874
|
+
completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
|
|
1875
|
+
}
|
|
1876
|
+
return buildFacadeResult({
|
|
1877
|
+
status: 'ok',
|
|
1878
|
+
summary: `Finalize result: clean read-only review completed with no source change for ${work.workId}.`,
|
|
1879
|
+
data: {
|
|
1880
|
+
work: summarizeWorkContract(completed),
|
|
1881
|
+
finalStatus: 'completed',
|
|
1882
|
+
completionOutcome: 'completed_no_change',
|
|
1883
|
+
completionReceipt: completed.completionReceipt,
|
|
1884
|
+
inspectedPathCount: reviewEvidence.inspectedPaths.length,
|
|
1885
|
+
hiddenFailure: false,
|
|
1886
|
+
},
|
|
1887
|
+
evidenceRefs: completed.evidenceRefs.slice(0, 5),
|
|
1888
|
+
suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// For a controller-local effect Work, the semantic Controller's explicit
|
|
1893
|
+
// finalize call is the terminalization decision, but it is admissible only
|
|
1894
|
+
// after durable result evidence exists. Record one canonical Work receipt here
|
|
1895
|
+
// instead of routing an effect-only Work through Git delivery. Remote effects
|
|
1896
|
+
// remain bound to their durable plugin action receipt in the plugin layer.
|
|
1897
|
+
if (work.workKind === 'local_effect' && !work.completionReceipt && completionEvidence.durableResultEvidence) {
|
|
1898
|
+
const recordedAt = nowIso(ctx);
|
|
1899
|
+
const completed = completeWorkWithReceipt(
|
|
1900
|
+
ctx.workStore,
|
|
1901
|
+
work.workId,
|
|
1902
|
+
{
|
|
1903
|
+
schemaVersion: 1,
|
|
1904
|
+
receiptId: `LFX-WORK-${randomUUID()}`,
|
|
1905
|
+
source: 'local_effect',
|
|
1906
|
+
workId: work.workId,
|
|
1907
|
+
operation: 'controller_work/local_effect',
|
|
1908
|
+
target: { kind: 'controller_local', id: work.workId },
|
|
1909
|
+
changed: true,
|
|
1910
|
+
recordedAt,
|
|
1911
|
+
},
|
|
1912
|
+
'completed_local',
|
|
1913
|
+
'local_effect',
|
|
1914
|
+
);
|
|
1915
|
+
if (completed.planId && completed.planStepId && ctx.planStore) {
|
|
1916
|
+
completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
|
|
1917
|
+
}
|
|
1918
|
+
return buildFacadeResult({
|
|
1919
|
+
status: 'ok',
|
|
1920
|
+
summary: `Finalize result: succeeded for ${work.workId}.`,
|
|
1921
|
+
data: {
|
|
1922
|
+
work: summarizeWorkContract(completed),
|
|
1923
|
+
finalStatus: 'completed',
|
|
1924
|
+
completionReceipt: completed.completionReceipt,
|
|
1925
|
+
validPasses: history.validPasses,
|
|
1926
|
+
hiddenFailure: false,
|
|
1927
|
+
},
|
|
1928
|
+
evidenceRefs: completed.evidenceRefs.slice(0, 5),
|
|
1929
|
+
suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1267
1933
|
// Checks and result evidence are necessary but not sufficient. Delivery and
|
|
1268
1934
|
// cleanup must produce the exact Work-owned receipt before Work can become
|
|
1269
1935
|
// terminal; a Run exit or a missing receipt is never completion authority.
|
|
@@ -1318,6 +1984,38 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1318
1984
|
});
|
|
1319
1985
|
}
|
|
1320
1986
|
|
|
1987
|
+
const owner = ctx.workStore.controllerHome
|
|
1988
|
+
? getControllerSession({ controllerHome: ctx.workStore.controllerHome, repoId: ctx.repoId }, work.workId)
|
|
1989
|
+
: undefined;
|
|
1990
|
+
if (owner) {
|
|
1991
|
+
const ownerPrincipal = owner.principalId?.trim() || owner.controllerId;
|
|
1992
|
+
const ownerInstanceId = owner.controllerInstanceId?.trim();
|
|
1993
|
+
const callerPrincipal = ctx.principalId?.trim();
|
|
1994
|
+
const callerInstanceId = ctx.controllerInstanceId?.trim();
|
|
1995
|
+
const sameAuthority = Boolean(
|
|
1996
|
+
callerPrincipal
|
|
1997
|
+
&& ownerPrincipal === callerPrincipal
|
|
1998
|
+
&& (!ownerInstanceId || ownerInstanceId === callerInstanceId),
|
|
1999
|
+
);
|
|
2000
|
+
if (!sameAuthority) {
|
|
2001
|
+
return buildFacadeResult({
|
|
2002
|
+
status: 'blocked',
|
|
2003
|
+
summary: `WORK_TERMINALIZATION_ACTIVE_CONTROLLER_FENCE: ${work.workId} has a newer active Controller claim generation ${owner.claimGeneration ?? 1}.`,
|
|
2004
|
+
data: {
|
|
2005
|
+
work: summarizeWorkContract(work),
|
|
2006
|
+
activeController: {
|
|
2007
|
+
controllerId: owner.controllerId,
|
|
2008
|
+
principalId: ownerPrincipal,
|
|
2009
|
+
controllerInstanceId: owner.controllerInstanceId,
|
|
2010
|
+
sessionId: owner.sessionId,
|
|
2011
|
+
claimGeneration: owner.claimGeneration ?? 1,
|
|
2012
|
+
},
|
|
2013
|
+
finalStatus: work.status,
|
|
2014
|
+
},
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1321
2019
|
const destructiveCleanup = input.authorizeDestructiveCleanup === true;
|
|
1322
2020
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1323
2021
|
status: 'cancelled',
|
|
@@ -1334,9 +2032,17 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1334
2032
|
// cleanup handler verifies ownership, cleanliness and successful removal.
|
|
1335
2033
|
worktreeRef: work.worktreeRef,
|
|
1336
2034
|
});
|
|
1337
|
-
const
|
|
1338
|
-
|
|
2035
|
+
const planId = updated.planId;
|
|
2036
|
+
const planStepId = updated.planStepId;
|
|
2037
|
+
let plan = planId && ctx.planStore
|
|
2038
|
+
? getPlanContract(ctx.planStore, planId)
|
|
1339
2039
|
: undefined;
|
|
2040
|
+
if (plan && planId && planStepId && ctx.planStore) {
|
|
2041
|
+
const step = plan.steps.find((entry) => entry.id === planStepId);
|
|
2042
|
+
if (step?.workId === updated.workId) {
|
|
2043
|
+
plan = completePlanStepForWork(ctx.planStore, { planId, stepId: planStepId, work: updated });
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
1340
2046
|
|
|
1341
2047
|
return buildFacadeResult({
|
|
1342
2048
|
status: 'ok',
|
|
@@ -1373,6 +2079,7 @@ export function runGoalWorkloop(
|
|
|
1373
2079
|
objective: String(args.objective ?? ''),
|
|
1374
2080
|
acceptanceCriteria: Array.isArray(args.acceptance_criteria) ? args.acceptance_criteria.map(String) : undefined,
|
|
1375
2081
|
allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
|
|
2082
|
+
initialLikelyPaths: Array.isArray(args.initial_likely_paths) ? args.initial_likely_paths.map(String) : undefined,
|
|
1376
2083
|
forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
|
|
1377
2084
|
checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
1378
2085
|
modeInput: {
|
|
@@ -1383,6 +2090,7 @@ export function runGoalWorkloop(
|
|
|
1383
2090
|
requiresInvestigation: args.requires_investigation === true,
|
|
1384
2091
|
requiresLongRunningChecks: args.requires_long_running_checks === true,
|
|
1385
2092
|
requiresParallelism: args.requires_parallelism === true,
|
|
2093
|
+
explicitMode: args.mode === 'scale' ? 'scale' : undefined,
|
|
1386
2094
|
needsDependencies: args.needs_dependencies === true,
|
|
1387
2095
|
requiresRecovery: args.requires_recovery === true,
|
|
1388
2096
|
requiresWorker: args.requires_worker === true,
|
|
@@ -1402,13 +2110,43 @@ export function runGoalWorkloop(
|
|
|
1402
2110
|
forceMode: args.force_mode === 'direct_control' || args.force_mode === 'goal_workloop' || args.force_mode === 'handoff_only'
|
|
1403
2111
|
? args.force_mode
|
|
1404
2112
|
: undefined,
|
|
2113
|
+
relatedWorkId: typeof args.related_work_id === 'string' ? args.related_work_id : undefined,
|
|
2114
|
+
workRelation: args.work_relation === 'continue' || args.work_relation === 'extend' || args.work_relation === 'parallel' || args.work_relation === 'new_goal'
|
|
2115
|
+
? args.work_relation
|
|
2116
|
+
: undefined,
|
|
2117
|
+
requirementId: typeof args.requirement_id === 'string' ? args.requirement_id : undefined,
|
|
1405
2118
|
planId: typeof args.plan_id === 'string' ? args.plan_id : undefined,
|
|
1406
2119
|
planStepId: typeof args.plan_step_id === 'string' ? args.plan_step_id : undefined,
|
|
2120
|
+
workKind: args.work_kind === 'repository_change'
|
|
2121
|
+
|| args.work_kind === 'completed_no_change'
|
|
2122
|
+
|| args.work_kind === 'read_only_review'
|
|
2123
|
+
|| args.work_kind === 'investigation'
|
|
2124
|
+
|| args.work_kind === 'local_effect'
|
|
2125
|
+
|| args.work_kind === 'remote_effect'
|
|
2126
|
+
|| args.work_kind === 'reconciliation'
|
|
2127
|
+
? args.work_kind
|
|
2128
|
+
: undefined,
|
|
1407
2129
|
});
|
|
1408
2130
|
case 'continue':
|
|
1409
2131
|
return continueGoalWorkloop(ctx, {
|
|
1410
2132
|
workId: String(args.work_id ?? ''),
|
|
1411
2133
|
note: typeof args.note === 'string' ? args.note : undefined,
|
|
2134
|
+
allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
|
|
2135
|
+
forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
|
|
2136
|
+
checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
2137
|
+
additionalLikelyPaths: Array.isArray(args.additional_likely_paths) ? args.additional_likely_paths.map(String) : undefined,
|
|
2138
|
+
inspectedPaths: Array.isArray(args.inspected_paths) ? args.inspected_paths.map(String) : undefined,
|
|
2139
|
+
reviewFindings: Array.isArray(args.review_findings) ? args.review_findings.map(String) : undefined,
|
|
2140
|
+
acceptanceEvidence: Array.isArray(args.acceptance_evidence)
|
|
2141
|
+
? args.acceptance_evidence.filter((value): value is Record<string, unknown> => Boolean(value) && typeof value === 'object').map((value) => ({
|
|
2142
|
+
criterion: typeof value.criterion === 'string' ? value.criterion : '',
|
|
2143
|
+
evidenceIds: Array.isArray(value.evidence_ids) ? value.evidence_ids.map(String) : [],
|
|
2144
|
+
rationale: typeof value.rationale === 'string' ? value.rationale : '',
|
|
2145
|
+
}))
|
|
2146
|
+
: undefined,
|
|
2147
|
+
acceptanceFailureDecision: args.acceptance_failure_decision === 'repair' || args.acceptance_failure_decision === 'rescope'
|
|
2148
|
+
? args.acceptance_failure_decision
|
|
2149
|
+
: undefined,
|
|
1412
2150
|
});
|
|
1413
2151
|
case 'verify':
|
|
1414
2152
|
return verifyGoalWorkloop(ctx, {
|