@moretea-labs/forge 1.6.0 → 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 +26 -0
- package/CLAUDE.md +2 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/prompt-guard.sh +5 -81
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +1 -1
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
- package/assets/templates/helpers/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/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 +26 -6
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +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-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-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/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/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +1 -1
- 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 +785 -469
- 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/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +0 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +0 -513
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/repository-tools.ts +247 -107
- package/src/cli/mcp/server.ts +537 -111
- 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 +101 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +101 -19
- 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/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +61 -3
- package/src/cli/repository/inspector.ts +553 -63
- 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 +47 -8
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-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 +143 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
- 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 +132 -1
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
- package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
- 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 +22 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +175 -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 +355 -116
- 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/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 +13 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +53 -0
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +43 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -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 +1192 -114
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +184 -82
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2901 -241
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff.ts +92 -2
- package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
- 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 +8 -2
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +123 -6
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +269 -29
- package/src/runtime/plugins/types.ts +35 -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/maintenance-executor.ts +393 -28
- 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 +19 -0
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +700 -92
- 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 +81 -3
- package/src/runtime/workflow/schedules/engine.ts +287 -151
- 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 +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
- package/docs/architecture/current/entity-model.md +0 -612
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -169
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -321
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import {
|
|
3
3
|
createHandoffItem,
|
|
4
|
+
listHandoffItems,
|
|
4
5
|
type HandoffInboxStoreOptions,
|
|
5
6
|
} from './handoff-inbox-store';
|
|
7
|
+
import { getControllerSession } from './controller-session-store';
|
|
6
8
|
import {
|
|
7
9
|
appendVerificationRecord,
|
|
8
10
|
appendWorkEvidence,
|
|
@@ -10,6 +12,8 @@ import {
|
|
|
10
12
|
createWorkContract,
|
|
11
13
|
getWorkContract,
|
|
12
14
|
listWorkContracts,
|
|
15
|
+
recordWorkCompletionReceipt,
|
|
16
|
+
recordWorkScopeEvidence,
|
|
13
17
|
summarizeWorkContract,
|
|
14
18
|
transitionWorkContractPhase,
|
|
15
19
|
updateWorkContract,
|
|
@@ -21,6 +25,8 @@ import {
|
|
|
21
25
|
getPlanContract,
|
|
22
26
|
type PlanContractStoreOptions,
|
|
23
27
|
} from './plan-contract-store';
|
|
28
|
+
import { withPrimaryWorkAdmissionLock } from './semantic-admission';
|
|
29
|
+
import { readRequirement } from '../persistence/requirement-store';
|
|
24
30
|
import {
|
|
25
31
|
classifyVerificationOutcome,
|
|
26
32
|
normalizeCheckIds,
|
|
@@ -41,6 +47,7 @@ import type {
|
|
|
41
47
|
SuggestedNextAction,
|
|
42
48
|
VerificationRecord,
|
|
43
49
|
WorkContract,
|
|
50
|
+
WorkKind,
|
|
44
51
|
WorkRisk,
|
|
45
52
|
} from './types';
|
|
46
53
|
import { selectExecutionMode } from './types';
|
|
@@ -58,8 +65,12 @@ export interface GoalWorkloopContext {
|
|
|
58
65
|
principalId?: string;
|
|
59
66
|
controllerInstanceId?: string;
|
|
60
67
|
workspaceFingerprint?: string;
|
|
68
|
+
/** Current net repository paths changed from the Work base plus dirty checkout paths. */
|
|
69
|
+
workspaceChangedPaths?: readonly string[];
|
|
61
70
|
now?: () => string;
|
|
62
|
-
|
|
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 };
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
export type WorkAdmissionRelation = 'continue' | 'extend' | 'parallel' | 'new_goal';
|
|
@@ -68,6 +79,8 @@ export interface GoalWorkloopStartInput {
|
|
|
68
79
|
objective: string;
|
|
69
80
|
acceptanceCriteria?: string[];
|
|
70
81
|
allowedPaths?: string[];
|
|
82
|
+
/** Non-authoritative first-pass discovery candidates. */
|
|
83
|
+
initialLikelyPaths?: string[];
|
|
71
84
|
forbiddenPaths?: string[];
|
|
72
85
|
checks?: string[];
|
|
73
86
|
constraints?: WorkContract['constraints'];
|
|
@@ -83,16 +96,35 @@ export interface GoalWorkloopStartInput {
|
|
|
83
96
|
forceMode?: WorkContract['mode'];
|
|
84
97
|
planId?: string;
|
|
85
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'>;
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
export interface GoalWorkloopContinueInput {
|
|
89
104
|
workId: string;
|
|
90
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';
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
export interface GoalWorkloopVerifyInput {
|
|
94
116
|
workId: string;
|
|
95
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'];
|
|
96
128
|
/** When true, simulate infrastructure failure rather than acceptance fail. */
|
|
97
129
|
infrastructureFailed?: boolean;
|
|
98
130
|
/** When true and check is valid, record acceptance failure. */
|
|
@@ -141,13 +173,13 @@ function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
|
|
|
141
173
|
return 'medium';
|
|
142
174
|
}
|
|
143
175
|
|
|
144
|
-
function
|
|
176
|
+
function suggestedForWorkIdentity(workId: string, checks: string[], extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
145
177
|
const base: SuggestedNextAction[] = [
|
|
146
178
|
{
|
|
147
179
|
label: 'Continue workloop',
|
|
148
180
|
tool: 'rh_work',
|
|
149
181
|
operation: 'continue',
|
|
150
|
-
payload: { work_id:
|
|
182
|
+
payload: { work_id: workId },
|
|
151
183
|
risk: 'readonly',
|
|
152
184
|
confidence: 'high',
|
|
153
185
|
},
|
|
@@ -155,24 +187,28 @@ function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []
|
|
|
155
187
|
label: 'Verify registered checks',
|
|
156
188
|
tool: 'rh_work',
|
|
157
189
|
operation: 'verify',
|
|
158
|
-
payload: { work_id:
|
|
190
|
+
payload: { work_id: workId, check_id: checks[0] },
|
|
159
191
|
risk: 'workspace_write',
|
|
160
|
-
confidence:
|
|
192
|
+
confidence: checks[0] ? 'high' : 'low',
|
|
161
193
|
},
|
|
162
194
|
{
|
|
163
195
|
label: 'Finalize when ready',
|
|
164
196
|
tool: 'rh_work',
|
|
165
197
|
operation: 'finalize',
|
|
166
|
-
payload: { work_id:
|
|
198
|
+
payload: { work_id: workId },
|
|
167
199
|
risk: 'readonly',
|
|
168
200
|
confidence: 'medium',
|
|
169
201
|
},
|
|
170
202
|
];
|
|
171
203
|
return validateSuggestedNextActions([...extras, ...base], {
|
|
172
|
-
validCheckIds:
|
|
204
|
+
validCheckIds: checks,
|
|
173
205
|
}).actions;
|
|
174
206
|
}
|
|
175
207
|
|
|
208
|
+
function suggestedForWork(work: WorkContract, extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
209
|
+
return suggestedForWorkIdentity(work.workId, work.checks, extras);
|
|
210
|
+
}
|
|
211
|
+
|
|
176
212
|
function initialEvidence(objective: string): EvidenceRef {
|
|
177
213
|
return {
|
|
178
214
|
title: 'work contract created',
|
|
@@ -191,14 +227,79 @@ interface WorkCompletionEvidenceEvaluation {
|
|
|
191
227
|
reasons: string[];
|
|
192
228
|
}
|
|
193
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
|
+
|
|
194
290
|
function evaluateWorkCompletionEvidence(
|
|
195
291
|
work: WorkContract,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
),
|
|
292
|
+
currentRevision?: string,
|
|
293
|
+
currentWorkspaceFingerprint?: string,
|
|
199
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
|
+
);
|
|
200
300
|
const missingChecks = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
201
|
-
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));
|
|
202
303
|
const reasons: string[] = [];
|
|
203
304
|
|
|
204
305
|
if (history.acceptanceFailures.length > 0) {
|
|
@@ -215,7 +316,12 @@ function evaluateWorkCompletionEvidence(
|
|
|
215
316
|
reasons.push(`Declared checks are missing valid_pass evidence: ${missingChecks.join(', ')}.`);
|
|
216
317
|
}
|
|
217
318
|
if (work.checks.length === 0 && !durableResultEvidence) {
|
|
218
|
-
|
|
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.');
|
|
219
325
|
}
|
|
220
326
|
|
|
221
327
|
const complete = history.infrastructureIssues.length === 0
|
|
@@ -252,6 +358,13 @@ export function routeWorkStart(
|
|
|
252
358
|
? false
|
|
253
359
|
: input.modeInput.requiresUserApproval === true || strategyConflictRequiresApproval,
|
|
254
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
|
+
}
|
|
255
368
|
const applyForcedMode = (selected: ReturnType<typeof selectExecutionMode>) => input.forceMode
|
|
256
369
|
? {
|
|
257
370
|
...selected,
|
|
@@ -472,6 +585,16 @@ export function startGoalWorkloop(
|
|
|
472
585
|
executionMode: 'direct_control' | 'goal_workloop' = 'goal_workloop',
|
|
473
586
|
routeDecision = selectExecutionMode({ ...input.modeInput, objective: input.objective, knownPaths: input.allowedPaths }).routeDecision,
|
|
474
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
|
+
}
|
|
475
598
|
const at = nowIso(ctx);
|
|
476
599
|
const available = ctx.availableChecks ?? [];
|
|
477
600
|
const workspaceMode = input.constraints?.workspaceMode ?? 'auto';
|
|
@@ -487,6 +610,26 @@ export function startGoalWorkloop(
|
|
|
487
610
|
: undefined;
|
|
488
611
|
const plan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
|
|
489
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
|
+
}
|
|
490
633
|
const sameStringSet = (provided: string[] | undefined, frozen: string[]): boolean => {
|
|
491
634
|
if (provided === undefined) return true;
|
|
492
635
|
const actual = [...new Set(provided)].sort();
|
|
@@ -513,17 +656,23 @@ export function startGoalWorkloop(
|
|
|
513
656
|
const effectiveForbiddenPaths = planStep?.forbiddenPaths ?? input.forbiddenPaths ?? [];
|
|
514
657
|
const effectiveChecks = planStep?.checks ?? input.checks ?? [];
|
|
515
658
|
const normalized = normalizeCheckIds(effectiveChecks, available);
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
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)
|
|
519
666
|
: [];
|
|
520
|
-
const deterministicTarget =
|
|
667
|
+
const deterministicTarget = input.relatedWorkId
|
|
668
|
+
? explicitRelatedWork
|
|
669
|
+
: planStepWork ?? (requirementWorks.length === 1 ? requirementWorks[0] : undefined);
|
|
521
670
|
const requestedRelation = input.workRelation ?? (input.modeInput.requiresParallelism === true ? 'parallel' : undefined);
|
|
522
671
|
// Only strong semantic bindings participate in ownership resolution. An
|
|
523
672
|
// unrelated active Work or a checkout writer is a placement fact, not a
|
|
524
673
|
// semantic candidate for continue/extend/parallel/new_goal.
|
|
525
674
|
const candidateWorks = [...new Map(
|
|
526
|
-
[explicitRelatedWork, planStepWork, ...requirementWorks]
|
|
675
|
+
[explicitRelatedWork, planStepWork, ...boundPlanStepWorks, ...requirementWorks]
|
|
527
676
|
.filter((candidate): candidate is WorkContract => Boolean(candidate))
|
|
528
677
|
.map((candidate) => [candidate.workId, candidate]),
|
|
529
678
|
).values()].slice(0, 8);
|
|
@@ -556,6 +705,27 @@ export function startGoalWorkloop(
|
|
|
556
705
|
rawAvailable: false,
|
|
557
706
|
});
|
|
558
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
|
+
|
|
559
729
|
if ((input.requestedBy ?? 'chatgpt') === 'scheduler') {
|
|
560
730
|
return resolutionRequired(
|
|
561
731
|
deterministicTarget
|
|
@@ -587,7 +757,7 @@ export function startGoalWorkloop(
|
|
|
587
757
|
}
|
|
588
758
|
if (requestedRelation === 'extend' && deterministicTarget.planId) {
|
|
589
759
|
return resolutionRequired(
|
|
590
|
-
`PLAN_EXTENSION_REQUIRES_REPLAN: Work ${deterministicTarget.workId} is governed by Plan ${deterministicTarget.planId};
|
|
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.`,
|
|
591
761
|
deterministicTarget,
|
|
592
762
|
);
|
|
593
763
|
}
|
|
@@ -597,6 +767,15 @@ export function startGoalWorkloop(
|
|
|
597
767
|
allowedPaths: [...new Set([...deterministicTarget.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
598
768
|
forbiddenPaths: [...new Set([...deterministicTarget.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
599
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
|
+
},
|
|
600
779
|
scopeSummary: `Extended serially: ${input.objective}`.slice(0, 500),
|
|
601
780
|
continuationPrompt: `Continue work ${ctx.repoId}: ${deterministicTarget.objective.slice(0, 160)}. Additional requested scope: ${input.objective.slice(0, 500)}`,
|
|
602
781
|
})
|
|
@@ -627,6 +806,7 @@ export function startGoalWorkloop(
|
|
|
627
806
|
const needsWorktree = executionMode === 'goal_workloop' && (input.constraints?.requireWorktree
|
|
628
807
|
?? (workspaceMode === 'isolated'
|
|
629
808
|
|| placementConflict
|
|
809
|
+
|| input.modeInput.requiresParallelism === true
|
|
630
810
|
|| (workspaceMode === 'auto' && routeDecision.requiresIsolation === true)));
|
|
631
811
|
if (!needsWorktree && workspaceOwner) {
|
|
632
812
|
return buildFacadeResult({
|
|
@@ -643,74 +823,92 @@ export function startGoalWorkloop(
|
|
|
643
823
|
});
|
|
644
824
|
}
|
|
645
825
|
const generatedWorkId = workIdFor(input.objective);
|
|
646
|
-
let isolatedWorkspace: ReturnType<NonNullable<GoalWorkloopContext['materializeIsolatedWorkspace']>> | undefined;
|
|
647
|
-
if (needsWorktree) {
|
|
648
|
-
if (!ctx.materializeIsolatedWorkspace) {
|
|
649
|
-
return buildFacadeResult({ status: 'blocked', summary: 'ISOLATED_WORKSPACE_MATERIALIZER_REQUIRED: isolated Work cannot enter running state without a concrete managed checkout.', data: { executionStarted: false, workContractCreated: false, worktreeRequired: true } });
|
|
650
|
-
}
|
|
651
|
-
try {
|
|
652
|
-
isolatedWorkspace = ctx.materializeIsolatedWorkspace({ workId: generatedWorkId, title: input.objective, baseRef: ctx.sourceRevision });
|
|
653
|
-
if (!isolatedWorkspace.checkoutId || !isolatedWorkspace.root || isolatedWorkspace.checkoutId === ctx.checkoutId) {
|
|
654
|
-
throw new Error('ISOLATED_WORKSPACE_NOT_DISTINCT');
|
|
655
|
-
}
|
|
656
|
-
} catch (error) {
|
|
657
|
-
return buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? `ISOLATED_WORKSPACE_MATERIALIZATION_FAILED: ${error.message}` : 'ISOLATED_WORKSPACE_MATERIALIZATION_FAILED', data: { executionStarted: false, workContractCreated: false, worktreeRequired: true } });
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
826
|
if (input.planId || input.planStepId) {
|
|
661
|
-
if (!input.planId || !input.planStepId || !ctx.planStore || !ctx.sourceRevision) {
|
|
662
|
-
return buildFacadeResult({ status: 'blocked', summary: 'PLAN_CONTEXT_REQUIRED: plan_id, plan_step_id and a current source revision are required.', data: { executionStarted: false } });
|
|
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 } });
|
|
663
829
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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, {
|
|
667
835
|
planId: input.planId,
|
|
668
836
|
stepId: input.planStepId,
|
|
669
837
|
workId: generatedWorkId,
|
|
670
838
|
sourceRevision: ctx.sourceRevision,
|
|
671
839
|
});
|
|
672
|
-
} catch (error) {
|
|
673
840
|
return buildFacadeResult({
|
|
674
841
|
status: 'blocked',
|
|
675
|
-
summary:
|
|
676
|
-
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 },
|
|
677
844
|
});
|
|
678
845
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
});
|
|
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 } });
|
|
685
852
|
}
|
|
686
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;
|
|
687
875
|
const work = createWorkContract(ctx.workStore, {
|
|
688
876
|
workId: generatedWorkId,
|
|
689
877
|
repoId: ctx.repoId,
|
|
690
|
-
checkoutId:
|
|
878
|
+
checkoutId: needsWorktree ? undefined : ctx.checkoutId,
|
|
691
879
|
principalId: ctx.principalId,
|
|
692
880
|
controllerInstanceId: ctx.controllerInstanceId,
|
|
693
|
-
baseRevision:
|
|
694
|
-
workspaceFingerprint:
|
|
881
|
+
baseRevision: ctx.sourceRevision,
|
|
882
|
+
workspaceFingerprint: needsWorktree ? undefined : ctx.workspaceFingerprint,
|
|
695
883
|
routeDecisionFingerprint: routeDecision.inputFingerprint,
|
|
696
884
|
routeDecision,
|
|
697
885
|
mode: executionMode,
|
|
698
886
|
objective: input.objective,
|
|
699
887
|
acceptanceCriteria: effectiveAcceptanceCriteria,
|
|
700
|
-
constraints:
|
|
888
|
+
constraints: {
|
|
889
|
+
...(input.constraints ?? { requireHandoffOnAmbiguity: true }),
|
|
890
|
+
...(remoteDeliveryRequired ? { remoteDeliveryRequired: true } : {}),
|
|
891
|
+
},
|
|
701
892
|
risk: workRiskFor(input),
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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,
|
|
705
897
|
status: 'running',
|
|
706
898
|
phase: 'implementation',
|
|
707
899
|
issueId: input.issueId,
|
|
708
900
|
taskId: input.taskId,
|
|
709
|
-
requirementId:
|
|
901
|
+
requirementId: effectiveRequirementId,
|
|
710
902
|
planId: input.planId,
|
|
711
903
|
planStepId: input.planStepId,
|
|
712
904
|
planSourceRevision: input.planId ? ctx.sourceRevision : undefined,
|
|
713
905
|
scopeSummary: input.modeInput.scopeClear ? 'scope declared at start' : 'scope incomplete',
|
|
906
|
+
scopeEvidence: {
|
|
907
|
+
initialLikelyPaths: [...new Set(input.initialLikelyPaths ?? effectiveAllowedPaths)].slice(0, 100),
|
|
908
|
+
inspectedPaths: [],
|
|
909
|
+
actualChangedPaths: [],
|
|
910
|
+
recordedAt: at,
|
|
911
|
+
},
|
|
714
912
|
allowedPaths: effectiveAllowedPaths,
|
|
715
913
|
forbiddenPaths: effectiveForbiddenPaths,
|
|
716
914
|
checks: normalized.validCheckIds,
|
|
@@ -727,7 +925,7 @@ export function startGoalWorkloop(
|
|
|
727
925
|
? 'Isolation was explicitly requested or required for parallel execution.'
|
|
728
926
|
: 'Current workspace is the stability-first default; isolation remains opt-in.',
|
|
729
927
|
},
|
|
730
|
-
worktreeRef:
|
|
928
|
+
worktreeRef: undefined,
|
|
731
929
|
evidencePolicy: {
|
|
732
930
|
defaultDetailLevel: 'summary',
|
|
733
931
|
allowRawOptIn: true,
|
|
@@ -746,21 +944,39 @@ export function startGoalWorkloop(
|
|
|
746
944
|
requestedBy: input.requestedBy ?? 'chatgpt',
|
|
747
945
|
evidenceRefs: [initialEvidence(input.objective)],
|
|
748
946
|
policyDecisions: policy ? [policy] : [],
|
|
749
|
-
suggestedNextActions:
|
|
947
|
+
suggestedNextActions: initialSuggestedNextActions,
|
|
750
948
|
continuationPrompt: `Continue work ${ctx.repoId}: ${input.objective.slice(0, 200)}`,
|
|
751
949
|
});
|
|
752
950
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
+
}
|
|
758
974
|
|
|
759
975
|
return buildFacadeResult({
|
|
760
976
|
status: 'ok',
|
|
761
977
|
summary: executionMode === 'direct_control'
|
|
762
|
-
? `Direct-control Work lineage started as ${
|
|
763
|
-
: `Goal workloop started as ${
|
|
978
|
+
? `Direct-control Work lineage started as ${work.workId}.`
|
|
979
|
+
: `Goal workloop started as ${work.workId}.`,
|
|
764
980
|
data: {
|
|
765
981
|
mode: {
|
|
766
982
|
mode: executionMode,
|
|
@@ -774,20 +990,20 @@ export function startGoalWorkloop(
|
|
|
774
990
|
routeDecision,
|
|
775
991
|
},
|
|
776
992
|
workContractCreated: true,
|
|
777
|
-
work: summarizeWorkContract(
|
|
778
|
-
worktreeRequired:
|
|
993
|
+
work: summarizeWorkContract(work),
|
|
994
|
+
worktreeRequired: work.worktreePolicy.required,
|
|
779
995
|
normalizedChecks: normalized,
|
|
780
996
|
policy,
|
|
781
997
|
},
|
|
782
|
-
evidenceRefs:
|
|
998
|
+
evidenceRefs: work.evidenceRefs,
|
|
783
999
|
warnings: normalized.warnings,
|
|
784
|
-
suggestedNextActions:
|
|
1000
|
+
suggestedNextActions: initialSuggestedNextActions,
|
|
785
1001
|
rawAvailable: false,
|
|
786
1002
|
});
|
|
787
1003
|
}
|
|
788
1004
|
|
|
789
1005
|
export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopContinueInput): FacadeResult {
|
|
790
|
-
|
|
1006
|
+
let work = getWorkContract(ctx.workStore, input.workId);
|
|
791
1007
|
if (!work) {
|
|
792
1008
|
return buildFacadeResult({
|
|
793
1009
|
status: 'not_found',
|
|
@@ -814,78 +1030,223 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
814
1030
|
});
|
|
815
1031
|
}
|
|
816
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));
|
|
817
1078
|
const history = reconcileVerificationHistory(
|
|
818
|
-
|
|
1079
|
+
currentCheckRefs.map((record) => ({ checkId: record.checkId, outcome: record.outcome, recordedAt: record.recordedAt })),
|
|
819
1080
|
);
|
|
820
1081
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
severity: 'needs_review',
|
|
830
|
-
creationReason: 'ambiguous_outcome',
|
|
831
|
-
reason: `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`,
|
|
832
|
-
summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
|
|
833
|
-
currentState: {
|
|
834
|
-
repoId: ctx.repoId,
|
|
835
|
-
workId: work.workId,
|
|
836
|
-
mode: work.mode,
|
|
837
|
-
statusSummary: 'waiting_for_review after acceptance failure',
|
|
838
|
-
checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
|
|
839
|
-
workSemantics: continuation.semantics,
|
|
840
|
-
reconciliationRequired: continuation.reconciliationRequired,
|
|
841
|
-
nextSafeAction: continuation.nextSafeAction,
|
|
842
|
-
},
|
|
843
|
-
attemptedActions: ['continue'],
|
|
844
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
845
|
-
blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
|
|
846
|
-
recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
|
|
847
|
-
recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
|
|
848
|
-
recommendedContinuationPrompt: continuation.continuationPrompt,
|
|
849
|
-
suggestedNextActions: [
|
|
850
|
-
{
|
|
851
|
-
label: 'Read work context',
|
|
852
|
-
tool: 'rh_context',
|
|
853
|
-
operation: 'get',
|
|
854
|
-
payload: { work_id: work.workId },
|
|
855
|
-
risk: 'readonly',
|
|
856
|
-
},
|
|
857
|
-
],
|
|
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',
|
|
858
1090
|
});
|
|
859
1091
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
860
|
-
status: '
|
|
861
|
-
phase: '
|
|
862
|
-
state: '
|
|
863
|
-
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.`,
|
|
864
1096
|
evidenceRefs: work.evidenceRefs,
|
|
865
1097
|
});
|
|
866
|
-
|
|
867
|
-
|
|
1098
|
+
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
868
1099
|
return buildFacadeResult({
|
|
869
|
-
status: '
|
|
870
|
-
summary: `Continue
|
|
1100
|
+
status: 'ok',
|
|
1101
|
+
summary: `Continue accepted explicit bounded ${decision} decision after acceptance failure; implementation may resume.`,
|
|
871
1102
|
data: {
|
|
872
|
-
work: summarizeWorkContract(
|
|
873
|
-
handoffId: handoff.id,
|
|
1103
|
+
work: summarizeWorkContract(work),
|
|
874
1104
|
acceptanceFailures: history.acceptanceFailures,
|
|
875
1105
|
infrastructureIssues: history.infrastructureIssues,
|
|
876
1106
|
backgroundCompleted: false,
|
|
1107
|
+
nextStep: 'execute',
|
|
1108
|
+
acceptanceFailureDecision: decision,
|
|
877
1109
|
},
|
|
878
1110
|
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
879
|
-
suggestedNextActions:
|
|
880
|
-
|
|
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: [{
|
|
881
1147
|
label: 'Get handoff',
|
|
882
1148
|
tool: 'rh_inbox',
|
|
883
1149
|
operation: 'get',
|
|
884
|
-
payload: { handoff_id:
|
|
1150
|
+
payload: { handoff_id: activeHandoff.id },
|
|
885
1151
|
risk: 'readonly',
|
|
886
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,
|
|
887
1178
|
},
|
|
888
|
-
|
|
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),
|
|
889
1250
|
});
|
|
890
1251
|
}
|
|
891
1252
|
|
|
@@ -927,6 +1288,47 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
927
1288
|
});
|
|
928
1289
|
}
|
|
929
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
|
+
|
|
930
1332
|
if (work.checks.length > 0 && history.validPasses.length < work.checks.length) {
|
|
931
1333
|
const remaining = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
932
1334
|
const suggested = validateSuggestedNextActions(
|
|
@@ -966,7 +1368,7 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
966
1368
|
});
|
|
967
1369
|
}
|
|
968
1370
|
|
|
969
|
-
const completionEvidence = evaluateWorkCompletionEvidence(work,
|
|
1371
|
+
const completionEvidence = evaluateWorkCompletionEvidence(work, ctx.sourceRevision, ctx.workspaceFingerprint);
|
|
970
1372
|
if (completionEvidence.status !== 'complete') {
|
|
971
1373
|
const suggested = validateSuggestedNextActions([
|
|
972
1374
|
{
|
|
@@ -1058,16 +1460,60 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
1058
1460
|
checkFailed: input.checkFailed,
|
|
1059
1461
|
skipped: input.skipped,
|
|
1060
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
|
+
}
|
|
1061
1489
|
|
|
1062
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;
|
|
1063
1502
|
const record: VerificationRecord = {
|
|
1064
|
-
checkId:
|
|
1503
|
+
checkId: resolvedCheckId,
|
|
1065
1504
|
outcome: classified.outcome,
|
|
1066
|
-
summary:
|
|
1505
|
+
summary: verificationSummary,
|
|
1067
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,
|
|
1068
1514
|
evidenceRef: {
|
|
1069
1515
|
title: `verification:${classified.outcome}`,
|
|
1070
|
-
summary:
|
|
1516
|
+
summary: verificationSummary,
|
|
1071
1517
|
detailLevel: 'summary',
|
|
1072
1518
|
},
|
|
1073
1519
|
};
|
|
@@ -1097,12 +1543,16 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
1097
1543
|
? 'failed'
|
|
1098
1544
|
: 'ok';
|
|
1099
1545
|
|
|
1546
|
+
const completionEvidence = classified.outcome === 'valid_pass'
|
|
1547
|
+
? evaluateWorkCompletionEvidence(updated, sourceRevision, input.workspaceFingerprint)
|
|
1548
|
+
: undefined;
|
|
1549
|
+
const validPassReadyToFinalize = completionEvidence?.status === 'complete';
|
|
1100
1550
|
const suggested = validateSuggestedNextActions(
|
|
1101
1551
|
classified.outcome === 'valid_pass'
|
|
1102
1552
|
? [{
|
|
1103
|
-
label: 'Continue workloop',
|
|
1553
|
+
label: validPassReadyToFinalize ? 'Finalize work' : 'Continue workloop',
|
|
1104
1554
|
tool: 'rh_work',
|
|
1105
|
-
operation: 'continue',
|
|
1555
|
+
operation: validPassReadyToFinalize ? 'finalize' : 'continue',
|
|
1106
1556
|
payload: { work_id: work.workId },
|
|
1107
1557
|
risk: 'readonly',
|
|
1108
1558
|
confidence: 'high',
|
|
@@ -1141,6 +1591,9 @@ export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloop
|
|
|
1141
1591
|
doesNotRequestTaskChanges: !classified.isAcceptanceFailure,
|
|
1142
1592
|
},
|
|
1143
1593
|
backgroundCompleted: false,
|
|
1594
|
+
...(classified.outcome === 'valid_pass'
|
|
1595
|
+
? { nextStep: validPassReadyToFinalize ? 'finalize' : 'continue' }
|
|
1596
|
+
: {}),
|
|
1144
1597
|
},
|
|
1145
1598
|
warnings: classified.warnings,
|
|
1146
1599
|
evidenceRefs: record.evidenceRef ? [record.evidenceRef] : [],
|
|
@@ -1189,7 +1642,7 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1189
1642
|
});
|
|
1190
1643
|
}
|
|
1191
1644
|
|
|
1192
|
-
const completionEvidence = evaluateWorkCompletionEvidence(work);
|
|
1645
|
+
const completionEvidence = evaluateWorkCompletionEvidence(work, ctx.sourceRevision, ctx.workspaceFingerprint);
|
|
1193
1646
|
const history = completionEvidence.history;
|
|
1194
1647
|
|
|
1195
1648
|
if (input.forceFailed || completionEvidence.status === 'failed') {
|
|
@@ -1264,6 +1717,47 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1264
1717
|
});
|
|
1265
1718
|
}
|
|
1266
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
|
+
|
|
1267
1761
|
// Checks and result evidence are necessary but not sufficient. Delivery and
|
|
1268
1762
|
// cleanup must produce the exact Work-owned receipt before Work can become
|
|
1269
1763
|
// terminal; a Run exit or a missing receipt is never completion authority.
|
|
@@ -1318,6 +1812,38 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1318
1812
|
});
|
|
1319
1813
|
}
|
|
1320
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
|
+
|
|
1321
1847
|
const destructiveCleanup = input.authorizeDestructiveCleanup === true;
|
|
1322
1848
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1323
1849
|
status: 'cancelled',
|
|
@@ -1334,9 +1860,17 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
1334
1860
|
// cleanup handler verifies ownership, cleanliness and successful removal.
|
|
1335
1861
|
worktreeRef: work.worktreeRef,
|
|
1336
1862
|
});
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1863
|
+
const planId = updated.planId;
|
|
1864
|
+
const planStepId = updated.planStepId;
|
|
1865
|
+
let plan = planId && ctx.planStore
|
|
1866
|
+
? getPlanContract(ctx.planStore, planId)
|
|
1339
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
|
+
}
|
|
1340
1874
|
|
|
1341
1875
|
return buildFacadeResult({
|
|
1342
1876
|
status: 'ok',
|
|
@@ -1373,6 +1907,7 @@ export function runGoalWorkloop(
|
|
|
1373
1907
|
objective: String(args.objective ?? ''),
|
|
1374
1908
|
acceptanceCriteria: Array.isArray(args.acceptance_criteria) ? args.acceptance_criteria.map(String) : undefined,
|
|
1375
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,
|
|
1376
1911
|
forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
|
|
1377
1912
|
checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
1378
1913
|
modeInput: {
|
|
@@ -1383,6 +1918,7 @@ export function runGoalWorkloop(
|
|
|
1383
1918
|
requiresInvestigation: args.requires_investigation === true,
|
|
1384
1919
|
requiresLongRunningChecks: args.requires_long_running_checks === true,
|
|
1385
1920
|
requiresParallelism: args.requires_parallelism === true,
|
|
1921
|
+
explicitMode: args.mode === 'scale' ? 'scale' : undefined,
|
|
1386
1922
|
needsDependencies: args.needs_dependencies === true,
|
|
1387
1923
|
requiresRecovery: args.requires_recovery === true,
|
|
1388
1924
|
requiresWorker: args.requires_worker === true,
|
|
@@ -1402,13 +1938,34 @@ export function runGoalWorkloop(
|
|
|
1402
1938
|
forceMode: args.force_mode === 'direct_control' || args.force_mode === 'goal_workloop' || args.force_mode === 'handoff_only'
|
|
1403
1939
|
? args.force_mode
|
|
1404
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,
|
|
1405
1946
|
planId: typeof args.plan_id === 'string' ? args.plan_id : undefined,
|
|
1406
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,
|
|
1407
1956
|
});
|
|
1408
1957
|
case 'continue':
|
|
1409
1958
|
return continueGoalWorkloop(ctx, {
|
|
1410
1959
|
workId: String(args.work_id ?? ''),
|
|
1411
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,
|
|
1412
1969
|
});
|
|
1413
1970
|
case 'verify':
|
|
1414
1971
|
return verifyGoalWorkloop(ctx, {
|