@moretea-labs/forge 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +2 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- 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/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +7 -83
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +54 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/hook-operations.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-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- 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/sprint-backlog.sh +6 -6
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- 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 +170 -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-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +38 -22
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +3 -3
- 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/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- 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/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-public-docs.sh +14 -1
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- 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 -1
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/chatgpt.ts +31 -2
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +37 -10
- 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 +106 -3
- 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 +898 -430
- 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/github.ts +133 -0
- package/src/cli/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +225 -67
- package/src/cli/local-bridge/console-view-models.ts +3 -35
- package/src/cli/local-bridge/facade-api.ts +2 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +8 -7
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +29 -532
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +356 -101
- package/src/cli/mcp/server.ts +625 -54
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +125 -15
- package/src/cli/mcp/transports/session-registry.ts +67 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +209 -14
- package/src/cli/repositories/command-executor.ts +55 -521
- 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 +121 -0
- package/src/cli/repositories/safe-patch.ts +6 -4
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +110 -10
- package/src/cli/repository/inspector.ts +565 -62
- 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.ts +275 -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 +196 -2
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- 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-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- 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 +55 -11
- package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
- 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 +145 -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 +273 -542
- 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 +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
- 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/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +19 -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/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 +52 -14
- package/src/runtime/execution/process-runtime/check-result.ts +63 -2
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
- package/src/runtime/execution/process-runtime/gc.ts +98 -12
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
- package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
- package/src/runtime/execution/process-runtime/runtime.ts +59 -4
- package/src/runtime/execution/process-runtime/store.ts +7 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- 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/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +185 -83
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2925 -318
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +92 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
- 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 +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +4 -2
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- 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 +125 -8
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +366 -24
- package/src/runtime/plugins/types.ts +39 -2
- 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/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +415 -18
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +162 -19
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -27
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/safe-tooling/types.ts +0 -30
- 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 +702 -94
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- 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 +258 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +509 -80
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +6 -3
- package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
- 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/bin/forge-desktop-helper.mjs +0 -204
- 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 -453
- package/docs/architecture/current/codegraph-context-provider.md +0 -226
- 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 -354
- package/docs/architecture/current/entity-model.md +0 -592
- 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 -144
- 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 -311
- 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 -83
- package/docs/operations/controller-desktop-plugin.md +0 -112
- 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/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
- 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,14 +1,19 @@
|
|
|
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,
|
|
9
11
|
appendWorkHandoffRef,
|
|
10
12
|
createWorkContract,
|
|
11
13
|
getWorkContract,
|
|
14
|
+
listWorkContracts,
|
|
15
|
+
recordWorkCompletionReceipt,
|
|
16
|
+
recordWorkScopeEvidence,
|
|
12
17
|
summarizeWorkContract,
|
|
13
18
|
transitionWorkContractPhase,
|
|
14
19
|
updateWorkContract,
|
|
@@ -17,8 +22,11 @@ import {
|
|
|
17
22
|
import {
|
|
18
23
|
claimPlanStepForWork,
|
|
19
24
|
completePlanStepForWork,
|
|
25
|
+
getPlanContract,
|
|
20
26
|
type PlanContractStoreOptions,
|
|
21
27
|
} from './plan-contract-store';
|
|
28
|
+
import { withPrimaryWorkAdmissionLock } from './semantic-admission';
|
|
29
|
+
import { readRequirement } from '../persistence/requirement-store';
|
|
22
30
|
import {
|
|
23
31
|
classifyVerificationOutcome,
|
|
24
32
|
normalizeCheckIds,
|
|
@@ -39,6 +47,7 @@ import type {
|
|
|
39
47
|
SuggestedNextAction,
|
|
40
48
|
VerificationRecord,
|
|
41
49
|
WorkContract,
|
|
50
|
+
WorkKind,
|
|
42
51
|
WorkRisk,
|
|
43
52
|
} from './types';
|
|
44
53
|
import { selectExecutionMode } from './types';
|
|
@@ -56,18 +65,30 @@ export interface GoalWorkloopContext {
|
|
|
56
65
|
principalId?: string;
|
|
57
66
|
controllerInstanceId?: string;
|
|
58
67
|
workspaceFingerprint?: string;
|
|
68
|
+
/** Current net repository paths changed from the Work base plus dirty checkout paths. */
|
|
69
|
+
workspaceChangedPaths?: readonly string[];
|
|
59
70
|
now?: () => string;
|
|
71
|
+
/** True only while a Gateway caller holds the cross-process primary Work admission lock. */
|
|
72
|
+
semanticAdmissionLocked?: boolean;
|
|
73
|
+
materializeIsolatedWorkspace?: (input: { workId: string; title: string; baseRef?: string; needsDependencies?: boolean }) => { checkoutId: string; root: string; baseRevision?: string | null; managed: true };
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
export type WorkAdmissionRelation = 'continue' | 'extend' | 'parallel' | 'new_goal';
|
|
77
|
+
|
|
62
78
|
export interface GoalWorkloopStartInput {
|
|
63
79
|
objective: string;
|
|
64
80
|
acceptanceCriteria?: string[];
|
|
65
81
|
allowedPaths?: string[];
|
|
82
|
+
/** Non-authoritative first-pass discovery candidates. */
|
|
83
|
+
initialLikelyPaths?: string[];
|
|
66
84
|
forbiddenPaths?: string[];
|
|
67
85
|
checks?: string[];
|
|
68
86
|
constraints?: WorkContract['constraints'];
|
|
69
87
|
modeInput: ExecutionModeSelectionInput;
|
|
70
88
|
requestedBy?: WorkContract['requestedBy'];
|
|
89
|
+
relatedWorkId?: string;
|
|
90
|
+
workRelation?: WorkAdmissionRelation;
|
|
91
|
+
requirementId?: string;
|
|
71
92
|
taskId?: string;
|
|
72
93
|
issueId?: string;
|
|
73
94
|
approvalConfirmed?: boolean;
|
|
@@ -75,16 +96,35 @@ export interface GoalWorkloopStartInput {
|
|
|
75
96
|
forceMode?: WorkContract['mode'];
|
|
76
97
|
planId?: string;
|
|
77
98
|
planStepId?: string;
|
|
99
|
+
/** Explicit technical evidence shape chosen by the semantic Controller. Never inferred from objective/check text. */
|
|
100
|
+
workKind?: Extract<WorkKind, 'repository_change' | 'completed_no_change' | 'investigation' | 'local_effect' | 'remote_effect' | 'reconciliation'>;
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
export interface GoalWorkloopContinueInput {
|
|
81
104
|
workId: string;
|
|
82
105
|
note?: string;
|
|
106
|
+
allowedPaths?: string[];
|
|
107
|
+
forbiddenPaths?: string[];
|
|
108
|
+
checks?: string[];
|
|
109
|
+
additionalLikelyPaths?: string[];
|
|
110
|
+
inspectedPaths?: string[];
|
|
111
|
+
/** Explicit Controller decision after deterministic acceptance failure; never inferred from note text. */
|
|
112
|
+
acceptanceFailureDecision?: 'repair' | 'rescope';
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
export interface GoalWorkloopVerifyInput {
|
|
86
116
|
workId: string;
|
|
87
117
|
checkId: string;
|
|
118
|
+
/** Exact Git revision observed by the authoritative Work-bound verification Process. */
|
|
119
|
+
sourceRevision?: string;
|
|
120
|
+
/** Exact dirty-workspace content identity observed by the authoritative check. */
|
|
121
|
+
workspaceFingerprint?: string;
|
|
122
|
+
/** Stable semantic check-input identity used for exact evidence reuse. */
|
|
123
|
+
verificationInputFingerprint?: string;
|
|
124
|
+
/** Bounded command/invocation audit identity; not part of reusable semantic inputs. */
|
|
125
|
+
commandFingerprint?: string;
|
|
126
|
+
/** Persistence-safe Process receipt. Accepted only when its Work/repo/check identity matches this verification. */
|
|
127
|
+
receipt?: VerificationRecord['receipt'];
|
|
88
128
|
/** When true, simulate infrastructure failure rather than acceptance fail. */
|
|
89
129
|
infrastructureFailed?: boolean;
|
|
90
130
|
/** When true and check is valid, record acceptance failure. */
|
|
@@ -133,13 +173,13 @@ function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
|
|
|
133
173
|
return 'medium';
|
|
134
174
|
}
|
|
135
175
|
|
|
136
|
-
function
|
|
176
|
+
function suggestedForWorkIdentity(workId: string, checks: string[], extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
137
177
|
const base: SuggestedNextAction[] = [
|
|
138
178
|
{
|
|
139
179
|
label: 'Continue workloop',
|
|
140
180
|
tool: 'rh_work',
|
|
141
181
|
operation: 'continue',
|
|
142
|
-
payload: { work_id:
|
|
182
|
+
payload: { work_id: workId },
|
|
143
183
|
risk: 'readonly',
|
|
144
184
|
confidence: 'high',
|
|
145
185
|
},
|
|
@@ -147,24 +187,28 @@ function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []
|
|
|
147
187
|
label: 'Verify registered checks',
|
|
148
188
|
tool: 'rh_work',
|
|
149
189
|
operation: 'verify',
|
|
150
|
-
payload: { work_id:
|
|
190
|
+
payload: { work_id: workId, check_id: checks[0] },
|
|
151
191
|
risk: 'workspace_write',
|
|
152
|
-
confidence:
|
|
192
|
+
confidence: checks[0] ? 'high' : 'low',
|
|
153
193
|
},
|
|
154
194
|
{
|
|
155
195
|
label: 'Finalize when ready',
|
|
156
196
|
tool: 'rh_work',
|
|
157
197
|
operation: 'finalize',
|
|
158
|
-
payload: { work_id:
|
|
198
|
+
payload: { work_id: workId },
|
|
159
199
|
risk: 'readonly',
|
|
160
200
|
confidence: 'medium',
|
|
161
201
|
},
|
|
162
202
|
];
|
|
163
203
|
return validateSuggestedNextActions([...extras, ...base], {
|
|
164
|
-
validCheckIds:
|
|
204
|
+
validCheckIds: checks,
|
|
165
205
|
}).actions;
|
|
166
206
|
}
|
|
167
207
|
|
|
208
|
+
function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
209
|
+
return suggestedForWorkIdentity(work.workId, work.checks, extras);
|
|
210
|
+
}
|
|
211
|
+
|
|
168
212
|
function initialEvidence(objective: string): EvidenceRef {
|
|
169
213
|
return {
|
|
170
214
|
title: 'work contract created',
|
|
@@ -183,14 +227,79 @@ interface WorkCompletionEvidenceEvaluation {
|
|
|
183
227
|
reasons: string[];
|
|
184
228
|
}
|
|
185
229
|
|
|
230
|
+
function verificationRecordAppliesToCurrentWorkspace(
|
|
231
|
+
record: VerificationRecord,
|
|
232
|
+
currentRevision?: string,
|
|
233
|
+
currentWorkspaceFingerprint?: string,
|
|
234
|
+
): boolean {
|
|
235
|
+
if (currentRevision && record.sourceRevision && record.sourceRevision !== currentRevision) return false;
|
|
236
|
+
if (
|
|
237
|
+
currentWorkspaceFingerprint
|
|
238
|
+
&& record.workspaceFingerprint
|
|
239
|
+
&& record.workspaceFingerprint !== currentWorkspaceFingerprint
|
|
240
|
+
) return false;
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isAuthoritativeCurrentWorkVerification(
|
|
245
|
+
work: WorkContract,
|
|
246
|
+
record: VerificationRecord,
|
|
247
|
+
currentRevision?: string,
|
|
248
|
+
): boolean {
|
|
249
|
+
const receipt = record.receipt;
|
|
250
|
+
return Boolean(
|
|
251
|
+
currentRevision
|
|
252
|
+
&& record.outcome === 'valid_pass'
|
|
253
|
+
&& record.sourceRevision === currentRevision
|
|
254
|
+
&& receipt
|
|
255
|
+
&& receipt.repoId === work.repoId
|
|
256
|
+
&& receipt.workId === work.workId
|
|
257
|
+
&& receipt.checkId === record.checkId
|
|
258
|
+
&& receipt.ok === true
|
|
259
|
+
&& receipt.timedOut === false
|
|
260
|
+
&& receipt.cancelled === false,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface WorkImplementationEvidenceEvaluation {
|
|
265
|
+
status: 'complete' | 'incomplete';
|
|
266
|
+
changedPaths: string[];
|
|
267
|
+
reasons: string[];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function evaluateWorkImplementationEvidence(
|
|
271
|
+
work: WorkContract,
|
|
272
|
+
currentWorkspaceChangedPaths: readonly string[] = [],
|
|
273
|
+
): WorkImplementationEvidenceEvaluation {
|
|
274
|
+
if (work.workKind !== 'repository_change') {
|
|
275
|
+
return { status: 'complete', changedPaths: [], reasons: [] };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const changedPaths = [...new Set(currentWorkspaceChangedPaths.map((path) => path.trim()).filter(Boolean))].sort();
|
|
279
|
+
if (changedPaths.length > 0) {
|
|
280
|
+
return { status: 'complete', changedPaths, reasons: [] };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
status: 'incomplete',
|
|
285
|
+
changedPaths: [],
|
|
286
|
+
reasons: ['Repository-change Work has no current net source changes relative to its base revision. Verification evidence cannot substitute for implementation evidence.'],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
186
290
|
function evaluateWorkCompletionEvidence(
|
|
187
291
|
work: WorkContract,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
),
|
|
292
|
+
currentRevision?: string,
|
|
293
|
+
currentWorkspaceFingerprint?: string,
|
|
191
294
|
): WorkCompletionEvidenceEvaluation {
|
|
295
|
+
const applicableCheckRefs = work.checkRefs.filter((record) =>
|
|
296
|
+
verificationRecordAppliesToCurrentWorkspace(record, currentRevision, currentWorkspaceFingerprint));
|
|
297
|
+
const history = reconcileVerificationHistory(
|
|
298
|
+
applicableCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
|
|
299
|
+
);
|
|
192
300
|
const missingChecks = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
193
|
-
const durableResultEvidence = work.evidenceRefs.some((evidence) => Boolean(evidence.evidenceId || evidence.artifactId))
|
|
301
|
+
const durableResultEvidence = work.evidenceRefs.some((evidence) => Boolean(evidence.evidenceId || evidence.artifactId))
|
|
302
|
+
|| applicableCheckRefs.some((record) => isAuthoritativeCurrentWorkVerification(work, record, currentRevision));
|
|
194
303
|
const reasons: string[] = [];
|
|
195
304
|
|
|
196
305
|
if (history.acceptanceFailures.length > 0) {
|
|
@@ -207,7 +316,12 @@ function evaluateWorkCompletionEvidence(
|
|
|
207
316
|
reasons.push(`Declared checks are missing valid_pass evidence: ${missingChecks.join(', ')}.`);
|
|
208
317
|
}
|
|
209
318
|
if (work.checks.length === 0 && !durableResultEvidence) {
|
|
210
|
-
|
|
319
|
+
const staleWorkVerification = work.checkRefs.some((record) =>
|
|
320
|
+
Boolean(record.receipt)
|
|
321
|
+
&& !verificationRecordAppliesToCurrentWorkspace(record, currentRevision, currentWorkspaceFingerprint));
|
|
322
|
+
reasons.push(staleWorkVerification
|
|
323
|
+
? 'Work-bound verification evidence is stale for the current source/workspace identity.'
|
|
324
|
+
: 'No durable result evidence (evidenceId, artifactId, or current Work-bound verification receipt) was recorded for this no-check WorkContract.');
|
|
211
325
|
}
|
|
212
326
|
|
|
213
327
|
const complete = history.infrastructureIssues.length === 0
|
|
@@ -244,6 +358,13 @@ export function routeWorkStart(
|
|
|
244
358
|
? false
|
|
245
359
|
: input.modeInput.requiresUserApproval === true || strategyConflictRequiresApproval,
|
|
246
360
|
};
|
|
361
|
+
if (effectiveModeInput.explicitMode === 'scale' && (!input.planId || !input.planStepId || !ctx.planStore)) {
|
|
362
|
+
return buildFacadeResult({
|
|
363
|
+
status: 'blocked',
|
|
364
|
+
summary: 'SCALE_PLAN_REQUIRED: explicit Scale execution requires a bound approved PlanContract step.',
|
|
365
|
+
data: { executionStarted: false, workContractCreated: false, planRequired: true, explicitMode: 'scale' },
|
|
366
|
+
});
|
|
367
|
+
}
|
|
247
368
|
const applyForcedMode = (selected: ReturnType<typeof selectExecutionMode>) => input.forceMode
|
|
248
369
|
? {
|
|
249
370
|
...selected,
|
|
@@ -280,11 +401,33 @@ export function routeWorkStart(
|
|
|
280
401
|
selectedMode = selectExecutionMode({ ...effectiveModeInput, approvalConfirmed: true });
|
|
281
402
|
policy = evaluateAccessPolicy(selectedMode);
|
|
282
403
|
}
|
|
283
|
-
|
|
404
|
+
let mode = applyForcedMode(selectedMode);
|
|
405
|
+
|
|
406
|
+
// Route Policy is the sole execution-depth authority. Existing Work is
|
|
407
|
+
// ownership context, not a reason to upgrade an otherwise Direct operation
|
|
408
|
+
// into Durable Work. Explicit Work/Plan ownership is resolved only after a
|
|
409
|
+
// durable route has already been selected; checkout writer conflicts affect
|
|
410
|
+
// placement, not semantic task identity.
|
|
284
411
|
|
|
285
412
|
if (mode.mode === 'direct_control') {
|
|
286
413
|
const available = ctx.availableChecks ?? [];
|
|
287
414
|
const normalized = normalizeCheckIds(input.checks ?? [], available);
|
|
415
|
+
// Explicit ownership may annotate a Direct operation, but it never changes
|
|
416
|
+
// the RouteDecision. The actual repository mutation can pass this Work id
|
|
417
|
+
// to repository_safe_patch_apply / repository_command_execute for evidence
|
|
418
|
+
// attribution while staying on the Direct/Process path.
|
|
419
|
+
const directOwnershipCandidates = input.relatedWorkId || (input.planId && input.planStepId)
|
|
420
|
+
? listWorkContracts({ ...ctx.workStore, status: 'active', limit: 100 })
|
|
421
|
+
.filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary')
|
|
422
|
+
: [];
|
|
423
|
+
const directPlan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
|
|
424
|
+
const directPlanStep = directPlan && input.planStepId ? directPlan.steps.find((step) => step.id === input.planStepId) : undefined;
|
|
425
|
+
const directOwner = (input.relatedWorkId
|
|
426
|
+
? directOwnershipCandidates.find((candidate) => candidate.workId === input.relatedWorkId)
|
|
427
|
+
: undefined)
|
|
428
|
+
?? (directPlanStep?.workId
|
|
429
|
+
? directOwnershipCandidates.find((candidate) => candidate.workId === directPlanStep.workId)
|
|
430
|
+
: undefined);
|
|
288
431
|
const suggested = validateSuggestedNextActions([
|
|
289
432
|
{
|
|
290
433
|
label: 'Apply bounded direct edit',
|
|
@@ -315,6 +458,13 @@ export function routeWorkStart(
|
|
|
315
458
|
directControlPreserved: true,
|
|
316
459
|
objective: input.objective.slice(0, 1_000),
|
|
317
460
|
normalizedChecks: normalized,
|
|
461
|
+
...(directOwner ? {
|
|
462
|
+
ownership: {
|
|
463
|
+
workId: directOwner.workId,
|
|
464
|
+
relation: input.workRelation ?? 'continue',
|
|
465
|
+
executionDepthPreserved: true,
|
|
466
|
+
},
|
|
467
|
+
} : {}),
|
|
318
468
|
},
|
|
319
469
|
warnings: [...policy.warnings, ...normalized.warnings],
|
|
320
470
|
suggestedNextActions: suggested,
|
|
@@ -435,66 +585,332 @@ export function startGoalWorkloop(
|
|
|
435
585
|
executionMode: 'direct_control' | 'goal_workloop' = 'goal_workloop',
|
|
436
586
|
routeDecision = selectExecutionMode({ ...input.modeInput, objective: input.objective, knownPaths: input.allowedPaths }).routeDecision,
|
|
437
587
|
): FacadeResult {
|
|
588
|
+
const hasStrongAdmissionBinding = Boolean(input.planId || input.planStepId || input.requirementId || input.relatedWorkId || input.workRelation);
|
|
589
|
+
if (hasStrongAdmissionBinding && !ctx.semanticAdmissionLocked) {
|
|
590
|
+
return withPrimaryWorkAdmissionLock(ctx.workStore, () => startGoalWorkloop(
|
|
591
|
+
{ ...ctx, semanticAdmissionLocked: true },
|
|
592
|
+
input,
|
|
593
|
+
policy,
|
|
594
|
+
executionMode,
|
|
595
|
+
routeDecision,
|
|
596
|
+
));
|
|
597
|
+
}
|
|
438
598
|
const at = nowIso(ctx);
|
|
439
599
|
const available = ctx.availableChecks ?? [];
|
|
440
|
-
const normalized = normalizeCheckIds(input.checks ?? [], available);
|
|
441
600
|
const workspaceMode = input.constraints?.workspaceMode ?? 'auto';
|
|
601
|
+
const activeWorks = listWorkContracts({ ...ctx.workStore, status: 'active', limit: 100 })
|
|
602
|
+
.filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary');
|
|
603
|
+
const workspaceOwner = ctx.checkoutId
|
|
604
|
+
? activeWorks.find((candidate) => candidate.checkoutId === ctx.checkoutId
|
|
605
|
+
&& candidate.worktreePolicy.required !== true
|
|
606
|
+
&& !candidate.worktreeRef)
|
|
607
|
+
: undefined;
|
|
608
|
+
const explicitRelatedWork = input.relatedWorkId
|
|
609
|
+
? activeWorks.find((candidate) => candidate.workId === input.relatedWorkId)
|
|
610
|
+
: undefined;
|
|
611
|
+
const plan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
|
|
612
|
+
const planStep = plan && input.planStepId ? plan.steps.find((step) => step.id === input.planStepId) : undefined;
|
|
613
|
+
const requestedRequirementId = input.requirementId?.trim() || undefined;
|
|
614
|
+
if (plan && requestedRequirementId && requestedRequirementId !== plan.requirementId) {
|
|
615
|
+
return buildFacadeResult({
|
|
616
|
+
status: 'blocked',
|
|
617
|
+
summary: `PLAN_REQUIREMENT_MISMATCH: ${plan.planId} is bound to ${plan.requirementId ?? 'no Requirement'}, not ${requestedRequirementId}. Use the Plan relationship authority or replan explicitly.`,
|
|
618
|
+
data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planRequirementId: plan.requirementId, requestedRequirementId },
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
const effectiveRequirementId = requestedRequirementId || plan?.requirementId;
|
|
622
|
+
if (
|
|
623
|
+
effectiveRequirementId
|
|
624
|
+
&& ctx.workStore.controllerHome
|
|
625
|
+
&& !readRequirement({ controllerHome: ctx.workStore.controllerHome }, effectiveRequirementId)
|
|
626
|
+
) {
|
|
627
|
+
return buildFacadeResult({
|
|
628
|
+
status: 'blocked',
|
|
629
|
+
summary: `${plan ? 'PLAN' : 'WORK'}_REQUIREMENT_NOT_FOUND: ${effectiveRequirementId}. Create or reconcile the Requirement authority before admitting Work.`,
|
|
630
|
+
data: { executionStarted: false, workContractCreated: false, requirementId: effectiveRequirementId, planId: plan?.planId },
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const sameStringSet = (provided: string[] | undefined, frozen: string[]): boolean => {
|
|
634
|
+
if (provided === undefined) return true;
|
|
635
|
+
const actual = [...new Set(provided)].sort();
|
|
636
|
+
const expected = [...new Set(frozen)].sort();
|
|
637
|
+
return actual.length === expected.length && actual.every((value, index) => value === expected[index]);
|
|
638
|
+
};
|
|
639
|
+
if (planStep) {
|
|
640
|
+
const mismatches = [
|
|
641
|
+
!sameStringSet(input.acceptanceCriteria, planStep.acceptanceCriteria) && 'acceptance_criteria',
|
|
642
|
+
!sameStringSet(input.allowedPaths, planStep.allowedPaths) && 'allowed_paths',
|
|
643
|
+
!sameStringSet(input.forbiddenPaths, planStep.forbiddenPaths) && 'forbidden_paths',
|
|
644
|
+
!sameStringSet(input.checks, planStep.checks) && 'check_ids',
|
|
645
|
+
].filter((value): value is string => Boolean(value));
|
|
646
|
+
if (mismatches.length > 0) {
|
|
647
|
+
return buildFacadeResult({
|
|
648
|
+
status: 'blocked',
|
|
649
|
+
summary: `PLAN_STEP_WORK_CONTRACT_MISMATCH: ${input.planId}/${input.planStepId} conflicts with frozen Plan step field(s): ${mismatches.join(', ')}. Replan instead of widening or narrowing Work admission.`,
|
|
650
|
+
data: { executionStarted: false, workContractCreated: false, planId: input.planId, planStepId: input.planStepId, mismatches },
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const effectiveAcceptanceCriteria = planStep?.acceptanceCriteria ?? input.acceptanceCriteria ?? [];
|
|
655
|
+
const effectiveAllowedPaths = planStep?.allowedPaths ?? input.allowedPaths ?? [];
|
|
656
|
+
const effectiveForbiddenPaths = planStep?.forbiddenPaths ?? input.forbiddenPaths ?? [];
|
|
657
|
+
const effectiveChecks = planStep?.checks ?? input.checks ?? [];
|
|
658
|
+
const normalized = normalizeCheckIds(effectiveChecks, available);
|
|
659
|
+
const boundPlanStepWorks = input.planId && input.planStepId
|
|
660
|
+
? activeWorks.filter((candidate) => candidate.planId === input.planId && candidate.planStepId === input.planStepId)
|
|
661
|
+
: [];
|
|
662
|
+
const explicitPlanStepWork = planStep?.workId ? activeWorks.find((candidate) => candidate.workId === planStep.workId) : undefined;
|
|
663
|
+
const planStepWork = explicitPlanStepWork ?? (boundPlanStepWorks.length === 1 ? boundPlanStepWorks[0] : undefined);
|
|
664
|
+
const requirementWorks = effectiveRequirementId
|
|
665
|
+
? activeWorks.filter((candidate) => candidate.requirementId === effectiveRequirementId)
|
|
666
|
+
: [];
|
|
667
|
+
const deterministicTarget = input.relatedWorkId
|
|
668
|
+
? explicitRelatedWork
|
|
669
|
+
: planStepWork ?? (requirementWorks.length === 1 ? requirementWorks[0] : undefined);
|
|
670
|
+
const requestedRelation = input.workRelation ?? (input.modeInput.requiresParallelism === true ? 'parallel' : undefined);
|
|
671
|
+
// Only strong semantic bindings participate in ownership resolution. An
|
|
672
|
+
// unrelated active Work or a checkout writer is a placement fact, not a
|
|
673
|
+
// semantic candidate for continue/extend/parallel/new_goal.
|
|
674
|
+
const candidateWorks = [...new Map(
|
|
675
|
+
[explicitRelatedWork, planStepWork, ...boundPlanStepWorks, ...requirementWorks]
|
|
676
|
+
.filter((candidate): candidate is WorkContract => Boolean(candidate))
|
|
677
|
+
.map((candidate) => [candidate.workId, candidate]),
|
|
678
|
+
).values()].slice(0, 8);
|
|
679
|
+
|
|
680
|
+
const resolutionRequired = (reason: string, target = deterministicTarget): FacadeResult => buildFacadeResult({
|
|
681
|
+
status: 'ok',
|
|
682
|
+
summary: reason,
|
|
683
|
+
data: {
|
|
684
|
+
executionStarted: false,
|
|
685
|
+
workContractCreated: false,
|
|
686
|
+
admissionDecision: 'resolution_required',
|
|
687
|
+
resolutionRequired: true,
|
|
688
|
+
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
689
|
+
...(target ? { recommendedWork: summarizeWorkContract(target) } : {}),
|
|
690
|
+
candidates: candidateWorks.map(summarizeWorkContract),
|
|
691
|
+
allowedRelations: ['continue', 'extend', 'parallel', 'new_goal'],
|
|
692
|
+
},
|
|
693
|
+
evidenceRefs: target?.evidenceRefs ?? [],
|
|
694
|
+
suggestedNextActions: target
|
|
695
|
+
? [{
|
|
696
|
+
label: 'Continue existing Work',
|
|
697
|
+
tool: 'rh_work',
|
|
698
|
+
operation: 'continue',
|
|
699
|
+
payload: { work_id: target.workId },
|
|
700
|
+
risk: 'readonly',
|
|
701
|
+
confidence: planStepWork || explicitRelatedWork ? 'high' : 'medium',
|
|
702
|
+
reason: 'Resolve intent before execution; do not create a second Work until the relationship is explicit.',
|
|
703
|
+
}]
|
|
704
|
+
: [],
|
|
705
|
+
rawAvailable: false,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
if (boundPlanStepWorks.length > 1) {
|
|
709
|
+
return resolutionRequired(`PLAN_STEP_MULTIPLE_PRIMARY_WORKS: ${input.planId}/${input.planStepId} has ${boundPlanStepWorks.length} active primary Work records and requires repair before execution.`);
|
|
710
|
+
}
|
|
711
|
+
if (planStep?.workId && !explicitPlanStepWork) {
|
|
712
|
+
return buildFacadeResult({
|
|
713
|
+
status: 'blocked',
|
|
714
|
+
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.`,
|
|
715
|
+
data: { executionStarted: false, workContractCreated: false, planId: input.planId, planStepId: input.planStepId, boundWorkId: planStep.workId, repairRequired: true },
|
|
716
|
+
suggestedNextActions: [{
|
|
717
|
+
label: 'Diagnose exact Plan step binding',
|
|
718
|
+
tool: 'rh_work',
|
|
719
|
+
operation: 'repair',
|
|
720
|
+
payload: { plan_id: input.planId, plan_step_id: input.planStepId, repair_operation: 'diagnose', dry_run: true },
|
|
721
|
+
risk: 'readonly',
|
|
722
|
+
confidence: 'high',
|
|
723
|
+
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.',
|
|
724
|
+
}],
|
|
725
|
+
rawAvailable: false,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if ((input.requestedBy ?? 'chatgpt') === 'scheduler') {
|
|
730
|
+
return resolutionRequired(
|
|
731
|
+
deterministicTarget
|
|
732
|
+
? `SCHEDULER_CONTINUATION_REQUIRED: scheduler-triggered execution must continue its bound Work ${deterministicTarget.workId}; it must not create a new durable Work.`
|
|
733
|
+
: 'SCHEDULER_WORK_BINDING_REQUIRED: scheduler-triggered execution must bind and wake an existing durable Work; it must not invent a new Work from a prompt.',
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (planStepWork && !requestedRelation) {
|
|
738
|
+
return buildFacadeResult({
|
|
739
|
+
status: 'ok',
|
|
740
|
+
summary: `PLAN_STEP_REUSES_ACTIVE_WORK: ${input.planId}/${input.planStepId} already executes as ${planStepWork.workId}; reuse that Work instead of creating another.`,
|
|
741
|
+
data: {
|
|
742
|
+
executionStarted: false,
|
|
743
|
+
workContractCreated: false,
|
|
744
|
+
admissionDecision: 'reuse_existing',
|
|
745
|
+
resolutionRequired: false,
|
|
746
|
+
work: summarizeWorkContract(planStepWork),
|
|
747
|
+
},
|
|
748
|
+
evidenceRefs: planStepWork.evidenceRefs,
|
|
749
|
+
suggestedNextActions: [{ label: 'Continue existing Work', tool: 'rh_work', operation: 'continue', payload: { work_id: planStepWork.workId }, risk: 'readonly', confidence: 'high' }],
|
|
750
|
+
rawAvailable: false,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (requestedRelation === 'continue' || requestedRelation === 'extend') {
|
|
755
|
+
if (!deterministicTarget) {
|
|
756
|
+
return resolutionRequired(`${requestedRelation.toUpperCase()}_TARGET_REQUIRED: select related_work_id or bind the request to an active Plan/Requirement before execution.`);
|
|
757
|
+
}
|
|
758
|
+
if (requestedRelation === 'extend' && deterministicTarget.planId) {
|
|
759
|
+
return resolutionRequired(
|
|
760
|
+
`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.`,
|
|
761
|
+
deterministicTarget,
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
const selected = requestedRelation === 'extend'
|
|
765
|
+
? updateWorkContract(ctx.workStore, deterministicTarget.workId, {
|
|
766
|
+
acceptanceCriteria: [...new Set([...deterministicTarget.acceptanceCriteria, ...(input.acceptanceCriteria ?? [])])].slice(0, 30),
|
|
767
|
+
allowedPaths: [...new Set([...deterministicTarget.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
768
|
+
forbiddenPaths: [...new Set([...deterministicTarget.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
769
|
+
checks: [...new Set([...deterministicTarget.checks, ...normalized.validCheckIds])].slice(0, 30),
|
|
770
|
+
scopeEvidence: {
|
|
771
|
+
initialLikelyPaths: [...new Set([
|
|
772
|
+
...(deterministicTarget.scopeEvidence?.initialLikelyPaths ?? deterministicTarget.allowedPaths),
|
|
773
|
+
...(input.initialLikelyPaths ?? input.allowedPaths ?? []),
|
|
774
|
+
])].slice(0, 100),
|
|
775
|
+
inspectedPaths: deterministicTarget.scopeEvidence?.inspectedPaths ?? [],
|
|
776
|
+
actualChangedPaths: deterministicTarget.scopeEvidence?.actualChangedPaths ?? [],
|
|
777
|
+
recordedAt: at,
|
|
778
|
+
},
|
|
779
|
+
scopeSummary: `Extended serially: ${input.objective}`.slice(0, 500),
|
|
780
|
+
continuationPrompt: `Continue work ${ctx.repoId}: ${deterministicTarget.objective.slice(0, 160)}. Additional requested scope: ${input.objective.slice(0, 500)}`,
|
|
781
|
+
})
|
|
782
|
+
: deterministicTarget;
|
|
783
|
+
return buildFacadeResult({
|
|
784
|
+
status: 'ok',
|
|
785
|
+
summary: `${requestedRelation === 'extend' ? 'Existing Work extended' : 'Existing Work selected'}: ${selected.workId}. No new WorkContract was created.`,
|
|
786
|
+
data: {
|
|
787
|
+
executionStarted: false,
|
|
788
|
+
workContractCreated: false,
|
|
789
|
+
admissionDecision: requestedRelation === 'extend' ? 'extend_existing' : 'reuse_existing',
|
|
790
|
+
resolutionRequired: false,
|
|
791
|
+
work: summarizeWorkContract(selected),
|
|
792
|
+
},
|
|
793
|
+
evidenceRefs: selected.evidenceRefs,
|
|
794
|
+
suggestedNextActions: [{ label: 'Continue existing Work', tool: 'rh_work', operation: 'continue', payload: { work_id: selected.workId }, risk: 'readonly', confidence: 'high' }],
|
|
795
|
+
rawAvailable: false,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (!requestedRelation && candidateWorks.length > 0) {
|
|
800
|
+
return resolutionRequired(
|
|
801
|
+
`WORK_ADMISSION_RESOLUTION_REQUIRED: ${candidateWorks.length} active Work candidate(s) exist. Classify this request as continue, extend, parallel, or new_goal before any durable Work is created.`,
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const placementConflict = Boolean(workspaceOwner);
|
|
442
806
|
const needsWorktree = executionMode === 'goal_workloop' && (input.constraints?.requireWorktree
|
|
443
807
|
?? (workspaceMode === 'isolated'
|
|
808
|
+
|| placementConflict
|
|
809
|
+
|| input.modeInput.requiresParallelism === true
|
|
444
810
|
|| (workspaceMode === 'auto' && routeDecision.requiresIsolation === true)));
|
|
811
|
+
if (!needsWorktree && workspaceOwner) {
|
|
812
|
+
return buildFacadeResult({
|
|
813
|
+
status: 'blocked',
|
|
814
|
+
summary: `WORKSPACE_OWNERSHIP_INVARIANT: unresolved admission reached checkout ${ctx.checkoutId} while active Work ${workspaceOwner.workId} owns it. This is an internal routing invariant, not a normal fallback path.`,
|
|
815
|
+
data: {
|
|
816
|
+
executionStarted: false,
|
|
817
|
+
workContractCreated: false,
|
|
818
|
+
conflictType: 'workspace_single_writer_invariant',
|
|
819
|
+
existingWork: summarizeWorkContract(workspaceOwner),
|
|
820
|
+
},
|
|
821
|
+
evidenceRefs: workspaceOwner.evidenceRefs,
|
|
822
|
+
rawAvailable: false,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
445
825
|
const generatedWorkId = workIdFor(input.objective);
|
|
446
826
|
if (input.planId || input.planStepId) {
|
|
447
|
-
if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision) {
|
|
448
|
-
return buildFacadeResult({ status: 'blocked', summary: 'PLAN_CONTEXT_REQUIRED: plan_id, plan_step_id and a current source revision are required.', data: { executionStarted: false } });
|
|
827
|
+
if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision || !plan || !planStep) {
|
|
828
|
+
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 } });
|
|
449
829
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
830
|
+
if (plan.status !== 'approved' && plan.status !== 'executing') {
|
|
831
|
+
return buildFacadeResult({ status: 'blocked', summary: `PLAN_NOT_EXECUTABLE: ${plan.planId} is ${plan.status}`, data: { executionStarted: false, planId: plan.planId } });
|
|
832
|
+
}
|
|
833
|
+
if (plan.sourceRevision !== ctx.sourceRevision) {
|
|
834
|
+
const invalidated = claimPlanStepForWork(ctx.planStore, {
|
|
453
835
|
planId: input.planId,
|
|
454
836
|
stepId: input.planStepId,
|
|
455
837
|
workId: generatedWorkId,
|
|
456
838
|
sourceRevision: ctx.sourceRevision,
|
|
457
839
|
});
|
|
458
|
-
} catch (error) {
|
|
459
840
|
return buildFacadeResult({
|
|
460
841
|
status: 'blocked',
|
|
461
|
-
summary:
|
|
462
|
-
data: { planId:
|
|
842
|
+
summary: `PLAN_SOURCE_DRIFT: ${invalidated.planId} was invalidated because its source revision no longer matches. Replan before execution.`,
|
|
843
|
+
data: { planId: invalidated.planId, executionStarted: false, workContractCreated: false, replanRequired: true },
|
|
463
844
|
});
|
|
464
845
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
});
|
|
846
|
+
const unresolved = planStep.dependencies.filter((dependency) => plan.steps.find((candidate) => candidate.id === dependency)?.status !== 'completed');
|
|
847
|
+
if (unresolved.length > 0) {
|
|
848
|
+
return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_DEPENDENCIES_PENDING: ${unresolved.join(', ')}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
|
|
849
|
+
}
|
|
850
|
+
if (planStep.status === 'completed') {
|
|
851
|
+
return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_ALREADY_COMPLETED: ${planStep.id}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
|
|
471
852
|
}
|
|
472
853
|
}
|
|
854
|
+
const initialSuggestedNextActions = suggestedForWorkIdentity(
|
|
855
|
+
generatedWorkId,
|
|
856
|
+
normalized.validCheckIds,
|
|
857
|
+
normalized.suggestedNextActions,
|
|
858
|
+
);
|
|
859
|
+
// allowed_paths and initial_likely_paths are policy/discovery fences, not proof
|
|
860
|
+
// that this Work owns a repository mutation. External-effect Work therefore
|
|
861
|
+
// needs either an explicit repository_change semantic choice or concrete
|
|
862
|
+
// expected source-change evidence before Forge treats it as repository_change.
|
|
863
|
+
const repositoryChangeIntent = input.workKind === 'repository_change'
|
|
864
|
+
|| (input.modeInput.expectedFiles ?? 0) > 0
|
|
865
|
+
|| (input.modeInput.expectedChangedLines ?? 0) > 0;
|
|
866
|
+
const resolvedWorkKind = input.workKind ?? (
|
|
867
|
+
input.modeInput.requiresExternalEffect === true
|
|
868
|
+
&& !repositoryChangeIntent
|
|
869
|
+
? (input.modeInput.remoteWrite === true ? 'remote_effect' : 'local_effect')
|
|
870
|
+
: 'repository_change'
|
|
871
|
+
);
|
|
872
|
+
const remoteDeliveryRequired = resolvedWorkKind === 'repository_change'
|
|
873
|
+
&& input.modeInput.requiresExternalEffect === true
|
|
874
|
+
&& input.modeInput.remoteWrite === true;
|
|
473
875
|
const work = createWorkContract(ctx.workStore, {
|
|
474
876
|
workId: generatedWorkId,
|
|
475
877
|
repoId: ctx.repoId,
|
|
476
|
-
checkoutId: ctx.checkoutId,
|
|
878
|
+
checkoutId: needsWorktree ? undefined : ctx.checkoutId,
|
|
477
879
|
principalId: ctx.principalId,
|
|
478
880
|
controllerInstanceId: ctx.controllerInstanceId,
|
|
479
881
|
baseRevision: ctx.sourceRevision,
|
|
480
|
-
workspaceFingerprint: ctx.workspaceFingerprint,
|
|
882
|
+
workspaceFingerprint: needsWorktree ? undefined : ctx.workspaceFingerprint,
|
|
481
883
|
routeDecisionFingerprint: routeDecision.inputFingerprint,
|
|
482
884
|
routeDecision,
|
|
483
885
|
mode: executionMode,
|
|
484
886
|
objective: input.objective,
|
|
485
|
-
acceptanceCriteria:
|
|
486
|
-
constraints:
|
|
887
|
+
acceptanceCriteria: effectiveAcceptanceCriteria,
|
|
888
|
+
constraints: {
|
|
889
|
+
...(input.constraints ?? { requireHandoffOnAmbiguity: true }),
|
|
890
|
+
...(remoteDeliveryRequired ? { remoteDeliveryRequired: true } : {}),
|
|
891
|
+
},
|
|
487
892
|
risk: workRiskFor(input),
|
|
893
|
+
// Remote delivery is a risk/effect dimension, not a reason to erase a
|
|
894
|
+
// repository-change Work's semantic identity. Pure external actions with no
|
|
895
|
+
// repository-change signal remain remote_effect and keep plugin receipt semantics.
|
|
896
|
+
workKind: resolvedWorkKind,
|
|
488
897
|
status: 'running',
|
|
489
898
|
phase: 'implementation',
|
|
490
899
|
issueId: input.issueId,
|
|
491
900
|
taskId: input.taskId,
|
|
901
|
+
requirementId: effectiveRequirementId,
|
|
492
902
|
planId: input.planId,
|
|
493
903
|
planStepId: input.planStepId,
|
|
494
904
|
planSourceRevision: input.planId ? ctx.sourceRevision : undefined,
|
|
495
905
|
scopeSummary: input.modeInput.scopeClear ? 'scope declared at start' : 'scope incomplete',
|
|
496
|
-
|
|
497
|
-
|
|
906
|
+
scopeEvidence: {
|
|
907
|
+
initialLikelyPaths: [...new Set(input.initialLikelyPaths ?? effectiveAllowedPaths)].slice(0, 100),
|
|
908
|
+
inspectedPaths: [],
|
|
909
|
+
actualChangedPaths: [],
|
|
910
|
+
recordedAt: at,
|
|
911
|
+
},
|
|
912
|
+
allowedPaths: effectiveAllowedPaths,
|
|
913
|
+
forbiddenPaths: effectiveForbiddenPaths,
|
|
498
914
|
checks: normalized.validCheckIds,
|
|
499
915
|
driver: {
|
|
500
916
|
preferred: executionMode === 'direct_control'
|
|
@@ -509,6 +925,7 @@ export function startGoalWorkloop(
|
|
|
509
925
|
? 'Isolation was explicitly requested or required for parallel execution.'
|
|
510
926
|
: 'Current workspace is the stability-first default; isolation remains opt-in.',
|
|
511
927
|
},
|
|
928
|
+
worktreeRef: undefined,
|
|
512
929
|
evidencePolicy: {
|
|
513
930
|
defaultDetailLevel: 'summary',
|
|
514
931
|
allowRawOptIn: true,
|
|
@@ -527,21 +944,39 @@ export function startGoalWorkloop(
|
|
|
527
944
|
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
528
945
|
evidenceRefs: [initialEvidence(input.objective)],
|
|
529
946
|
policyDecisions: policy ? [policy] : [],
|
|
530
|
-
suggestedNextActions:
|
|
947
|
+
suggestedNextActions: initialSuggestedNextActions,
|
|
531
948
|
continuationPrompt: `Continue work ${ctx.repoId}: ${input.objective.slice(0, 200)}`,
|
|
532
949
|
});
|
|
533
950
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
951
|
+
if (input.planId && input.planStepId && ctx.planStore && ctx.sourceRevision) {
|
|
952
|
+
try {
|
|
953
|
+
const claimed = claimPlanStepForWork(ctx.planStore, {
|
|
954
|
+
planId: input.planId,
|
|
955
|
+
stepId: input.planStepId,
|
|
956
|
+
workId: work.workId,
|
|
957
|
+
sourceRevision: ctx.sourceRevision,
|
|
958
|
+
});
|
|
959
|
+
if (claimed.status === 'invalidated_by_drift') {
|
|
960
|
+
return buildFacadeResult({
|
|
961
|
+
status: 'blocked',
|
|
962
|
+
summary: `PLAN_SOURCE_DRIFT: ${claimed.planId} was invalidated because its source revision no longer matches. Repair or replan before execution.`,
|
|
963
|
+
data: { executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), planId: claimed.planId, canonicalWorkRetained: true, replanRequired: true },
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
} catch (error) {
|
|
967
|
+
return buildFacadeResult({
|
|
968
|
+
status: 'blocked',
|
|
969
|
+
summary: error instanceof Error ? error.message : 'PLAN_STEP_CLAIM_FAILED',
|
|
970
|
+
data: { planId: input.planId, planStepId: input.planStepId, executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), canonicalWorkRetained: true },
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
539
974
|
|
|
540
975
|
return buildFacadeResult({
|
|
541
976
|
status: 'ok',
|
|
542
977
|
summary: executionMode === 'direct_control'
|
|
543
|
-
? `Direct-control Work lineage started as ${
|
|
544
|
-
: `Goal workloop started as ${
|
|
978
|
+
? `Direct-control Work lineage started as ${work.workId}.`
|
|
979
|
+
: `Goal workloop started as ${work.workId}.`,
|
|
545
980
|
data: {
|
|
546
981
|
mode: {
|
|
547
982
|
mode: executionMode,
|
|
@@ -555,20 +990,20 @@ export function startGoalWorkloop(
|
|
|
555
990
|
routeDecision,
|
|
556
991
|
},
|
|
557
992
|
workContractCreated: true,
|
|
558
|
-
work: summarizeWorkContract(
|
|
559
|
-
worktreeRequired:
|
|
993
|
+
work: summarizeWorkContract(work),
|
|
994
|
+
worktreeRequired: work.worktreePolicy.required,
|
|
560
995
|
normalizedChecks: normalized,
|
|
561
996
|
policy,
|
|
562
997
|
},
|
|
563
|
-
evidenceRefs:
|
|
998
|
+
evidenceRefs: work.evidenceRefs,
|
|
564
999
|
warnings: normalized.warnings,
|
|
565
|
-
suggestedNextActions:
|
|
1000
|
+
suggestedNextActions: initialSuggestedNextActions,
|
|
566
1001
|
rawAvailable: false,
|
|
567
1002
|
});
|
|
568
1003
|
}
|
|
569
1004
|
|
|
570
1005
|
export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopContinueInput): FacadeResult {
|
|
571
|
-
|
|
1006
|
+
let work = getWorkContract(ctx.workStore, input.workId);
|
|
572
1007
|
if (!work) {
|
|
573
1008
|
return buildFacadeResult({
|
|
574
1009
|
status: 'not_found',
|
|
@@ -595,78 +1030,223 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
595
1030
|
});
|
|
596
1031
|
}
|
|
597
1032
|
|
|
1033
|
+
const explicitPolicyScope = input.allowedPaths !== undefined
|
|
1034
|
+
|| input.forbiddenPaths !== undefined
|
|
1035
|
+
|| input.checks !== undefined;
|
|
1036
|
+
if (explicitPolicyScope) {
|
|
1037
|
+
if (work.planId) {
|
|
1038
|
+
return buildFacadeResult({
|
|
1039
|
+
status: 'blocked',
|
|
1040
|
+
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.`,
|
|
1041
|
+
data: { work: summarizeWorkContract(work), planId: work.planId, policyScopeUpdated: false },
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
const normalizedChecks = normalizeCheckIds(input.checks ?? [], ctx.availableChecks ?? []);
|
|
1045
|
+
if (normalizedChecks.invalidCheckIds.length > 0) {
|
|
1046
|
+
return buildFacadeResult({
|
|
1047
|
+
status: 'blocked',
|
|
1048
|
+
summary: `WORK_CHECKS_INVALID: ${normalizedChecks.invalidCheckIds.join(', ')}. Work scope was not updated.`,
|
|
1049
|
+
data: { work: summarizeWorkContract(work), policyScopeUpdated: false, normalizedChecks },
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
work = updateWorkContract(ctx.workStore, work.workId, {
|
|
1053
|
+
allowedPaths: [...new Set([...work.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
1054
|
+
forbiddenPaths: [...new Set([...work.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
1055
|
+
checks: [...new Set([...work.checks, ...normalizedChecks.validCheckIds])].slice(0, 30),
|
|
1056
|
+
scopeEvidence: {
|
|
1057
|
+
initialLikelyPaths: [...new Set([
|
|
1058
|
+
...(work.scopeEvidence?.initialLikelyPaths ?? work.allowedPaths),
|
|
1059
|
+
...(input.allowedPaths ?? []),
|
|
1060
|
+
])].slice(0, 100),
|
|
1061
|
+
inspectedPaths: work.scopeEvidence?.inspectedPaths ?? [],
|
|
1062
|
+
actualChangedPaths: work.scopeEvidence?.actualChangedPaths ?? [],
|
|
1063
|
+
recordedAt: nowIso(ctx),
|
|
1064
|
+
},
|
|
1065
|
+
scopeSummary: `Continue adopted explicit policy scope for ${work.workId}.`.slice(0, 500),
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if ((input.additionalLikelyPaths?.length ?? 0) > 0 || (input.inspectedPaths?.length ?? 0) > 0) {
|
|
1070
|
+
work = recordWorkScopeEvidence(ctx.workStore, work.workId, {
|
|
1071
|
+
initialLikelyPaths: input.additionalLikelyPaths,
|
|
1072
|
+
inspectedPaths: input.inspectedPaths,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const currentCheckRefs = work.checkRefs.filter((record) =>
|
|
1077
|
+
verificationRecordAppliesToCurrentWorkspace(record, ctx.sourceRevision, ctx.workspaceFingerprint));
|
|
598
1078
|
const history = reconcileVerificationHistory(
|
|
599
|
-
|
|
1079
|
+
currentCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
|
|
600
1080
|
);
|
|
601
1081
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
severity: 'needs_review',
|
|
611
|
-
creationReason: 'ambiguous_outcome',
|
|
612
|
-
reason: `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`,
|
|
613
|
-
summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
|
|
614
|
-
currentState: {
|
|
615
|
-
repoId: ctx.repoId,
|
|
616
|
-
workId: work.workId,
|
|
617
|
-
mode: work.mode,
|
|
618
|
-
statusSummary: 'waiting_for_review after acceptance failure',
|
|
619
|
-
checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
|
|
620
|
-
workSemantics: continuation.semantics,
|
|
621
|
-
reconciliationRequired: continuation.reconciliationRequired,
|
|
622
|
-
nextSafeAction: continuation.nextSafeAction,
|
|
623
|
-
},
|
|
624
|
-
attemptedActions: ['continue'],
|
|
625
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
626
|
-
blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
|
|
627
|
-
recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
|
|
628
|
-
recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
|
|
629
|
-
recommendedContinuationPrompt: continuation.continuationPrompt,
|
|
630
|
-
suggestedNextActions: [
|
|
631
|
-
{
|
|
632
|
-
label: 'Read work context',
|
|
633
|
-
tool: 'rh_context',
|
|
634
|
-
operation: 'get',
|
|
635
|
-
payload: { work_id: work.workId },
|
|
636
|
-
risk: 'readonly',
|
|
637
|
-
},
|
|
638
|
-
],
|
|
1082
|
+
const explicitAcceptanceRepair = history.acceptanceFailures.length > 0
|
|
1083
|
+
&& (input.acceptanceFailureDecision === 'repair' || input.acceptanceFailureDecision === 'rescope');
|
|
1084
|
+
if (explicitAcceptanceRepair) {
|
|
1085
|
+
const decision = input.acceptanceFailureDecision!;
|
|
1086
|
+
work = appendWorkEvidence(ctx.workStore, work.workId, {
|
|
1087
|
+
title: 'Controller acceptance-failure continuation decision',
|
|
1088
|
+
summary: `Controller explicitly chose bounded ${decision} after deterministic acceptance failure: ${history.acceptanceFailures.join(', ')}.`,
|
|
1089
|
+
detailLevel: 'summary',
|
|
639
1090
|
});
|
|
640
1091
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
641
|
-
status: '
|
|
642
|
-
phase: '
|
|
643
|
-
state: '
|
|
644
|
-
summary: `
|
|
1092
|
+
status: 'running',
|
|
1093
|
+
phase: 'implementation',
|
|
1094
|
+
state: 'active',
|
|
1095
|
+
summary: `Controller chose bounded ${decision} after acceptance failure; implementation may resume before re-verification.`,
|
|
645
1096
|
evidenceRefs: work.evidenceRefs,
|
|
646
1097
|
});
|
|
647
|
-
|
|
648
|
-
|
|
1098
|
+
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
649
1099
|
return buildFacadeResult({
|
|
650
|
-
status: '
|
|
651
|
-
summary: `Continue
|
|
1100
|
+
status: 'ok',
|
|
1101
|
+
summary: `Continue accepted explicit bounded ${decision} decision after acceptance failure; implementation may resume.`,
|
|
652
1102
|
data: {
|
|
653
|
-
work: summarizeWorkContract(
|
|
654
|
-
handoffId: handoff.id,
|
|
1103
|
+
work: summarizeWorkContract(work),
|
|
655
1104
|
acceptanceFailures: history.acceptanceFailures,
|
|
656
1105
|
infrastructureIssues: history.infrastructureIssues,
|
|
657
1106
|
backgroundCompleted: false,
|
|
1107
|
+
nextStep: 'execute',
|
|
1108
|
+
acceptanceFailureDecision: decision,
|
|
658
1109
|
},
|
|
659
1110
|
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
660
|
-
suggestedNextActions:
|
|
661
|
-
|
|
1111
|
+
suggestedNextActions: suggestedForWork(work),
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Ambiguous acceptance failure without an explicit bounded Controller decision →
|
|
1116
|
+
// ask once per distinct failure evidence. Repeated continue calls must not
|
|
1117
|
+
// manufacture an unbounded handoff loop. A resolved handoff remains the decision
|
|
1118
|
+
// authority until newer valid-fail evidence; an unresolved matching handoff is reused.
|
|
1119
|
+
if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair && work.recoveryPolicy.handoffOnAmbiguity) {
|
|
1120
|
+
const failureReason = `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`;
|
|
1121
|
+
const latestFailureAt = work.checkRefs
|
|
1122
|
+
.filter((record) => record.outcome === 'valid_fail' && history.acceptanceFailures.includes(record.checkId))
|
|
1123
|
+
.map((record) => record.recordedAt)
|
|
1124
|
+
.sort((left, right) => right.localeCompare(left))[0];
|
|
1125
|
+
const matchingHandoffs = listHandoffItems({ ...ctx.handoffStore, status: 'all', limit: 100 })
|
|
1126
|
+
.filter((item) => (
|
|
1127
|
+
item.workId === input.workId
|
|
1128
|
+
&& item.creationReason === 'ambiguous_outcome'
|
|
1129
|
+
&& item.title === 'Acceptance failure needs review'
|
|
1130
|
+
&& item.reason === failureReason
|
|
1131
|
+
&& (!latestFailureAt || item.createdAt >= latestFailureAt)
|
|
1132
|
+
));
|
|
1133
|
+
const activeHandoff = matchingHandoffs.find((item) => item.status === 'pending' || item.status === 'acknowledged');
|
|
1134
|
+
if (activeHandoff) {
|
|
1135
|
+
return buildFacadeResult({
|
|
1136
|
+
status: 'blocked',
|
|
1137
|
+
summary: `Continue remains paused for ChatGPT review through existing handoff ${activeHandoff.id}.`,
|
|
1138
|
+
data: {
|
|
1139
|
+
work: summarizeWorkContract(work),
|
|
1140
|
+
handoffId: activeHandoff.id,
|
|
1141
|
+
acceptanceFailures: history.acceptanceFailures,
|
|
1142
|
+
infrastructureIssues: history.infrastructureIssues,
|
|
1143
|
+
backgroundCompleted: false,
|
|
1144
|
+
},
|
|
1145
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1146
|
+
suggestedNextActions: [{
|
|
662
1147
|
label: 'Get handoff',
|
|
663
1148
|
tool: 'rh_inbox',
|
|
664
1149
|
operation: 'get',
|
|
665
|
-
payload: { handoff_id:
|
|
1150
|
+
payload: { handoff_id: activeHandoff.id },
|
|
666
1151
|
risk: 'readonly',
|
|
667
1152
|
confidence: 'high',
|
|
1153
|
+
}],
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const resolvedHandoff = matchingHandoffs.find((item) => item.status === 'resolved');
|
|
1158
|
+
if (!resolvedHandoff) {
|
|
1159
|
+
const continuation = buildWorkContinuationSnapshot(work);
|
|
1160
|
+
const handoff = createHandoffItem(ctx.handoffStore, {
|
|
1161
|
+
id: handoffIdFor('continue'),
|
|
1162
|
+
repoId: ctx.repoId,
|
|
1163
|
+
workId: work.workId,
|
|
1164
|
+
title: 'Acceptance failure needs review',
|
|
1165
|
+
severity: 'needs_review',
|
|
1166
|
+
creationReason: 'ambiguous_outcome',
|
|
1167
|
+
reason: failureReason,
|
|
1168
|
+
summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
|
|
1169
|
+
currentState: {
|
|
1170
|
+
repoId: ctx.repoId,
|
|
1171
|
+
workId: work.workId,
|
|
1172
|
+
mode: work.mode,
|
|
1173
|
+
statusSummary: 'waiting_for_review after acceptance failure',
|
|
1174
|
+
checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
|
|
1175
|
+
workSemantics: continuation.semantics,
|
|
1176
|
+
reconciliationRequired: continuation.reconciliationRequired,
|
|
1177
|
+
nextSafeAction: continuation.nextSafeAction,
|
|
668
1178
|
},
|
|
669
|
-
|
|
1179
|
+
attemptedActions: ['continue'],
|
|
1180
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1181
|
+
blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
|
|
1182
|
+
recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
|
|
1183
|
+
recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
|
|
1184
|
+
recommendedContinuationPrompt: continuation.continuationPrompt,
|
|
1185
|
+
suggestedNextActions: [
|
|
1186
|
+
{
|
|
1187
|
+
label: 'Read work context',
|
|
1188
|
+
tool: 'rh_context',
|
|
1189
|
+
operation: 'get',
|
|
1190
|
+
payload: { work_id: work.workId },
|
|
1191
|
+
risk: 'readonly',
|
|
1192
|
+
},
|
|
1193
|
+
],
|
|
1194
|
+
});
|
|
1195
|
+
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1196
|
+
status: 'ready',
|
|
1197
|
+
phase: 'verification',
|
|
1198
|
+
state: 'blocked',
|
|
1199
|
+
summary: `Acceptance failure requires review through handoff ${handoff.id}.`,
|
|
1200
|
+
evidenceRefs: work.evidenceRefs,
|
|
1201
|
+
});
|
|
1202
|
+
const updated = appendWorkHandoffRef(ctx.workStore, work.workId, handoff.id);
|
|
1203
|
+
|
|
1204
|
+
return buildFacadeResult({
|
|
1205
|
+
status: 'blocked',
|
|
1206
|
+
summary: `Continue paused for ChatGPT review; handoff ${handoff.id} created. No background execution pretended.`,
|
|
1207
|
+
data: {
|
|
1208
|
+
work: summarizeWorkContract(updated),
|
|
1209
|
+
handoffId: handoff.id,
|
|
1210
|
+
acceptanceFailures: history.acceptanceFailures,
|
|
1211
|
+
infrastructureIssues: history.infrastructureIssues,
|
|
1212
|
+
backgroundCompleted: false,
|
|
1213
|
+
},
|
|
1214
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1215
|
+
suggestedNextActions: [
|
|
1216
|
+
{
|
|
1217
|
+
label: 'Get handoff',
|
|
1218
|
+
tool: 'rh_inbox',
|
|
1219
|
+
operation: 'get',
|
|
1220
|
+
payload: { handoff_id: handoff.id },
|
|
1221
|
+
risk: 'readonly',
|
|
1222
|
+
confidence: 'high',
|
|
1223
|
+
},
|
|
1224
|
+
],
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1229
|
+
status: 'running',
|
|
1230
|
+
phase: 'verification',
|
|
1231
|
+
state: 'active',
|
|
1232
|
+
summary: `Resolved acceptance-failure handoff ${resolvedHandoff.id} authorizes bounded continuation to re-verification.`,
|
|
1233
|
+
evidenceRefs: work.evidenceRefs,
|
|
1234
|
+
});
|
|
1235
|
+
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
1236
|
+
return buildFacadeResult({
|
|
1237
|
+
status: 'ok',
|
|
1238
|
+
summary: `Continue accepted resolved acceptance-failure handoff ${resolvedHandoff.id}; re-verification may resume.`,
|
|
1239
|
+
data: {
|
|
1240
|
+
work: summarizeWorkContract(work),
|
|
1241
|
+
handoffId: resolvedHandoff.id,
|
|
1242
|
+
acceptanceFailures: history.acceptanceFailures,
|
|
1243
|
+
infrastructureIssues: history.infrastructureIssues,
|
|
1244
|
+
backgroundCompleted: false,
|
|
1245
|
+
nextStep: 'verify',
|
|
1246
|
+
remainingChecks: history.acceptanceFailures,
|
|
1247
|
+
},
|
|
1248
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1249
|
+
suggestedNextActions: suggestedForWork(work),
|
|
670
1250
|
});
|
|
671
1251
|
}
|
|
672
1252
|
|
|
@@ -708,6 +1288,47 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
708
1288
|
});
|
|
709
1289
|
}
|
|
710
1290
|
|
|
1291
|
+
const implementationEvidence = evaluateWorkImplementationEvidence(work, ctx.workspaceChangedPaths);
|
|
1292
|
+
if (implementationEvidence.status !== 'complete') {
|
|
1293
|
+
const suggested = validateSuggestedNextActions([
|
|
1294
|
+
{
|
|
1295
|
+
label: 'Implement the repository change before verification',
|
|
1296
|
+
tool: 'rh_context',
|
|
1297
|
+
operation: 'get',
|
|
1298
|
+
payload: { work_id: work.workId },
|
|
1299
|
+
risk: 'readonly',
|
|
1300
|
+
confidence: 'high',
|
|
1301
|
+
reason: 'Verification proves behavior only after the repository-change Work has current source changes.',
|
|
1302
|
+
},
|
|
1303
|
+
]).actions;
|
|
1304
|
+
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1305
|
+
status: 'running',
|
|
1306
|
+
phase: 'implementation',
|
|
1307
|
+
state: 'active',
|
|
1308
|
+
summary: implementationEvidence.reasons.join(' '),
|
|
1309
|
+
evidenceRefs: work.evidenceRefs,
|
|
1310
|
+
});
|
|
1311
|
+
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
1312
|
+
return buildFacadeResult({
|
|
1313
|
+
status: 'blocked',
|
|
1314
|
+
summary: `Continue requires implementation before verification. ${implementationEvidence.reasons.join(' ')}`,
|
|
1315
|
+
data: {
|
|
1316
|
+
work: summarizeWorkContract(updated),
|
|
1317
|
+
backgroundCompleted: false,
|
|
1318
|
+
nextStep: 'execute',
|
|
1319
|
+
implementationEvidencePresent: false,
|
|
1320
|
+
workspaceChangedPaths: implementationEvidence.changedPaths,
|
|
1321
|
+
},
|
|
1322
|
+
suggestedNextActions: suggested,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
if (implementationEvidence.changedPaths.length > 0) {
|
|
1327
|
+
recordWorkScopeEvidence(ctx.workStore, work.workId, {
|
|
1328
|
+
actualChangedPaths: implementationEvidence.changedPaths,
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
|
|
711
1332
|
if (work.checks.length > 0 && history.validPasses.length < work.checks.length) {
|
|
712
1333
|
const remaining = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
713
1334
|
const suggested = validateSuggestedNextActions(
|
|
@@ -747,7 +1368,7 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
747
1368
|
});
|
|
748
1369
|
}
|
|
749
1370
|
|
|
750
|
-
const completionEvidence = evaluateWorkCompletionEvidence(work,
|
|
1371
|
+
const completionEvidence = evaluateWorkCompletionEvidence(work, ctx.sourceRevision, ctx.workspaceFingerprint);
|
|
751
1372
|
if (completionEvidence.status !== 'complete') {
|
|
752
1373
|
const suggested = validateSuggestedNextActions([
|
|
753
1374
|
{
|
|
@@ -839,16 +1460,60 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
839
1460
|
checkFailed: input.checkFailed,
|
|
840
1461
|
skipped: input.skipped,
|
|
841
1462
|
});
|
|
1463
|
+
if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
|
|
1464
|
+
const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
|
|
1465
|
+
const existing = [...work.checkRefs].reverse().find((record) => record.checkId === resolvedCheckId);
|
|
1466
|
+
const completed = work.status === 'completed';
|
|
1467
|
+
return buildFacadeResult({
|
|
1468
|
+
status: completed ? 'ok' : 'blocked',
|
|
1469
|
+
summary: `WorkContract ${work.workId} is terminal (${work.status}); verification was not re-executed.`,
|
|
1470
|
+
data: {
|
|
1471
|
+
work: summarizeWorkContract(work),
|
|
1472
|
+
verification: {
|
|
1473
|
+
checkId: resolvedCheckId,
|
|
1474
|
+
...(existing ? { outcome: existing.outcome } : {}),
|
|
1475
|
+
terminal: true,
|
|
1476
|
+
idempotent: true,
|
|
1477
|
+
reexecuted: false,
|
|
1478
|
+
isAcceptanceFailure: existing?.outcome === 'valid_fail',
|
|
1479
|
+
isInfrastructureIssue: existing?.outcome === 'invalid_check_id' || existing?.outcome === 'infrastructure_failure',
|
|
1480
|
+
doesNotRequestTaskChanges: existing?.outcome !== 'valid_fail',
|
|
1481
|
+
},
|
|
1482
|
+
backgroundCompleted: false,
|
|
1483
|
+
},
|
|
1484
|
+
evidenceRefs: existing?.evidenceRef ? [existing.evidenceRef] : [],
|
|
1485
|
+
warnings: classified.warnings,
|
|
1486
|
+
suggestedNextActions: [{ label: 'Inspect work via context', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }],
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
842
1489
|
|
|
843
1490
|
const at = nowIso(ctx);
|
|
1491
|
+
const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
|
|
1492
|
+
const receipt = input.receipt
|
|
1493
|
+
&& input.receipt.repoId === work.repoId
|
|
1494
|
+
&& input.receipt.workId === work.workId
|
|
1495
|
+
&& input.receipt.checkId === resolvedCheckId
|
|
1496
|
+
? input.receipt
|
|
1497
|
+
: undefined;
|
|
1498
|
+
const sourceRevision = receipt && input.sourceRevision?.trim() ? input.sourceRevision.trim() : undefined;
|
|
1499
|
+
const verificationSummary = receipt
|
|
1500
|
+
? `${classified.summary} Durable Process receipt ${receipt.receiptId}.`
|
|
1501
|
+
: classified.summary;
|
|
844
1502
|
const record: VerificationRecord = {
|
|
845
|
-
checkId:
|
|
1503
|
+
checkId: resolvedCheckId,
|
|
846
1504
|
outcome: classified.outcome,
|
|
847
|
-
summary:
|
|
1505
|
+
summary: verificationSummary,
|
|
848
1506
|
recordedAt: at,
|
|
1507
|
+
sourceRevision,
|
|
1508
|
+
workspaceFingerprint: receipt && sourceRevision ? input.workspaceFingerprint : undefined,
|
|
1509
|
+
verificationInputFingerprint: receipt && sourceRevision ? input.verificationInputFingerprint : undefined,
|
|
1510
|
+
commandFingerprint: receipt && sourceRevision ? input.commandFingerprint : undefined,
|
|
1511
|
+
startedAt: receipt?.startedAt,
|
|
1512
|
+
completedAt: receipt?.finishedAt,
|
|
1513
|
+
receipt,
|
|
849
1514
|
evidenceRef: {
|
|
850
1515
|
title: `verification:${classified.outcome}`,
|
|
851
|
-
summary:
|
|
1516
|
+
summary: verificationSummary,
|
|
852
1517
|
detailLevel: 'summary',
|
|
853
1518
|
},
|
|
854
1519
|
};
|
|
@@ -878,12 +1543,16 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
878
1543
|
? 'failed'
|
|
879
1544
|
: 'ok';
|
|
880
1545
|
|
|
1546
|
+
const completionEvidence = classified.outcome === 'valid_pass'
|
|
1547
|
+
? evaluateWorkCompletionEvidence(updated, sourceRevision, input.workspaceFingerprint)
|
|
1548
|
+
: undefined;
|
|
1549
|
+
const validPassReadyToFinalize = completionEvidence?.status === 'complete';
|
|
881
1550
|
const suggested = validateSuggestedNextActions(
|
|
882
1551
|
classified.outcome === 'valid_pass'
|
|
883
1552
|
? [{
|
|
884
|
-
label: 'Continue workloop',
|
|
1553
|
+
label: validPassReadyToFinalize ? 'Finalize work' : 'Continue workloop',
|
|
885
1554
|
tool: 'rh_work',
|
|
886
|
-
operation: 'continue',
|
|
1555
|
+
operation: validPassReadyToFinalize ? 'finalize' : 'continue',
|
|
887
1556
|
payload: { work_id: work.workId },
|
|
888
1557
|
risk: 'readonly',
|
|
889
1558
|
confidence: 'high',
|
|
@@ -922,6 +1591,9 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
922
1591
|
doesNotRequestTaskChanges: !classified.isAcceptanceFailure,
|
|
923
1592
|
},
|
|
924
1593
|
backgroundCompleted: false,
|
|
1594
|
+
...(classified.outcome === 'valid_pass'
|
|
1595
|
+
? { nextStep: validPassReadyToFinalize ? 'finalize' : 'continue' }
|
|
1596
|
+
: {}),
|
|
925
1597
|
},
|
|
926
1598
|
warnings: classified.warnings,
|
|
927
1599
|
evidenceRefs: record.evidenceRef ? [record.evidenceRef] : [],
|
|
@@ -970,7 +1642,7 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
970
1642
|
});
|
|
971
1643
|
}
|
|
972
1644
|
|
|
973
|
-
const completionEvidence = evaluateWorkCompletionEvidence(work);
|
|
1645
|
+
const completionEvidence = evaluateWorkCompletionEvidence(work, ctx.sourceRevision, ctx.workspaceFingerprint);
|
|
974
1646
|
const history = completionEvidence.history;
|
|
975
1647
|
|
|
976
1648
|
if (input.forceFailed || completionEvidence.status === 'failed') {
|
|
@@ -1045,6 +1717,47 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1045
1717
|
});
|
|
1046
1718
|
}
|
|
1047
1719
|
|
|
1720
|
+
// For a controller-local effect Work, the semantic Controller's explicit
|
|
1721
|
+
// finalize call is the terminalization decision, but it is admissible only
|
|
1722
|
+
// after durable result evidence exists. Record one canonical Work receipt here
|
|
1723
|
+
// instead of routing an effect-only Work through Git delivery. Remote effects
|
|
1724
|
+
// remain bound to their durable plugin action receipt in the plugin layer.
|
|
1725
|
+
if (work.workKind === 'local_effect' && !work.completionReceipt && completionEvidence.durableResultEvidence) {
|
|
1726
|
+
const recordedAt = nowIso(ctx);
|
|
1727
|
+
const completed = recordWorkCompletionReceipt(
|
|
1728
|
+
ctx.workStore,
|
|
1729
|
+
work.workId,
|
|
1730
|
+
{
|
|
1731
|
+
schemaVersion: 1,
|
|
1732
|
+
receiptId: `LFX-WORK-${randomUUID()}`,
|
|
1733
|
+
source: 'local_effect',
|
|
1734
|
+
workId: work.workId,
|
|
1735
|
+
operation: 'controller_work/local_effect',
|
|
1736
|
+
target: { kind: 'controller_local', id: work.workId },
|
|
1737
|
+
changed: true,
|
|
1738
|
+
recordedAt,
|
|
1739
|
+
},
|
|
1740
|
+
'completed_local',
|
|
1741
|
+
'local_effect',
|
|
1742
|
+
);
|
|
1743
|
+
if (completed.planId && completed.planStepId && ctx.planStore) {
|
|
1744
|
+
completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
|
|
1745
|
+
}
|
|
1746
|
+
return buildFacadeResult({
|
|
1747
|
+
status: 'ok',
|
|
1748
|
+
summary: `Finalize result: succeeded for ${work.workId}.`,
|
|
1749
|
+
data: {
|
|
1750
|
+
work: summarizeWorkContract(completed),
|
|
1751
|
+
finalStatus: 'completed',
|
|
1752
|
+
completionReceipt: completed.completionReceipt,
|
|
1753
|
+
validPasses: history.validPasses,
|
|
1754
|
+
hiddenFailure: false,
|
|
1755
|
+
},
|
|
1756
|
+
evidenceRefs: completed.evidenceRefs.slice(0, 5),
|
|
1757
|
+
suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1048
1761
|
// Checks and result evidence are necessary but not sufficient. Delivery and
|
|
1049
1762
|
// cleanup must produce the exact Work-owned receipt before Work can become
|
|
1050
1763
|
// terminal; a Run exit or a missing receipt is never completion authority.
|
|
@@ -1099,6 +1812,38 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1099
1812
|
});
|
|
1100
1813
|
}
|
|
1101
1814
|
|
|
1815
|
+
const owner = ctx.workStore.controllerHome
|
|
1816
|
+
? getControllerSession({ controllerHome: ctx.workStore.controllerHome, repoId: ctx.repoId }, work.workId)
|
|
1817
|
+
: undefined;
|
|
1818
|
+
if (owner) {
|
|
1819
|
+
const ownerPrincipal = owner.principalId?.trim() || owner.controllerId;
|
|
1820
|
+
const ownerInstanceId = owner.controllerInstanceId?.trim();
|
|
1821
|
+
const callerPrincipal = ctx.principalId?.trim();
|
|
1822
|
+
const callerInstanceId = ctx.controllerInstanceId?.trim();
|
|
1823
|
+
const sameAuthority = Boolean(
|
|
1824
|
+
callerPrincipal
|
|
1825
|
+
&& ownerPrincipal === callerPrincipal
|
|
1826
|
+
&& (!ownerInstanceId || ownerInstanceId === callerInstanceId),
|
|
1827
|
+
);
|
|
1828
|
+
if (!sameAuthority) {
|
|
1829
|
+
return buildFacadeResult({
|
|
1830
|
+
status: 'blocked',
|
|
1831
|
+
summary: `WORK_TERMINALIZATION_ACTIVE_CONTROLLER_FENCE: ${work.workId} has a newer active Controller claim generation ${owner.claimGeneration ?? 1}.`,
|
|
1832
|
+
data: {
|
|
1833
|
+
work: summarizeWorkContract(work),
|
|
1834
|
+
activeController: {
|
|
1835
|
+
controllerId: owner.controllerId,
|
|
1836
|
+
principalId: ownerPrincipal,
|
|
1837
|
+
controllerInstanceId: owner.controllerInstanceId,
|
|
1838
|
+
sessionId: owner.sessionId,
|
|
1839
|
+
claimGeneration: owner.claimGeneration ?? 1,
|
|
1840
|
+
},
|
|
1841
|
+
finalStatus: work.status,
|
|
1842
|
+
},
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1102
1847
|
const destructiveCleanup = input.authorizeDestructiveCleanup === true;
|
|
1103
1848
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1104
1849
|
status: 'cancelled',
|
|
@@ -1115,6 +1860,17 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1115
1860
|
// cleanup handler verifies ownership, cleanliness and successful removal.
|
|
1116
1861
|
worktreeRef: work.worktreeRef,
|
|
1117
1862
|
});
|
|
1863
|
+
const planId = updated.planId;
|
|
1864
|
+
const planStepId = updated.planStepId;
|
|
1865
|
+
let plan = planId && ctx.planStore
|
|
1866
|
+
? getPlanContract(ctx.planStore, planId)
|
|
1867
|
+
: undefined;
|
|
1868
|
+
if (plan && planId && planStepId && ctx.planStore) {
|
|
1869
|
+
const step = plan.steps.find((entry) => entry.id === planStepId);
|
|
1870
|
+
if (step?.workId === updated.workId) {
|
|
1871
|
+
plan = completePlanStepForWork(ctx.planStore, { planId, stepId: planStepId, work: updated });
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1118
1874
|
|
|
1119
1875
|
return buildFacadeResult({
|
|
1120
1876
|
status: 'ok',
|
|
@@ -1126,6 +1882,7 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1126
1882
|
worktreeDeleted: false,
|
|
1127
1883
|
cleanupPending: destructiveCleanup && Boolean(work.worktreeRef),
|
|
1128
1884
|
destructiveCleanupAuthorized: destructiveCleanup,
|
|
1885
|
+
...(plan ? { plan } : {}),
|
|
1129
1886
|
},
|
|
1130
1887
|
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1131
1888
|
suggestedNextActions: [
|
|
@@ -1150,6 +1907,7 @@ export function runGoalWorkloop(
|
|
|
1150
1907
|
objective: String(args.objective ?? ''),
|
|
1151
1908
|
acceptanceCriteria: Array.isArray(args.acceptance_criteria) ? args.acceptance_criteria.map(String) : undefined,
|
|
1152
1909
|
allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
|
|
1910
|
+
initialLikelyPaths: Array.isArray(args.initial_likely_paths) ? args.initial_likely_paths.map(String) : undefined,
|
|
1153
1911
|
forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
|
|
1154
1912
|
checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
1155
1913
|
modeInput: {
|
|
@@ -1160,6 +1918,7 @@ export function runGoalWorkloop(
|
|
|
1160
1918
|
requiresInvestigation: args.requires_investigation === true,
|
|
1161
1919
|
requiresLongRunningChecks: args.requires_long_running_checks === true,
|
|
1162
1920
|
requiresParallelism: args.requires_parallelism === true,
|
|
1921
|
+
explicitMode: args.mode === 'scale' ? 'scale' : undefined,
|
|
1163
1922
|
needsDependencies: args.needs_dependencies === true,
|
|
1164
1923
|
requiresRecovery: args.requires_recovery === true,
|
|
1165
1924
|
requiresWorker: args.requires_worker === true,
|
|
@@ -1179,13 +1938,34 @@ export function runGoalWorkloop(
|
|
|
1179
1938
|
forceMode: args.force_mode === 'direct_control' || args.force_mode === 'goal_workloop' || args.force_mode === 'handoff_only'
|
|
1180
1939
|
? args.force_mode
|
|
1181
1940
|
: undefined,
|
|
1941
|
+
relatedWorkId: typeof args.related_work_id === 'string' ? args.related_work_id : undefined,
|
|
1942
|
+
workRelation: args.work_relation === 'continue' || args.work_relation === 'extend' || args.work_relation === 'parallel' || args.work_relation === 'new_goal'
|
|
1943
|
+
? args.work_relation
|
|
1944
|
+
: undefined,
|
|
1945
|
+
requirementId: typeof args.requirement_id === 'string' ? args.requirement_id : undefined,
|
|
1182
1946
|
planId: typeof args.plan_id === 'string' ? args.plan_id : undefined,
|
|
1183
1947
|
planStepId: typeof args.plan_step_id === 'string' ? args.plan_step_id : undefined,
|
|
1948
|
+
workKind: args.work_kind === 'repository_change'
|
|
1949
|
+
|| args.work_kind === 'completed_no_change'
|
|
1950
|
+
|| args.work_kind === 'investigation'
|
|
1951
|
+
|| args.work_kind === 'local_effect'
|
|
1952
|
+
|| args.work_kind === 'remote_effect'
|
|
1953
|
+
|| args.work_kind === 'reconciliation'
|
|
1954
|
+
? args.work_kind
|
|
1955
|
+
: undefined,
|
|
1184
1956
|
});
|
|
1185
1957
|
case 'continue':
|
|
1186
1958
|
return continueGoalWorkloop(ctx, {
|
|
1187
1959
|
workId: String(args.work_id ?? ''),
|
|
1188
1960
|
note: typeof args.note === 'string' ? args.note : undefined,
|
|
1961
|
+
allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
|
|
1962
|
+
forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
|
|
1963
|
+
checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
1964
|
+
additionalLikelyPaths: Array.isArray(args.additional_likely_paths) ? args.additional_likely_paths.map(String) : undefined,
|
|
1965
|
+
inspectedPaths: Array.isArray(args.inspected_paths) ? args.inspected_paths.map(String) : undefined,
|
|
1966
|
+
acceptanceFailureDecision: args.acceptance_failure_decision === 'repair' || args.acceptance_failure_decision === 'rescope'
|
|
1967
|
+
? args.acceptance_failure_decision
|
|
1968
|
+
: undefined,
|
|
1189
1969
|
});
|
|
1190
1970
|
case 'verify':
|
|
1191
1971
|
return verifyGoalWorkloop(ctx, {
|