@moretea-labs/forge 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +70 -7
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +228 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
- package/adapters/computer/chatgpt-extension-target.ts +463 -0
- package/adapters/computer/chatgpt-target-router.ts +103 -0
- package/adapters/computer/chrome-extension/background.js +385 -0
- package/adapters/computer/chrome-extension/content.js +378 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +30 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/interaction-target-authority.ts +142 -18
- package/adapters/mcp/auth.ts +0 -1
- package/adapters/mcp/instructions.ts +5 -4
- package/adapters/mcp/multi-repository.ts +3 -19
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
- package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +18 -504
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +67 -23
- package/adapters/mcp/setup.ts +1 -3
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
- package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
- package/adapters/mcp/toolset-names.ts +5 -0
- package/adapters/mcp/toolset.ts +6 -5
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +154 -677
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/skill-version.json +2 -2
- package/docs/architecture/CURRENT.md +111 -41
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/domains/verification.md +1 -0
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/forge-plugin-management.md +2 -2
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +1 -1
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +2 -0
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +14 -6
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +25 -14
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +17 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +25 -63
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +48 -136
- package/scripts/check-runtime-architecture.mjs +622 -217
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/lib/project-init-lib.sh +3 -0
- package/scripts/open-source-audit-allowlist.txt +1 -2
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +0 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-mcp-http-runtime.ts +5 -0
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +11 -34
- package/src/cli/agent-jobs/integration.ts +18 -49
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/recovery.ts +135 -121
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +4 -43
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/context/source-materializer.ts +81 -100
- package/src/cli/controller/context-pack.ts +10 -8
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +24 -51
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +39 -1
- package/src/cli/editing/edit-session.ts +1 -7
- package/src/cli/local-bridge/console-automations.ts +18 -13
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +64 -191
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +35 -16
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/registry.ts +77 -11
- package/src/cli/repositories/structured-git.ts +62 -2
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/semantic-navigation-contract.ts +9 -0
- package/src/runtime/context/semantic-navigation.ts +6 -34
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
- package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
- package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +35 -2
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/workers/executor.ts +2 -6
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +85 -28
- package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
- package/src/runtime/plugins/browser-adapter.ts +757 -34
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
- package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime.ts +14 -1
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +222 -11
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/gmail-adapter.ts +23 -2
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +14 -7
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios-adapter.ts +1 -1
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -2
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +602 -428
- package/src/runtime/plugins/types.ts +20 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +296 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +105 -19
- package/src/runtime/root/package-runtime-release.ts +117 -18
- package/src/runtime/root/package-runtime-service.ts +17 -11
- package/src/runtime/root/release-execution-canary.ts +44 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -6
- package/src/runtime/root/release-materialize.ts +372 -28
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +159 -27
- package/src/runtime/root/scheduled-controller-composition.ts +28 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +36 -33
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -3
- package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3702 -342
- package/src/runtime/standalone-recovery/entry.ts +469 -39
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
- package/src/runtime/workflow/schedules/engine.ts +138 -172
- package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +201 -0
- package/supervisor/control-plane.ts +868 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +80 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +786 -0
- package/supervisor/native-messaging/host.ts +39 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +202 -0
- package/supervisor/server.ts +404 -0
- package/supervisor/store.ts +1451 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +255 -0
- package/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/stage-runtime-release.ts +0 -44
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -1,89 +1,64 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { globMatches } from '../../../cli/mcp/paths';
|
|
3
2
|
import {
|
|
4
3
|
createHandoffItem,
|
|
5
|
-
listHandoffItems,
|
|
6
4
|
type HandoffInboxStoreOptions,
|
|
7
5
|
} from './handoff-inbox-store';
|
|
8
|
-
import { getControllerSession } from '../../../../packages/kernel/controller/api/index';
|
|
9
6
|
import { executionPlacement, readForgeInstanceIdentity } from '../../../../packages/kernel/identity/api/index';
|
|
10
|
-
import { projectAutonomousGoalProgression, type ProgressionWorkSnapshot } from '../../../../packages/kernel/progression/api/index';
|
|
11
7
|
import {
|
|
12
8
|
applyEngineeringBlockerDisposition,
|
|
13
9
|
buildEngineeringBlockerDispositionReceipt,
|
|
14
10
|
buildEngineeringContextReceipt,
|
|
15
11
|
engineeringWorkProfileForRisk,
|
|
16
|
-
evaluateEngineeringAdmission,
|
|
17
|
-
appendVerificationRecord,
|
|
18
12
|
appendWorkEvidence,
|
|
19
13
|
appendWorkHandoffRef,
|
|
20
|
-
cancelWorkContract,
|
|
21
14
|
createWorkContract,
|
|
22
15
|
failWorkContract,
|
|
23
16
|
getWorkContract,
|
|
24
|
-
|
|
17
|
+
semanticWorkState,
|
|
25
18
|
listWorkContracts,
|
|
26
19
|
readActiveWorkCandidates,
|
|
27
20
|
recordWorkEvidenceState,
|
|
28
21
|
recordWorkScopeEvidence,
|
|
29
|
-
|
|
30
|
-
requestWorkImplementationReview,
|
|
22
|
+
reviseWorkSemanticContext,
|
|
31
23
|
summarizeWorkContract,
|
|
32
24
|
transitionWorkContractPhase,
|
|
33
25
|
updateWorkContract,
|
|
34
26
|
type EngineeringAdmissionEvidence,
|
|
27
|
+
type EvaluationPromotionReceipt,
|
|
35
28
|
type WorkContractStoreOptions,
|
|
36
29
|
} from '../../../../packages/kernel/work/api/index';
|
|
37
30
|
import {
|
|
38
|
-
claimPlanStepForWork,
|
|
39
|
-
completePlanStepForWork,
|
|
40
31
|
getPlanContract,
|
|
32
|
+
currentPlanSemanticRevision,
|
|
41
33
|
type PlanContractStoreOptions,
|
|
42
34
|
} from './plan-contract-store';
|
|
43
35
|
import { withPrimaryWorkAdmissionLock } from './semantic-admission';
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import { readRequirement } from '../persistence/requirement-store';
|
|
36
|
+
import { effectiveCurrentWorkVerificationRecords, evaluateWorkCompletionEvidence, evaluateWorkImplementationEvidence } from '../execution/work-evidence-policy';
|
|
37
|
+
import { hasSettledWorkDeliveryReceipt } from '../execution/work-completion-authority';
|
|
38
|
+
import { currentRequirementSemanticRevision, readRequirement } from '../persistence/requirement-store';
|
|
47
39
|
import {
|
|
48
|
-
classifyVerificationOutcome,
|
|
49
40
|
normalizeCheckIds,
|
|
50
41
|
reconcileVerificationHistory,
|
|
51
42
|
type CheckDefinitionLike,
|
|
52
43
|
} from './check-normalization';
|
|
53
44
|
import { evaluatePolicyGate } from './policy-gate';
|
|
54
|
-
import {
|
|
55
|
-
assertImplementationReviewPreDeliveryBoundary,
|
|
56
|
-
authoritativeImplementationReviewVerificationEvidence,
|
|
57
|
-
implementationReviewChangedPathDigest,
|
|
58
|
-
latestImplementationReview,
|
|
59
|
-
normalizeImplementationReviewChangedPaths,
|
|
60
|
-
workRequiresImplementationReview,
|
|
61
|
-
type ImplementationReviewDecision,
|
|
62
|
-
type ImplementationReviewCandidateIdentity,
|
|
63
|
-
type WorkImplementationReviewFinding,
|
|
64
|
-
type WorkImplementationReviewRecord,
|
|
65
|
-
} from '../../../../packages/kernel/work/api/index';
|
|
66
45
|
import { buildFacadeResult } from './facade-result';
|
|
67
46
|
import { validateSuggestedNextActions } from './suggested-actions';
|
|
68
47
|
import { buildWorkContinuationSnapshot } from './work-continuation';
|
|
69
48
|
import type {
|
|
70
49
|
CapabilityRisk,
|
|
71
50
|
EvidenceRef,
|
|
72
|
-
ExecutionModeSelectionInput,
|
|
73
51
|
FacadeResult,
|
|
74
52
|
PlanContract,
|
|
75
53
|
PolicyDecision,
|
|
76
54
|
SuggestedNextAction,
|
|
77
|
-
VerificationRecord,
|
|
78
55
|
WorkContract,
|
|
56
|
+
WorkStartFacts,
|
|
79
57
|
WorkKind,
|
|
80
58
|
WorkRisk,
|
|
81
59
|
} from './types';
|
|
82
|
-
import { selectExecutionMode } from './types';
|
|
83
|
-
import { resolveWorkspaceAdmissionConstraint } from '../routing/workspace-admission';
|
|
84
|
-
import { resolveContextPlane, type ContextScope } from '../../context/context-plane';
|
|
85
60
|
|
|
86
|
-
export type GoalWorkloopOperation = 'start' | 'continue' | '
|
|
61
|
+
export type GoalWorkloopOperation = 'start' | 'continue' | 'stop';
|
|
87
62
|
|
|
88
63
|
export interface GoalWorkloopContext {
|
|
89
64
|
workStore: WorkContractStoreOptions;
|
|
@@ -125,7 +100,8 @@ export interface GoalWorkloopStartInput {
|
|
|
125
100
|
forbiddenPaths?: string[];
|
|
126
101
|
checks?: string[];
|
|
127
102
|
constraints?: WorkContract['constraints'];
|
|
128
|
-
|
|
103
|
+
/** Concrete mechanical request facts. This capability is itself the explicit durable-Work choice. */
|
|
104
|
+
request: WorkStartFacts;
|
|
129
105
|
requestedBy?: WorkContract['requestedBy'];
|
|
130
106
|
/** Stable durable admission identity for system-originated Work such as recurrent incident repair. */
|
|
131
107
|
requestId?: string;
|
|
@@ -136,7 +112,6 @@ export interface GoalWorkloopStartInput {
|
|
|
136
112
|
issueId?: string;
|
|
137
113
|
approvalConfirmed?: boolean;
|
|
138
114
|
dryRun?: boolean;
|
|
139
|
-
forceMode?: WorkContract['mode'];
|
|
140
115
|
planId?: string;
|
|
141
116
|
planStepId?: string;
|
|
142
117
|
/** Explicit technical evidence shape chosen by the semantic Controller. Never inferred from objective/check text. */
|
|
@@ -170,45 +145,14 @@ export interface GoalWorkloopContinueInput {
|
|
|
170
145
|
/** Semantic Controller blocker classification; Forge derives the permitted action and persists the receipt. */
|
|
171
146
|
engineeringBlocker?: {
|
|
172
147
|
blockerId: string;
|
|
173
|
-
classification: 'same_root_cause' | 'unrelated';
|
|
148
|
+
classification: 'same_root_cause' | 'same_root_cause_scope_extension' | 'unrelated';
|
|
174
149
|
rationale: string;
|
|
175
150
|
semanticScopeKeys?: string[];
|
|
151
|
+
/** Required for classification=unrelated: the exact Work that already owns the blocker. */
|
|
152
|
+
linkedWorkId?: string;
|
|
176
153
|
};
|
|
177
154
|
}
|
|
178
155
|
|
|
179
|
-
export interface GoalWorkloopVerifyInput {
|
|
180
|
-
workId: string;
|
|
181
|
-
checkId: string;
|
|
182
|
-
/** Exact Git revision observed by the authoritative Work-bound verification Process. */
|
|
183
|
-
sourceRevision?: string;
|
|
184
|
-
/** Exact dirty-workspace content identity observed by the authoritative check. */
|
|
185
|
-
workspaceFingerprint?: string;
|
|
186
|
-
/** Stable semantic check-input identity used for exact evidence reuse. */
|
|
187
|
-
verificationInputFingerprint?: string;
|
|
188
|
-
/** Bounded command/invocation audit identity; not part of reusable semantic inputs. */
|
|
189
|
-
commandFingerprint?: string;
|
|
190
|
-
/** Persistence-safe Process receipt. Accepted only when its Work/repo/check identity matches this verification. */
|
|
191
|
-
receipt?: VerificationRecord['receipt'];
|
|
192
|
-
/** When true, simulate infrastructure failure rather than acceptance fail. */
|
|
193
|
-
infrastructureFailed?: boolean;
|
|
194
|
-
/** When true and check is valid, record acceptance failure. */
|
|
195
|
-
checkFailed?: boolean;
|
|
196
|
-
/** When true, skip without acceptance implication. */
|
|
197
|
-
skipped?: boolean;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export interface GoalWorkloopReviewInput {
|
|
201
|
-
workId: string;
|
|
202
|
-
decision: ImplementationReviewDecision;
|
|
203
|
-
rationale: string;
|
|
204
|
-
findings?: WorkImplementationReviewFinding[];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export interface GoalWorkloopFinalizeInput {
|
|
208
|
-
workId: string;
|
|
209
|
-
forceFailed?: boolean;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
156
|
export interface GoalWorkloopStopInput {
|
|
213
157
|
workId: string;
|
|
214
158
|
reason?: string;
|
|
@@ -220,57 +164,6 @@ function nowIso(ctx: GoalWorkloopContext): string {
|
|
|
220
164
|
return ctx.now?.() ?? new Date().toISOString();
|
|
221
165
|
}
|
|
222
166
|
|
|
223
|
-
function currentImplementationReviewCandidate(
|
|
224
|
-
ctx: GoalWorkloopContext,
|
|
225
|
-
work: WorkContract,
|
|
226
|
-
): ImplementationReviewCandidateIdentity {
|
|
227
|
-
const sourceRevision = ctx.sourceRevision?.trim() ?? '';
|
|
228
|
-
const verificationWorkspaceFingerprint = ctx.workspaceFingerprint?.trim() ?? '';
|
|
229
|
-
const workspaceFingerprint = ctx.implementationReviewWorkspaceFingerprint?.trim() ?? '';
|
|
230
|
-
const changedPaths = normalizeImplementationReviewChangedPaths(
|
|
231
|
-
ctx.workspaceChangedPaths ?? work.scopeEvidence?.actualChangedPaths ?? [],
|
|
232
|
-
);
|
|
233
|
-
if (!sourceRevision || !verificationWorkspaceFingerprint || !workspaceFingerprint) {
|
|
234
|
-
throw new Error('WORK_IMPLEMENTATION_REVIEW_SOURCE_IDENTITY_REQUIRED');
|
|
235
|
-
}
|
|
236
|
-
const verification = authoritativeImplementationReviewVerificationEvidence({
|
|
237
|
-
repoId: work.repoId,
|
|
238
|
-
workId: work.workId,
|
|
239
|
-
requiredCheckIds: work.checks,
|
|
240
|
-
records: work.checkRefs,
|
|
241
|
-
sourceRevision,
|
|
242
|
-
workspaceFingerprint: verificationWorkspaceFingerprint,
|
|
243
|
-
});
|
|
244
|
-
if (verification.missingCheckIds.length > 0) {
|
|
245
|
-
throw new Error(`WORK_IMPLEMENTATION_REVIEW_VERIFICATION_REQUIRED: ${verification.missingCheckIds.join(', ')}`);
|
|
246
|
-
}
|
|
247
|
-
return {
|
|
248
|
-
sourceRevision,
|
|
249
|
-
workspaceFingerprint,
|
|
250
|
-
verificationWorkspaceFingerprint,
|
|
251
|
-
changedPaths,
|
|
252
|
-
verificationEvidence: verification.evidence,
|
|
253
|
-
architectureEvidence: [],
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function implementationReviewAcceptanceSummary(work: WorkContract): string {
|
|
258
|
-
const summary = work.acceptanceCriteria.map((criterion) => criterion.trim()).filter(Boolean).join(' | ');
|
|
259
|
-
return (summary || 'No explicit acceptance criteria; reviewer assessed the Work objective and exact current implementation evidence.').slice(0, 2_000);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function implementationReviewSuggestedAction(workId: string): SuggestedNextAction {
|
|
263
|
-
return {
|
|
264
|
-
label: 'Review implementation',
|
|
265
|
-
tool: 'rh_work',
|
|
266
|
-
operation: 'review',
|
|
267
|
-
payload: { work_id: workId },
|
|
268
|
-
risk: 'workspace_write',
|
|
269
|
-
confidence: 'high',
|
|
270
|
-
reason: 'Verification is complete; an explicit Controller implementation decision is required before delivery.',
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
167
|
function workIdFor(objective: string): string {
|
|
275
168
|
const slug = objective
|
|
276
169
|
.toLowerCase()
|
|
@@ -280,71 +173,47 @@ function workIdFor(objective: string): string {
|
|
|
280
173
|
return `work-${slug}-${randomUUID().slice(0, 8)}`;
|
|
281
174
|
}
|
|
282
175
|
|
|
283
|
-
function linkedEngineeringBlockerWorkId(parentWorkId: string, blockerId: string): string {
|
|
284
|
-
const digest = createHash('sha256').update(`${parentWorkId}\0${blockerId.trim()}`).digest('hex').slice(0, 24);
|
|
285
|
-
return `work-linked-engineering-blocker-${digest}`;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
176
|
function handoffIdFor(prefix: string): string {
|
|
289
177
|
return `hnd-${prefix}-${randomUUID().slice(0, 8)}`;
|
|
290
178
|
}
|
|
291
179
|
|
|
292
180
|
function workRiskFor(input: GoalWorkloopStartInput): WorkRisk {
|
|
293
181
|
if (input.workKind === 'read_only_review') return 'readonly';
|
|
294
|
-
const risk = input.
|
|
295
|
-
if (input.
|
|
296
|
-
if (input.
|
|
182
|
+
const risk = input.request.risk;
|
|
183
|
+
if (input.request.secretAccess === true || input.request.destructive === true) return 'destructive';
|
|
184
|
+
if (input.request.remoteWrite === true) return 'high';
|
|
297
185
|
if (risk === 'readonly') return 'readonly';
|
|
298
186
|
if (risk === 'destructive' || risk === 'destructive_remote' || risk === 'raw_secret_config') return 'destructive';
|
|
299
187
|
if (risk === 'remote_write') return 'high';
|
|
300
188
|
if (risk === 'local_repo_write') return 'low';
|
|
189
|
+
if (risk === undefined) return 'low';
|
|
301
190
|
return 'medium';
|
|
302
191
|
}
|
|
303
192
|
|
|
304
193
|
function resolvedWorkKindFor(input: GoalWorkloopStartInput): WorkKind {
|
|
305
194
|
if (input.workKind) return input.workKind;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
&& input.modeInput.requiresRecovery === true;
|
|
313
|
-
if (typedRecoverableReadOnlyReview) return 'read_only_review';
|
|
314
|
-
if (input.modeInput.requiresExternalEffect === true && !repositoryChangeIntent) {
|
|
315
|
-
return input.modeInput.remoteWrite === true ? 'remote_effect' : 'local_effect';
|
|
195
|
+
if (input.request.requiresExternalEffect === true) {
|
|
196
|
+
// Mechanical effect classification only: a remote effect differs from a local
|
|
197
|
+
// effect in receipt identity. Predicted scope size is model strategy and is
|
|
198
|
+
// never consulted; callers declare `work_kind` when a repository change is
|
|
199
|
+
// also involved.
|
|
200
|
+
return input.request.remoteWrite === true ? 'remote_effect' : 'local_effect';
|
|
316
201
|
}
|
|
317
202
|
return 'repository_change';
|
|
318
203
|
}
|
|
319
204
|
|
|
320
205
|
function suggestedForWorkIdentity(workId: string, checks: string[], extras: SuggestedNextAction[] = []): SuggestedNextAction[] {
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
tool: 'rh_work',
|
|
333
|
-
operation: 'verify',
|
|
334
|
-
payload: { work_id: workId, check_id: checks[0] },
|
|
335
|
-
risk: 'workspace_write',
|
|
336
|
-
confidence: checks[0] ? 'high' : 'low',
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
label: 'Finalize when ready',
|
|
340
|
-
tool: 'rh_work',
|
|
341
|
-
operation: 'finalize',
|
|
342
|
-
payload: { work_id: workId },
|
|
343
|
-
risk: 'readonly',
|
|
344
|
-
confidence: 'medium',
|
|
345
|
-
},
|
|
346
|
-
];
|
|
347
|
-
return validateSuggestedNextActions([...extras, ...base], {
|
|
206
|
+
const semanticRead: SuggestedNextAction = {
|
|
207
|
+
label: 'Read current Work semantic context',
|
|
208
|
+
tool: 'rh_work',
|
|
209
|
+
operation: 'get',
|
|
210
|
+
payload: { work_id: workId },
|
|
211
|
+
risk: 'readonly',
|
|
212
|
+
confidence: 'high',
|
|
213
|
+
};
|
|
214
|
+
// Suggested actions expose facts/capabilities only. The model decides whether
|
|
215
|
+
// implementation, checks, review, delivery, or semantic completion is next.
|
|
216
|
+
return validateSuggestedNextActions([...extras, semanticRead], {
|
|
348
217
|
validCheckIds: checks,
|
|
349
218
|
}).actions;
|
|
350
219
|
}
|
|
@@ -369,21 +238,10 @@ export function routeWorkStart(
|
|
|
369
238
|
ctx: GoalWorkloopContext,
|
|
370
239
|
input: GoalWorkloopStartInput,
|
|
371
240
|
): FacadeResult {
|
|
372
|
-
const
|
|
373
|
-
if (placementResolution.ok === false) {
|
|
374
|
-
return buildFacadeResult({
|
|
375
|
-
status: 'blocked',
|
|
376
|
-
summary: `${placementResolution.code}: ${placementResolution.message}`,
|
|
377
|
-
data: { executionStarted: false, workContractCreated: false, placementConstraintConflict: true },
|
|
378
|
-
rawAvailable: false,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
const placementConstraint = placementResolution.constraint;
|
|
241
|
+
const workspaceMode: 'current' | 'isolated' = input.constraints?.workspaceMode === 'isolated' || input.constraints?.workspaceMode === 'auto' ? 'isolated' : 'current';
|
|
382
242
|
const canonicalConstraints: WorkContract['constraints'] = {
|
|
383
243
|
...(input.constraints ?? {}),
|
|
384
|
-
workspaceMode
|
|
385
|
-
requireWorktree: placementConstraint.requireWorktree,
|
|
386
|
-
directMainProhibited: placementConstraint.directMainProhibited,
|
|
244
|
+
workspaceMode,
|
|
387
245
|
};
|
|
388
246
|
const strategyConflictRequiresApproval = input.constraints?.architectureStrategyChange === true
|
|
389
247
|
|| input.constraints?.conflictsWithThinHarnessPolicy === true
|
|
@@ -409,7 +267,7 @@ export function routeWorkStart(
|
|
|
409
267
|
input.workKind === undefined
|
|
410
268
|
&& input.workRelation === 'new_goal'
|
|
411
269
|
&& relatedLifecycleSource?.workKind === 'read_only_review'
|
|
412
|
-
&& (relatedLifecycleSource
|
|
270
|
+
&& semanticWorkState(relatedLifecycleSource) !== 'open',
|
|
413
271
|
);
|
|
414
272
|
if (
|
|
415
273
|
inheritedReadOnlyReview
|
|
@@ -430,24 +288,17 @@ export function routeWorkStart(
|
|
|
430
288
|
const normalized = value.trim().replace(/\\/g, '/');
|
|
431
289
|
return normalized === '*' || normalized === '**' || normalized === '**/*';
|
|
432
290
|
});
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
(input.modeInput.mutation === false || (input.modeInput.mutation === undefined && input.modeInput.risk === 'readonly'))
|
|
438
|
-
&& input.modeInput.requiresInvestigation === true
|
|
439
|
-
&& input.modeInput.requiresRecovery === true
|
|
440
|
-
)
|
|
441
|
-
);
|
|
291
|
+
// A read-only review is an explicit `work_kind`, or an explicit source-mutation
|
|
292
|
+
// fence that proves no repository path may change. Forge never infers it from
|
|
293
|
+
// caller hints about investigation depth or recovery needs.
|
|
294
|
+
const typedReadOnlyReviewRequested = requestedWorkKind === undefined && explicitSourceMutationFence;
|
|
442
295
|
const effectiveWorkKind = requestedWorkKind ?? (typedReadOnlyReviewRequested ? 'read_only_review' : undefined);
|
|
443
296
|
const readOnlyReviewRequested = effectiveWorkKind === 'read_only_review';
|
|
444
297
|
const readOnlyMutationConflict = readOnlyReviewRequested && (
|
|
445
|
-
input.
|
|
446
|
-
||
|
|
447
|
-
||
|
|
448
|
-
|| input.
|
|
449
|
-
|| input.modeInput.remoteWrite === true
|
|
450
|
-
|| input.modeInput.destructive === true
|
|
298
|
+
input.request.mutation === true
|
|
299
|
+
|| input.request.requiresExternalEffect === true
|
|
300
|
+
|| input.request.remoteWrite === true
|
|
301
|
+
|| input.request.destructive === true
|
|
451
302
|
);
|
|
452
303
|
if (readOnlyMutationConflict) {
|
|
453
304
|
return buildFacadeResult({
|
|
@@ -456,185 +307,49 @@ export function routeWorkStart(
|
|
|
456
307
|
data: { executionStarted: false, workContractCreated: false, workKind: effectiveWorkKind },
|
|
457
308
|
});
|
|
458
309
|
}
|
|
459
|
-
const contextScopes: ContextScope[] = [];
|
|
460
|
-
if (input.requirementId?.trim()) contextScopes.push({ schemaVersion: 1, kind: 'requirement', id: input.requirementId.trim() });
|
|
461
|
-
if (input.planId?.trim()) contextScopes.push({ schemaVersion: 1, kind: 'plan', id: input.planId.trim() });
|
|
462
|
-
if (input.relatedWorkId?.trim()) contextScopes.push({ schemaVersion: 1, kind: 'work', id: input.relatedWorkId.trim() });
|
|
463
|
-
const resolvedContext = ctx.workStore.controllerHome
|
|
464
|
-
? resolveContextPlane({
|
|
465
|
-
controllerHome: ctx.workStore.controllerHome,
|
|
466
|
-
scopes: contextScopes,
|
|
467
|
-
intent: input.modeInput.routePolicyInput?.intent.taskIntent ?? 'implementation',
|
|
468
|
-
now: nowIso(ctx),
|
|
469
|
-
})
|
|
470
|
-
: undefined;
|
|
471
310
|
// A terminal continuation is not a new semantic request. Its durable predecessor
|
|
472
311
|
// and Plan lineage already define the goal scope, while the successor Plan step
|
|
473
|
-
// becomes the Work objective later in startGoalWorkloop.
|
|
474
|
-
// Controller to duplicate semantic text merely to satisfy generic route selection.
|
|
475
|
-
// This fallback is routing context only and never becomes successor Work authority.
|
|
312
|
+
// becomes the Work objective later in startGoalWorkloop.
|
|
476
313
|
const routeObjective = input.objective.trim()
|
|
477
|
-
|| (input.workRelation === 'continue' && relatedLifecycleSource
|
|
314
|
+
|| (input.workRelation === 'continue' && relatedLifecycleSource && semanticWorkState(relatedLifecycleSource) === 'completed'
|
|
478
315
|
? relatedLifecycleSource.objective.trim()
|
|
479
316
|
: '');
|
|
480
|
-
const
|
|
481
|
-
...input.
|
|
482
|
-
contextRouteHints: input.modeInput.contextRouteHints ?? resolvedContext?.routeHints,
|
|
317
|
+
const effectiveRequest: WorkStartFacts = {
|
|
318
|
+
...input.request,
|
|
483
319
|
objective: routeObjective,
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
workspaceDirty: ctx.workspaceDirty ?? input.modeInput.workspaceDirty,
|
|
488
|
-
mutation: readOnlyReviewRequested ? false : input.modeInput.mutation ?? input.modeInput.risk !== 'readonly',
|
|
489
|
-
risk: readOnlyReviewRequested ? 'readonly' : input.modeInput.risk,
|
|
320
|
+
workspaceDirty: ctx.workspaceDirty ?? input.request.workspaceDirty,
|
|
321
|
+
mutation: readOnlyReviewRequested ? false : input.request.mutation ?? input.request.risk !== 'readonly',
|
|
322
|
+
risk: readOnlyReviewRequested ? 'readonly' : input.request.risk,
|
|
490
323
|
approvalConfirmed: input.approvalConfirmed === true,
|
|
491
324
|
requiresUserApproval: input.approvalConfirmed === true
|
|
492
325
|
? false
|
|
493
|
-
: input.
|
|
326
|
+
: input.request.requiresUserApproval === true || strategyConflictRequiresApproval,
|
|
494
327
|
};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
? {
|
|
504
|
-
...selected,
|
|
505
|
-
mode: input.forceMode,
|
|
506
|
-
reason: `Forced mode: ${input.forceMode}. ${selected.reason}`,
|
|
507
|
-
createWorkContract: input.forceMode === 'handoff_only' ? false : selected.requiresWork,
|
|
508
|
-
createHandoff: input.forceMode === 'handoff_only',
|
|
509
|
-
}
|
|
510
|
-
: selected;
|
|
511
|
-
const evaluateAccessPolicy = (selected: ReturnType<typeof selectExecutionMode>) => evaluatePolicyGate({
|
|
512
|
-
capabilityId: selected.mode === 'direct_control' ? 'repository.direct_edit' : selected.mode === 'goal_workloop' ? 'controller.goal_workloop' : 'controller.handoff_inbox',
|
|
513
|
-
risk: effectiveModeInput.risk
|
|
514
|
-
?? (input.modeInput.secretAccess === true ? 'raw_secret_config'
|
|
515
|
-
: input.modeInput.destructive === true ? 'destructive'
|
|
516
|
-
: input.modeInput.remoteWrite === true ? 'remote_write'
|
|
517
|
-
: input.modeInput.requiresApproval === true || input.modeInput.requiresUserApproval === true ? 'workspace_write'
|
|
518
|
-
: selected.mode === 'direct_control' ? 'local_repo_write'
|
|
519
|
-
: selected.mode === 'goal_workloop' ? 'workspace_write'
|
|
520
|
-
: 'readonly'),
|
|
328
|
+
const evaluateAccessPolicy = () => evaluatePolicyGate({
|
|
329
|
+
capabilityId: 'controller.work_contract',
|
|
330
|
+
risk: effectiveRequest.risk
|
|
331
|
+
?? (input.request.secretAccess === true ? 'raw_secret_config'
|
|
332
|
+
: input.request.destructive === true ? 'destructive'
|
|
333
|
+
: input.request.remoteWrite === true ? 'remote_write'
|
|
334
|
+
: input.request.requiresApproval === true || input.request.requiresUserApproval === true ? 'workspace_write'
|
|
335
|
+
: 'workspace_write'),
|
|
521
336
|
accessMode: input.constraints?.accessMode,
|
|
522
337
|
approvalConfirmed: input.approvalConfirmed === true,
|
|
523
338
|
dryRun: input.dryRun === true,
|
|
524
|
-
directEditBoundary: {
|
|
525
|
-
scopeClear: effectiveModeInput.scopeClear,
|
|
526
|
-
maxChangedFiles: effectiveModeInput.expectedFiles,
|
|
527
|
-
maxChangedLines: effectiveModeInput.expectedChangedLines,
|
|
528
|
-
pathsExplicit: effectiveModeInput.scopeClear,
|
|
529
|
-
},
|
|
530
339
|
});
|
|
340
|
+
// Missing mechanical admission fields are reported to the caller verbatim.
|
|
341
|
+
// Forge does not translate them into an engineering-mode recommendation.
|
|
342
|
+
const missingContractFields: string[] = [];
|
|
343
|
+
if (!input.request.scopeClear) missingContractFields.push('scopeSummary', 'acceptanceCriteria', 'allowedPaths');
|
|
344
|
+
if (input.objective.trim().length === 0) missingContractFields.push('objective');
|
|
531
345
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return buildFacadeResult({
|
|
535
|
-
status: 'blocked',
|
|
536
|
-
summary: 'WORKSPACE_PLACEMENT_DIRECT_CONTROL_FORBIDDEN: typed placement requires isolation, so force_mode=direct_control cannot override admission policy.',
|
|
537
|
-
data: { executionStarted: false, workContractCreated: false, routeDecision: selectedMode.routeDecision },
|
|
538
|
-
rawAvailable: false,
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
let policy = evaluateAccessPolicy(selectedMode);
|
|
542
|
-
if (policy.decision === 'allowed' && effectiveModeInput.requiresUserApproval !== true) {
|
|
543
|
-
selectedMode = selectExecutionMode({ ...effectiveModeInput, approvalConfirmed: true });
|
|
544
|
-
policy = evaluateAccessPolicy(selectedMode);
|
|
545
|
-
}
|
|
546
|
-
let mode = applyForcedMode(selectedMode);
|
|
547
|
-
const routeApprovalRequired = mode.routeDecision.requiresApproval
|
|
548
|
-
&& mode.routeDecision.waitForUser
|
|
549
|
-
&& mode.routeDecision.approvalState !== 'blocked_by_policy';
|
|
550
|
-
const approvalRequired = policy.decision === 'approval_required' || routeApprovalRequired;
|
|
551
|
-
|
|
552
|
-
// Route Policy is the sole execution-depth authority. Existing Work is
|
|
553
|
-
// ownership context, not a reason to upgrade an otherwise Direct operation
|
|
554
|
-
// into Durable Work. Explicit Work/Plan ownership is resolved only after a
|
|
555
|
-
// durable route has already been selected; checkout writer conflicts affect
|
|
556
|
-
// placement, not semantic task identity.
|
|
346
|
+
const policy = evaluateAccessPolicy();
|
|
347
|
+
const approvalRequired = policy.decision === 'approval_required';
|
|
557
348
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const normalized = normalizeCheckIds(input.checks ?? [], available);
|
|
561
|
-
// Explicit ownership may annotate a Direct operation, but it never changes
|
|
562
|
-
// the RouteDecision. The actual repository mutation can pass this Work id
|
|
563
|
-
// to repository_safe_patch_apply / repository_command_execute for evidence
|
|
564
|
-
// attribution while staying on the Direct/Process path.
|
|
565
|
-
const directAdmissionSnapshot = input.relatedWorkId || (input.planId && input.planStepId)
|
|
566
|
-
? readActiveWorkCandidates({ ...ctx.workStore, limit: 100 })
|
|
567
|
-
: { contracts: [], invalid: [] };
|
|
568
|
-
const invalidDirectOwner = directAdmissionSnapshot.invalid.find((candidate) =>
|
|
569
|
-
candidate.workId === input.relatedWorkId
|
|
570
|
-
|| (input.planId && input.planStepId && candidate.planId === input.planId && candidate.planStepId === input.planStepId));
|
|
571
|
-
if (invalidDirectOwner) {
|
|
572
|
-
return buildFacadeResult({
|
|
573
|
-
status: 'blocked',
|
|
574
|
-
summary: `WORK_ADMISSION_INVALID_OWNER: ${invalidDirectOwner.workId} is malformed and cannot be used as Direct Control ownership authority.`,
|
|
575
|
-
data: { executionStarted: false, workContractCreated: false, invalidWorkId: invalidDirectOwner.workId, invalidWorkError: invalidDirectOwner.error },
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
const directOwnershipCandidates = directAdmissionSnapshot.contracts
|
|
579
|
-
.filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary');
|
|
580
|
-
const directPlan = input.planId && ctx.planStore ? getPlanContract(ctx.planStore, input.planId) : undefined;
|
|
581
|
-
const directPlanStep = directPlan && input.planStepId ? directPlan.steps.find((step) => step.id === input.planStepId) : undefined;
|
|
582
|
-
const directOwner = (input.relatedWorkId
|
|
583
|
-
? directOwnershipCandidates.find((candidate) => candidate.workId === input.relatedWorkId)
|
|
584
|
-
: undefined)
|
|
585
|
-
?? (directPlanStep?.workId
|
|
586
|
-
? directOwnershipCandidates.find((candidate) => candidate.workId === directPlanStep.workId)
|
|
587
|
-
: undefined);
|
|
588
|
-
const suggested = validateSuggestedNextActions([
|
|
589
|
-
{
|
|
590
|
-
label: 'Apply bounded direct edit',
|
|
591
|
-
tool: 'rh_work',
|
|
592
|
-
operation: 'start',
|
|
593
|
-
payload: { mode: 'direct_control', objective: input.objective.slice(0, 200) },
|
|
594
|
-
risk: 'local_repo_write',
|
|
595
|
-
confidence: 'high',
|
|
596
|
-
reason: 'Small supervised task stays on Direct Control; no WorkContract created.',
|
|
597
|
-
},
|
|
598
|
-
...normalized.suggestedNextActions,
|
|
599
|
-
{
|
|
600
|
-
label: 'Read repository context',
|
|
601
|
-
tool: 'rh_context',
|
|
602
|
-
operation: 'get',
|
|
603
|
-
risk: 'readonly',
|
|
604
|
-
confidence: 'medium',
|
|
605
|
-
},
|
|
606
|
-
], { validCheckIds: normalized.validCheckIds }).actions;
|
|
607
|
-
|
|
608
|
-
return buildFacadeResult({
|
|
609
|
-
status: policy.decision === 'denied' ? 'blocked' : 'ok',
|
|
610
|
-
summary: `Direct control recommended. No WorkContract created. ${mode.reason}`,
|
|
611
|
-
data: {
|
|
612
|
-
mode,
|
|
613
|
-
policy,
|
|
614
|
-
workContractCreated: false,
|
|
615
|
-
directControlPreserved: true,
|
|
616
|
-
objective: input.objective.slice(0, 1_000),
|
|
617
|
-
normalizedChecks: normalized,
|
|
618
|
-
...(directOwner ? {
|
|
619
|
-
ownership: {
|
|
620
|
-
workId: directOwner.workId,
|
|
621
|
-
relation: input.workRelation ?? 'continue',
|
|
622
|
-
executionDepthPreserved: true,
|
|
623
|
-
},
|
|
624
|
-
} : {}),
|
|
625
|
-
},
|
|
626
|
-
warnings: [...policy.warnings, ...normalized.warnings],
|
|
627
|
-
suggestedNextActions: suggested,
|
|
628
|
-
rawAvailable: false,
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// Policy approval decisions stop before Work creation. Ordinary host-managed local work
|
|
633
|
-
// reaches this point only after the same Route Policy has been replayed with the Access
|
|
634
|
-
// Policy's explicit allowed decision as authorization evidence.
|
|
349
|
+
// Policy approval decisions stop before Work creation. Ordinary host-managed
|
|
350
|
+
// local work reaches this point only after the Access Policy authorized it.
|
|
635
351
|
const blockForHandoff =
|
|
636
|
-
|
|
637
|
-
|| policy.decision === 'denied'
|
|
352
|
+
policy.decision === 'denied'
|
|
638
353
|
|| policy.decision === 'approval_required';
|
|
639
354
|
|
|
640
355
|
if (blockForHandoff) {
|
|
@@ -643,20 +358,19 @@ export function routeWorkStart(
|
|
|
643
358
|
repoId: ctx.repoId,
|
|
644
359
|
title: 'Work blocked pending decision',
|
|
645
360
|
severity: policy.decision === 'denied' ? 'blocked' : 'needs_review',
|
|
646
|
-
creationReason: !input.
|
|
361
|
+
creationReason: !input.request.scopeClear
|
|
647
362
|
? 'invalid_objective'
|
|
648
363
|
: approvalRequired
|
|
649
|
-
? (input.
|
|
364
|
+
? (input.request.destructive ? 'destructive_action_requires_confirmation' : 'policy_approval_required')
|
|
650
365
|
: 'missing_authorization',
|
|
651
|
-
reason:
|
|
652
|
-
summary: `
|
|
366
|
+
reason: policy.reason,
|
|
367
|
+
summary: `Execution blocked by access policy: ${policy.reason}`,
|
|
653
368
|
currentState: {
|
|
654
369
|
repoId: ctx.repoId,
|
|
655
|
-
mode: 'handoff_only',
|
|
656
370
|
statusSummary: 'waiting for ChatGPT or user decision; no execution started',
|
|
657
|
-
blockedBy:
|
|
371
|
+
blockedBy: missingContractFields,
|
|
658
372
|
},
|
|
659
|
-
attemptedActions: ['
|
|
373
|
+
attemptedActions: ['work_start'],
|
|
660
374
|
evidenceRefs: [],
|
|
661
375
|
blockingDecision: approvalRequired
|
|
662
376
|
? 'Approve side effects or restate a safer objective.'
|
|
@@ -669,27 +383,19 @@ export function routeWorkStart(
|
|
|
669
383
|
operation: 'start',
|
|
670
384
|
label: 'Approve and start work',
|
|
671
385
|
summary: 'Create the work contract with the original scope and explicit approval.',
|
|
672
|
-
risk: input.
|
|
386
|
+
risk: input.request.destructive ? 'destructive' : 'workspace_write',
|
|
673
387
|
payload: {
|
|
674
388
|
objective: input.objective,
|
|
675
389
|
acceptanceCriteria: input.acceptanceCriteria,
|
|
676
390
|
allowedPaths: input.allowedPaths,
|
|
677
391
|
forbiddenPaths: input.forbiddenPaths,
|
|
678
392
|
checkIds: input.checks,
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
requiresInvestigation: input.modeInput.requiresInvestigation,
|
|
683
|
-
requiresLongRunningChecks: input.modeInput.requiresLongRunningChecks,
|
|
684
|
-
requiresWorker: input.modeInput.requiresWorker,
|
|
685
|
-
requiresApproval: input.modeInput.requiresApproval === true || input.modeInput.requiresUserApproval === true,
|
|
686
|
-
destructive: input.modeInput.destructive === true,
|
|
393
|
+
scopeClear: input.request.scopeClear,
|
|
394
|
+
requiresApproval: input.request.requiresApproval === true || input.request.requiresUserApproval === true,
|
|
395
|
+
destructive: input.request.destructive === true,
|
|
687
396
|
accessMode: input.constraints?.accessMode,
|
|
688
|
-
workspaceMode
|
|
689
|
-
requireWorktree: placementConstraint.requireWorktree,
|
|
690
|
-
directMainProhibited: placementConstraint.directMainProhibited,
|
|
397
|
+
workspaceMode,
|
|
691
398
|
approvalConfirmed: true,
|
|
692
|
-
forceMode: 'goal_workloop',
|
|
693
399
|
},
|
|
694
400
|
}
|
|
695
401
|
: undefined,
|
|
@@ -706,9 +412,8 @@ export function routeWorkStart(
|
|
|
706
412
|
|
|
707
413
|
return buildFacadeResult({
|
|
708
414
|
status: policy.decision === 'denied' ? 'blocked' : approvalRequired ? 'approval_required' : 'blocked',
|
|
709
|
-
summary:
|
|
415
|
+
summary: 'Blocked: no WorkContract created and no execution started until the policy decision is resolved.',
|
|
710
416
|
data: {
|
|
711
|
-
mode,
|
|
712
417
|
policy,
|
|
713
418
|
workContractCreated: false,
|
|
714
419
|
handoffId: handoff.id,
|
|
@@ -738,33 +443,20 @@ export function routeWorkStart(
|
|
|
738
443
|
return startGoalWorkloop(ctx, {
|
|
739
444
|
...input,
|
|
740
445
|
constraints: canonicalConstraints,
|
|
741
|
-
|
|
446
|
+
request: effectiveRequest,
|
|
742
447
|
workKind: effectiveWorkKind,
|
|
743
|
-
}, policy
|
|
448
|
+
}, policy);
|
|
744
449
|
}
|
|
745
450
|
|
|
746
451
|
export function startGoalWorkloop(
|
|
747
452
|
ctx: GoalWorkloopContext,
|
|
748
453
|
input: GoalWorkloopStartInput,
|
|
749
454
|
policy?: PolicyDecision,
|
|
750
|
-
executionMode: 'direct_control' | 'goal_workloop' = 'goal_workloop',
|
|
751
|
-
routeDecision = selectExecutionMode({ ...input.modeInput, objective: input.objective, knownPaths: input.allowedPaths }).routeDecision,
|
|
752
455
|
): FacadeResult {
|
|
753
|
-
const
|
|
754
|
-
if (placementResolution.ok === false) {
|
|
755
|
-
return buildFacadeResult({
|
|
756
|
-
status: 'blocked',
|
|
757
|
-
summary: `${placementResolution.code}: ${placementResolution.message}`,
|
|
758
|
-
data: { executionStarted: false, workContractCreated: false, placementConstraintConflict: true },
|
|
759
|
-
rawAvailable: false,
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
const placementConstraint = placementResolution.constraint;
|
|
456
|
+
const workspaceMode: 'current' | 'isolated' = input.constraints?.workspaceMode === 'isolated' || input.constraints?.workspaceMode === 'auto' ? 'isolated' : 'current';
|
|
763
457
|
const canonicalConstraints: WorkContract['constraints'] = {
|
|
764
458
|
...(input.constraints ?? {}),
|
|
765
|
-
workspaceMode
|
|
766
|
-
requireWorktree: placementConstraint.requireWorktree,
|
|
767
|
-
directMainProhibited: placementConstraint.directMainProhibited,
|
|
459
|
+
workspaceMode,
|
|
768
460
|
};
|
|
769
461
|
const hasStrongAdmissionBinding = Boolean(input.planId || input.planStepId || input.requirementId || input.relatedWorkId || input.workRelation);
|
|
770
462
|
if (hasStrongAdmissionBinding && !ctx.semanticAdmissionLocked) {
|
|
@@ -772,12 +464,20 @@ export function startGoalWorkloop(
|
|
|
772
464
|
{ ...ctx, semanticAdmissionLocked: true },
|
|
773
465
|
input,
|
|
774
466
|
policy,
|
|
775
|
-
executionMode,
|
|
776
|
-
routeDecision,
|
|
777
467
|
));
|
|
778
468
|
}
|
|
779
469
|
const at = nowIso(ctx);
|
|
780
|
-
|
|
470
|
+
let resolvedWorkKind: WorkKind;
|
|
471
|
+
try {
|
|
472
|
+
resolvedWorkKind = resolvedWorkKindFor(input);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
return buildFacadeResult({
|
|
475
|
+
status: 'blocked',
|
|
476
|
+
summary: error instanceof Error ? error.message : 'WORK_KIND_RESOLUTION_INVALID',
|
|
477
|
+
data: { executionStarted: false, workContractCreated: false },
|
|
478
|
+
rawAvailable: false,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
781
481
|
const workRisk = workRiskFor(input);
|
|
782
482
|
// External-effect risk is governed by authorization/confirmation/receipt
|
|
783
483
|
// semantics. Engineering admission is specifically for repository mutation.
|
|
@@ -804,27 +504,11 @@ export function startGoalWorkloop(
|
|
|
804
504
|
});
|
|
805
505
|
}
|
|
806
506
|
const engineeringMutation = resolvedWorkKind === 'repository_change'
|
|
807
|
-
&& (input.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
mutation: engineeringMutation,
|
|
812
|
-
});
|
|
813
|
-
if (!engineeringAdmission.allowed) {
|
|
814
|
-
return buildFacadeResult({
|
|
815
|
-
status: 'blocked',
|
|
816
|
-
summary: `${engineeringAdmission.code}: ${engineeringProfile.riskClass} mutation is missing required pre-mutation engineering evidence.`,
|
|
817
|
-
data: {
|
|
818
|
-
executionStarted: false,
|
|
819
|
-
workContractCreated: false,
|
|
820
|
-
engineeringProfile,
|
|
821
|
-
engineeringContext,
|
|
822
|
-
missingEngineeringEvidence: engineeringAdmission.missing,
|
|
823
|
-
},
|
|
824
|
-
});
|
|
825
|
-
}
|
|
507
|
+
&& (input.request.mutation ?? input.request.risk !== 'readonly');
|
|
508
|
+
// Engineering context is durable model/project guidance only. Safety and
|
|
509
|
+
// capability admission is enforced by the concrete executor, never here.
|
|
510
|
+
void engineeringMutation;
|
|
826
511
|
const available = ctx.availableChecks ?? [];
|
|
827
|
-
const workspaceMode = placementConstraint.workspaceMode;
|
|
828
512
|
const activeAdmissionSnapshot = readActiveWorkCandidates({ ...ctx.workStore, limit: 100 });
|
|
829
513
|
const activeWorks = activeAdmissionSnapshot.contracts
|
|
830
514
|
.filter((candidate) => (candidate.lifecycleRole ?? 'primary') === 'primary');
|
|
@@ -841,14 +525,9 @@ export function startGoalWorkloop(
|
|
|
841
525
|
const relatedLifecycleSource = input.relatedWorkId
|
|
842
526
|
? getWorkContract(ctx.workStore, input.relatedWorkId)
|
|
843
527
|
: undefined;
|
|
844
|
-
const workspaceOwner = ctx.checkoutId
|
|
845
|
-
? activeWorks.find((candidate) => candidate.checkoutId === ctx.checkoutId
|
|
846
|
-
&& candidate.worktreePolicy.required !== true
|
|
847
|
-
&& !candidate.worktreeRef)
|
|
848
|
-
: undefined;
|
|
849
528
|
const terminalContinuationSource = input.workRelation === 'continue'
|
|
850
529
|
&& relatedLifecycleSource
|
|
851
|
-
&&
|
|
530
|
+
&& semanticWorkState(relatedLifecycleSource) !== 'open'
|
|
852
531
|
? relatedLifecycleSource
|
|
853
532
|
: undefined;
|
|
854
533
|
if (terminalContinuationSource && !terminalContinuationSource.requirementId && !terminalContinuationSource.planId) {
|
|
@@ -863,7 +542,7 @@ export function startGoalWorkloop(
|
|
|
863
542
|
: undefined;
|
|
864
543
|
|
|
865
544
|
let resolvedPlanId = input.planId?.trim() || undefined;
|
|
866
|
-
|
|
545
|
+
const resolvedPlanStepId = input.planStepId?.trim() || undefined;
|
|
867
546
|
if (terminalContinuationSource?.planId) {
|
|
868
547
|
if (resolvedPlanId && resolvedPlanId !== terminalContinuationSource.planId) {
|
|
869
548
|
return buildFacadeResult({
|
|
@@ -874,109 +553,25 @@ export function startGoalWorkloop(
|
|
|
874
553
|
}
|
|
875
554
|
resolvedPlanId = terminalContinuationSource.planId;
|
|
876
555
|
}
|
|
556
|
+
// Plan and Plan item references are recorded provenance for this Work. They
|
|
557
|
+
// never select the successor, own the scope, gate admission or require
|
|
558
|
+
// acceptance; only the Plan's existence is validated so a Work cannot record a
|
|
559
|
+
// reference to a Plan authority that does not exist.
|
|
877
560
|
const plan = resolvedPlanId && ctx.planStore ? getPlanContract(ctx.planStore, resolvedPlanId) : undefined;
|
|
878
|
-
if (
|
|
561
|
+
if (resolvedPlanId && !plan) {
|
|
879
562
|
return buildFacadeResult({
|
|
880
563
|
status: 'blocked',
|
|
881
|
-
summary:
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
status: 'blocked',
|
|
890
|
-
summary: `WORK_SUCCESSOR_PLAN_STEP_NOT_FOUND: predecessor ${terminalContinuationSource.workId} references ${terminalContinuationSource.planStepId}.`,
|
|
891
|
-
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, predecessorPlanStepId: terminalContinuationSource.planStepId },
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
const requirementRecord = plan.requirementId && ctx.workStore.controllerHome
|
|
895
|
-
? readRequirement({ controllerHome: ctx.workStore.controllerHome }, plan.requirementId)
|
|
896
|
-
: undefined;
|
|
897
|
-
const progressionWorks: ProgressionWorkSnapshot[] = plan.steps
|
|
898
|
-
.flatMap((candidate) => candidate.workId ? [getWorkContract(ctx.workStore, candidate.workId)] : [])
|
|
899
|
-
.filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate))
|
|
900
|
-
.map((candidate) => ({
|
|
901
|
-
workId: candidate.workId,
|
|
902
|
-
requirementId: candidate.requirementId,
|
|
903
|
-
planId: candidate.planId,
|
|
904
|
-
planStepId: candidate.planStepId,
|
|
905
|
-
status: candidate.status,
|
|
906
|
-
baseRevision: candidate.baseRevision,
|
|
907
|
-
completionTargetRevision: candidate.completionReceipt && 'targetRevision' in candidate.completionReceipt
|
|
908
|
-
? candidate.completionReceipt.targetRevision
|
|
909
|
-
: undefined,
|
|
910
|
-
}));
|
|
911
|
-
const progression = projectAutonomousGoalProgression({
|
|
912
|
-
requirement: {
|
|
913
|
-
requirementId: plan.requirementId ?? terminalContinuationSource.requirementId ?? `plan:${plan.planId}`,
|
|
914
|
-
state: requirementRecord?.value.state ?? 'active',
|
|
915
|
-
revision: requirementRecord?.revision ?? 0,
|
|
916
|
-
},
|
|
917
|
-
plan: {
|
|
918
|
-
planId: plan.planId,
|
|
919
|
-
requirementId: plan.requirementId,
|
|
920
|
-
sourceRevision: plan.sourceRevision,
|
|
921
|
-
status: plan.status,
|
|
922
|
-
steps: plan.steps.map((candidate) => ({ id: candidate.id, dependencies: candidate.dependencies, status: candidate.status, workId: candidate.workId })),
|
|
564
|
+
summary: terminalContinuationSource?.planId
|
|
565
|
+
? `WORK_SUCCESSOR_PLAN_NOT_FOUND: ${terminalContinuationSource.planId}. Recover the durable Plan authority before creating a successor Work.`
|
|
566
|
+
: `PLAN_NOT_FOUND: ${resolvedPlanId}. Recover or create the semantic Plan before using it as Work provenance.`,
|
|
567
|
+
data: {
|
|
568
|
+
executionStarted: false,
|
|
569
|
+
workContractCreated: false,
|
|
570
|
+
planId: resolvedPlanId,
|
|
571
|
+
...(terminalContinuationSource?.planId ? { predecessorWorkId: terminalContinuationSource.workId } : {}),
|
|
923
572
|
},
|
|
924
|
-
currentSourceRevision: ctx.sourceRevision ?? plan.sourceRevision,
|
|
925
|
-
works: progressionWorks,
|
|
926
|
-
controllerRounds: [],
|
|
927
573
|
});
|
|
928
|
-
if (progression.kind === 'request_controller_acceptance') {
|
|
929
|
-
return buildFacadeResult({
|
|
930
|
-
status: 'blocked',
|
|
931
|
-
summary: `PLAN_STEP_SEMANTIC_ACCEPTANCE_REQUIRED: ${plan.planId}/${progression.planStepId ?? predecessorStep.id} has machine-complete delivery. Accept the delivered step before advancing the same semantic goal.`,
|
|
932
|
-
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, planStepId: progression.planStepId ?? predecessorStep.id, semanticAcceptanceRequired: true, progression },
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
if (progression.reasonCode === 'PLAN_STEP_TERMINAL_WORK_RECONCILIATION_REQUIRED') {
|
|
936
|
-
return buildFacadeResult({
|
|
937
|
-
status: 'blocked',
|
|
938
|
-
summary: `PLAN_STEP_TERMINAL_WORK_RECONCILIATION_REQUIRED: ${plan.planId}/${progression.planStepId ?? predecessorStep.id} must project terminal Work ${terminalContinuationSource.workId} before semantic progression.`,
|
|
939
|
-
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, planStepId: progression.planStepId ?? predecessorStep.id, repairRequired: true, progression },
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
if (progression.kind === 'start_next_plan_step' && progression.planStepId) {
|
|
943
|
-
resolvedPlanStepId = progression.planStepId;
|
|
944
|
-
} else if (progression.kind === 'request_requirement_acceptance') {
|
|
945
|
-
return buildFacadeResult({
|
|
946
|
-
status: 'ok',
|
|
947
|
-
summary: `CONTINUATION_REQUIREMENT_ACCEPTANCE_REQUIRED: ${plan.planId} is finalized; the semantic Controller must record goal_complete before the Requirement becomes done.`,
|
|
948
|
-
data: { executionStarted: false, workContractCreated: false, admissionDecision: 'requirement_acceptance_required', requirementAcceptanceRequired: true, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, requirementId: plan.requirementId, progression },
|
|
949
|
-
});
|
|
950
|
-
} else if (progression.kind === 'goal_complete') {
|
|
951
|
-
return buildFacadeResult({
|
|
952
|
-
status: 'ok',
|
|
953
|
-
summary: `CONTINUATION_GOAL_COMPLETE: ${plan.planId} Requirement is already semantically complete; no successor Work was created.`,
|
|
954
|
-
data: { executionStarted: false, workContractCreated: false, admissionDecision: 'goal_complete', goalComplete: true, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, requirementId: plan.requirementId, progression },
|
|
955
|
-
});
|
|
956
|
-
} else if (progression.reasonCode === 'MULTIPLE_READY_PLAN_STEPS') {
|
|
957
|
-
const candidates = (progression.dependencyStepIds ?? [])
|
|
958
|
-
.map((stepId) => plan.steps.find((candidate) => candidate.id === stepId))
|
|
959
|
-
.filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate));
|
|
960
|
-
return buildFacadeResult({
|
|
961
|
-
status: 'ok',
|
|
962
|
-
summary: `PLAN_SUCCESSOR_STEP_RESOLUTION_REQUIRED: ${plan.planId} has ${candidates.length} executable successor steps. The semantic Controller must select one; Forge will not invent ordering.`,
|
|
963
|
-
data: { executionStarted: false, workContractCreated: false, admissionDecision: 'resolution_required', resolutionRequired: true, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, candidatePlanSteps: candidates.map((candidate) => ({ id: candidate.id, objective: candidate.objective })), progression },
|
|
964
|
-
});
|
|
965
|
-
} else if (progression.kind === 'request_replan') {
|
|
966
|
-
return buildFacadeResult({
|
|
967
|
-
status: 'blocked',
|
|
968
|
-
summary: `PLAN_SUCCESSOR_REPLAN_REQUIRED: ${plan.planId} ${progression.reasonCode}.`,
|
|
969
|
-
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, progression },
|
|
970
|
-
});
|
|
971
|
-
} else if (progression.kind !== 'start_next_plan_step') {
|
|
972
|
-
return buildFacadeResult({
|
|
973
|
-
status: 'blocked',
|
|
974
|
-
summary: `PLAN_SUCCESSOR_PROGRESSION_BLOCKED: ${plan.planId} ${progression.reasonCode}.`,
|
|
975
|
-
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, planId: plan.planId, progression },
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
574
|
}
|
|
979
|
-
const planStep = plan && resolvedPlanStepId ? plan.steps.find((step) => step.id === resolvedPlanStepId) : undefined;
|
|
980
575
|
const requestedRequirementId = input.requirementId?.trim() || undefined;
|
|
981
576
|
const predecessorRequirementId = terminalContinuationSource?.requirementId;
|
|
982
577
|
if (predecessorRequirementId && requestedRequirementId && predecessorRequirementId !== requestedRequirementId) {
|
|
@@ -986,25 +581,17 @@ export function startGoalWorkloop(
|
|
|
986
581
|
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource.workId, predecessorRequirementId, requestedRequirementId },
|
|
987
582
|
});
|
|
988
583
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
if (plan?.requirementId && predecessorRequirementId && plan.requirementId !== predecessorRequirementId) {
|
|
997
|
-
return buildFacadeResult({
|
|
998
|
-
status: 'blocked',
|
|
999
|
-
summary: `WORK_SUCCESSOR_PLAN_REQUIREMENT_MISMATCH: predecessor ${terminalContinuationSource!.workId} belongs to ${predecessorRequirementId}, while ${plan.planId} belongs to ${plan.requirementId}.`,
|
|
1000
|
-
data: { executionStarted: false, workContractCreated: false, predecessorWorkId: terminalContinuationSource!.workId, predecessorRequirementId, planId: plan.planId, planRequirementId: plan.requirementId },
|
|
1001
|
-
});
|
|
584
|
+
// A selected Plan supplies its Requirement when no explicit/predecessor link exists.
|
|
585
|
+
// Requirement membership alone never selects sibling Plans or successor Works.
|
|
586
|
+
const effectiveRequirementId = requestedRequirementId || predecessorRequirementId || plan?.requirementId;
|
|
587
|
+
if (plan?.requirementId && effectiveRequirementId && plan.requirementId !== effectiveRequirementId) {
|
|
588
|
+
return buildFacadeResult({ status: 'blocked', summary: `WORK_PLAN_REQUIREMENT_MISMATCH: ${resolvedPlanId} belongs to ${plan.requirementId}, not ${effectiveRequirementId}.`, data: { executionStarted: false, workContractCreated: false } });
|
|
1002
589
|
}
|
|
1003
|
-
const
|
|
590
|
+
const effectiveRequirementRecord = effectiveRequirementId && ctx.workStore.controllerHome ? readRequirement({ controllerHome: ctx.workStore.controllerHome }, effectiveRequirementId) : undefined;
|
|
1004
591
|
if (
|
|
1005
592
|
effectiveRequirementId
|
|
1006
593
|
&& ctx.workStore.controllerHome
|
|
1007
|
-
&& !
|
|
594
|
+
&& !effectiveRequirementRecord
|
|
1008
595
|
) {
|
|
1009
596
|
return buildFacadeResult({
|
|
1010
597
|
status: 'blocked',
|
|
@@ -1012,36 +599,16 @@ export function startGoalWorkloop(
|
|
|
1012
599
|
data: { executionStarted: false, workContractCreated: false, requirementId: effectiveRequirementId, planId: plan?.planId },
|
|
1013
600
|
});
|
|
1014
601
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const mismatches = [
|
|
1023
|
-
!sameStringSet(input.acceptanceCriteria, planStep.acceptanceCriteria) && 'acceptance_criteria',
|
|
1024
|
-
!sameStringSet(input.allowedPaths, planStep.allowedPaths) && 'allowed_paths',
|
|
1025
|
-
!sameStringSet(input.forbiddenPaths, planStep.forbiddenPaths) && 'forbidden_paths',
|
|
1026
|
-
!sameStringSet(input.checks, planStep.checks) && 'check_ids',
|
|
1027
|
-
].filter((value): value is string => Boolean(value));
|
|
1028
|
-
if (mismatches.length > 0) {
|
|
1029
|
-
return buildFacadeResult({
|
|
1030
|
-
status: 'blocked',
|
|
1031
|
-
summary: `PLAN_STEP_WORK_CONTRACT_MISMATCH: ${resolvedPlanId}/${resolvedPlanStepId} conflicts with frozen Plan step field(s): ${mismatches.join(', ')}. Replan instead of widening or narrowing Work admission.`,
|
|
1032
|
-
data: { executionStarted: false, workContractCreated: false, planId: resolvedPlanId, planStepId: resolvedPlanStepId, mismatches },
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
const effectiveObjective = planStep?.objective ?? input.objective;
|
|
1037
|
-
const effectiveAcceptanceCriteria = planStep?.acceptanceCriteria ?? input.acceptanceCriteria ?? [];
|
|
1038
|
-
const effectiveAllowedPaths = planStep?.allowedPaths ?? input.allowedPaths ?? [];
|
|
1039
|
-
const effectiveForbiddenPaths = planStep?.forbiddenPaths ?? input.forbiddenPaths ?? [];
|
|
1040
|
-
const effectiveChecks = planStep?.checks ?? input.checks ?? [];
|
|
602
|
+
// Work scope is authored by the caller. A referenced Plan item contributes no
|
|
603
|
+
// objective, paths, checks or acceptance criteria.
|
|
604
|
+
const effectiveObjective = input.objective;
|
|
605
|
+
const effectiveAcceptanceCriteria = input.acceptanceCriteria ?? [];
|
|
606
|
+
const effectiveAllowedPaths = input.allowedPaths ?? [];
|
|
607
|
+
const effectiveForbiddenPaths = input.forbiddenPaths ?? [];
|
|
608
|
+
const effectiveChecks = input.checks ?? [];
|
|
1041
609
|
const normalized = normalizeCheckIds(effectiveChecks, available);
|
|
1042
610
|
const invalidLineageWork = activeAdmissionSnapshot.invalid.find((candidate) =>
|
|
1043
|
-
(input.relatedWorkId && candidate.workId === input.relatedWorkId)
|
|
1044
|
-
|| (resolvedPlanId && resolvedPlanStepId && candidate.planId === resolvedPlanId && candidate.planStepId === resolvedPlanStepId));
|
|
611
|
+
(input.relatedWorkId && candidate.workId === input.relatedWorkId));
|
|
1045
612
|
if (invalidLineageWork) {
|
|
1046
613
|
return buildFacadeResult({
|
|
1047
614
|
status: 'blocked',
|
|
@@ -1057,10 +624,7 @@ export function startGoalWorkloop(
|
|
|
1057
624
|
},
|
|
1058
625
|
});
|
|
1059
626
|
}
|
|
1060
|
-
const newWorkWillBeIsolated =
|
|
1061
|
-
|| placementConstraint.workspaceMode === 'isolated'
|
|
1062
|
-
|| input.modeInput.requiresParallelism === true
|
|
1063
|
-
|| routeDecision.requiresIsolation === true;
|
|
627
|
+
const newWorkWillBeIsolated = workspaceMode === 'isolated';
|
|
1064
628
|
const invalidSharedWorkspaceOwner = !newWorkWillBeIsolated
|
|
1065
629
|
? activeAdmissionSnapshot.invalid.find((candidate) => candidate.isolation === 'shared'
|
|
1066
630
|
&& (!candidate.checkoutId || candidate.checkoutId === ctx.checkoutId))
|
|
@@ -1072,24 +636,18 @@ export function startGoalWorkloop(
|
|
|
1072
636
|
data: { executionStarted: false, workContractCreated: false, invalidWorkId: invalidSharedWorkspaceOwner.workId, invalidWorkError: invalidSharedWorkspaceOwner.error },
|
|
1073
637
|
});
|
|
1074
638
|
}
|
|
1075
|
-
const boundPlanStepWorks = resolvedPlanId && resolvedPlanStepId
|
|
1076
|
-
? activeWorks.filter((candidate) => candidate.planId === resolvedPlanId && candidate.planStepId === resolvedPlanStepId)
|
|
1077
|
-
: [];
|
|
1078
|
-
const explicitPlanStepWork = planStep?.workId ? activeWorks.find((candidate) => candidate.workId === planStep.workId) : undefined;
|
|
1079
|
-
const planStepWork = explicitPlanStepWork ?? (boundPlanStepWorks.length === 1 ? boundPlanStepWorks[0] : undefined);
|
|
1080
639
|
// Requirement membership is portfolio ownership, not semantic Work identity.
|
|
1081
|
-
// Only an explicit related Work
|
|
1082
|
-
//
|
|
1083
|
-
// for semantic admission and meet only in
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
const requestedRelation = input.workRelation ?? (input.modeInput.requiresParallelism === true ? 'parallel' : undefined);
|
|
640
|
+
// Only an explicit related Work may select an existing Work authority. A Plan
|
|
641
|
+
// item never selects, owns or reuses Work. Siblings under one Requirement
|
|
642
|
+
// remain unrelated for semantic admission and meet only in
|
|
643
|
+
// placement/resource arbitration.
|
|
644
|
+
const deterministicTarget = input.relatedWorkId ? explicitRelatedWork : undefined;
|
|
645
|
+
const requestedRelation = input.workRelation;
|
|
1088
646
|
// Only strong semantic bindings participate in ownership resolution. An
|
|
1089
647
|
// unrelated active Work or a checkout writer is a placement fact, not a
|
|
1090
648
|
// semantic candidate for continue/extend/parallel/new_goal.
|
|
1091
649
|
const candidateWorks = [...new Map(
|
|
1092
|
-
[explicitRelatedWork
|
|
650
|
+
[explicitRelatedWork]
|
|
1093
651
|
.filter((candidate): candidate is WorkContract => Boolean(candidate))
|
|
1094
652
|
.map((candidate) => [candidate.workId, candidate]),
|
|
1095
653
|
).values()].slice(0, 8);
|
|
@@ -1110,63 +668,18 @@ export function startGoalWorkloop(
|
|
|
1110
668
|
evidenceRefs: target?.evidenceRefs ?? [],
|
|
1111
669
|
suggestedNextActions: target
|
|
1112
670
|
? [{
|
|
1113
|
-
label: '
|
|
671
|
+
label: 'Inspect existing Work',
|
|
1114
672
|
tool: 'rh_work',
|
|
1115
|
-
operation: '
|
|
673
|
+
operation: 'get',
|
|
1116
674
|
payload: { work_id: target.workId },
|
|
1117
675
|
risk: 'readonly',
|
|
1118
|
-
confidence:
|
|
1119
|
-
reason: 'Resolve intent before execution;
|
|
676
|
+
confidence: explicitRelatedWork ? 'high' : 'medium',
|
|
677
|
+
reason: 'Resolve intent before execution; semantic Work inspection does not progress lifecycle state.',
|
|
1120
678
|
}]
|
|
1121
679
|
: [],
|
|
1122
680
|
rawAvailable: false,
|
|
1123
681
|
});
|
|
1124
682
|
|
|
1125
|
-
if (boundPlanStepWorks.length > 1) {
|
|
1126
|
-
return resolutionRequired(`PLAN_STEP_MULTIPLE_PRIMARY_WORKS: ${resolvedPlanId}/${resolvedPlanStepId} has ${boundPlanStepWorks.length} active primary Work records and requires repair before execution.`);
|
|
1127
|
-
}
|
|
1128
|
-
if (terminalContinuationSource && requestedRelation === 'continue' && planStepWork) {
|
|
1129
|
-
if (planStepWork.predecessorWorkId !== terminalContinuationSource.workId) {
|
|
1130
|
-
return resolutionRequired(
|
|
1131
|
-
`PLAN_STEP_SUCCESSOR_LINEAGE_CONFLICT: ${resolvedPlanId}/${resolvedPlanStepId} is already owned by ${planStepWork.workId}, but its predecessor is ${planStepWork.predecessorWorkId ?? 'none'} rather than ${terminalContinuationSource.workId}.`,
|
|
1132
|
-
planStepWork,
|
|
1133
|
-
);
|
|
1134
|
-
}
|
|
1135
|
-
return buildFacadeResult({
|
|
1136
|
-
status: 'ok',
|
|
1137
|
-
summary: `PLAN_STEP_REUSES_SUCCESSOR_WORK: ${resolvedPlanId}/${resolvedPlanStepId} already continues terminal ${terminalContinuationSource.workId} as ${planStepWork.workId}.`,
|
|
1138
|
-
data: {
|
|
1139
|
-
executionStarted: false,
|
|
1140
|
-
workContractCreated: false,
|
|
1141
|
-
admissionDecision: 'reuse_existing',
|
|
1142
|
-
resolutionRequired: false,
|
|
1143
|
-
predecessorWorkId: terminalContinuationSource.workId,
|
|
1144
|
-
work: summarizeWorkContract(planStepWork),
|
|
1145
|
-
},
|
|
1146
|
-
evidenceRefs: planStepWork.evidenceRefs,
|
|
1147
|
-
suggestedNextActions: [{ label: 'Continue successor Work', tool: 'rh_work', operation: 'continue', payload: { work_id: planStepWork.workId }, risk: 'readonly', confidence: 'high' }],
|
|
1148
|
-
rawAvailable: false,
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
if (planStep?.workId && !explicitPlanStepWork) {
|
|
1153
|
-
return buildFacadeResult({
|
|
1154
|
-
status: 'blocked',
|
|
1155
|
-
summary: `PLAN_STEP_BOUND_WORK_MISSING: ${resolvedPlanId}/${resolvedPlanStepId} is bound to ${planStep.workId}, but that Work is not active. Repair the exact binding; do not create a replacement Work.`,
|
|
1156
|
-
data: { executionStarted: false, workContractCreated: false, planId: resolvedPlanId, planStepId: resolvedPlanStepId, boundWorkId: planStep.workId, repairRequired: true },
|
|
1157
|
-
suggestedNextActions: [{
|
|
1158
|
-
label: 'Diagnose exact Plan step binding',
|
|
1159
|
-
tool: 'rh_work',
|
|
1160
|
-
operation: 'repair',
|
|
1161
|
-
payload: { plan_id: resolvedPlanId, plan_step_id: resolvedPlanStepId, repair_operation: 'diagnose', dry_run: true },
|
|
1162
|
-
risk: 'readonly',
|
|
1163
|
-
confidence: 'high',
|
|
1164
|
-
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.',
|
|
1165
|
-
}],
|
|
1166
|
-
rawAvailable: false,
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
683
|
if ((input.requestedBy ?? 'chatgpt') === 'scheduler') {
|
|
1171
684
|
return resolutionRequired(
|
|
1172
685
|
deterministicTarget
|
|
@@ -1175,42 +688,17 @@ export function startGoalWorkloop(
|
|
|
1175
688
|
);
|
|
1176
689
|
}
|
|
1177
690
|
|
|
1178
|
-
if (planStepWork && !requestedRelation) {
|
|
1179
|
-
return buildFacadeResult({
|
|
1180
|
-
status: 'ok',
|
|
1181
|
-
summary: `PLAN_STEP_REUSES_ACTIVE_WORK: ${resolvedPlanId}/${resolvedPlanStepId} already executes as ${planStepWork.workId}; reuse that Work instead of creating another.`,
|
|
1182
|
-
data: {
|
|
1183
|
-
executionStarted: false,
|
|
1184
|
-
workContractCreated: false,
|
|
1185
|
-
admissionDecision: 'reuse_existing',
|
|
1186
|
-
resolutionRequired: false,
|
|
1187
|
-
work: summarizeWorkContract(planStepWork),
|
|
1188
|
-
},
|
|
1189
|
-
evidenceRefs: planStepWork.evidenceRefs,
|
|
1190
|
-
suggestedNextActions: [{ label: 'Continue existing Work', tool: 'rh_work', operation: 'continue', payload: { work_id: planStepWork.workId }, risk: 'readonly', confidence: 'high' }],
|
|
1191
|
-
rawAvailable: false,
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
691
|
if ((requestedRelation === 'continue' || requestedRelation === 'extend') && !terminalContinuationSource) {
|
|
1196
692
|
if (!deterministicTarget) {
|
|
1197
|
-
return resolutionRequired(`${requestedRelation.toUpperCase()}_TARGET_REQUIRED: select related_work_id
|
|
1198
|
-
}
|
|
1199
|
-
if (requestedRelation === 'extend' && deterministicTarget.planId) {
|
|
1200
|
-
return resolutionRequired(
|
|
1201
|
-
`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.`,
|
|
1202
|
-
deterministicTarget,
|
|
1203
|
-
);
|
|
693
|
+
return resolutionRequired(`${requestedRelation.toUpperCase()}_TARGET_REQUIRED: select related_work_id before execution.`);
|
|
1204
694
|
}
|
|
1205
695
|
const selected = requestedRelation === 'extend'
|
|
1206
696
|
? updateWorkContract(ctx.workStore, deterministicTarget.workId, {
|
|
1207
697
|
acceptanceCriteria: [...new Set([...deterministicTarget.acceptanceCriteria, ...(input.acceptanceCriteria ?? [])])].slice(0, 30),
|
|
1208
|
-
allowedPaths: [...new Set([...deterministicTarget.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
1209
|
-
forbiddenPaths: [...new Set([...deterministicTarget.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
1210
698
|
checks: [...new Set([...deterministicTarget.checks, ...normalized.validCheckIds])].slice(0, 30),
|
|
1211
699
|
scopeEvidence: {
|
|
1212
700
|
initialLikelyPaths: [...new Set([
|
|
1213
|
-
...(deterministicTarget.scopeEvidence?.initialLikelyPaths ??
|
|
701
|
+
...(deterministicTarget.scopeEvidence?.initialLikelyPaths ?? []),
|
|
1214
702
|
...(input.initialLikelyPaths ?? input.allowedPaths ?? []),
|
|
1215
703
|
])].slice(0, 100),
|
|
1216
704
|
inspectedPaths: deterministicTarget.scopeEvidence?.inspectedPaths ?? [],
|
|
@@ -1232,7 +720,7 @@ export function startGoalWorkloop(
|
|
|
1232
720
|
work: summarizeWorkContract(selected),
|
|
1233
721
|
},
|
|
1234
722
|
evidenceRefs: selected.evidenceRefs,
|
|
1235
|
-
suggestedNextActions: [{ label: '
|
|
723
|
+
suggestedNextActions: [{ label: 'Read existing Work context', tool: 'rh_work', operation: 'get', payload: { work_id: selected.workId }, risk: 'readonly', confidence: 'high' }],
|
|
1236
724
|
rawAvailable: false,
|
|
1237
725
|
});
|
|
1238
726
|
}
|
|
@@ -1243,51 +731,10 @@ export function startGoalWorkloop(
|
|
|
1243
731
|
);
|
|
1244
732
|
}
|
|
1245
733
|
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1250
|
-
// genuinely declares a repository workspace requirement.
|
|
1251
|
-
const repositoryWorkspaceParticipant = resolvedWorkKind !== 'remote_effect';
|
|
1252
|
-
const placementConflict = repositoryWorkspaceParticipant && Boolean(workspaceOwner);
|
|
1253
|
-
const trustedDirtyPaths = ctx.workspaceChangedPaths
|
|
1254
|
-
? [...new Set(ctx.workspaceChangedPaths.map((path) => path.trim()).filter(Boolean))].sort()
|
|
1255
|
-
: undefined;
|
|
1256
|
-
const dirtyWorkspaceOwnershipConflict = repositoryWorkspaceParticipant
|
|
1257
|
-
&& input.modeInput.workspaceDirty === true
|
|
1258
|
-
&& (
|
|
1259
|
-
!trustedDirtyPaths
|
|
1260
|
-
|| trustedDirtyPaths.length === 0
|
|
1261
|
-
|| effectiveAllowedPaths.length === 0
|
|
1262
|
-
|| trustedDirtyPaths.some((path) => (
|
|
1263
|
-
effectiveForbiddenPaths.some((pattern) => globMatches(pattern, path))
|
|
1264
|
-
|| !effectiveAllowedPaths.some((pattern) => globMatches(pattern, path))
|
|
1265
|
-
))
|
|
1266
|
-
);
|
|
1267
|
-
const automaticRepositoryIsolation = repositoryWorkspaceParticipant && (
|
|
1268
|
-
placementConflict
|
|
1269
|
-
|| dirtyWorkspaceOwnershipConflict
|
|
1270
|
-
|| input.modeInput.requiresParallelism === true
|
|
1271
|
-
);
|
|
1272
|
-
const needsWorktree = executionMode === 'goal_workloop' && (
|
|
1273
|
-
placementConstraint.requireWorktree
|
|
1274
|
-
|| automaticRepositoryIsolation
|
|
1275
|
-
|| routeDecision.requiresIsolation === true
|
|
1276
|
-
);
|
|
1277
|
-
if (!needsWorktree && workspaceOwner && repositoryWorkspaceParticipant) {
|
|
1278
|
-
return buildFacadeResult({
|
|
1279
|
-
status: 'blocked',
|
|
1280
|
-
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.`,
|
|
1281
|
-
data: {
|
|
1282
|
-
executionStarted: false,
|
|
1283
|
-
workContractCreated: false,
|
|
1284
|
-
conflictType: 'workspace_single_writer_invariant',
|
|
1285
|
-
existingWork: summarizeWorkContract(workspaceOwner),
|
|
1286
|
-
},
|
|
1287
|
-
evidenceRefs: workspaceOwner.evidenceRefs,
|
|
1288
|
-
rawAvailable: false,
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
734
|
+
// Repository topology is caller-owned. Forge records the selected placement
|
|
735
|
+
// and lets concrete WorkHandle/Lease/Git authorities report real conflicts at
|
|
736
|
+
// the mutation boundary instead of inferring isolation from semantic context.
|
|
737
|
+
const needsWorktree = workspaceMode === 'isolated';
|
|
1291
738
|
const requestedWorkId = input.workId?.trim();
|
|
1292
739
|
if (requestedWorkId && !/^work-[a-z0-9][a-z0-9-]{0,199}$/i.test(requestedWorkId)) {
|
|
1293
740
|
return buildFacadeResult({
|
|
@@ -1297,56 +744,21 @@ export function startGoalWorkloop(
|
|
|
1297
744
|
});
|
|
1298
745
|
}
|
|
1299
746
|
const generatedWorkId = requestedWorkId ?? workIdFor(effectiveObjective);
|
|
1300
|
-
if (resolvedPlanId || resolvedPlanStepId) {
|
|
1301
|
-
if (!resolvedPlanId || !resolvedPlanStepId || !ctx.planStore || !ctx.sourceRevision || !plan || !planStep) {
|
|
1302
|
-
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 } });
|
|
1303
|
-
}
|
|
1304
|
-
if (plan.status !== 'approved' && plan.status !== 'executing') {
|
|
1305
|
-
return buildFacadeResult({ status: 'blocked', summary: `PLAN_NOT_EXECUTABLE: ${plan.planId} is ${plan.status}`, data: { executionStarted: false, planId: plan.planId } });
|
|
1306
|
-
}
|
|
1307
|
-
if (plan.sourceRevision !== ctx.sourceRevision) {
|
|
1308
|
-
const invalidated = claimPlanStepForWork(ctx.planStore, {
|
|
1309
|
-
planId: resolvedPlanId,
|
|
1310
|
-
stepId: resolvedPlanStepId,
|
|
1311
|
-
workId: generatedWorkId,
|
|
1312
|
-
sourceRevision: ctx.sourceRevision,
|
|
1313
|
-
});
|
|
1314
|
-
return buildFacadeResult({
|
|
1315
|
-
status: 'blocked',
|
|
1316
|
-
summary: `PLAN_SOURCE_DRIFT: ${invalidated.planId} was invalidated because its source revision no longer matches. Replan before execution.`,
|
|
1317
|
-
data: { planId: invalidated.planId, executionStarted: false, workContractCreated: false, replanRequired: true },
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
const unresolved = planStep.dependencies.filter((dependency) => plan.steps.find((candidate) => candidate.id === dependency)?.status !== 'completed');
|
|
1321
|
-
if (unresolved.length > 0) {
|
|
1322
|
-
return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_DEPENDENCIES_PENDING: ${unresolved.join(', ')}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
|
|
1323
|
-
}
|
|
1324
|
-
if (planStep.status === 'completed') {
|
|
1325
|
-
return buildFacadeResult({ status: 'blocked', summary: `PLAN_STEP_ALREADY_COMPLETED: ${planStep.id}`, data: { executionStarted: false, workContractCreated: false, planId: plan.planId, planStepId: planStep.id } });
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
747
|
const initialSuggestedNextActions = suggestedForWorkIdentity(
|
|
1329
748
|
generatedWorkId,
|
|
1330
749
|
normalized.validCheckIds,
|
|
1331
750
|
normalized.suggestedNextActions,
|
|
1332
751
|
);
|
|
1333
752
|
const remoteDeliveryRequired = resolvedWorkKind === 'repository_change'
|
|
1334
|
-
&& input.
|
|
1335
|
-
&& input.
|
|
753
|
+
&& input.request.requiresExternalEffect === true
|
|
754
|
+
&& input.request.remoteWrite === true;
|
|
1336
755
|
const effectiveConstraints: WorkContract['constraints'] = {
|
|
1337
756
|
...canonicalConstraints,
|
|
1338
|
-
...(needsWorktree ? { workspaceMode: 'isolated' as const, requireWorktree: true } : {}),
|
|
1339
757
|
...(remoteDeliveryRequired ? { remoteDeliveryRequired: true } : {}),
|
|
1340
758
|
};
|
|
1341
|
-
const worktreeReason =
|
|
1342
|
-
? '
|
|
1343
|
-
:
|
|
1344
|
-
? 'Trusted repository observation found dirty paths outside or ambiguous to the Work path fence; isolated placement prevents unrelated changes from entering Work ownership or verification.'
|
|
1345
|
-
: placementConflict
|
|
1346
|
-
? 'The selected checkout is already owned by another active Work; this Work requires isolated placement.'
|
|
1347
|
-
: repositoryWorkspaceParticipant && input.modeInput.requiresParallelism === true
|
|
1348
|
-
? 'Parallel Work requires isolated placement.'
|
|
1349
|
-
: 'Current workspace is the stability-first default; isolation remains opt-in.';
|
|
759
|
+
const worktreeReason = needsWorktree
|
|
760
|
+
? 'Caller explicitly selected isolated repository placement.'
|
|
761
|
+
: 'Caller selected the current repository checkout.';
|
|
1350
762
|
const forgeInstanceId = ctx.workStore.controllerHome
|
|
1351
763
|
? readForgeInstanceIdentity(ctx.workStore.controllerHome)?.instanceId
|
|
1352
764
|
: undefined;
|
|
@@ -1364,9 +776,6 @@ export function startGoalWorkloop(
|
|
|
1364
776
|
baseRevision: ctx.sourceRevision,
|
|
1365
777
|
repositoryBaseState: ctx.sourceBaseState ?? (ctx.sourceRevision ? 'revision' : undefined),
|
|
1366
778
|
workspaceFingerprint: needsWorktree ? undefined : ctx.workspaceFingerprint,
|
|
1367
|
-
routeDecisionFingerprint: routeDecision.inputFingerprint,
|
|
1368
|
-
routeDecision,
|
|
1369
|
-
mode: executionMode,
|
|
1370
779
|
objective: effectiveObjective,
|
|
1371
780
|
acceptanceCriteria: effectiveAcceptanceCriteria,
|
|
1372
781
|
constraints: effectiveConstraints,
|
|
@@ -1376,16 +785,18 @@ export function startGoalWorkloop(
|
|
|
1376
785
|
// repository-change Work's semantic identity. Pure external actions with no
|
|
1377
786
|
// repository-change signal remain remote_effect and keep plugin receipt semantics.
|
|
1378
787
|
workKind: resolvedWorkKind,
|
|
1379
|
-
|
|
788
|
+
dispatchState: 'running',
|
|
1380
789
|
phase: 'implementation',
|
|
1381
790
|
issueId: input.issueId,
|
|
1382
791
|
taskId: input.taskId,
|
|
1383
792
|
requirementId: effectiveRequirementId,
|
|
793
|
+
requirementRevision: effectiveRequirementRecord ? currentRequirementSemanticRevision(effectiveRequirementRecord.value) : undefined,
|
|
1384
794
|
predecessorWorkId: terminalContinuationSource?.workId,
|
|
1385
795
|
planId: resolvedPlanId,
|
|
796
|
+
planRevision: plan ? currentPlanSemanticRevision(plan) : undefined,
|
|
1386
797
|
planStepId: resolvedPlanStepId,
|
|
1387
|
-
planSourceRevision:
|
|
1388
|
-
scopeSummary: input.
|
|
798
|
+
planSourceRevision: resolvedPlanStepId ? plan?.sourceRevision : undefined,
|
|
799
|
+
scopeSummary: input.request.scopeClear ? 'scope declared at start' : 'scope incomplete',
|
|
1389
800
|
scopeEvidence: {
|
|
1390
801
|
initialLikelyPaths: [...new Set(input.initialLikelyPaths ?? effectiveAllowedPaths)].slice(0, 100),
|
|
1391
802
|
inspectedPaths: [],
|
|
@@ -1395,13 +806,6 @@ export function startGoalWorkloop(
|
|
|
1395
806
|
allowedPaths: effectiveAllowedPaths,
|
|
1396
807
|
forbiddenPaths: effectiveForbiddenPaths,
|
|
1397
808
|
checks: normalized.validCheckIds,
|
|
1398
|
-
driver: {
|
|
1399
|
-
preferred: executionMode === 'direct_control'
|
|
1400
|
-
? 'direct_edit'
|
|
1401
|
-
: input.modeInput.requiresWorker === true ? 'external_controller' : needsWorktree ? 'isolated_worktree' : 'direct_edit',
|
|
1402
|
-
allowWorker: false,
|
|
1403
|
-
allowDirectEdit: executionMode === 'direct_control' || (input.modeInput.requiresWorker !== true && !needsWorktree),
|
|
1404
|
-
},
|
|
1405
809
|
worktreePolicy: {
|
|
1406
810
|
required: needsWorktree,
|
|
1407
811
|
reason: worktreeReason,
|
|
@@ -1413,8 +817,8 @@ export function startGoalWorkloop(
|
|
|
1413
817
|
maxEvidenceRefs: 20,
|
|
1414
818
|
},
|
|
1415
819
|
approvalPolicy: {
|
|
1416
|
-
required: input.
|
|
1417
|
-
reasons: input.
|
|
820
|
+
required: input.request.requiresApproval === true || input.request.requiresUserApproval === true,
|
|
821
|
+
reasons: input.request.requiresApproval || input.request.requiresUserApproval ? ['approval requested at start'] : [],
|
|
1418
822
|
confirmed: input.approvalConfirmed === true,
|
|
1419
823
|
},
|
|
1420
824
|
recoveryPolicy: {
|
|
@@ -1430,48 +834,19 @@ export function startGoalWorkloop(
|
|
|
1430
834
|
continuationPrompt: `Continue work ${ctx.repoId}: ${effectiveObjective.slice(0, 200)}`,
|
|
1431
835
|
});
|
|
1432
836
|
|
|
1433
|
-
if (resolvedPlanId && resolvedPlanStepId && ctx.planStore && ctx.sourceRevision) {
|
|
1434
|
-
try {
|
|
1435
|
-
const claimed = claimPlanStepForWork(ctx.planStore, {
|
|
1436
|
-
planId: resolvedPlanId,
|
|
1437
|
-
stepId: resolvedPlanStepId,
|
|
1438
|
-
workId: work.workId,
|
|
1439
|
-
sourceRevision: ctx.sourceRevision,
|
|
1440
|
-
});
|
|
1441
|
-
if (claimed.status === 'invalidated_by_drift') {
|
|
1442
|
-
return buildFacadeResult({
|
|
1443
|
-
status: 'blocked',
|
|
1444
|
-
summary: `PLAN_SOURCE_DRIFT: ${claimed.planId} was invalidated because its source revision no longer matches. Repair or replan before execution.`,
|
|
1445
|
-
data: { executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), planId: claimed.planId, canonicalWorkRetained: true, replanRequired: true },
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
} catch (error) {
|
|
1449
|
-
return buildFacadeResult({
|
|
1450
|
-
status: 'blocked',
|
|
1451
|
-
summary: error instanceof Error ? error.message : 'PLAN_STEP_CLAIM_FAILED',
|
|
1452
|
-
data: { planId: resolvedPlanId, planStepId: resolvedPlanStepId, executionStarted: false, workContractCreated: true, work: summarizeWorkContract(work), canonicalWorkRetained: true },
|
|
1453
|
-
});
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
837
|
return buildFacadeResult({
|
|
1458
838
|
status: 'ok',
|
|
1459
839
|
summary: terminalContinuationSource
|
|
1460
840
|
? `Successor Work ${work.workId} continues semantic lineage from terminal ${terminalContinuationSource.workId}${resolvedPlanId && resolvedPlanStepId ? ` at ${resolvedPlanId}/${resolvedPlanStepId}` : ''}.`
|
|
1461
|
-
:
|
|
1462
|
-
? `Direct-control Work lineage started as ${work.workId}.`
|
|
1463
|
-
: `Goal workloop started as ${work.workId}.`,
|
|
841
|
+
: `Work started as ${work.workId}.`,
|
|
1464
842
|
data: {
|
|
1465
|
-
mode
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
missingContractFields: [],
|
|
1473
|
-
requiresWork: true,
|
|
1474
|
-
routeDecision,
|
|
843
|
+
// Caller-visible placement facts only. Execution mode tokens, route reasons
|
|
844
|
+
// and RouteDecision are legacy orchestration metadata, not Work semantics.
|
|
845
|
+
placement: {
|
|
846
|
+
workContractCreated: true,
|
|
847
|
+
worktreeRequired: work.worktreePolicy.required,
|
|
848
|
+
isolated: work.worktreePolicy.required,
|
|
849
|
+
requiresRecovery: work.recoveryPolicy.allowSelfHealing === true || false,
|
|
1475
850
|
},
|
|
1476
851
|
workContractCreated: true,
|
|
1477
852
|
work: summarizeWorkContract(work),
|
|
@@ -1497,10 +872,10 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1497
872
|
});
|
|
1498
873
|
}
|
|
1499
874
|
|
|
1500
|
-
if (work
|
|
875
|
+
if (semanticWorkState(work) !== 'open') {
|
|
1501
876
|
return buildFacadeResult({
|
|
1502
877
|
status: 'blocked',
|
|
1503
|
-
summary: `WorkContract ${work.workId} is terminal (${work
|
|
878
|
+
summary: `WorkContract ${work.workId} is terminal (${semanticWorkState(work)}); continue is not allowed.`,
|
|
1504
879
|
data: { work: summarizeWorkContract(work) },
|
|
1505
880
|
suggestedNextActions: [
|
|
1506
881
|
{
|
|
@@ -1514,6 +889,21 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1514
889
|
});
|
|
1515
890
|
}
|
|
1516
891
|
|
|
892
|
+
if (semanticWorkState(work) === 'open' && hasSettledWorkDeliveryReceipt(work)) {
|
|
893
|
+
return buildFacadeResult({
|
|
894
|
+
status: 'ok',
|
|
895
|
+
summary: `Continue: physical delivery is settled for ${work.workId}; semantic Work remains open for explicit Controller judgment.`,
|
|
896
|
+
data: {
|
|
897
|
+
work: summarizeWorkContract(work),
|
|
898
|
+
backgroundCompleted: false,
|
|
899
|
+
nextStep: 'semantic_decision',
|
|
900
|
+
deliverySettled: true,
|
|
901
|
+
semanticWorkState: 'open',
|
|
902
|
+
},
|
|
903
|
+
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
1517
907
|
if (input.verifiedEngineeringEvidence) {
|
|
1518
908
|
const sourceIdentity = ctx.sourceRevision?.trim()
|
|
1519
909
|
? { kind: 'revision' as const, revision: ctx.sourceRevision.trim() }
|
|
@@ -1535,42 +925,9 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1535
925
|
data: { work: summarizeWorkContract(work), engineeringEvidenceUpdated: false },
|
|
1536
926
|
});
|
|
1537
927
|
}
|
|
1538
|
-
const priorDesign = work.engineeringContext?.evidence.designDecisionReceipt;
|
|
1539
|
-
if (work.engineeringContext?.designState === 'revisit_required' && priorDesign) {
|
|
1540
|
-
const nextDesign = refreshedEngineeringContext.evidence.designDecisionReceipt;
|
|
1541
|
-
if (!nextDesign || nextDesign.supersedesReceiptId !== priorDesign.receiptId) {
|
|
1542
|
-
return buildFacadeResult({
|
|
1543
|
-
status: 'blocked',
|
|
1544
|
-
summary: 'ENGINEERING_DESIGN_SUPERSESSION_REQUIRED: same-root-cause re-entry requires a new design receipt that explicitly supersedes the prior design.',
|
|
1545
|
-
data: { work: summarizeWorkContract(work), priorDesignReceiptId: priorDesign.receiptId },
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
if (refreshedEngineeringContext.evidence.independentCritiqueReceipt?.decision !== 'approved') {
|
|
1549
|
-
return buildFacadeResult({
|
|
1550
|
-
status: 'blocked',
|
|
1551
|
-
summary: 'ENGINEERING_DESIGN_CRITIQUE_APPROVAL_REQUIRED: same-root-cause re-entry requires an independently approved critique of the superseding design.',
|
|
1552
|
-
data: { work: summarizeWorkContract(work), priorDesignReceiptId: priorDesign.receiptId, nextDesignReceiptId: nextDesign.receiptId },
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
928
|
work = updateWorkContract(ctx.workStore, work.workId, { engineeringContext: refreshedEngineeringContext });
|
|
1557
929
|
}
|
|
1558
930
|
|
|
1559
|
-
if (work.engineeringContext?.designState === 'revisit_required') {
|
|
1560
|
-
return buildFacadeResult({
|
|
1561
|
-
status: 'blocked',
|
|
1562
|
-
summary: 'ENGINEERING_DESIGN_REVISIT_REQUIRED: same-root-cause design authority must be explicitly superseded and independently approved before further Work mutation.',
|
|
1563
|
-
data: { work: summarizeWorkContract(work) },
|
|
1564
|
-
suggestedNextActions: [{
|
|
1565
|
-
label: 'Refresh design evidence',
|
|
1566
|
-
tool: 'rh_context',
|
|
1567
|
-
operation: 'search',
|
|
1568
|
-
payload: { work_id: work.workId, query: 'Refresh current source and design evidence for this Work before engineering re-entry.' },
|
|
1569
|
-
risk: 'readonly',
|
|
1570
|
-
}],
|
|
1571
|
-
});
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
931
|
if (input.engineeringBlocker) {
|
|
1575
932
|
if (!work.engineeringContext || work.engineeringContext.sourceIdentity.kind !== 'revision') {
|
|
1576
933
|
return buildFacadeResult({ status: 'blocked', summary: 'ENGINEERING_BLOCKER_SOURCE_IDENTITY_REQUIRED', data: { work: summarizeWorkContract(work) } });
|
|
@@ -1580,43 +937,34 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1580
937
|
try {
|
|
1581
938
|
let linkedWorkId: string | undefined;
|
|
1582
939
|
if (input.engineeringBlocker.classification === 'unrelated') {
|
|
1583
|
-
|
|
1584
|
-
|
|
940
|
+
// Forge does not decompose work on the model's behalf. An unrelated blocker
|
|
941
|
+
// must name the Work that already owns it; the caller creates that Work
|
|
942
|
+
// explicitly (rh_work start) and passes its exact id here.
|
|
943
|
+
const declaredLinkedWorkId = input.engineeringBlocker.linkedWorkId?.trim();
|
|
944
|
+
if (!declaredLinkedWorkId) {
|
|
945
|
+
return buildFacadeResult({
|
|
946
|
+
status: 'blocked',
|
|
947
|
+
summary: 'ENGINEERING_BLOCKER_LINKED_WORK_REQUIRED: an unrelated blocker must name the Work that owns it via linked_work_id; Forge never creates a child Work automatically.',
|
|
948
|
+
data: { work: summarizeWorkContract(work), linkedWorkCreated: false },
|
|
949
|
+
suggestedNextActions: [{
|
|
950
|
+
label: 'Start the owning Work explicitly',
|
|
951
|
+
tool: 'rh_work',
|
|
952
|
+
operation: 'start',
|
|
953
|
+
payload: { objective: `Resolve unrelated blocker ${input.engineeringBlocker.blockerId}: ${input.engineeringBlocker.rationale}` },
|
|
954
|
+
risk: 'readonly',
|
|
955
|
+
confidence: 'medium',
|
|
956
|
+
}],
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
linkedWork = getWorkContract(ctx.workStore, declaredLinkedWorkId);
|
|
1585
960
|
if (!linkedWork) {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
`Resolve blocker ${input.engineeringBlocker.blockerId} without widening Work ${work.workId}.`,
|
|
1591
|
-
'Return bounded evidence or a precise wake/blocker condition to the owning Controller.',
|
|
1592
|
-
],
|
|
1593
|
-
allowedPaths: [],
|
|
1594
|
-
initialLikelyPaths: [],
|
|
1595
|
-
forbiddenPaths: [],
|
|
1596
|
-
checks: [],
|
|
1597
|
-
modeInput: {
|
|
1598
|
-
scopeClear: false,
|
|
1599
|
-
mutation: false,
|
|
1600
|
-
requiresInvestigation: true,
|
|
1601
|
-
requiresRecovery: true,
|
|
1602
|
-
requiresParallelism: true,
|
|
1603
|
-
},
|
|
1604
|
-
requestedBy: 'chatgpt',
|
|
1605
|
-
relatedWorkId: work.workId,
|
|
1606
|
-
workRelation: 'parallel',
|
|
1607
|
-
requirementId: work.requirementId,
|
|
1608
|
-
workKind: 'investigation',
|
|
1609
|
-
constraints: { accessMode: work.constraints.accessMode },
|
|
961
|
+
return buildFacadeResult({
|
|
962
|
+
status: 'blocked',
|
|
963
|
+
summary: `ENGINEERING_BLOCKER_LINKED_WORK_UNKNOWN: ${declaredLinkedWorkId} is not a readable Work.`,
|
|
964
|
+
data: { work: summarizeWorkContract(work), linkedWorkId: declaredLinkedWorkId, linkedWorkCreated: false },
|
|
1610
965
|
});
|
|
1611
|
-
linkedWork = getWorkContract(ctx.workStore, linkedWorkId);
|
|
1612
|
-
if (linked.status !== 'ok' || !linkedWork) {
|
|
1613
|
-
return buildFacadeResult({
|
|
1614
|
-
status: 'blocked',
|
|
1615
|
-
summary: `ENGINEERING_LINKED_WORK_ADMISSION_FAILED: ${linked.summary}`,
|
|
1616
|
-
data: { work: summarizeWorkContract(work), linkedWorkId, linkedWorkCreated: false },
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
966
|
}
|
|
967
|
+
linkedWorkId = linkedWork.workId;
|
|
1620
968
|
}
|
|
1621
969
|
blocker = buildEngineeringBlockerDispositionReceipt({
|
|
1622
970
|
sourceRevision: work.engineeringContext.sourceIdentity.revision,
|
|
@@ -1632,30 +980,19 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1632
980
|
} catch (error) {
|
|
1633
981
|
return buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? error.message : 'ENGINEERING_BLOCKER_INVALID', data: { work: summarizeWorkContract(work) } });
|
|
1634
982
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
? [{
|
|
1649
|
-
label: 'Refresh design evidence',
|
|
1650
|
-
tool: 'rh_context',
|
|
1651
|
-
operation: 'search',
|
|
1652
|
-
payload: { work_id: work.workId, query: 'Refresh current source and design evidence for this Work before engineering re-entry.' },
|
|
1653
|
-
risk: 'readonly',
|
|
1654
|
-
}]
|
|
1655
|
-
: linkedWork
|
|
1656
|
-
? [{ label: 'Continue linked Work', tool: 'rh_work', operation: 'continue', payload: { work_id: linkedWork.workId }, risk: 'workspace_write' }]
|
|
1657
|
-
: [],
|
|
1658
|
-
});
|
|
983
|
+
if (blocker.action === 'extend_candidate') {
|
|
984
|
+
work = appendWorkEvidence(ctx.workStore, work.workId, {
|
|
985
|
+
title: 'same-root candidate scope extension',
|
|
986
|
+
summary: `Blocker ${blocker.blockerId} remains inside the current architecture authority; continue the exact candidate without Design re-entry or sibling Work.`,
|
|
987
|
+
detailLevel: 'summary',
|
|
988
|
+
});
|
|
989
|
+
} else {
|
|
990
|
+
work = appendWorkEvidence(ctx.workStore, work.workId, {
|
|
991
|
+
title: 'engineering blocker observation',
|
|
992
|
+
summary: `Recorded ${blocker.classification} blocker ${blocker.blockerId}; it is model guidance, not a Work continuation gate.`,
|
|
993
|
+
detailLevel: 'summary',
|
|
994
|
+
});
|
|
995
|
+
}
|
|
1659
996
|
}
|
|
1660
997
|
|
|
1661
998
|
if ((input.reviewFindings?.length ?? 0) > 0 && work.workKind !== 'read_only_review') {
|
|
@@ -1665,28 +1002,10 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1665
1002
|
data: { work: summarizeWorkContract(work), reviewFindingsRecorded: false },
|
|
1666
1003
|
});
|
|
1667
1004
|
}
|
|
1668
|
-
if (work.workKind === 'read_only_review') {
|
|
1669
|
-
const sourceIdentity = evaluateReadOnlyReviewSourceIdentity(work, ctx.sourceRevision, ctx.workspaceChangedPaths);
|
|
1670
|
-
if (sourceIdentity.status !== 'complete') {
|
|
1671
|
-
return buildFacadeResult({
|
|
1672
|
-
status: 'blocked',
|
|
1673
|
-
summary: `READ_ONLY_REVIEW_SOURCE_IDENTITY_REQUIRED: ${sourceIdentity.reasons.join(' ')}`,
|
|
1674
|
-
data: { work: summarizeWorkContract(work), sourceIdentityProven: false },
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
1005
|
const explicitPolicyScope = input.allowedPaths !== undefined
|
|
1680
1006
|
|| input.forbiddenPaths !== undefined
|
|
1681
1007
|
|| input.checks !== undefined;
|
|
1682
1008
|
if (explicitPolicyScope) {
|
|
1683
|
-
if (work.planId) {
|
|
1684
|
-
return buildFacadeResult({
|
|
1685
|
-
status: 'blocked',
|
|
1686
|
-
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.`,
|
|
1687
|
-
data: { work: summarizeWorkContract(work), planId: work.planId, policyScopeUpdated: false },
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
1009
|
const normalizedChecks = normalizeCheckIds(input.checks ?? [], ctx.availableChecks ?? []);
|
|
1691
1010
|
if (normalizedChecks.invalidCheckIds.length > 0) {
|
|
1692
1011
|
return buildFacadeResult({
|
|
@@ -1696,12 +1015,10 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1696
1015
|
});
|
|
1697
1016
|
}
|
|
1698
1017
|
work = updateWorkContract(ctx.workStore, work.workId, {
|
|
1699
|
-
allowedPaths: [...new Set([...work.allowedPaths, ...(input.allowedPaths ?? [])])].slice(0, 50),
|
|
1700
|
-
forbiddenPaths: [...new Set([...work.forbiddenPaths, ...(input.forbiddenPaths ?? [])])].slice(0, 50),
|
|
1701
1018
|
checks: [...new Set([...work.checks, ...normalizedChecks.validCheckIds])].slice(0, 30),
|
|
1702
1019
|
scopeEvidence: {
|
|
1703
1020
|
initialLikelyPaths: [...new Set([
|
|
1704
|
-
...(work.scopeEvidence?.initialLikelyPaths ??
|
|
1021
|
+
...(work.scopeEvidence?.initialLikelyPaths ?? []),
|
|
1705
1022
|
...(input.allowedPaths ?? []),
|
|
1706
1023
|
])].slice(0, 100),
|
|
1707
1024
|
inspectedPaths: work.scopeEvidence?.inspectedPaths ?? [],
|
|
@@ -1836,7 +1153,6 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1836
1153
|
detailLevel: 'summary',
|
|
1837
1154
|
});
|
|
1838
1155
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1839
|
-
status: 'running',
|
|
1840
1156
|
phase: 'implementation',
|
|
1841
1157
|
state: 'active',
|
|
1842
1158
|
summary: `Controller chose bounded ${decision} after acceptance failure; implementation may resume before re-verification.`,
|
|
@@ -1859,148 +1175,36 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
1859
1175
|
});
|
|
1860
1176
|
}
|
|
1861
1177
|
|
|
1862
|
-
//
|
|
1863
|
-
//
|
|
1864
|
-
|
|
1865
|
-
// authority until newer valid-fail evidence; an unresolved matching handoff is reused.
|
|
1866
|
-
if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair && work.recoveryPolicy.handoffOnAmbiguity) {
|
|
1178
|
+
// Acceptance failure is a model/controller decision point, not a human blocker.
|
|
1179
|
+
// Do not manufacture Handoff/UserRequest state for repair vs re-scope.
|
|
1180
|
+
if (history.acceptanceFailures.length > 0 && !explicitAcceptanceRepair) {
|
|
1867
1181
|
const failureReason = `Acceptance checks failed: ${history.acceptanceFailures.join(', ')}`;
|
|
1868
|
-
const latestFailureAt = work.checkRefs
|
|
1869
|
-
.filter((record) => record.outcome === 'valid_fail' && history.acceptanceFailures.includes(record.checkId))
|
|
1870
|
-
.map((record) => record.recordedAt)
|
|
1871
|
-
.sort((left, right) => right.localeCompare(left))[0];
|
|
1872
|
-
const matchingHandoffs = listHandoffItems({ ...ctx.handoffStore, status: 'all', limit: 100 })
|
|
1873
|
-
.filter((item) => (
|
|
1874
|
-
item.workId === input.workId
|
|
1875
|
-
&& item.creationReason === 'ambiguous_outcome'
|
|
1876
|
-
&& item.title === 'Acceptance failure needs review'
|
|
1877
|
-
&& item.reason === failureReason
|
|
1878
|
-
&& (!latestFailureAt || item.createdAt >= latestFailureAt)
|
|
1879
|
-
));
|
|
1880
|
-
const activeHandoff = matchingHandoffs.find((item) => item.status === 'pending' || item.status === 'acknowledged');
|
|
1881
|
-
if (activeHandoff) {
|
|
1882
|
-
return buildFacadeResult({
|
|
1883
|
-
status: 'blocked',
|
|
1884
|
-
summary: `Continue remains paused for ChatGPT review through existing handoff ${activeHandoff.id}.`,
|
|
1885
|
-
data: {
|
|
1886
|
-
work: summarizeWorkContract(work),
|
|
1887
|
-
handoffId: activeHandoff.id,
|
|
1888
|
-
acceptanceFailures: history.acceptanceFailures,
|
|
1889
|
-
infrastructureIssues: history.infrastructureIssues,
|
|
1890
|
-
backgroundCompleted: false,
|
|
1891
|
-
},
|
|
1892
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1893
|
-
suggestedNextActions: [{
|
|
1894
|
-
label: 'Get handoff',
|
|
1895
|
-
tool: 'rh_inbox',
|
|
1896
|
-
operation: 'get',
|
|
1897
|
-
payload: { handoff_id: activeHandoff.id },
|
|
1898
|
-
risk: 'readonly',
|
|
1899
|
-
confidence: 'high',
|
|
1900
|
-
}],
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
const resolvedHandoff = matchingHandoffs.find((item) => item.status === 'resolved');
|
|
1905
|
-
if (!resolvedHandoff) {
|
|
1906
|
-
const continuation = buildWorkContinuationSnapshot(work);
|
|
1907
|
-
const handoff = createHandoffItem(ctx.handoffStore, {
|
|
1908
|
-
id: handoffIdFor('continue'),
|
|
1909
|
-
repoId: ctx.repoId,
|
|
1910
|
-
workId: work.workId,
|
|
1911
|
-
title: 'Acceptance failure needs review',
|
|
1912
|
-
severity: 'needs_review',
|
|
1913
|
-
creationReason: 'ambiguous_outcome',
|
|
1914
|
-
reason: failureReason,
|
|
1915
|
-
summary: 'Continue paused; ChatGPT must decide repair vs re-scope.',
|
|
1916
|
-
currentState: {
|
|
1917
|
-
repoId: ctx.repoId,
|
|
1918
|
-
workId: work.workId,
|
|
1919
|
-
mode: work.mode,
|
|
1920
|
-
statusSummary: 'waiting_for_review after acceptance failure',
|
|
1921
|
-
checks: history.acceptanceFailures.map((checkId) => ({ checkId, ok: false, outcome: 'valid_fail' as const })),
|
|
1922
|
-
workSemantics: continuation.semantics,
|
|
1923
|
-
reconciliationRequired: continuation.reconciliationRequired,
|
|
1924
|
-
nextSafeAction: continuation.nextSafeAction,
|
|
1925
|
-
},
|
|
1926
|
-
attemptedActions: ['continue'],
|
|
1927
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1928
|
-
blockingDecision: 'Decide whether to repair code, adjust acceptance criteria, or stop.',
|
|
1929
|
-
recommendedDecision: 'Inspect evidence and either repair or stop the workloop.',
|
|
1930
|
-
recommendedPrompt: work.continuationPrompt ?? `Continue from work ${work.workId}.`,
|
|
1931
|
-
recommendedContinuationPrompt: continuation.continuationPrompt,
|
|
1932
|
-
suggestedNextActions: [
|
|
1933
|
-
{
|
|
1934
|
-
label: 'Read work context',
|
|
1935
|
-
tool: 'rh_context',
|
|
1936
|
-
operation: 'get',
|
|
1937
|
-
payload: { work_id: work.workId },
|
|
1938
|
-
risk: 'readonly',
|
|
1939
|
-
},
|
|
1940
|
-
],
|
|
1941
|
-
});
|
|
1942
|
-
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1943
|
-
status: 'ready',
|
|
1944
|
-
phase: 'verification',
|
|
1945
|
-
state: 'blocked',
|
|
1946
|
-
summary: `Acceptance failure requires review through handoff ${handoff.id}.`,
|
|
1947
|
-
evidenceRefs: work.evidenceRefs,
|
|
1948
|
-
});
|
|
1949
|
-
const updated = appendWorkHandoffRef(ctx.workStore, work.workId, handoff.id);
|
|
1950
|
-
|
|
1951
|
-
return buildFacadeResult({
|
|
1952
|
-
status: 'blocked',
|
|
1953
|
-
summary: `Continue paused for ChatGPT review; handoff ${handoff.id} created. No background execution pretended.`,
|
|
1954
|
-
data: {
|
|
1955
|
-
work: summarizeWorkContract(updated),
|
|
1956
|
-
handoffId: handoff.id,
|
|
1957
|
-
acceptanceFailures: history.acceptanceFailures,
|
|
1958
|
-
infrastructureIssues: history.infrastructureIssues,
|
|
1959
|
-
backgroundCompleted: false,
|
|
1960
|
-
},
|
|
1961
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1962
|
-
suggestedNextActions: [
|
|
1963
|
-
{
|
|
1964
|
-
label: 'Get handoff',
|
|
1965
|
-
tool: 'rh_inbox',
|
|
1966
|
-
operation: 'get',
|
|
1967
|
-
payload: { handoff_id: handoff.id },
|
|
1968
|
-
risk: 'readonly',
|
|
1969
|
-
confidence: 'high',
|
|
1970
|
-
},
|
|
1971
|
-
],
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
1976
|
-
status: 'running',
|
|
1977
|
-
phase: 'verification',
|
|
1978
|
-
state: 'active',
|
|
1979
|
-
summary: `Resolved acceptance-failure handoff ${resolvedHandoff.id} authorizes bounded continuation to re-verification.`,
|
|
1980
|
-
evidenceRefs: work.evidenceRefs,
|
|
1981
|
-
});
|
|
1982
|
-
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
1983
1182
|
return buildFacadeResult({
|
|
1984
|
-
status: '
|
|
1985
|
-
summary:
|
|
1183
|
+
status: 'blocked',
|
|
1184
|
+
summary: `${failureReason}. The model must explicitly choose bounded repair or re-scope before continuing; no human Handoff was created.`,
|
|
1986
1185
|
data: {
|
|
1987
1186
|
work: summarizeWorkContract(work),
|
|
1988
|
-
handoffId: resolvedHandoff.id,
|
|
1989
1187
|
acceptanceFailures: history.acceptanceFailures,
|
|
1990
1188
|
infrastructureIssues: history.infrastructureIssues,
|
|
1991
1189
|
backgroundCompleted: false,
|
|
1992
|
-
|
|
1993
|
-
remainingChecks: history.acceptanceFailures,
|
|
1190
|
+
acceptanceFailureDecisionRequired: true,
|
|
1994
1191
|
},
|
|
1995
1192
|
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
1996
|
-
suggestedNextActions:
|
|
1193
|
+
suggestedNextActions: [{
|
|
1194
|
+
label: 'Inspect Work before repair or re-scope',
|
|
1195
|
+
tool: 'rh_context',
|
|
1196
|
+
operation: 'get',
|
|
1197
|
+
payload: { work_id: work.workId },
|
|
1198
|
+
risk: 'readonly',
|
|
1199
|
+
confidence: 'high',
|
|
1200
|
+
reason: 'Acceptance evidence is factual input; the model chooses the next capability without a Work lifecycle transition.',
|
|
1201
|
+
}],
|
|
1997
1202
|
});
|
|
1998
1203
|
}
|
|
1999
1204
|
|
|
2000
1205
|
// Infrastructure issues: suggest self-healing, not acceptance failure.
|
|
2001
1206
|
if (history.infrastructureIssues.length > 0) {
|
|
2002
1207
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2003
|
-
status: 'running',
|
|
2004
1208
|
phase: 'implementation',
|
|
2005
1209
|
state: 'active',
|
|
2006
1210
|
summary: `Infrastructure issues require repair: ${history.infrastructureIssues.join(', ')}.`,
|
|
@@ -2049,7 +1253,6 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
2049
1253
|
},
|
|
2050
1254
|
]).actions;
|
|
2051
1255
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2052
|
-
status: 'running',
|
|
2053
1256
|
phase: 'implementation',
|
|
2054
1257
|
state: 'active',
|
|
2055
1258
|
summary: implementationEvidence.reasons.join(' '),
|
|
@@ -2078,24 +1281,15 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
2078
1281
|
|
|
2079
1282
|
if (work.checks.length > 0 && history.validPasses.length < work.checks.length) {
|
|
2080
1283
|
const remaining = work.checks.filter((checkId) => !history.validPasses.includes(checkId));
|
|
2081
|
-
const suggested = validateSuggestedNextActions(
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
{ validCheckIds: work.checks },
|
|
2091
|
-
).actions;
|
|
2092
|
-
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2093
|
-
status: 'running',
|
|
2094
|
-
phase: 'verification',
|
|
2095
|
-
state: 'active',
|
|
2096
|
-
summary: `Verification remains for: ${remaining.join(', ')}.`,
|
|
2097
|
-
evidenceRefs: work.evidenceRefs,
|
|
2098
|
-
});
|
|
1284
|
+
const suggested = validateSuggestedNextActions([{
|
|
1285
|
+
label: 'Inspect Work and check evidence',
|
|
1286
|
+
tool: 'rh_context',
|
|
1287
|
+
operation: 'get',
|
|
1288
|
+
payload: { work_id: work.workId },
|
|
1289
|
+
risk: 'readonly',
|
|
1290
|
+
confidence: 'high',
|
|
1291
|
+
reason: 'Registered checks are independent execution evidence; they do not progress Work lifecycle state.',
|
|
1292
|
+
}]).actions;
|
|
2099
1293
|
const updated = updateWorkContract(ctx.workStore, work.workId, {
|
|
2100
1294
|
suggestedNextActions: suggested,
|
|
2101
1295
|
continuationPrompt: input.note
|
|
@@ -2104,12 +1298,12 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
2104
1298
|
});
|
|
2105
1299
|
return buildFacadeResult({
|
|
2106
1300
|
status: 'ok',
|
|
2107
|
-
summary: `
|
|
1301
|
+
summary: `Registered check evidence is still missing for: ${remaining.join(', ')}. Run checks through the check capability; Work semantic state is unchanged.`,
|
|
2108
1302
|
data: {
|
|
2109
1303
|
work: summarizeWorkContract(updated),
|
|
2110
1304
|
remainingChecks: remaining,
|
|
2111
1305
|
backgroundCompleted: false,
|
|
2112
|
-
nextStep: '
|
|
1306
|
+
nextStep: 'run_check',
|
|
2113
1307
|
},
|
|
2114
1308
|
suggestedNextActions: suggested,
|
|
2115
1309
|
});
|
|
@@ -2135,7 +1329,6 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
2135
1329
|
},
|
|
2136
1330
|
]).actions;
|
|
2137
1331
|
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2138
|
-
status: 'running',
|
|
2139
1332
|
phase: work.workKind === 'read_only_review' ? 'verification' : 'implementation',
|
|
2140
1333
|
state: 'active',
|
|
2141
1334
|
summary: work.workKind === 'read_only_review'
|
|
@@ -2165,293 +1358,13 @@ export function continueGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
2165
1358
|
});
|
|
2166
1359
|
}
|
|
2167
1360
|
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
status: 'running',
|
|
2173
|
-
phase: 'verification',
|
|
2174
|
-
state: 'satisfied',
|
|
2175
|
-
summary: 'Exact Work completion evidence proves the current candidate is verified and eligible for implementation review.',
|
|
2176
|
-
evidenceRefs: work.evidenceRefs,
|
|
2177
|
-
});
|
|
2178
|
-
requestWorkImplementationReview(ctx.workStore, work.workId, 'Implementation and verification evidence are complete; explicit Controller implementation review is required before delivery.');
|
|
2179
|
-
const suggested = validateSuggestedNextActions([implementationReviewSuggestedAction(work.workId)]).actions;
|
|
2180
|
-
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
2181
|
-
return buildFacadeResult({
|
|
2182
|
-
status: 'ok',
|
|
2183
|
-
summary: 'Continue: implementation and verification evidence are complete; explicit implementation review is next.',
|
|
2184
|
-
data: { work: summarizeWorkContract(updated), backgroundCompleted: false, nextStep: 'review' },
|
|
2185
|
-
suggestedNextActions: suggested,
|
|
2186
|
-
});
|
|
2187
|
-
}
|
|
2188
|
-
const suggested = validateSuggestedNextActions([{
|
|
2189
|
-
label: 'Finalize work', tool: 'rh_work', operation: 'finalize', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high',
|
|
2190
|
-
}]).actions;
|
|
2191
|
-
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2192
|
-
status: 'running', phase: 'delivery', state: 'active',
|
|
2193
|
-
summary: 'Source-free Work evidence is complete; implementation review is not required and semantic finalization is next.', evidenceRefs: work.evidenceRefs,
|
|
2194
|
-
});
|
|
2195
|
-
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
2196
|
-
return buildFacadeResult({ status: 'ok', summary: 'Continue: evidence is complete; ready to finalize.', data: { work: summarizeWorkContract(updated), backgroundCompleted: false, nextStep: 'finalize' }, suggestedNextActions: suggested });
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
export function verifyGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopVerifyInput): FacadeResult {
|
|
2200
|
-
const work = getWorkContract(ctx.workStore, input.workId);
|
|
2201
|
-
if (!work) {
|
|
2202
|
-
return buildFacadeResult({
|
|
2203
|
-
status: 'not_found',
|
|
2204
|
-
summary: `WorkContract ${input.workId} not found.`,
|
|
2205
|
-
data: { workId: input.workId },
|
|
2206
|
-
});
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
const available = ctx.availableChecks ?? work.checks.map((id) => ({ id }));
|
|
2210
|
-
const classified = classifyVerificationOutcome({
|
|
2211
|
-
checkId: input.checkId,
|
|
2212
|
-
available,
|
|
2213
|
-
infrastructureFailed: input.infrastructureFailed,
|
|
2214
|
-
checkFailed: input.checkFailed,
|
|
2215
|
-
skipped: input.skipped,
|
|
2216
|
-
});
|
|
2217
|
-
if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
|
|
2218
|
-
const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
|
|
2219
|
-
const existing = [...work.checkRefs].reverse().find((record) => record.checkId === resolvedCheckId);
|
|
2220
|
-
const completed = work.status === 'completed';
|
|
2221
|
-
return buildFacadeResult({
|
|
2222
|
-
status: completed ? 'ok' : 'blocked',
|
|
2223
|
-
summary: `WorkContract ${work.workId} is terminal (${work.status}); verification was not re-executed.`,
|
|
2224
|
-
data: {
|
|
2225
|
-
work: summarizeWorkContract(work),
|
|
2226
|
-
verification: {
|
|
2227
|
-
checkId: resolvedCheckId,
|
|
2228
|
-
...(existing ? { outcome: existing.outcome } : {}),
|
|
2229
|
-
terminal: true,
|
|
2230
|
-
idempotent: true,
|
|
2231
|
-
reexecuted: false,
|
|
2232
|
-
isAcceptanceFailure: existing?.outcome === 'valid_fail',
|
|
2233
|
-
isInfrastructureIssue: existing?.outcome === 'invalid_check_id' || existing?.outcome === 'infrastructure_failure',
|
|
2234
|
-
doesNotRequestTaskChanges: existing?.outcome !== 'valid_fail',
|
|
2235
|
-
},
|
|
2236
|
-
backgroundCompleted: false,
|
|
2237
|
-
},
|
|
2238
|
-
evidenceRefs: existing?.evidenceRef ? [existing.evidenceRef] : [],
|
|
2239
|
-
warnings: classified.warnings,
|
|
2240
|
-
suggestedNextActions: [{ label: 'Inspect work via context', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }],
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
const at = nowIso(ctx);
|
|
2245
|
-
const resolvedCheckId = classified.normalizedCheckId ?? classified.checkId;
|
|
2246
|
-
const receipt = input.receipt
|
|
2247
|
-
&& input.receipt.repoId === work.repoId
|
|
2248
|
-
&& input.receipt.workId === work.workId
|
|
2249
|
-
&& input.receipt.checkId === resolvedCheckId
|
|
2250
|
-
? input.receipt
|
|
2251
|
-
: undefined;
|
|
2252
|
-
const sourceRevision = receipt && input.sourceRevision?.trim() ? input.sourceRevision.trim() : undefined;
|
|
2253
|
-
const verificationSummary = receipt
|
|
2254
|
-
? `${classified.summary} Durable Process receipt ${receipt.receiptId}.`
|
|
2255
|
-
: classified.summary;
|
|
2256
|
-
const record: VerificationRecord = {
|
|
2257
|
-
checkId: resolvedCheckId,
|
|
2258
|
-
outcome: classified.outcome,
|
|
2259
|
-
summary: verificationSummary,
|
|
2260
|
-
recordedAt: at,
|
|
2261
|
-
sourceRevision,
|
|
2262
|
-
workspaceFingerprint: receipt && sourceRevision ? input.workspaceFingerprint : undefined,
|
|
2263
|
-
verificationInputFingerprint: receipt && sourceRevision ? input.verificationInputFingerprint : undefined,
|
|
2264
|
-
commandFingerprint: receipt && sourceRevision ? input.commandFingerprint : undefined,
|
|
2265
|
-
startedAt: receipt?.startedAt,
|
|
2266
|
-
completedAt: receipt?.finishedAt,
|
|
2267
|
-
receipt,
|
|
2268
|
-
evidenceRef: {
|
|
2269
|
-
title: `verification:${classified.outcome}`,
|
|
2270
|
-
summary: verificationSummary,
|
|
2271
|
-
detailLevel: 'summary',
|
|
2272
|
-
},
|
|
2273
|
-
};
|
|
2274
|
-
|
|
2275
|
-
// Supersede prior invalid/infrastructure noise when a valid outcome arrives for the same check.
|
|
2276
|
-
let checkRefs = work.checkRefs;
|
|
2277
|
-
if (classified.outcome === 'valid_pass' || classified.outcome === 'valid_fail') {
|
|
2278
|
-
checkRefs = work.checkRefs.map((existing) => {
|
|
2279
|
-
if (
|
|
2280
|
-
existing.checkId === record.checkId
|
|
2281
|
-
&& (existing.outcome === 'invalid_check_id' || existing.outcome === 'infrastructure_failure')
|
|
2282
|
-
) {
|
|
2283
|
-
return { ...existing, outcome: 'superseded' as const, summary: `Superseded by ${classified.outcome} at ${at}` };
|
|
2284
|
-
}
|
|
2285
|
-
return existing;
|
|
2286
|
-
});
|
|
2287
|
-
updateWorkContract(ctx.workStore, work.workId, { checkRefs });
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
const updated = appendVerificationRecord(ctx.workStore, work.workId, record);
|
|
2291
|
-
if (record.evidenceRef) appendWorkEvidence(ctx.workStore, work.workId, record.evidenceRef);
|
|
2292
|
-
|
|
2293
|
-
const status =
|
|
2294
|
-
classified.outcome === 'invalid_check_id' || classified.outcome === 'infrastructure_failure'
|
|
2295
|
-
? 'ok'
|
|
2296
|
-
: classified.outcome === 'valid_fail'
|
|
2297
|
-
? 'failed'
|
|
2298
|
-
: 'ok';
|
|
2299
|
-
|
|
2300
|
-
const completionEvidence = classified.outcome === 'valid_pass'
|
|
2301
|
-
? evaluateWorkCompletionEvidence(updated, sourceRevision, input.workspaceFingerprint)
|
|
2302
|
-
: undefined;
|
|
2303
|
-
const validPassReadyForNextBoundary = completionEvidence?.status === 'complete';
|
|
2304
|
-
const currentChangedPaths = normalizeImplementationReviewChangedPaths(ctx.workspaceChangedPaths ?? updated.scopeEvidence?.actualChangedPaths ?? []);
|
|
2305
|
-
const latestReview = latestImplementationReview(updated.implementationReviews);
|
|
2306
|
-
const approvedReviewRemainsAuthoritative = Boolean(
|
|
2307
|
-
validPassReadyForNextBoundary
|
|
2308
|
-
&& updated.phase === 'delivery'
|
|
2309
|
-
&& updated.phaseEvidence.review.state === 'satisfied'
|
|
2310
|
-
&& latestReview?.decision === 'approved'
|
|
2311
|
-
&& sourceRevision
|
|
2312
|
-
&& input.workspaceFingerprint
|
|
2313
|
-
&& latestReview.sourceRevision === sourceRevision
|
|
2314
|
-
&& latestReview.verificationWorkspaceFingerprint === input.workspaceFingerprint
|
|
2315
|
-
&& latestReview.changedPathDigest === implementationReviewChangedPathDigest(currentChangedPaths)
|
|
2316
|
-
);
|
|
2317
|
-
const reviewRequiredAfterPass = validPassReadyForNextBoundary
|
|
2318
|
-
&& !approvedReviewRemainsAuthoritative
|
|
2319
|
-
&& workRequiresImplementationReview(updated.workKind, currentChangedPaths);
|
|
2320
|
-
if (approvedReviewRemainsAuthoritative && updated.evidenceState !== 'valid') {
|
|
2321
|
-
recordWorkEvidenceState(ctx.workStore, updated.workId, 'valid');
|
|
2322
|
-
}
|
|
2323
|
-
if (reviewRequiredAfterPass) {
|
|
2324
|
-
recordWorkScopeEvidence(ctx.workStore, updated.workId, { actualChangedPaths: [...(ctx.workspaceChangedPaths ?? [])] });
|
|
2325
|
-
transitionWorkContractPhase(ctx.workStore, updated.workId, {
|
|
2326
|
-
status: 'running',
|
|
2327
|
-
phase: 'verification',
|
|
2328
|
-
state: 'satisfied',
|
|
2329
|
-
summary: 'All required exact Work verification receipts are current; implementation review admission is now allowed.',
|
|
2330
|
-
evidenceRefs: updated.evidenceRefs,
|
|
2331
|
-
});
|
|
2332
|
-
requestWorkImplementationReview(ctx.workStore, updated.workId, 'Verification is complete; explicit Controller implementation review is required before delivery.');
|
|
2333
|
-
}
|
|
2334
|
-
const suggested = validateSuggestedNextActions(
|
|
2335
|
-
classified.outcome === 'valid_pass'
|
|
2336
|
-
? [reviewRequiredAfterPass
|
|
2337
|
-
? implementationReviewSuggestedAction(work.workId)
|
|
2338
|
-
: {
|
|
2339
|
-
label: validPassReadyForNextBoundary ? 'Finalize work' : 'Continue workloop',
|
|
2340
|
-
tool: 'rh_work',
|
|
2341
|
-
operation: validPassReadyForNextBoundary ? 'finalize' : 'continue',
|
|
2342
|
-
payload: { work_id: work.workId },
|
|
2343
|
-
risk: 'readonly',
|
|
2344
|
-
confidence: 'high',
|
|
2345
|
-
}]
|
|
2346
|
-
: classified.outcome === 'valid_fail'
|
|
2347
|
-
? [{
|
|
2348
|
-
label: 'Continue for review handoff',
|
|
2349
|
-
tool: 'rh_work',
|
|
2350
|
-
operation: 'continue',
|
|
2351
|
-
payload: { work_id: work.workId },
|
|
2352
|
-
risk: 'readonly',
|
|
2353
|
-
confidence: 'high',
|
|
2354
|
-
}]
|
|
2355
|
-
: [{
|
|
2356
|
-
label: 'Diagnose infrastructure (dry-run)',
|
|
2357
|
-
tool: 'rh_work',
|
|
2358
|
-
operation: 'repair',
|
|
2359
|
-
payload: { work_id: work.workId, repair_operation: 'diagnose', dry_run: true },
|
|
2360
|
-
risk: 'readonly',
|
|
2361
|
-
confidence: 'high',
|
|
2362
|
-
}],
|
|
2363
|
-
{ validCheckIds: work.checks },
|
|
2364
|
-
).actions;
|
|
2365
|
-
|
|
2366
|
-
return buildFacadeResult({
|
|
2367
|
-
status: status === 'failed' ? 'failed' : 'ok',
|
|
2368
|
-
summary: classified.summary,
|
|
2369
|
-
data: {
|
|
2370
|
-
work: summarizeWorkContract(updated),
|
|
2371
|
-
verification: {
|
|
2372
|
-
checkId: record.checkId,
|
|
2373
|
-
outcome: classified.outcome,
|
|
2374
|
-
isAcceptanceFailure: classified.isAcceptanceFailure,
|
|
2375
|
-
isInfrastructureIssue: classified.isInfrastructureIssue,
|
|
2376
|
-
// Explicitly separate pollution classes for ChatGPT.
|
|
2377
|
-
doesNotRequestTaskChanges: !classified.isAcceptanceFailure,
|
|
2378
|
-
},
|
|
2379
|
-
backgroundCompleted: false,
|
|
2380
|
-
...(classified.outcome === 'valid_pass'
|
|
2381
|
-
? { nextStep: reviewRequiredAfterPass ? 'review' : validPassReadyForNextBoundary ? 'finalize' : 'continue' }
|
|
2382
|
-
: {}),
|
|
2383
|
-
},
|
|
2384
|
-
warnings: classified.warnings,
|
|
2385
|
-
evidenceRefs: record.evidenceRef ? [record.evidenceRef] : [],
|
|
2386
|
-
suggestedNextActions: suggested,
|
|
2387
|
-
});
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
export function reviewGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopReviewInput): FacadeResult {
|
|
2391
|
-
let work = getWorkContract(ctx.workStore, input.workId);
|
|
2392
|
-
if (!work) return buildFacadeResult({ status: 'not_found', summary: `WorkContract ${input.workId} not found.`, data: { workId: input.workId } });
|
|
2393
|
-
if (work.status === 'completed' || work.status === 'cancelled' || work.status === 'failed') {
|
|
2394
|
-
return buildFacadeResult({ status: 'blocked', summary: `WorkContract ${work.workId} is terminal (${work.status}); implementation review was not recorded.`, data: { work: summarizeWorkContract(work) } });
|
|
2395
|
-
}
|
|
2396
|
-
const reviewerPrincipalId = ctx.principalId?.trim() ?? '';
|
|
2397
|
-
if (!reviewerPrincipalId) {
|
|
2398
|
-
return buildFacadeResult({ status: 'blocked', summary: 'WORK_IMPLEMENTATION_REVIEW_REVIEWER_REQUIRED: authenticated reviewer principal is required.', data: { work: summarizeWorkContract(work) } });
|
|
2399
|
-
}
|
|
2400
|
-
if (!input.rationale?.trim()) {
|
|
2401
|
-
return buildFacadeResult({ status: 'blocked', summary: 'WORK_IMPLEMENTATION_REVIEW_RATIONALE_REQUIRED: explicit review rationale is required.', data: { work: summarizeWorkContract(work) } });
|
|
2402
|
-
}
|
|
2403
|
-
try {
|
|
2404
|
-
const candidate = currentImplementationReviewCandidate(ctx, work);
|
|
2405
|
-
recordWorkScopeEvidence(ctx.workStore, work.workId, { actualChangedPaths: [...candidate.changedPaths] });
|
|
2406
|
-
work = getWorkContract(ctx.workStore, work.workId) ?? work;
|
|
2407
|
-
if (work.phase !== 'review') {
|
|
2408
|
-
throw new Error('WORK_IMPLEMENTATION_REVIEW_PHASE_REQUIRED: verify or continue the exact candidate into review before recording a decision.');
|
|
2409
|
-
}
|
|
2410
|
-
const at = nowIso(ctx);
|
|
2411
|
-
const review: WorkImplementationReviewRecord = {
|
|
2412
|
-
schemaVersion: 1,
|
|
2413
|
-
reviewId: `impl-review-${randomUUID().slice(0, 12)}`,
|
|
2414
|
-
workId: work.workId,
|
|
2415
|
-
reviewerPrincipalId,
|
|
2416
|
-
decision: input.decision,
|
|
2417
|
-
rationale: input.rationale.trim().slice(0, 4_000),
|
|
2418
|
-
findings: (input.findings ?? []).slice(0, 100).map((finding) => ({
|
|
2419
|
-
severity: finding.severity,
|
|
2420
|
-
category: finding.category.trim().slice(0, 160),
|
|
2421
|
-
summary: finding.summary.trim().slice(0, 1_000),
|
|
2422
|
-
...(finding.path?.trim() ? { path: finding.path.trim() } : {}),
|
|
2423
|
-
...(finding.symbol?.trim() ? { symbol: finding.symbol.trim().slice(0, 240) } : {}),
|
|
2424
|
-
})),
|
|
2425
|
-
sourceRevision: candidate.sourceRevision,
|
|
2426
|
-
workspaceFingerprint: candidate.workspaceFingerprint,
|
|
2427
|
-
verificationWorkspaceFingerprint: candidate.verificationWorkspaceFingerprint,
|
|
2428
|
-
changedPaths: [...candidate.changedPaths],
|
|
2429
|
-
changedPathDigest: implementationReviewChangedPathDigest(candidate.changedPaths),
|
|
2430
|
-
acceptanceCriteriaSummary: implementationReviewAcceptanceSummary(work),
|
|
2431
|
-
verificationEvidence: [...candidate.verificationEvidence],
|
|
2432
|
-
architectureEvidence: [...(candidate.architectureEvidence ?? [])],
|
|
2433
|
-
recordedAt: at,
|
|
2434
|
-
};
|
|
2435
|
-
const updated = recordWorkImplementationReview(ctx.workStore, work.workId, review);
|
|
2436
|
-
const suggested = validateSuggestedNextActions(input.decision === 'approved'
|
|
2437
|
-
? [{ label: 'Finalize reviewed work', tool: 'rh_work', operation: 'finalize', payload: { work_id: work.workId }, risk: 'workspace_write', confidence: 'high' }]
|
|
2438
|
-
: input.decision === 'changes_required'
|
|
2439
|
-
? [{ label: 'Continue implementation repairs', tool: 'rh_work', operation: 'continue', payload: { work_id: work.workId }, risk: 'workspace_write', confidence: 'high' }]
|
|
2440
|
-
: [{ label: 'Inspect blocked review evidence', tool: 'rh_context', operation: 'get', payload: { work_id: work.workId }, risk: 'readonly', confidence: 'high' }]).actions;
|
|
2441
|
-
const persisted = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
2442
|
-
return buildFacadeResult({
|
|
2443
|
-
status: input.decision === 'blocked' ? 'blocked' : 'ok',
|
|
2444
|
-
summary: `Implementation review ${review.reviewId}: ${input.decision}.`,
|
|
2445
|
-
data: { work: summarizeWorkContract(persisted), review, nextStep: input.decision === 'approved' ? 'finalize' : input.decision === 'changes_required' ? 'continue' : 'blocked' },
|
|
2446
|
-
suggestedNextActions: suggested,
|
|
2447
|
-
});
|
|
2448
|
-
} catch (error) {
|
|
2449
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2450
|
-
return buildFacadeResult({ status: 'blocked', summary: message, data: { work: summarizeWorkContract(work) }, suggestedNextActions: suggestedForWork(work) });
|
|
2451
|
-
}
|
|
1361
|
+
// Completion evidence is observational. It cannot choose a review/finalize
|
|
1362
|
+
// transition or tell the model which capability to invoke next.
|
|
1363
|
+
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: [] });
|
|
1364
|
+
return buildFacadeResult({ status: 'ok', summary: 'Continue: current Work evidence recorded.', data: { work: summarizeWorkContract(updated), backgroundCompleted: false }, suggestedNextActions: [] });
|
|
2452
1365
|
}
|
|
2453
1366
|
|
|
2454
|
-
export function
|
|
1367
|
+
export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopStopInput): FacadeResult {
|
|
2455
1368
|
const work = getWorkContract(ctx.workStore, input.workId);
|
|
2456
1369
|
if (!work) {
|
|
2457
1370
|
return buildFacadeResult({
|
|
@@ -2461,380 +1374,38 @@ export function finalizeGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorklo
|
|
|
2461
1374
|
});
|
|
2462
1375
|
}
|
|
2463
1376
|
|
|
2464
|
-
|
|
1377
|
+
const destructiveCleanup = input.authorizeDestructiveCleanup === true;
|
|
1378
|
+
if (semanticWorkState(work) === 'completed') {
|
|
2465
1379
|
return buildFacadeResult({
|
|
2466
1380
|
status: 'blocked',
|
|
2467
|
-
summary: `
|
|
1381
|
+
summary: `WORK_CANCEL_COMPLETED: ${work.workId}`,
|
|
2468
1382
|
data: { work: summarizeWorkContract(work) },
|
|
2469
1383
|
});
|
|
2470
1384
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
status: 'ok',
|
|
2482
|
-
summary: `Finalize result: succeeded for ${work.workId}.`,
|
|
2483
|
-
data: {
|
|
2484
|
-
work: summarizeWorkContract(work),
|
|
2485
|
-
finalStatus: 'completed',
|
|
2486
|
-
completionReceipt: work.completionReceipt,
|
|
2487
|
-
idempotent: true,
|
|
2488
|
-
hiddenFailure: false,
|
|
2489
|
-
},
|
|
2490
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
2491
|
-
suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
|
|
2492
|
-
});
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
const currentChangedPaths = normalizeImplementationReviewChangedPaths(ctx.workspaceChangedPaths ?? work.scopeEvidence?.actualChangedPaths ?? []);
|
|
2496
|
-
if (workRequiresImplementationReview(work.workKind, currentChangedPaths)) {
|
|
2497
|
-
try {
|
|
2498
|
-
const candidate = currentImplementationReviewCandidate(ctx, work);
|
|
2499
|
-
assertImplementationReviewPreDeliveryBoundary({
|
|
2500
|
-
repoId: work.repoId,
|
|
2501
|
-
workId: work.workId,
|
|
2502
|
-
workKind: work.workKind,
|
|
2503
|
-
reviews: work.implementationReviews,
|
|
2504
|
-
candidate,
|
|
2505
|
-
requiredCheckIds: work.checks,
|
|
2506
|
-
verificationRecords: work.checkRefs,
|
|
2507
|
-
});
|
|
2508
|
-
} catch (error) {
|
|
2509
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2510
|
-
const reviewDecisionBlocked = /WORK_IMPLEMENTATION_REVIEW_(REQUIRED|STALE|CHANGES_REQUIRED|BLOCKED)/.test(message);
|
|
2511
|
-
if (reviewDecisionBlocked) {
|
|
2512
|
-
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2513
|
-
status: 'running',
|
|
2514
|
-
phase: 'verification',
|
|
2515
|
-
state: 'satisfied',
|
|
2516
|
-
summary: `Current exact verification remains authoritative, but implementation review must be renewed before delivery: ${message}`,
|
|
2517
|
-
evidenceRefs: work.evidenceRefs,
|
|
2518
|
-
});
|
|
2519
|
-
requestWorkImplementationReview(ctx.workStore, work.workId, `Pre-delivery implementation review gate requires a renewed Controller decision: ${message}`);
|
|
2520
|
-
const suggested = validateSuggestedNextActions([implementationReviewSuggestedAction(work.workId)]).actions;
|
|
2521
|
-
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
2522
|
-
return buildFacadeResult({
|
|
2523
|
-
status: 'blocked',
|
|
2524
|
-
summary: message,
|
|
2525
|
-
data: { work: summarizeWorkContract(updated), nextStep: 'review' },
|
|
2526
|
-
suggestedNextActions: suggested,
|
|
2527
|
-
});
|
|
2528
|
-
}
|
|
2529
|
-
transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2530
|
-
status: 'running',
|
|
2531
|
-
phase: 'verification',
|
|
2532
|
-
state: 'active',
|
|
2533
|
-
summary: `Pre-delivery review could not prove an exact verified candidate: ${message}`,
|
|
2534
|
-
evidenceRefs: work.evidenceRefs,
|
|
2535
|
-
});
|
|
2536
|
-
const suggested = validateSuggestedNextActions([{
|
|
2537
|
-
label: 'Verify exact candidate',
|
|
2538
|
-
tool: 'rh_work',
|
|
2539
|
-
operation: 'verify',
|
|
2540
|
-
payload: { work_id: work.workId, check_id: work.checks[0] },
|
|
2541
|
-
risk: 'workspace_write',
|
|
2542
|
-
confidence: work.checks[0] ? 'high' : 'medium',
|
|
2543
|
-
}], { validCheckIds: work.checks }).actions;
|
|
2544
|
-
const updated = updateWorkContract(ctx.workStore, work.workId, { suggestedNextActions: suggested });
|
|
2545
|
-
return buildFacadeResult({
|
|
2546
|
-
status: 'blocked',
|
|
2547
|
-
summary: message,
|
|
2548
|
-
data: { work: summarizeWorkContract(updated), nextStep: 'verify' },
|
|
2549
|
-
suggestedNextActions: suggested,
|
|
2550
|
-
});
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
const completionEvidence = evaluateWorkCompletionEvidence(
|
|
2555
|
-
work,
|
|
2556
|
-
ctx.sourceRevision,
|
|
2557
|
-
ctx.workspaceFingerprint,
|
|
2558
|
-
ctx.workBoundProcessEvidenceIds,
|
|
2559
|
-
ctx.workspaceChangedPaths,
|
|
2560
|
-
);
|
|
2561
|
-
const history = completionEvidence.history;
|
|
2562
|
-
|
|
2563
|
-
if (input.forceFailed || completionEvidence.status === 'failed') {
|
|
2564
|
-
const updated = failWorkContract(ctx.workStore, work.workId, {
|
|
2565
|
-
phase: work.phase,
|
|
2566
|
-
summary: `Work failed acceptance/finalization while in ${work.phase}: ${history.acceptanceFailures.join(', ') || 'forced failure'}.`,
|
|
2567
|
-
evidenceRefs: work.evidenceRefs,
|
|
2568
|
-
});
|
|
2569
|
-
if (updated.planId && updated.planStepId && ctx.planStore) {
|
|
2570
|
-
completePlanStepForWork(ctx.planStore, { planId: updated.planId, stepId: updated.planStepId, work: updated });
|
|
2571
|
-
}
|
|
2572
|
-
return buildFacadeResult({
|
|
2573
|
-
status: 'failed',
|
|
2574
|
-
summary: `Finalize result: failed. Acceptance failures: ${history.acceptanceFailures.join(', ') || 'forced'}.`,
|
|
2575
|
-
data: {
|
|
2576
|
-
work: summarizeWorkContract(updated),
|
|
2577
|
-
finalStatus: 'failed',
|
|
2578
|
-
acceptanceFailures: history.acceptanceFailures,
|
|
2579
|
-
infrastructureIssues: history.infrastructureIssues,
|
|
2580
|
-
invalidCheckIds: history.invalidCheckIds,
|
|
2581
|
-
// Failures are not hidden.
|
|
2582
|
-
hiddenFailure: false,
|
|
2583
|
-
},
|
|
2584
|
-
suggestedNextActions: [
|
|
2585
|
-
{
|
|
2586
|
-
label: 'List handoffs',
|
|
2587
|
-
tool: 'rh_inbox',
|
|
2588
|
-
operation: 'list',
|
|
2589
|
-
risk: 'readonly',
|
|
2590
|
-
},
|
|
2591
|
-
],
|
|
2592
|
-
});
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
// Weak refs, partial checks, invalid ids, and infrastructure issues never imply successful completion.
|
|
2596
|
-
if (completionEvidence.status === 'incomplete') {
|
|
2597
|
-
const updated = transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2598
|
-
status: 'ready',
|
|
2599
|
-
phase: 'verification',
|
|
2600
|
-
state: 'blocked',
|
|
2601
|
-
summary: `Completion evidence remains incomplete: ${completionEvidence.reasons.join(' ')}`,
|
|
2602
|
-
evidenceRefs: work.evidenceRefs,
|
|
2603
|
-
});
|
|
2604
|
-
return buildFacadeResult({
|
|
2605
|
-
status: 'blocked',
|
|
2606
|
-
summary: `Finalize result: waiting_for_review. ${completionEvidence.reasons.join(' ')}`,
|
|
2607
|
-
data: {
|
|
2608
|
-
work: summarizeWorkContract(updated),
|
|
2609
|
-
finalStatus: 'ready',
|
|
2610
|
-
infrastructureIssues: history.infrastructureIssues,
|
|
2611
|
-
invalidCheckIds: history.invalidCheckIds,
|
|
2612
|
-
validPasses: history.validPasses,
|
|
2613
|
-
missingChecks: completionEvidence.missingChecks,
|
|
2614
|
-
durableResultEvidence: completionEvidence.durableResultEvidence,
|
|
2615
|
-
ignoredWeakReferences: {
|
|
2616
|
-
workerRef: Boolean(work.workerRef),
|
|
2617
|
-
worktreeRef: Boolean(work.worktreeRef),
|
|
2618
|
-
},
|
|
2619
|
-
hiddenFailure: false,
|
|
2620
|
-
},
|
|
2621
|
-
suggestedNextActions: [
|
|
2622
|
-
{
|
|
2623
|
-
label: 'Continue workloop',
|
|
2624
|
-
tool: 'rh_work',
|
|
2625
|
-
operation: 'continue',
|
|
2626
|
-
payload: { work_id: work.workId },
|
|
2627
|
-
risk: 'readonly',
|
|
2628
|
-
},
|
|
2629
|
-
],
|
|
2630
|
-
});
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
// Read-only review completion is semantic no-change authority, not Git delivery.
|
|
2634
|
-
// The exact source/workspace fence is re-evaluated above at finalize time; only
|
|
2635
|
-
// a clean review with persisted inspected paths and zero findings can close.
|
|
2636
|
-
if (work.workKind === 'read_only_review' && !work.completionReceipt) {
|
|
2637
|
-
const reviewEvidence = work.readOnlyReviewEvidence!;
|
|
2638
|
-
const recordedAt = nowIso(ctx);
|
|
2639
|
-
const completed = completeWorkWithReceipt(
|
|
2640
|
-
ctx.workStore,
|
|
2641
|
-
work.workId,
|
|
2642
|
-
{
|
|
2643
|
-
schemaVersion: 1,
|
|
2644
|
-
receiptId: `ROR-WORK-${randomUUID()}`,
|
|
2645
|
-
source: 'read_only_review',
|
|
2646
|
-
workId: work.workId,
|
|
2647
|
-
baseRevision: work.baseRevision!,
|
|
2648
|
-
sourceRevision: ctx.sourceRevision!,
|
|
2649
|
-
...(ctx.workspaceFingerprint ? { workspaceFingerprint: ctx.workspaceFingerprint } : {}),
|
|
2650
|
-
workspaceChangedPaths: [],
|
|
2651
|
-
inspectedPaths: reviewEvidence.inspectedPaths,
|
|
2652
|
-
findingCount: 0,
|
|
2653
|
-
recordedAt,
|
|
2654
|
-
},
|
|
2655
|
-
'completed_no_change',
|
|
2656
|
-
'read_only_review',
|
|
2657
|
-
);
|
|
2658
|
-
if (completed.planId && completed.planStepId && ctx.planStore) {
|
|
2659
|
-
completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
|
|
2660
|
-
}
|
|
2661
|
-
return buildFacadeResult({
|
|
2662
|
-
status: 'ok',
|
|
2663
|
-
summary: `Finalize result: clean read-only review completed with no source change for ${work.workId}.`,
|
|
2664
|
-
data: {
|
|
2665
|
-
work: summarizeWorkContract(completed),
|
|
2666
|
-
finalStatus: 'completed',
|
|
2667
|
-
completionOutcome: 'completed_no_change',
|
|
2668
|
-
completionReceipt: completed.completionReceipt,
|
|
2669
|
-
inspectedPathCount: reviewEvidence.inspectedPaths.length,
|
|
2670
|
-
hiddenFailure: false,
|
|
2671
|
-
},
|
|
2672
|
-
evidenceRefs: completed.evidenceRefs.slice(0, 5),
|
|
2673
|
-
suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
|
|
2674
|
-
});
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
|
-
// For a controller-local effect Work, the semantic Controller's explicit
|
|
2678
|
-
// finalize call is the terminalization decision, but it is admissible only
|
|
2679
|
-
// after durable result evidence exists. Record one canonical Work receipt here
|
|
2680
|
-
// instead of routing an effect-only Work through Git delivery. Remote effects
|
|
2681
|
-
// remain bound to their durable plugin action receipt in the plugin layer.
|
|
2682
|
-
if (work.workKind === 'local_effect' && !work.completionReceipt && completionEvidence.durableResultEvidence) {
|
|
2683
|
-
const recordedAt = nowIso(ctx);
|
|
2684
|
-
const completed = completeWorkWithReceipt(
|
|
2685
|
-
ctx.workStore,
|
|
2686
|
-
work.workId,
|
|
2687
|
-
{
|
|
2688
|
-
schemaVersion: 1,
|
|
2689
|
-
receiptId: `LFX-WORK-${randomUUID()}`,
|
|
2690
|
-
source: 'local_effect',
|
|
2691
|
-
workId: work.workId,
|
|
2692
|
-
operation: 'controller_work/local_effect',
|
|
2693
|
-
target: { kind: 'controller_local', id: work.workId },
|
|
2694
|
-
changed: true,
|
|
2695
|
-
recordedAt,
|
|
2696
|
-
},
|
|
2697
|
-
'completed_local',
|
|
2698
|
-
'local_effect',
|
|
2699
|
-
);
|
|
2700
|
-
if (completed.planId && completed.planStepId && ctx.planStore) {
|
|
2701
|
-
completePlanStepForWork(ctx.planStore, { planId: completed.planId, stepId: completed.planStepId, work: completed });
|
|
2702
|
-
}
|
|
2703
|
-
return buildFacadeResult({
|
|
2704
|
-
status: 'ok',
|
|
2705
|
-
summary: `Finalize result: succeeded for ${work.workId}.`,
|
|
2706
|
-
data: {
|
|
2707
|
-
work: summarizeWorkContract(completed),
|
|
2708
|
-
finalStatus: 'completed',
|
|
2709
|
-
completionReceipt: completed.completionReceipt,
|
|
2710
|
-
validPasses: history.validPasses,
|
|
2711
|
-
hiddenFailure: false,
|
|
2712
|
-
},
|
|
2713
|
-
evidenceRefs: completed.evidenceRefs.slice(0, 5),
|
|
2714
|
-
suggestedNextActions: [{ label: 'Read controller status', tool: 'rh_status', operation: 'get', risk: 'readonly' }],
|
|
2715
|
-
});
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
// Checks and result evidence are necessary but not sufficient. Delivery and
|
|
2719
|
-
// cleanup must produce the exact Work-owned receipt before Work can become
|
|
2720
|
-
// terminal; a Run exit or a missing receipt is never completion authority.
|
|
2721
|
-
if (!work.completionReceipt) {
|
|
2722
|
-
const updated = transitionWorkContractPhase(ctx.workStore, work.workId, {
|
|
2723
|
-
status: 'ready',
|
|
2724
|
-
phase: 'delivery',
|
|
2725
|
-
state: 'blocked',
|
|
2726
|
-
summary: 'Exact delivery and cleanup completion receipt is required.',
|
|
2727
|
-
evidenceRefs: work.evidenceRefs,
|
|
2728
|
-
});
|
|
2729
|
-
return buildFacadeResult({
|
|
2730
|
-
status: 'blocked',
|
|
2731
|
-
summary: `Finalize blocked for ${work.workId}: an exact delivery and cleanup completion receipt is required.`,
|
|
2732
|
-
data: { work: summarizeWorkContract(updated), finalStatus: 'ready', completionReceiptRequired: true },
|
|
2733
|
-
suggestedNextActions: [{ label: 'Complete Work delivery and cleanup', tool: 'rh_work', operation: 'finalize', payload: { work_id: work.workId }, risk: 'local_repo_write' }],
|
|
2734
|
-
});
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
const updated = getWorkContract(ctx.workStore, work.workId)!;
|
|
2738
|
-
if (updated.planId && updated.planStepId && ctx.planStore) {
|
|
2739
|
-
completePlanStepForWork(ctx.planStore, { planId: updated.planId, stepId: updated.planStepId, work: updated });
|
|
2740
|
-
}
|
|
2741
|
-
return buildFacadeResult({
|
|
2742
|
-
status: 'ok',
|
|
2743
|
-
summary: `Finalize result: succeeded for ${work.workId}.`,
|
|
2744
|
-
data: {
|
|
2745
|
-
work: summarizeWorkContract(updated),
|
|
2746
|
-
finalStatus: 'completed',
|
|
2747
|
-
validPasses: history.validPasses,
|
|
2748
|
-
hiddenFailure: false,
|
|
2749
|
-
},
|
|
2750
|
-
evidenceRefs: work.evidenceRefs.slice(0, 5),
|
|
2751
|
-
suggestedNextActions: [
|
|
2752
|
-
{
|
|
2753
|
-
label: 'Read controller status',
|
|
2754
|
-
tool: 'rh_status',
|
|
2755
|
-
operation: 'get',
|
|
2756
|
-
risk: 'readonly',
|
|
2757
|
-
},
|
|
2758
|
-
],
|
|
2759
|
-
});
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2762
|
-
export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopStopInput): FacadeResult {
|
|
2763
|
-
const work = getWorkContract(ctx.workStore, input.workId);
|
|
2764
|
-
if (!work) {
|
|
2765
|
-
return buildFacadeResult({
|
|
2766
|
-
status: 'not_found',
|
|
2767
|
-
summary: `WorkContract ${input.workId} not found.`,
|
|
2768
|
-
data: { workId: input.workId },
|
|
2769
|
-
});
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
const owner = ctx.workStore.controllerHome
|
|
2773
|
-
? getControllerSession({ controllerHome: ctx.workStore.controllerHome, repoId: ctx.repoId }, work.workId)
|
|
2774
|
-
: undefined;
|
|
2775
|
-
if (owner) {
|
|
2776
|
-
const ownerPrincipal = owner.principalId?.trim() || owner.controllerId;
|
|
2777
|
-
const ownerInstanceId = owner.controllerInstanceId?.trim();
|
|
2778
|
-
const callerPrincipal = ctx.principalId?.trim();
|
|
2779
|
-
const callerInstanceId = ctx.controllerInstanceId?.trim();
|
|
2780
|
-
const sameAuthority = Boolean(
|
|
2781
|
-
callerPrincipal
|
|
2782
|
-
&& ownerPrincipal === callerPrincipal
|
|
2783
|
-
&& (!ownerInstanceId || ownerInstanceId === callerInstanceId),
|
|
2784
|
-
);
|
|
2785
|
-
if (!sameAuthority) {
|
|
2786
|
-
return buildFacadeResult({
|
|
2787
|
-
status: 'blocked',
|
|
2788
|
-
summary: `WORK_TERMINALIZATION_ACTIVE_CONTROLLER_FENCE: ${work.workId} has a newer active Controller claim generation ${owner.claimGeneration ?? 1}.`,
|
|
2789
|
-
data: {
|
|
2790
|
-
work: summarizeWorkContract(work),
|
|
2791
|
-
activeController: {
|
|
2792
|
-
controllerId: owner.controllerId,
|
|
2793
|
-
principalId: ownerPrincipal,
|
|
2794
|
-
controllerInstanceId: owner.controllerInstanceId,
|
|
2795
|
-
sessionId: owner.sessionId,
|
|
2796
|
-
claimGeneration: owner.claimGeneration ?? 1,
|
|
2797
|
-
},
|
|
2798
|
-
finalStatus: work.status,
|
|
2799
|
-
},
|
|
2800
|
-
});
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
|
|
2804
|
-
const destructiveCleanup = input.authorizeDestructiveCleanup === true;
|
|
2805
|
-
cancelWorkContract(ctx.workStore, work.workId, {
|
|
2806
|
-
summary: input.reason ? `Stopped: ${input.reason}` : 'Work stopped without destructive cleanup.',
|
|
2807
|
-
evidenceRefs: work.evidenceRefs,
|
|
2808
|
-
});
|
|
2809
|
-
const updated = updateWorkContract(ctx.workStore, work.workId, {
|
|
2810
|
-
continuationPrompt: input.reason
|
|
2811
|
-
? `Stopped: ${input.reason}`.slice(0, 2_000)
|
|
2812
|
-
: work.continuationPrompt,
|
|
2813
|
-
// Authorization is not proof of cleanup. Preserve the reference until a real
|
|
2814
|
-
// cleanup handler verifies ownership, cleanliness and successful removal.
|
|
2815
|
-
worktreeRef: work.worktreeRef,
|
|
1385
|
+
const semanticRevision = Number.isInteger(work.semanticRevision) && Number(work.semanticRevision) > 0
|
|
1386
|
+
? Number(work.semanticRevision)
|
|
1387
|
+
: 1;
|
|
1388
|
+
const updated = reviseWorkSemanticContext(ctx.workStore, work.workId, {
|
|
1389
|
+
expectedRevision: semanticRevision,
|
|
1390
|
+
state: 'cancelled',
|
|
1391
|
+
resultRefs: work.evidenceRefs
|
|
1392
|
+
.flatMap((evidence) => [evidence.evidenceId, evidence.artifactId])
|
|
1393
|
+
.map((value) => value?.trim())
|
|
1394
|
+
.filter((value): value is string => Boolean(value)),
|
|
2816
1395
|
});
|
|
2817
1396
|
const planId = updated.planId;
|
|
2818
1397
|
const planStepId = updated.planStepId;
|
|
2819
1398
|
let plan = planId && ctx.planStore
|
|
2820
1399
|
? getPlanContract(ctx.planStore, planId)
|
|
2821
1400
|
: undefined;
|
|
2822
|
-
if (plan && planId && planStepId && ctx.planStore) {
|
|
2823
|
-
const step = plan.steps.find((entry) => entry.id === planStepId);
|
|
2824
|
-
if (step?.workId === updated.workId) {
|
|
2825
|
-
plan = completePlanStepForWork(ctx.planStore, { planId, stepId: planStepId, work: updated });
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
1401
|
|
|
2829
1402
|
return buildFacadeResult({
|
|
2830
1403
|
status: 'ok',
|
|
2831
|
-
summary: `WorkContract ${work.workId} cancelled/stopped. Evidence retained.
|
|
1404
|
+
summary: `WorkContract ${work.workId} cancelled/stopped. Evidence retained. Managed resource cleanup/retention is settled by the canonical Work finalization service.`,
|
|
2832
1405
|
data: {
|
|
2833
1406
|
work: summarizeWorkContract(updated),
|
|
2834
1407
|
finalStatus: 'cancelled',
|
|
2835
1408
|
evidenceRetained: true,
|
|
2836
|
-
worktreeDeleted: false,
|
|
2837
|
-
cleanupPending: destructiveCleanup && Boolean(work.worktreeRef),
|
|
2838
1409
|
destructiveCleanupAuthorized: destructiveCleanup,
|
|
2839
1410
|
...(plan ? { plan } : {}),
|
|
2840
1411
|
},
|
|
@@ -2853,6 +1424,11 @@ export function stopGoalWorkloop(ctx: GoalWorkloopContext, input: GoalWorkloopSt
|
|
|
2853
1424
|
export interface GoalWorkloopTrustedInput {
|
|
2854
1425
|
/** Runtime-composed source-bound evidence. This channel is intentionally separate from raw MCP/tool arguments. */
|
|
2855
1426
|
verifiedEngineeringEvidence?: EngineeringAdmissionEvidence;
|
|
1427
|
+
/**
|
|
1428
|
+
* Evaluator-minted evidence for one exact candidate. Kept off the raw MCP
|
|
1429
|
+
* argument surface so callers cannot manufacture architecture evidence.
|
|
1430
|
+
*/
|
|
1431
|
+
evaluationPromotionReceipt?: EvaluationPromotionReceipt;
|
|
2856
1432
|
}
|
|
2857
1433
|
|
|
2858
1434
|
export function runGoalWorkloop(
|
|
@@ -2871,18 +1447,10 @@ export function runGoalWorkloop(
|
|
|
2871
1447
|
initialLikelyPaths: Array.isArray(args.initial_likely_paths) ? args.initial_likely_paths.map(String) : undefined,
|
|
2872
1448
|
forbiddenPaths: Array.isArray(args.forbidden_paths) ? args.forbidden_paths.map(String) : undefined,
|
|
2873
1449
|
checks: Array.isArray(args.check_ids) ? args.check_ids.map(String) : undefined,
|
|
2874
|
-
|
|
1450
|
+
request: {
|
|
2875
1451
|
objective: typeof args.objective === 'string' ? args.objective : undefined,
|
|
2876
|
-
expectedFiles: typeof args.expected_files === 'number' ? args.expected_files : undefined,
|
|
2877
|
-
expectedChangedLines: typeof args.expected_changed_lines === 'number' ? args.expected_changed_lines : undefined,
|
|
2878
1452
|
scopeClear: args.scope_clear === undefined ? true : args.scope_clear === true,
|
|
2879
|
-
requiresInvestigation: args.requires_investigation === true,
|
|
2880
|
-
requiresLongRunningChecks: args.requires_long_running_checks === true,
|
|
2881
|
-
requiresParallelism: args.requires_parallelism === true,
|
|
2882
|
-
explicitMode: args.mode === 'plan' || args.mode === 'scale' ? args.mode : undefined,
|
|
2883
|
-
needsDependencies: args.needs_dependencies === true,
|
|
2884
1453
|
requiresRecovery: args.requires_recovery === true,
|
|
2885
|
-
requiresWorker: args.requires_worker === true,
|
|
2886
1454
|
requiresExternalEffect: args.requires_external_effect === true,
|
|
2887
1455
|
requiresApproval: args.requires_approval === true,
|
|
2888
1456
|
requiresUserApproval: args.requires_user_approval === true,
|
|
@@ -2896,9 +1464,6 @@ export function runGoalWorkloop(
|
|
|
2896
1464
|
issueId: typeof args.issue_id === 'string' ? args.issue_id : undefined,
|
|
2897
1465
|
approvalConfirmed: args.approval_confirmed === true,
|
|
2898
1466
|
dryRun: args.dry_run === true,
|
|
2899
|
-
forceMode: args.force_mode === 'direct_control' || args.force_mode === 'goal_workloop' || args.force_mode === 'handoff_only'
|
|
2900
|
-
? args.force_mode
|
|
2901
|
-
: undefined,
|
|
2902
1467
|
relatedWorkId: typeof args.related_work_id === 'string' ? args.related_work_id : undefined,
|
|
2903
1468
|
workRelation: args.work_relation === 'continue' || args.work_relation === 'extend' || args.work_relation === 'parallel' || args.work_relation === 'new_goal'
|
|
2904
1469
|
? args.work_relation
|
|
@@ -2925,7 +1490,7 @@ export function runGoalWorkloop(
|
|
|
2925
1490
|
}
|
|
2926
1491
|
const rawBlocker = args.engineering_blocker as Record<string, unknown>;
|
|
2927
1492
|
const classification = rawBlocker.classification;
|
|
2928
|
-
if (classification !== 'same_root_cause' && classification !== 'unrelated') {
|
|
1493
|
+
if (classification !== 'same_root_cause' && classification !== 'same_root_cause_scope_extension' && classification !== 'unrelated') {
|
|
2929
1494
|
return buildFacadeResult({ status: 'blocked', summary: 'ENGINEERING_BLOCKER_CLASSIFICATION_INVALID', data: { workId: String(args.work_id ?? '') } });
|
|
2930
1495
|
}
|
|
2931
1496
|
engineeringBlocker = {
|
|
@@ -2933,6 +1498,7 @@ export function runGoalWorkloop(
|
|
|
2933
1498
|
classification,
|
|
2934
1499
|
rationale: String(rawBlocker.rationale ?? ''),
|
|
2935
1500
|
semanticScopeKeys: Array.isArray(rawBlocker.semantic_scope_keys) ? rawBlocker.semantic_scope_keys.map(String) : undefined,
|
|
1501
|
+
linkedWorkId: typeof rawBlocker.linked_work_id === 'string' ? rawBlocker.linked_work_id : undefined,
|
|
2936
1502
|
};
|
|
2937
1503
|
}
|
|
2938
1504
|
return continueGoalWorkloop(ctx, {
|
|
@@ -2958,44 +1524,6 @@ export function runGoalWorkloop(
|
|
|
2958
1524
|
engineeringBlocker,
|
|
2959
1525
|
});
|
|
2960
1526
|
}
|
|
2961
|
-
case 'verify':
|
|
2962
|
-
return verifyGoalWorkloop(ctx, {
|
|
2963
|
-
workId: String(args.work_id ?? ''),
|
|
2964
|
-
checkId: String(args.check_id ?? args.checkId ?? ''),
|
|
2965
|
-
infrastructureFailed: args.infrastructure_failed === true,
|
|
2966
|
-
checkFailed: args.check_failed === true,
|
|
2967
|
-
skipped: args.skipped === true,
|
|
2968
|
-
});
|
|
2969
|
-
case 'review': {
|
|
2970
|
-
if (args.review_decision !== 'approved' && args.review_decision !== 'changes_required' && args.review_decision !== 'blocked') {
|
|
2971
|
-
return buildFacadeResult({
|
|
2972
|
-
status: 'blocked',
|
|
2973
|
-
summary: 'WORK_IMPLEMENTATION_REVIEW_DECISION_REQUIRED: operation=review requires an explicit approved, changes_required, or blocked decision.',
|
|
2974
|
-
data: { workId: String(args.work_id ?? '') },
|
|
2975
|
-
});
|
|
2976
|
-
}
|
|
2977
|
-
return reviewGoalWorkloop(ctx, {
|
|
2978
|
-
workId: String(args.work_id ?? ''),
|
|
2979
|
-
decision: args.review_decision,
|
|
2980
|
-
rationale: typeof args.review_rationale === 'string' ? args.review_rationale : '',
|
|
2981
|
-
findings: Array.isArray(args.implementation_review_findings)
|
|
2982
|
-
? args.implementation_review_findings
|
|
2983
|
-
.filter((value): value is Record<string, unknown> => Boolean(value) && typeof value === 'object')
|
|
2984
|
-
.map((value) => ({
|
|
2985
|
-
severity: value.severity === 'critical' || value.severity === 'high' || value.severity === 'medium' || value.severity === 'low' || value.severity === 'info' ? value.severity : 'info',
|
|
2986
|
-
category: typeof value.category === 'string' ? value.category : '',
|
|
2987
|
-
summary: typeof value.summary === 'string' ? value.summary : '',
|
|
2988
|
-
...(typeof value.path === 'string' ? { path: value.path } : {}),
|
|
2989
|
-
...(typeof value.symbol === 'string' ? { symbol: value.symbol } : {}),
|
|
2990
|
-
}))
|
|
2991
|
-
: undefined,
|
|
2992
|
-
});
|
|
2993
|
-
}
|
|
2994
|
-
case 'finalize':
|
|
2995
|
-
return finalizeGoalWorkloop(ctx, {
|
|
2996
|
-
workId: String(args.work_id ?? ''),
|
|
2997
|
-
forceFailed: args.force_failed === true,
|
|
2998
|
-
});
|
|
2999
1527
|
case 'stop':
|
|
3000
1528
|
return stopGoalWorkloop(ctx, {
|
|
3001
1529
|
workId: String(args.work_id ?? ''),
|