@tacuchi/agent-workflow-cli 25.8.0 → 26.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/README.md +6 -3
- package/dist/adapters/git-cli.js +225 -17
- package/dist/adapters/git-cli.js.map +1 -1
- package/dist/adapters/node-file-system.js +48 -0
- package/dist/adapters/node-file-system.js.map +1 -1
- package/dist/application/amend-service.js +51 -14
- package/dist/application/amend-service.js.map +1 -1
- package/dist/application/branch-resolver.js +6 -3
- package/dist/application/branch-resolver.js.map +1 -1
- package/dist/application/capability/design-handler.js +218 -43
- package/dist/application/capability/design-handler.js.map +1 -1
- package/dist/application/capability/dispatcher.js +182 -19
- package/dist/application/capability/dispatcher.js.map +1 -1
- package/dist/application/capability/durable-effect.js +3 -0
- package/dist/application/capability/durable-effect.js.map +1 -1
- package/dist/application/capability/readiness.js +8 -54
- package/dist/application/capability/readiness.js.map +1 -1
- package/dist/application/capability/resolution.js +124 -192
- package/dist/application/capability/resolution.js.map +1 -1
- package/dist/application/check-branch-service.js +158 -19
- package/dist/application/check-branch-service.js.map +1 -1
- package/dist/application/checkpoint/markdown.js +2 -2
- package/dist/application/checkpoint/markdown.js.map +1 -1
- package/dist/application/checkpoint/state-reader.js +12 -0
- package/dist/application/checkpoint/state-reader.js.map +1 -1
- package/dist/application/checkpoint-service.js +12 -4
- package/dist/application/checkpoint-service.js.map +1 -1
- package/dist/application/checkpoint-write-service.js +91 -17
- package/dist/application/checkpoint-write-service.js.map +1 -1
- package/dist/application/claims-ledger.js +137 -11
- package/dist/application/claims-ledger.js.map +1 -1
- package/dist/application/claims-recovery.js +85 -44
- package/dist/application/claims-recovery.js.map +1 -1
- package/dist/application/database-tool-catalog.js +33 -0
- package/dist/application/database-tool-catalog.js.map +1 -1
- package/dist/application/design/consumer-document.js +2 -2
- package/dist/application/design/consumer-document.js.map +1 -1
- package/dist/application/design/design-content-gate-service.js +20 -2
- package/dist/application/design/design-content-gate-service.js.map +1 -1
- package/dist/application/design/design-gate-service.js +2 -0
- package/dist/application/design/design-gate-service.js.map +1 -1
- package/dist/application/design/design-index-service.js +4 -0
- package/dist/application/design/design-index-service.js.map +1 -1
- package/dist/application/design/design-publish-service.js +1 -4
- package/dist/application/design/design-publish-service.js.map +1 -1
- package/dist/application/design/design-record-service.js +13 -7
- package/dist/application/design/design-record-service.js.map +1 -1
- package/dist/application/design/design-resolver-service.js +22 -3
- package/dist/application/design/design-resolver-service.js.map +1 -1
- package/dist/application/design/design-simple-service.js +24 -5
- package/dist/application/design/design-simple-service.js.map +1 -1
- package/dist/application/design/digest.js +41 -0
- package/dist/application/design/digest.js.map +1 -0
- package/dist/application/dev-only-services.js +166 -20
- package/dist/application/dev-only-services.js.map +1 -1
- package/dist/application/doc-branch-ledger.js +145 -0
- package/dist/application/doc-branch-ledger.js.map +1 -0
- package/dist/application/docs-canon-service.js +1 -1
- package/dist/application/docs-canon-service.js.map +1 -1
- package/dist/application/doctor/dist-integrity.js +113 -0
- package/dist/application/doctor/dist-integrity.js.map +1 -0
- package/dist/application/doctor/hosts.js +5 -2
- package/dist/application/doctor/hosts.js.map +1 -1
- package/dist/application/doctor/prepare.js +3 -3
- package/dist/application/doctor/prepare.js.map +1 -1
- package/dist/application/doctor/provider-installation.js +39 -19
- package/dist/application/doctor/provider-installation.js.map +1 -1
- package/dist/application/doctor/provider-mcps.js +9 -1
- package/dist/application/doctor/provider-mcps.js.map +1 -1
- package/dist/application/doctor/provider-skills.js +29 -115
- package/dist/application/doctor/provider-skills.js.map +1 -1
- package/dist/application/doctor/provider-visibility.js +76 -3
- package/dist/application/doctor/provider-visibility.js.map +1 -1
- package/dist/application/doctor/provider-workspace-block.js +156 -0
- package/dist/application/doctor/provider-workspace-block.js.map +1 -0
- package/dist/application/doctor/repair-runner.js +36 -39
- package/dist/application/doctor/repair-runner.js.map +1 -1
- package/dist/application/doctor/report.js +24 -4
- package/dist/application/doctor/report.js.map +1 -1
- package/dist/application/export-catalog-check.js +200 -0
- package/dist/application/export-catalog-check.js.map +1 -0
- package/dist/application/export-service.js +716 -79
- package/dist/application/export-service.js.map +1 -1
- package/dist/application/fix-git-service.js +285 -64
- package/dist/application/fix-git-service.js.map +1 -1
- package/dist/application/flow/advance.js +202 -25
- package/dist/application/flow/advance.js.map +1 -1
- package/dist/application/flow/annul-service.js +3 -1
- package/dist/application/flow/annul-service.js.map +1 -1
- package/dist/application/flow/batch-commit.js +189 -0
- package/dist/application/flow/batch-commit.js.map +1 -0
- package/dist/application/flow/checkout-observation.js +63 -10
- package/dist/application/flow/checkout-observation.js.map +1 -1
- package/dist/application/flow/close-artifacts.js +105 -0
- package/dist/application/flow/close-artifacts.js.map +1 -0
- package/dist/application/flow/close-at-boundary.js +25 -14
- package/dist/application/flow/close-at-boundary.js.map +1 -1
- package/dist/application/flow/close-document-guidance.js +70 -0
- package/dist/application/flow/close-document-guidance.js.map +1 -0
- package/dist/application/flow/flow-service.js +46 -3
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +300 -82
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/internal-drive.js +24 -1
- package/dist/application/flow/internal-drive.js.map +1 -1
- package/dist/application/flow/plan-entry.js +82 -0
- package/dist/application/flow/plan-entry.js.map +1 -0
- package/dist/application/flow/prove.js +32 -1
- package/dist/application/flow/prove.js.map +1 -1
- package/dist/application/flow/quick-checkouts.js +48 -0
- package/dist/application/flow/quick-checkouts.js.map +1 -0
- package/dist/application/flow/reinfer-batch.js +177 -0
- package/dist/application/flow/reinfer-batch.js.map +1 -0
- package/dist/application/flow/reopen-run.js +12 -5
- package/dist/application/flow/reopen-run.js.map +1 -1
- package/dist/application/flow/run-state-service.js +2 -0
- package/dist/application/flow/run-state-service.js.map +1 -1
- package/dist/application/flow/submit.js +584 -117
- package/dist/application/flow/submit.js.map +1 -1
- package/dist/application/generate-launch-service.js +16 -6
- package/dist/application/generate-launch-service.js.map +1 -1
- package/dist/application/git-flow-service.js +34 -3
- package/dist/application/git-flow-service.js.map +1 -1
- package/dist/application/history-publications.js +24 -0
- package/dist/application/history-publications.js.map +1 -1
- package/dist/application/history-reconcile-service.js +45 -0
- package/dist/application/history-reconcile-service.js.map +1 -0
- package/dist/application/history-table.js +61 -12
- package/dist/application/history-table.js.map +1 -1
- package/dist/application/history-update-service.js +25 -4
- package/dist/application/history-update-service.js.map +1 -1
- package/dist/application/hook-branch-check.js +25 -3
- package/dist/application/hook-branch-check.js.map +1 -1
- package/dist/application/hook-git-commit-advisor.js +3 -4
- package/dist/application/hook-git-commit-advisor.js.map +1 -1
- package/dist/application/hook-turn-start.js +51 -0
- package/dist/application/hook-turn-start.js.map +1 -0
- package/dist/application/host-memory/codex.js +1 -1
- package/dist/application/host-memory/codex.js.map +1 -1
- package/dist/application/hub-registry.js +135 -0
- package/dist/application/hub-registry.js.map +1 -0
- package/dist/application/local-proposal.js +72 -19
- package/dist/application/local-proposal.js.map +1 -1
- package/dist/application/mcp-host-reader.js +1 -1
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-writer.js +16 -10
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/merge-state-service.js +150 -34
- package/dist/application/merge-state-service.js.map +1 -1
- package/dist/application/multiroot/claude.js +17 -10
- package/dist/application/multiroot/claude.js.map +1 -1
- package/dist/application/multiroot/codex.js +17 -11
- package/dist/application/multiroot/codex.js.map +1 -1
- package/dist/application/multiroot-service.js +52 -28
- package/dist/application/multiroot-service.js.map +1 -1
- package/dist/application/parsers/execution-batches.js +64 -0
- package/dist/application/parsers/execution-batches.js.map +1 -0
- package/dist/application/parsers/phases.js +23 -2
- package/dist/application/parsers/phases.js.map +1 -1
- package/dist/application/parsers/project-block.js +146 -17
- package/dist/application/parsers/project-block.js.map +1 -1
- package/dist/application/parsers/tasks.js +16 -2
- package/dist/application/parsers/tasks.js.map +1 -1
- package/dist/application/parsers/toml.js +19 -0
- package/dist/application/parsers/toml.js.map +1 -0
- package/dist/application/paths-service.js +13 -16
- package/dist/application/paths-service.js.map +1 -1
- package/dist/application/persist-service.js +118 -5
- package/dist/application/persist-service.js.map +1 -1
- package/dist/application/plan-exec-batch-service.js +162 -22
- package/dist/application/plan-exec-batch-service.js.map +1 -1
- package/dist/application/plan-exec-decision-service.js +61 -12
- package/dist/application/plan-exec-decision-service.js.map +1 -1
- package/dist/application/plan-exec-plan-diff.js +105 -0
- package/dist/application/plan-exec-plan-diff.js.map +1 -0
- package/dist/application/plan-lint-service.js +33 -5
- package/dist/application/plan-lint-service.js.map +1 -1
- package/dist/application/plan-passes.js +41 -0
- package/dist/application/plan-passes.js.map +1 -0
- package/dist/application/preparation-receipts.js +58 -0
- package/dist/application/preparation-receipts.js.map +1 -0
- package/dist/application/project-md-upsert-service.js +191 -52
- package/dist/application/project-md-upsert-service.js.map +1 -1
- package/dist/application/project-tab-data.js +40 -10
- package/dist/application/project-tab-data.js.map +1 -1
- package/dist/application/release-data/artifacts.js +15 -7
- package/dist/application/release-data/artifacts.js.map +1 -1
- package/dist/application/release-data/bundles.js +52 -6
- package/dist/application/release-data/bundles.js.map +1 -1
- package/dist/application/release-data/common.js +2 -2
- package/dist/application/release-data/common.js.map +1 -1
- package/dist/application/release-data/rollback.js +7 -0
- package/dist/application/release-data/rollback.js.map +1 -0
- package/dist/application/release-data/sessions.js +2 -2
- package/dist/application/release-data/sessions.js.map +1 -1
- package/dist/application/release-pass-ledger.js +1 -1
- package/dist/application/render/history-row.js +2 -0
- package/dist/application/render/history-row.js.map +1 -1
- package/dist/application/render/project-block.js +28 -11
- package/dist/application/render/project-block.js.map +1 -1
- package/dist/application/reseal-service.js +1 -0
- package/dist/application/reseal-service.js.map +1 -1
- package/dist/application/resume-service.js +55 -8
- package/dist/application/resume-service.js.map +1 -1
- package/dist/application/retirement/apply.js +25 -7
- package/dist/application/retirement/apply.js.map +1 -1
- package/dist/application/retirement/attribution.js +122 -50
- package/dist/application/retirement/attribution.js.map +1 -1
- package/dist/application/retirement/prepare.js +17 -11
- package/dist/application/retirement/prepare.js.map +1 -1
- package/dist/application/retirement/preview.js +2 -1
- package/dist/application/retirement/preview.js.map +1 -1
- package/dist/application/self/clean-legacy.js +1 -2
- package/dist/application/self/clean-legacy.js.map +1 -1
- package/dist/application/self/codex-plugin.js +1 -1
- package/dist/application/self/codex-plugin.js.map +1 -1
- package/dist/application/self/host-states.js +3 -3
- package/dist/application/self/host-states.js.map +1 -1
- package/dist/application/self/install-hooks.js +1 -1
- package/dist/application/self/install-hooks.js.map +1 -1
- package/dist/application/self/install-skill.js +5 -49
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/mcp-config.js +19 -11
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/opencode-plugin.js +1 -0
- package/dist/application/self/opencode-plugin.js.map +1 -1
- package/dist/application/self/skill-files.js +29 -0
- package/dist/application/self/skill-files.js.map +1 -0
- package/dist/application/self/uninstall.js +1 -1
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/application/semantic-operation/protocol.js +66 -5
- package/dist/application/semantic-operation/protocol.js.map +1 -1
- package/dist/application/session-archive-service.js +113 -0
- package/dist/application/session-archive-service.js.map +1 -0
- package/dist/application/session-binding-service.js +35 -0
- package/dist/application/session-binding-service.js.map +1 -1
- package/dist/application/session-close-service.js +390 -54
- package/dist/application/session-close-service.js.map +1 -1
- package/dist/application/session-create-service.js +97 -5
- package/dist/application/session-create-service.js.map +1 -1
- package/dist/application/session-custody-recorder.js +15 -12
- package/dist/application/session-custody-recorder.js.map +1 -1
- package/dist/application/session-custody-service.js +1 -0
- package/dist/application/session-custody-service.js.map +1 -1
- package/dist/application/session-narrative.js +42 -12
- package/dist/application/session-narrative.js.map +1 -1
- package/dist/application/session-pause-service.js +46 -0
- package/dist/application/session-pause-service.js.map +1 -0
- package/dist/application/session-resolver.js +36 -11
- package/dist/application/session-resolver.js.map +1 -1
- package/dist/application/session-resume-service.js +134 -13
- package/dist/application/session-resume-service.js.map +1 -1
- package/dist/application/sessions-service.js +4 -0
- package/dist/application/sessions-service.js.map +1 -1
- package/dist/application/sessions-sweep-service.js +77 -0
- package/dist/application/sessions-sweep-service.js.map +1 -0
- package/dist/application/settle-service.js +3 -1
- package/dist/application/settle-service.js.map +1 -1
- package/dist/application/skills-resolver-service.js +6 -92
- package/dist/application/skills-resolver-service.js.map +1 -1
- package/dist/application/source-add-service.js +37 -0
- package/dist/application/source-add-service.js.map +1 -0
- package/dist/application/source-boundary-policy.js +105 -8
- package/dist/application/source-boundary-policy.js.map +1 -1
- package/dist/application/source-pipeline.js +68 -0
- package/dist/application/source-pipeline.js.map +1 -0
- package/dist/application/source-remove-service.js +15 -3
- package/dist/application/source-remove-service.js.map +1 -1
- package/dist/application/sources-service.js +71 -16
- package/dist/application/sources-service.js.map +1 -1
- package/dist/application/status-service.js +55 -5
- package/dist/application/status-service.js.map +1 -1
- package/dist/application/unit-dependencies.js +100 -0
- package/dist/application/unit-dependencies.js.map +1 -0
- package/dist/application/unit-membership.js +37 -0
- package/dist/application/unit-membership.js.map +1 -0
- package/dist/application/unit-residue.js +72 -0
- package/dist/application/unit-residue.js.map +1 -0
- package/dist/application/visibility-doctor-service.js +66 -4
- package/dist/application/visibility-doctor-service.js.map +1 -1
- package/dist/application/working-branch-service.js +6 -0
- package/dist/application/working-branch-service.js.map +1 -1
- package/dist/application/workline-graph.js +1 -1
- package/dist/application/workline-graph.js.map +1 -1
- package/dist/application/workline-index-service.js +118 -17
- package/dist/application/workline-index-service.js.map +1 -1
- package/dist/application/workspace-commit-service.js +218 -0
- package/dist/application/workspace-commit-service.js.map +1 -0
- package/dist/application/workspace-init-service.js +64 -34
- package/dist/application/workspace-init-service.js.map +1 -1
- package/dist/application/workspace-local-config.js +90 -0
- package/dist/application/workspace-local-config.js.map +1 -0
- package/dist/application/workspace-materialization-service.js +52 -3
- package/dist/application/workspace-materialization-service.js.map +1 -1
- package/dist/application/workspace-migrate/apply.js +525 -4
- package/dist/application/workspace-migrate/apply.js.map +1 -1
- package/dist/application/workspace-migrate/markers.js +9 -6
- package/dist/application/workspace-migrate/markers.js.map +1 -1
- package/dist/application/workspace-migrate/plan.js +97 -5
- package/dist/application/workspace-migrate/plan.js.map +1 -1
- package/dist/application/workspace-move-service.js +270 -0
- package/dist/application/workspace-move-service.js.map +1 -0
- package/dist/application/workspace-untrack-service.js +28 -0
- package/dist/application/workspace-untrack-service.js.map +1 -0
- package/dist/application/worktree-service.js +654 -105
- package/dist/application/worktree-service.js.map +1 -1
- package/dist/cli/ascii.js +2 -0
- package/dist/cli/ascii.js.map +1 -1
- package/dist/cli/commands/add-source.js +38 -0
- package/dist/cli/commands/add-source.js.map +1 -0
- package/dist/cli/commands/amend.js +14 -3
- package/dist/cli/commands/amend.js.map +1 -1
- package/dist/cli/commands/capability.js +23 -4
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/designs.js +14 -5
- package/dist/cli/commands/designs.js.map +1 -1
- package/dist/cli/commands/dev-only.js +19 -6
- package/dist/cli/commands/dev-only.js.map +1 -1
- package/dist/cli/commands/doc-branch.js +184 -0
- package/dist/cli/commands/doc-branch.js.map +1 -0
- package/dist/cli/commands/doctor.js +5 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/export.js +78 -18
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/fix-git.js +89 -14
- package/dist/cli/commands/fix-git.js.map +1 -1
- package/dist/cli/commands/flow.js +120 -15
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/generate-launch.js +14 -2
- package/dist/cli/commands/generate-launch.js.map +1 -1
- package/dist/cli/commands/history-update.js +1 -1
- package/dist/cli/commands/history-update.js.map +1 -1
- package/dist/cli/commands/history.js +14 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/hook.js +15 -2
- package/dist/cli/commands/hook.js.map +1 -1
- package/dist/cli/commands/index.js +18 -5
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/merge-state.js +2 -2
- package/dist/cli/commands/merge-state.js.map +1 -1
- package/dist/cli/commands/persist.js +6 -5
- package/dist/cli/commands/persist.js.map +1 -1
- package/dist/cli/commands/project-md-upsert.js +5 -3
- package/dist/cli/commands/project-md-upsert.js.map +1 -1
- package/dist/cli/commands/release-pass.js +3 -3
- package/dist/cli/commands/release-pass.js.map +1 -1
- package/dist/cli/commands/resume.js +12 -3
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/self.js +8 -24
- package/dist/cli/commands/self.js.map +1 -1
- package/dist/cli/commands/session-close.js +64 -9
- package/dist/cli/commands/session-close.js.map +1 -1
- package/dist/cli/commands/session-create.js +4 -2
- package/dist/cli/commands/session-create.js.map +1 -1
- package/dist/cli/commands/session-pause.js +14 -0
- package/dist/cli/commands/session-pause.js.map +1 -0
- package/dist/cli/commands/sessions.js +16 -3
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/set-branch.js +28 -4
- package/dist/cli/commands/set-branch.js.map +1 -1
- package/dist/cli/commands/set-edit-mode.js +20 -0
- package/dist/cli/commands/set-edit-mode.js.map +1 -0
- package/dist/cli/commands/set-pipeline.js +35 -0
- package/dist/cli/commands/set-pipeline.js.map +1 -0
- package/dist/cli/commands/skills.js +4 -11
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/sources.js +4 -0
- package/dist/cli/commands/sources.js.map +1 -1
- package/dist/cli/commands/status.js +35 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/unknown-flags.js +1 -0
- package/dist/cli/commands/unknown-flags.js.map +1 -1
- package/dist/cli/commands/visibility.js +1 -0
- package/dist/cli/commands/visibility.js.map +1 -1
- package/dist/cli/commands/workspace-commit.js +62 -0
- package/dist/cli/commands/workspace-commit.js.map +1 -0
- package/dist/cli/commands/workspace-init.js +39 -2
- package/dist/cli/commands/workspace-init.js.map +1 -1
- package/dist/cli/commands/workspace-migrate.js +31 -3
- package/dist/cli/commands/workspace-migrate.js.map +1 -1
- package/dist/cli/commands/workspace-move.js +42 -0
- package/dist/cli/commands/workspace-move.js.map +1 -0
- package/dist/cli/commands/worktree.js +4 -4
- package/dist/cli/commands/worktree.js.map +1 -1
- package/dist/cli/context-id.js +6 -0
- package/dist/cli/context-id.js.map +1 -1
- package/dist/cli/full-cli.js +555 -0
- package/dist/cli/full-cli.js.map +1 -0
- package/dist/cli/help-groups.js +76 -19
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/hook-stdin-cache.js +12 -0
- package/dist/cli/hook-stdin-cache.js.map +1 -0
- package/dist/cli/main.js +47 -453
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parser.js +4 -0
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/tui/app.js +3 -4
- package/dist/cli/tui/app.js.map +1 -1
- package/dist/cli/tui/components/host-admin-section.js +2 -13
- package/dist/cli/tui/components/host-admin-section.js.map +1 -1
- package/dist/cli/tui/components/tabs-config.js +1 -2
- package/dist/cli/tui/components/tabs-config.js.map +1 -1
- package/dist/cli/tui/components/workspace-init-form.js +4 -1
- package/dist/cli/tui/components/workspace-init-form.js.map +1 -1
- package/dist/cli/tui/data/workflow-content.js +6 -1
- package/dist/cli/tui/data/workflow-content.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +4 -3
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/cli/tui/tabs/project-tab.js +7 -2
- package/dist/cli/tui/tabs/project-tab.js.map +1 -1
- package/dist/cli/tui/tui-prefs.js +2 -0
- package/dist/cli/tui/tui-prefs.js.map +1 -1
- package/dist/dist-manifest.json +3973 -0
- package/dist/domain/capability/protocol.js +3 -1
- package/dist/domain/capability/protocol.js.map +1 -1
- package/dist/domain/decision-note.js +13 -0
- package/dist/domain/decision-note.js.map +1 -1
- package/dist/domain/design/artifact.js +22 -3
- package/dist/domain/design/artifact.js.map +1 -1
- package/dist/domain/design/capability.js +1 -0
- package/dist/domain/design/capability.js.map +1 -1
- package/dist/domain/design/expansion.js +1 -1
- package/dist/domain/design/expansion.js.map +1 -1
- package/dist/domain/design/maturity.js +16 -0
- package/dist/domain/design/maturity.js.map +1 -1
- package/dist/domain/design/reference.js +5 -0
- package/dist/domain/design/reference.js.map +1 -1
- package/dist/domain/doctor/operations.js +20 -12
- package/dist/domain/doctor/operations.js.map +1 -1
- package/dist/domain/effective-contract.js +3 -1
- package/dist/domain/effective-contract.js.map +1 -1
- package/dist/domain/elicitation.js +1 -1
- package/dist/domain/flow/answer.js +265 -130
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +194 -37
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/flow/batch-review.js +65 -0
- package/dist/domain/flow/batch-review.js.map +1 -0
- package/dist/domain/flow/decision-schema.js +47 -0
- package/dist/domain/flow/decision-schema.js.map +1 -0
- package/dist/domain/flow/directive.js +16 -0
- package/dist/domain/flow/directive.js.map +1 -1
- package/dist/domain/flow/execution-result.js +91 -2
- package/dist/domain/flow/execution-result.js.map +1 -1
- package/dist/domain/flow/run-state.js +352 -24
- package/dist/domain/flow/run-state.js.map +1 -1
- package/dist/domain/flow/test-run-evidence.js +285 -0
- package/dist/domain/flow/test-run-evidence.js.map +1 -0
- package/dist/domain/flow/unchanged-phase.js +15 -0
- package/dist/domain/flow/unchanged-phase.js.map +1 -0
- package/dist/domain/harnesses.js +14 -5
- package/dist/domain/harnesses.js.map +1 -1
- package/dist/domain/isolation-unit.js +2 -2
- package/dist/domain/isolation-unit.js.map +1 -1
- package/dist/domain/lineage.js +7 -2
- package/dist/domain/lineage.js.map +1 -1
- package/dist/domain/obligation-correspondence.js +4 -2
- package/dist/domain/obligation-correspondence.js.map +1 -1
- package/dist/domain/plan-handoff.js +5 -0
- package/dist/domain/plan-handoff.js.map +1 -0
- package/dist/domain/proposal.js +18 -7
- package/dist/domain/proposal.js.map +1 -1
- package/dist/domain/reservation.js +5 -3
- package/dist/domain/reservation.js.map +1 -1
- package/dist/domain/retirement/proposal.js +1 -0
- package/dist/domain/retirement/proposal.js.map +1 -1
- package/dist/domain/session/custody.js +10 -6
- package/dist/domain/session/custody.js.map +1 -1
- package/dist/domain/session/narrative.js +4 -1
- package/dist/domain/session/narrative.js.map +1 -1
- package/dist/domain/skills.js +4 -6
- package/dist/domain/skills.js.map +1 -1
- package/dist/domain/sql-lexer.js +1 -1
- package/dist/domain/sql-lexer.js.map +1 -1
- package/dist/runtime/namespace-resolver.js +4 -0
- package/dist/runtime/namespace-resolver.js.map +1 -1
- package/dist/runtime/workline-marker.js +35 -8
- package/dist/runtime/workline-marker.js.map +1 -1
- package/dist/runtime/workspace-resolution.js +123 -0
- package/dist/runtime/workspace-resolution.js.map +1 -0
- package/package.json +6 -5
- package/skills/w/SKILL.md +12 -17
- package/skills/w/artifacts/artifacts-core/CHECKPOINT.md +1 -1
- package/skills/w/artifacts/artifacts-core/SCRIPTS.rollback.sql +8 -0
- package/skills/w/artifacts/artifacts-core/SCRIPTS.sql +4 -8
- package/skills/w/commands/export-diagrams.md +3 -3
- package/skills/w/commands/export-manuals.md +6 -6
- package/skills/w/commands/export-reports.md +3 -3
- package/skills/w/commands/export-scripts.md +5 -5
- package/skills/w/commands/fix-git.md +12 -13
- package/skills/w/commands/persist.md +2 -2
- package/skills/w/commands/plan-exec.md +4 -2
- package/skills/w/commands/spec-new.md +1 -1
- package/skills/w/commands/workspace-init.md +2 -0
- package/skills/w/context/MANIFEST.json +1 -1
- package/skills/w/exports/README.md +4 -4
- package/skills/w/exports/export-diagrams/EXPORT.md +4 -4
- package/skills/w/exports/export-manuals/EXPORT.md +14 -16
- package/skills/w/exports/export-reports/EXPORT.md +3 -3
- package/skills/w/exports/export-scripts/EXPORT.md +46 -22
- package/skills/w/harness/HARNESS.md +4 -7
- package/skills/w/hooks/hooks.template.json +6 -4
- package/skills/w/loops/CHASSIS.md +12 -6
- package/skills/w/loops/CODE-POLICIES.md +8 -5
- package/skills/w/loops/README.md +2 -2
- package/skills/w/loops/plan-exec-loop/LOOP.md +23 -22
- package/skills/w/loops/plan-new-loop/LOOP.md +23 -31
- package/skills/w/loops/plan-refine-loop/LOOP.md +8 -2
- package/skills/w/loops/quick-loop/LOOP.md +7 -6
- package/skills/w/loops/spec-refine-loop/LOOP.md +1 -1
- package/skills/w/modules/DESIGN-REFERENCES.md +9 -11
- package/skills/w/modules/PLAN-EXECUTION-BATCHES.md +4 -4
- package/skills/w/modules/SESSION-NUMBERING.md +2 -0
- package/skills/w/roles/README.md +11 -47
- package/skills/w/roles/design/CONTRACT.md +6 -4
- package/skills/w/roles/design/ROLE.md +7 -3
- package/skills/w/roles/diagrams/ROLE.md +1 -1
- package/skills/w/roles/git/ROLE.md +17 -10
- package/skills/w/roles/research/ROLE.md +2 -2
- package/skills/w/roles/sql/ROLE.md +17 -15
- package/skills/w/schemas/design/ui-screen.v1.schema.json +9 -0
- package/dist/application/capability/descriptor-loader.js +0 -109
- package/dist/application/capability/descriptor-loader.js.map +0 -1
- package/dist/application/capability/installed-inventory.js +0 -168
- package/dist/application/capability/installed-inventory.js.map +0 -1
- package/dist/application/self/install-plugin-skills-git.js +0 -226
- package/dist/application/self/install-plugin-skills-git.js.map +0 -1
- package/dist/application/self/install-plugin-skills.js +0 -203
- package/dist/application/self/install-plugin-skills.js.map +0 -1
- package/dist/application/self/plugin-cache-clear.js +0 -133
- package/dist/application/self/plugin-cache-clear.js.map +0 -1
- package/dist/application/self/plugin-cache-reload.js +0 -141
- package/dist/application/self/plugin-cache-reload.js.map +0 -1
- package/dist/application/self/skills-apply.js +0 -623
- package/dist/application/self/skills-apply.js.map +0 -1
- package/dist/application/self/skills-catalog.js +0 -35
- package/dist/application/self/skills-catalog.js.map +0 -1
- package/dist/application/self/skills-change.js +0 -479
- package/dist/application/self/skills-change.js.map +0 -1
- package/dist/application/self/skills-discovery.js +0 -275
- package/dist/application/self/skills-discovery.js.map +0 -1
- package/dist/application/self/skills-manager.js +0 -253
- package/dist/application/self/skills-manager.js.map +0 -1
- package/dist/application/self/skills-payload.js +0 -566
- package/dist/application/self/skills-payload.js.map +0 -1
- package/dist/application/self/skills-registry.js +0 -107
- package/dist/application/self/skills-registry.js.map +0 -1
- package/dist/application/skill-index-service.js +0 -78
- package/dist/application/skill-index-service.js.map +0 -1
- package/dist/cli/commands/plugin-cache.js +0 -25
- package/dist/cli/commands/plugin-cache.js.map +0 -1
- package/dist/cli/commands/skill-index.js +0 -19
- package/dist/cli/commands/skill-index.js.map +0 -1
- package/dist/cli/tui/data/recommended-skills.js +0 -482
- package/dist/cli/tui/data/recommended-skills.js.map +0 -1
- package/dist/cli/tui/tabs/skills-tab.js +0 -664
- package/dist/cli/tui/tabs/skills-tab.js.map +0 -1
|
@@ -27,30 +27,86 @@ import { DEFAULT_CORE_DOCS_CANON, coreDocumentKindForPath } from "../../domain/d
|
|
|
27
27
|
import { claimedSeal, parseFlowAnswer, spendsAttempt, } from "../../domain/flow/answer.js";
|
|
28
28
|
import { FIX_PREVIEW_TRANSITION, SETTLEMENT_READINGS, actionOf, alternativesOf, approvalGrantOf, effectsOf, internalActionOf, isRouteEvaluation, occurrenceAt, proposalContractOf, publishApprovalOf, reentryOf, routeControlOf, scopesSources, } from "../../domain/flow/authority.js";
|
|
29
29
|
import { effectApprovalDigest } from "../../domain/flow/authorization.js";
|
|
30
|
+
import { isBatchReview } from "../../domain/flow/batch-review.js";
|
|
31
|
+
import { routeShape, settlementList, sourceList } from "../../domain/flow/decision-schema.js";
|
|
30
32
|
import { PAUSE_LABEL, STOP_LABEL, stepOf, } from "../../domain/flow/directive.js";
|
|
31
33
|
import { batchCreditVerdict, executionVerdict, } from "../../domain/flow/execution-result.js";
|
|
32
34
|
import { ROUTE_ACCEPT_LABEL, ROUTE_ADJUST_LABEL, isRouteDisposition, } from "../../domain/flow/route.js";
|
|
33
|
-
import { applyTransition, checkAgainstJourney, iterationOf, restartInvocation, restatesLastEvent, sameIteration, settlementAmbiguous, withApproval, withAttempt, withBoundary, withContinuation, withDecisionPreparation, withEvent, withFixPreview, withHandoff, withObservation, withPlanExecBatchCredit, withPlanExecBatchStageForTransition, withProposal, withReentry, withRouteDecisions, withRouteProposal, withScope, withSelectedChoice, withSettlementDeclarations, } from "../../domain/flow/run-state.js";
|
|
35
|
+
import { applyTransition, checkAgainstJourney, iterationOf, planRefineHandoff, restartInvocation, restatesLastEvent, sameIteration, settlementAmbiguous, withApproval, withAttempt, withBoundary, withContinuation, withDecisionDate, withDecisionPreparation, withEvent, withFixPreview, withHandoff, withObservation, withPlanExecBatchCredit, withPlanExecBatchReview, withPlanExecBatchStageForTransition, withPlanExecBatchUpdate, withPlanExecEntry, withProposal, withQuickCheckouts, withReentry, withRouteDecisions, withRouteProposal, withScope, withSelectedChoice, withSettlementDeclarations, } from "../../domain/flow/run-state.js";
|
|
36
|
+
import { APPROVE_UNCHANGED_PHASE, UNCHANGED_PHASE_CONSENT, approvedValidationOnly, } from "../../domain/flow/unchanged-phase.js";
|
|
34
37
|
import { withSpecBaseline } from "../../domain/lineage.js";
|
|
35
38
|
import { destinationsOf, observedEffects, sealProposal } from "../../domain/proposal.js";
|
|
36
39
|
import { baseDigest } from "../../domain/proposal.js";
|
|
37
40
|
import { reservationMarker } from "../../domain/reservation.js";
|
|
38
41
|
import { checkSafeRelativePath } from "../../domain/safe-path.js";
|
|
39
42
|
import { resolveCoreDocsCanon } from "../docs-canon-service.js";
|
|
43
|
+
import { parseMdSectionBilingual } from "../markdown.js";
|
|
44
|
+
import { parsePhases } from "../parsers/phases.js";
|
|
40
45
|
import { readWorkspaceBlock } from "../parsers/project-block.js";
|
|
41
46
|
import { parseDerivedFromPath, parseSpecRelation } from "../parsers/spec-relation.js";
|
|
47
|
+
import { parseTasks } from "../parsers/tasks.js";
|
|
42
48
|
import { resolveWorkspaceRootFrom } from "../paths-service.js";
|
|
43
49
|
import { commitStoredPlanExecDecision, preparePlanExecDecision, } from "../plan-exec-decision-service.js";
|
|
44
50
|
import { planBoundaryAction, planGrammarAtEntry, planGrammarAtPublication, } from "../plan-lint-service.js";
|
|
45
51
|
import { semanticDigest } from "../semantic-operation/protocol.js";
|
|
46
52
|
import { resolveSessionTarget } from "../session-resolver.js";
|
|
47
53
|
import { sourceAliasesOfPlan, } from "../source-boundary-policy.js";
|
|
54
|
+
import { readSourcePipelines, resolveFinalValidation } from "../source-pipeline.js";
|
|
55
|
+
import { runWorkspaceCommit } from "../workspace-commit-service.js";
|
|
48
56
|
import { actionDigest, advanceFlowRun, awaitingCliRerun, directiveFor, effectsOfTransition, resolveBoundary, } from "./advance.js";
|
|
49
57
|
import { observeCheckout, observeScopedFingerprints, withObservedCheckouts, } from "./checkout-observation.js";
|
|
50
58
|
import { resolveCheckoutCandidates } from "./checkout-observation.js";
|
|
59
|
+
import { closeAtBoundaryState } from "./close-at-boundary.js";
|
|
51
60
|
import { driveInternalActions } from "./internal-drive.js";
|
|
61
|
+
import { observePlanEntry } from "./plan-entry.js";
|
|
62
|
+
import { observeQuickCheckouts } from "./quick-checkouts.js";
|
|
52
63
|
import { journeyForRun } from "./run-journey.js";
|
|
53
64
|
import { applyUnderLock, locateRun, readRun } from "./run-state-service.js";
|
|
65
|
+
/** The same decision against the current boundary, without a run lock or writes. */
|
|
66
|
+
export async function checkFlow(fs, paths, input) {
|
|
67
|
+
const canon = await resolveCoreDocsCanon(fs, paths);
|
|
68
|
+
if (!canon.ok)
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
failure: { code: "DOCS_CANON_INVALID", message: canon.error, action: "corregí [docs]" },
|
|
72
|
+
};
|
|
73
|
+
const resolution = await resolveSessionTarget(fs, paths, {
|
|
74
|
+
intent: "read",
|
|
75
|
+
...(input.code !== undefined ? { code: input.code } : {}),
|
|
76
|
+
...(input.contextId !== undefined ? { contextId: input.contextId } : {}),
|
|
77
|
+
allowClosed: false,
|
|
78
|
+
bind: false,
|
|
79
|
+
});
|
|
80
|
+
if (resolution.outcome !== "resolved")
|
|
81
|
+
return { ok: false, session: resolution };
|
|
82
|
+
const session = resolution.session.folder;
|
|
83
|
+
const read = await readRun(fs, locateRun(paths, session));
|
|
84
|
+
if (!read.ok)
|
|
85
|
+
return { ok: false, failure: read.failure };
|
|
86
|
+
const snapshot = await observe(fs, paths, input.raw, session, input.git);
|
|
87
|
+
const decision = await decide(fs, paths, read.state, input, snapshot, true);
|
|
88
|
+
if (!decision.ok)
|
|
89
|
+
return { ok: false, failure: decision.failure };
|
|
90
|
+
const error = decision.value.directive.error;
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
receipt: {
|
|
94
|
+
check: true,
|
|
95
|
+
valid: error === null,
|
|
96
|
+
error,
|
|
97
|
+
violations: error?.violations ??
|
|
98
|
+
(error === null
|
|
99
|
+
? []
|
|
100
|
+
: [
|
|
101
|
+
{
|
|
102
|
+
code: error.code,
|
|
103
|
+
field: "answer",
|
|
104
|
+
message: error.message,
|
|
105
|
+
},
|
|
106
|
+
]),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
54
110
|
export async function submitFlow(fs, paths, input) {
|
|
55
111
|
const canon = await resolveCoreDocsCanon(fs, paths);
|
|
56
112
|
if (!canon.ok) {
|
|
@@ -112,7 +168,7 @@ export async function submitFlow(fs, paths, input) {
|
|
|
112
168
|
ok: true,
|
|
113
169
|
state: applied.state,
|
|
114
170
|
value: applied.value.directive,
|
|
115
|
-
});
|
|
171
|
+
}, paths);
|
|
116
172
|
if (!driven.ok)
|
|
117
173
|
return { ok: false, failure: driven.failure };
|
|
118
174
|
return { ok: true, directive: withObservedCheckouts(driven.value, observed) };
|
|
@@ -120,13 +176,18 @@ export async function submitFlow(fs, paths, input) {
|
|
|
120
176
|
async function observe(fs, paths, raw, session, git) {
|
|
121
177
|
const root = await resolveWorkspaceRootFrom(fs, paths);
|
|
122
178
|
const plans = await observePlanArtifacts(fs, paths, raw);
|
|
179
|
+
const checkouts = await observeCheckouts(fs, paths, session, git);
|
|
123
180
|
return {
|
|
124
181
|
root,
|
|
182
|
+
quickCheckouts: await observeQuickCheckouts(fs, paths, session, git),
|
|
125
183
|
destinations: await observeDestinations(fs, paths, raw),
|
|
126
184
|
scope: await observeScope(fs, paths, raw),
|
|
127
185
|
plans: plans.evidence,
|
|
128
|
-
checkouts:
|
|
186
|
+
checkouts: checkouts.states,
|
|
187
|
+
checkoutFailure: checkouts.failure,
|
|
129
188
|
scoped: await observeBatchSources(fs, paths, session, git),
|
|
189
|
+
preexisting: await observePreexistingFailures(fs, paths, session, root),
|
|
190
|
+
validation_only_current: await observeValidationOnly(fs, paths, session, root),
|
|
130
191
|
baselines: plans.baselines,
|
|
131
192
|
};
|
|
132
193
|
}
|
|
@@ -150,6 +211,43 @@ async function observeBatchSources(fs, paths, session, git) {
|
|
|
150
211
|
return observed.ok ? observed.base : {};
|
|
151
212
|
}
|
|
152
213
|
const PHASE_VALIDATION = "plan-exec.validation-execution";
|
|
214
|
+
async function observeValidationOnly(fs, paths, session, root) {
|
|
215
|
+
const read = await readRun(fs, locateRun(paths, session));
|
|
216
|
+
if (!read.ok || read.state.boundary !== PHASE_VALIDATION || read.state.scope === null)
|
|
217
|
+
return false;
|
|
218
|
+
const batch = read.state.batches?.find((item) => item.iteration === read.state.batch_loop?.iteration);
|
|
219
|
+
if (batch === undefined || !approvedValidationOnly(read.state, batch))
|
|
220
|
+
return false;
|
|
221
|
+
try {
|
|
222
|
+
const text = await fs.readText(join(root, read.state.scope.plan));
|
|
223
|
+
return !parseTasks(text).items.some((task) => task.status === "open" && batch.phases.includes(task.phase ?? -1));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function observePreexistingFailures(fs, paths, session, root) {
|
|
230
|
+
const read = await readRun(fs, locateRun(paths, session));
|
|
231
|
+
if (!read.ok || read.state.boundary !== PHASE_VALIDATION)
|
|
232
|
+
return [];
|
|
233
|
+
const state = read.state;
|
|
234
|
+
if (state.scope === null)
|
|
235
|
+
return null;
|
|
236
|
+
const batch = state.batches?.find((entry) => entry.iteration === state.batch_loop?.iteration);
|
|
237
|
+
if (batch === undefined)
|
|
238
|
+
return [];
|
|
239
|
+
try {
|
|
240
|
+
const phases = parsePhases(await fs.readText(join(root, state.scope.plan))).items;
|
|
241
|
+
const selected = phases.filter((phase) => batch.phases.includes(phase.n));
|
|
242
|
+
if (selected.length !== batch.phases.length ||
|
|
243
|
+
selected.some((phase) => phase.preexisting_failures === null))
|
|
244
|
+
return null;
|
|
245
|
+
return selected.flatMap((phase) => phase.preexisting_failures ?? []);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
153
251
|
/**
|
|
154
252
|
* Judge the plan bytes a proposal is carrying, and derive the seal each one gets.
|
|
155
253
|
*
|
|
@@ -186,7 +284,7 @@ async function observePlanArtifacts(fs, paths, raw) {
|
|
|
186
284
|
continue;
|
|
187
285
|
if (coreDocumentKindForPath(artifact.path, canon.canon) !== "plan")
|
|
188
286
|
continue;
|
|
189
|
-
const { failures, seal } = await planGrammarAtPublication(fs, root, artifact.content, declared, canon.canon.spec);
|
|
287
|
+
const { failures, seal } = await planGrammarAtPublication(fs, root, artifact.content, declared, canon.canon.spec, paths);
|
|
190
288
|
if (seal.status === "sealed")
|
|
191
289
|
baselines.set(artifact.path, seal.baseline);
|
|
192
290
|
evidence.push({ path: artifact.path, failures });
|
|
@@ -230,8 +328,10 @@ async function observeScope(fs, paths, raw) {
|
|
|
230
328
|
declared: null,
|
|
231
329
|
plan: null,
|
|
232
330
|
sources: null,
|
|
331
|
+
final_validation: null,
|
|
233
332
|
boundary_failures: null,
|
|
234
333
|
plan_error: null,
|
|
334
|
+
isolation: "unit",
|
|
235
335
|
};
|
|
236
336
|
let parsed;
|
|
237
337
|
try {
|
|
@@ -252,7 +352,7 @@ async function observeScope(fs, paths, raw) {
|
|
|
252
352
|
const named = decisions.plan;
|
|
253
353
|
const plan = typeof named === "string" ? named.trim() : null;
|
|
254
354
|
if (plan === null || plan.length === 0 || !checkSafeRelativePath(plan).ok) {
|
|
255
|
-
return { ...empty, declared, plan };
|
|
355
|
+
return { ...empty, declared, plan, isolation: block?.edit_mode ?? "unit" };
|
|
256
356
|
}
|
|
257
357
|
const canon = await resolveCoreDocsCanon(fs, paths);
|
|
258
358
|
if (!canon.ok)
|
|
@@ -276,8 +376,12 @@ async function observeScope(fs, paths, raw) {
|
|
|
276
376
|
declared,
|
|
277
377
|
plan,
|
|
278
378
|
sources: sourceAliasesOfPlan(text),
|
|
379
|
+
final_validation: resolveFinalValidation(text, sourceAliasesOfPlan(text), await readSourcePipelines(fs, paths)),
|
|
279
380
|
boundary_failures: planGrammarAtEntry(text, declared),
|
|
280
381
|
plan_error: null,
|
|
382
|
+
isolation: /^> Aislamiento: unidad\s*$/m.test(text.split(/^## /m, 1)[0] ?? "")
|
|
383
|
+
? "unit"
|
|
384
|
+
: (block?.edit_mode ?? "unit"),
|
|
281
385
|
};
|
|
282
386
|
}
|
|
283
387
|
/**
|
|
@@ -289,7 +393,7 @@ async function observeScope(fs, paths, raw) {
|
|
|
289
393
|
*/
|
|
290
394
|
async function observeCheckouts(fs, paths, session, git) {
|
|
291
395
|
if (git === undefined)
|
|
292
|
-
return null;
|
|
396
|
+
return { states: null, failure: null };
|
|
293
397
|
// The SAME resolution the directive published, from the same function: two
|
|
294
398
|
// callers computing this apart is what made a rejection claim the tree moved
|
|
295
399
|
// when all that differed was the directory each side measured.
|
|
@@ -298,11 +402,24 @@ async function observeCheckouts(fs, paths, session, git) {
|
|
|
298
402
|
for (const candidate of candidates) {
|
|
299
403
|
// An unreadable checkout is deliberately absent from the eligible set: a proof
|
|
300
404
|
// against it fails closed instead of being treated as a clean tree.
|
|
301
|
-
|
|
405
|
+
let state;
|
|
406
|
+
try {
|
|
407
|
+
state = await observeCheckout(fs, git, candidate);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
return {
|
|
411
|
+
states,
|
|
412
|
+
failure: {
|
|
413
|
+
code: "WORKLINE_CHECKOUT_UNOBSERVABLE",
|
|
414
|
+
message: error instanceof Error ? error.message : String(error),
|
|
415
|
+
action: "corregí el fallo de git en ese checkout y recapturá la prueba",
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
302
419
|
if (state !== null)
|
|
303
420
|
states.push(state);
|
|
304
421
|
}
|
|
305
|
-
return states;
|
|
422
|
+
return { states, failure: null };
|
|
306
423
|
}
|
|
307
424
|
/**
|
|
308
425
|
* The alias→root pairs worth publishing: the ones actually OBSERVED.
|
|
@@ -369,11 +486,25 @@ async function observeDestinations(fs, paths, raw) {
|
|
|
369
486
|
return snapshot;
|
|
370
487
|
}
|
|
371
488
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: resend, drift, admission, scope, sealing, preview, grant and handoff checks are one intentional precedence contract.
|
|
372
|
-
async function decide(fs, paths, state, input, snapshot) {
|
|
489
|
+
async function decide(fs, paths, state, input, snapshot, checkOnly = false) {
|
|
373
490
|
const journey = journeyForRun(state);
|
|
491
|
+
// Human controls are exits from the run, not answers to the current kind of
|
|
492
|
+
// boundary. They remain usable with an exhausted or incoherent attempt ledger.
|
|
493
|
+
const control = controlFromRaw(input.raw);
|
|
494
|
+
if (control === STOP_LABEL)
|
|
495
|
+
return closeFromAnswer(state);
|
|
496
|
+
if (control === PAUSE_LABEL) {
|
|
497
|
+
const standing = resolveBoundary(state, journey);
|
|
498
|
+
return reject(state, standing, `'${PAUSE_LABEL}': la frontera queda en pie`, {
|
|
499
|
+
code: "FLOW_BOUNDARY_PAUSED",
|
|
500
|
+
action: `escribí el CHECKPOINT con 'aw checkpoint-write --code ${state.session}' y retomá con 'aw flow advance --code ${state.session}'`,
|
|
501
|
+
outcome: "needs_input",
|
|
502
|
+
});
|
|
503
|
+
}
|
|
374
504
|
const incoherent = checkAgainstJourney(state, journey);
|
|
375
|
-
if (incoherent !== null)
|
|
376
|
-
return
|
|
505
|
+
if (incoherent !== null) {
|
|
506
|
+
return reject(state, resolveBoundary(state, journey), incoherent.message, incoherent);
|
|
507
|
+
}
|
|
377
508
|
const resolved = resolveBoundary(state, journey);
|
|
378
509
|
if (resolved.stopped === null) {
|
|
379
510
|
return reject(state, resolved, "el recorrido ya terminó: no hay frontera que responder", {
|
|
@@ -424,8 +555,11 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
424
555
|
const drifted = actionDrift(state, resolved, cost);
|
|
425
556
|
if (drifted !== null)
|
|
426
557
|
return drifted;
|
|
558
|
+
if (resolved.kind === "execution" && snapshot.checkoutFailure !== null) {
|
|
559
|
+
return reject(state, resolved, snapshot.checkoutFailure.message, snapshot.checkoutFailure, cost);
|
|
560
|
+
}
|
|
427
561
|
// 2 · The boundary in force decides what is admissible.
|
|
428
|
-
const admissible = admit(state, resolved, resolved.stopped, input, cost, snapshot.checkouts, snapshot.scoped);
|
|
562
|
+
const admissible = admit(state, resolved, resolved.stopped, input, cost, snapshot.checkouts, snapshot.scoped, snapshot.preexisting, snapshot.validation_only_current);
|
|
429
563
|
if ("decision" in admissible)
|
|
430
564
|
return admissible.decision;
|
|
431
565
|
const parsed = admissible;
|
|
@@ -439,24 +573,73 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
439
573
|
if (route.kind === "adjust") {
|
|
440
574
|
return adjustRouteProposal(state, journey);
|
|
441
575
|
}
|
|
576
|
+
if (checkOnly && resolved.kind === "execution")
|
|
577
|
+
return checked(state, resolved);
|
|
442
578
|
const accepted = route.kind === "accept" ? withRouteDecisions(state, route.decisions) : state;
|
|
443
579
|
// Sealed with the transition it earned, in the same state publication: a batch
|
|
444
580
|
// is never seen validated without the proofs that validated it.
|
|
445
|
-
|
|
581
|
+
let routed = parsed.credit === undefined
|
|
446
582
|
? accepted
|
|
447
583
|
: withPlanExecBatchCredit(accepted, parsed.credit.batch, parsed.credit.proofs);
|
|
584
|
+
if (state.flow === "plan-exec" &&
|
|
585
|
+
[
|
|
586
|
+
"plan-exec.entry-gate",
|
|
587
|
+
"plan-exec.entry-gap-recognition",
|
|
588
|
+
"plan-exec.normalization-consent",
|
|
589
|
+
].includes(resolved.stopped.id)) {
|
|
590
|
+
routed = withPlanExecEntry(routed, await observePlanEntry(fs, paths, state));
|
|
591
|
+
}
|
|
592
|
+
if (resolved.stopped.id === UNCHANGED_PHASE_CONSENT &&
|
|
593
|
+
parsed.answer.choice === APPROVE_UNCHANGED_PHASE) {
|
|
594
|
+
const entry = state.plan_exec_entry;
|
|
595
|
+
if (entry?.plan && entry.phases_without_open_tasks) {
|
|
596
|
+
routed = withPlanExecEntry(routed, {
|
|
597
|
+
...entry,
|
|
598
|
+
approved_without_changes: [...entry.phases_without_open_tasks],
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
448
602
|
// The registry, not the Spanish consequence text, owns what a selected
|
|
449
603
|
// alternative does. A handoff must stop before any later plan-exec row can
|
|
450
604
|
// mark work, validate or commit; a decision selection is durably recorded
|
|
451
605
|
// even while its registration is handled by the decision bridge below.
|
|
452
606
|
const selected = choiceOutcomeOf(resolved.stopped, parsed.answer.choice);
|
|
607
|
+
const withEvidence = parsed.answer.choice === "Copiar evidencia y aprobar commit del workspace";
|
|
608
|
+
let approvedWorkspaceCommit = null;
|
|
609
|
+
if (resolved.stopped.id === "chassis.commit-choice" &&
|
|
610
|
+
(parsed.answer.choice === "Aprobar commit del workspace" || withEvidence)) {
|
|
611
|
+
if (!input.git || !input.process)
|
|
612
|
+
return reject(state, resolved, "no se puede observar Git para aprobar el commit", {
|
|
613
|
+
code: "WORKSPACE_COMMIT_UNAVAILABLE",
|
|
614
|
+
action: "cerrá sin commit o reintentá con un repositorio Git legible",
|
|
615
|
+
}, cost);
|
|
616
|
+
const prepared = await runWorkspaceCommit(fs, input.git, input.process, paths, {
|
|
617
|
+
code: state.session,
|
|
618
|
+
withEvidence,
|
|
619
|
+
});
|
|
620
|
+
if ("error" in prepared)
|
|
621
|
+
return reject(state, resolved, prepared.error, {
|
|
622
|
+
code: "WORKSPACE_COMMIT_UNAVAILABLE",
|
|
623
|
+
action: "cerrá sin commit o repará la propuesta y volvé a prepararla",
|
|
624
|
+
}, cost);
|
|
625
|
+
if (parsed.answer.decisions.commit_approval !== prepared.proposal.approval) {
|
|
626
|
+
const command = `aw workspace-commit prepare --code ${state.session}${withEvidence ? " --with-evidence" : ""}`;
|
|
627
|
+
return reject(state, resolved, "la aprobación no coincide con la lista de rutas y el mensaje", {
|
|
628
|
+
code: "WORKSPACE_COMMIT_APPROVAL_INVALID",
|
|
629
|
+
action: `revisá ${command} y contestá con decisions.commit_approval: ${prepared.proposal.approval}`,
|
|
630
|
+
}, cost);
|
|
631
|
+
}
|
|
632
|
+
approvedWorkspaceCommit = prepared.proposal;
|
|
633
|
+
}
|
|
453
634
|
if (selected?.kind === "handoff") {
|
|
454
635
|
// Only `spec-refine` needs a document the run never declared: the plan's
|
|
455
636
|
// scope names the plan, and the spec is one hop away through its lineage.
|
|
456
637
|
const specPath = selected.destination === "spec-refine"
|
|
457
638
|
? await specPathOfScopedPlan(fs, paths, snapshot.root, state.scope?.plan ?? null)
|
|
458
639
|
: null;
|
|
459
|
-
return applyAndHandoff(
|
|
640
|
+
return applyAndHandoff(routed, journey, resolved.stopped, identity, parsed.answer, selected, specPath, state.flow === "quick"
|
|
641
|
+
? (parseMdSectionBilingual(await fs.readText(join(paths.cwdSessionsDir(), state.session, "SESSION.md")), "Objective")?.trim() ?? "")
|
|
642
|
+
: null);
|
|
460
643
|
}
|
|
461
644
|
// The fix preview is checked HERE and nowhere else: it is a declaration, so no
|
|
462
645
|
// seal, no destination and no evidence would ever notice its absence, and the
|
|
@@ -474,27 +657,63 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
474
657
|
// preview showed. Neither ever applies the step by itself.
|
|
475
658
|
const scoped = scopeFrom(routed, resolved.stopped, parsed.answer, snapshot.scope);
|
|
476
659
|
if ("failure" in scoped) {
|
|
477
|
-
|
|
660
|
+
// A run without an input plan only learns the plan's identity at source-scope.
|
|
661
|
+
// Once its grammar is read, asking for another scope cannot repair the plan:
|
|
662
|
+
// retain that observation and show a blocked boundary with the refine action.
|
|
663
|
+
const grammar = snapshot.scope.boundary_failures?.[0];
|
|
664
|
+
if (grammar !== undefined && scoped.failure.code === grammar.code && snapshot.scope.plan) {
|
|
665
|
+
const blocked = withPlanExecEntry(state, {
|
|
666
|
+
plan: snapshot.scope.plan,
|
|
667
|
+
phases_without_open_tasks: state.plan_exec_entry?.phases_without_open_tasks ?? null,
|
|
668
|
+
grammar_failure: {
|
|
669
|
+
...scoped.failure,
|
|
670
|
+
action: `${scoped.failure.action}; corregí el plan con /w:plan-refine antes de reanudar ejecución`,
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
const refusal = reject(blocked, resolveBoundary(blocked, journey), scoped.failure.message, scoped.failure, cost);
|
|
674
|
+
return refusal.ok ? { ...refusal, persist: true } : refusal;
|
|
675
|
+
}
|
|
676
|
+
return reject(state, resolved, scoped.failure.message, scoped.failure, cost);
|
|
677
|
+
}
|
|
678
|
+
const batchProposal = await commitProposalFrom(scoped.state, resolved.stopped, parsed.answer, input.git, fs, paths);
|
|
679
|
+
if ("failure" in batchProposal) {
|
|
680
|
+
return reject(state, resolved, batchProposal.failure.message, batchProposal.failure, cost);
|
|
478
681
|
}
|
|
479
682
|
// The closure-evidence gate runs over the proposed bytes BEFORE the seal: a
|
|
480
683
|
// plan whose validation names no observable check of the checkout is refused
|
|
481
684
|
// while it is being written, not when somebody tries to execute it.
|
|
482
685
|
const evidence = planEvidenceFrom(resolved.stopped, parsed.answer, snapshot.plans);
|
|
483
686
|
if (evidence !== null) {
|
|
484
|
-
return reject(state, resolved, evidence.failure.message,
|
|
687
|
+
return reject(state, resolved, evidence.failure.message, evidence.failure, cost);
|
|
485
688
|
}
|
|
486
|
-
const sealed = sealFrom(
|
|
689
|
+
const sealed = sealFrom(batchProposal.state, resolved.stopped, parsed.answer, snapshot.destinations, snapshot.baselines);
|
|
487
690
|
if ("failure" in sealed) {
|
|
488
|
-
return reject(state, resolved, sealed.failure.message,
|
|
691
|
+
return reject(state, resolved, sealed.failure.message, sealed.failure, cost);
|
|
489
692
|
}
|
|
490
693
|
const preparedForGate = await prepareDecisionForGate(fs, sealed.state, snapshot, parsed.answer, resolved, cost);
|
|
491
694
|
if (!preparedForGate.ok)
|
|
492
695
|
return preparedForGate.decision;
|
|
696
|
+
if (checkOnly)
|
|
697
|
+
return checked(state, resolved);
|
|
493
698
|
const settlement = recordSettlement(preparedForGate.state, parsed.answer, resolved, cost);
|
|
494
699
|
if (!settlement.ok)
|
|
495
700
|
return settlement.decision;
|
|
496
701
|
const granted = resolved.kind === "authorization" ? (resolved.authorization?.planned ?? []) : [];
|
|
497
|
-
|
|
702
|
+
let approved = grantOf(settlement.state, resolved, parsed.answer, granted, journey);
|
|
703
|
+
if (resolved.stopped.id === "plan-exec.batch-commit-authorization" &&
|
|
704
|
+
parsed.answer.choice === "Aprobar los commits del lote") {
|
|
705
|
+
const batch = (approved.batches ?? []).find((entry) => entry.iteration === approved.batch_loop?.iteration);
|
|
706
|
+
if (batch?.commit_proposal !== undefined) {
|
|
707
|
+
const proposal = batch.commit_proposal;
|
|
708
|
+
approved = withPlanExecBatchUpdate(approved, batch.id, (entry) => ({
|
|
709
|
+
...entry,
|
|
710
|
+
commit_proposal: {
|
|
711
|
+
...proposal,
|
|
712
|
+
approved_digest: proposal.digest,
|
|
713
|
+
},
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
498
717
|
// All pure scope/proposal checks have now passed. A durable decision is a
|
|
499
718
|
// material write, so it must never land when a later validation of the same
|
|
500
719
|
// answer would refuse the boundary and leave its cursor standing.
|
|
@@ -517,6 +736,29 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
517
736
|
outcome: selected,
|
|
518
737
|
})
|
|
519
738
|
: approved;
|
|
739
|
+
if (approvedWorkspaceCommit !== null) {
|
|
740
|
+
selectedState = withEvent(selectedState, {
|
|
741
|
+
kind: "executed",
|
|
742
|
+
transition: "chassis.commit-choice",
|
|
743
|
+
operation: "workspace.commit-approved",
|
|
744
|
+
summary: JSON.stringify(approvedWorkspaceCommit),
|
|
745
|
+
output_digest: semanticDigest(approvedWorkspaceCommit),
|
|
746
|
+
effects: [],
|
|
747
|
+
evidence: ["workspace.commit-paths-approved"],
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
if (resolved.stopped.id === "chassis.commit-choice" &&
|
|
751
|
+
(parsed.answer.choice === "Copiar evidencia y cerrar" || withEvidence)) {
|
|
752
|
+
selectedState = withEvent(selectedState, {
|
|
753
|
+
kind: "executed",
|
|
754
|
+
transition: "chassis.commit-choice",
|
|
755
|
+
operation: "workspace.evidence-approved",
|
|
756
|
+
summary: "copia explícita de evidencia efímera",
|
|
757
|
+
output_digest: semanticDigest("workspace.evidence-approved"),
|
|
758
|
+
effects: [],
|
|
759
|
+
evidence: ["workspace.evidence-copy-approved"],
|
|
760
|
+
});
|
|
761
|
+
}
|
|
520
762
|
if (registered?.ok && registered.reconciliation !== null) {
|
|
521
763
|
selectedState = withContinuation(selectedState, registered.reconciliation);
|
|
522
764
|
}
|
|
@@ -536,15 +778,160 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
536
778
|
// cuya respuesta se tiraba —así que `Compactar` ahí volvía a una pregunta que
|
|
537
779
|
// ya nadie podía mostrar.
|
|
538
780
|
if (preview.preview !== null) {
|
|
539
|
-
selectedState = withFixPreview(selectedState,
|
|
781
|
+
selectedState = withFixPreview(selectedState, {
|
|
782
|
+
...preview.preview,
|
|
783
|
+
...(snapshot.quickCheckouts === null ? {} : { checkouts: snapshot.quickCheckouts }),
|
|
784
|
+
});
|
|
540
785
|
}
|
|
786
|
+
selectedState = withQuickCheckouts(selectedState, snapshot.quickCheckouts);
|
|
541
787
|
// A redraft recorded by `grantOf` inserted rows after this boundary, so the
|
|
542
788
|
// transition is applied against the journey as it now reads.
|
|
543
789
|
const walked = journeyForRun(selectedState);
|
|
544
790
|
const outcome = holds
|
|
545
791
|
? holdAfterApproval(selectedState, walked, identity)
|
|
546
792
|
: applyAndAdvance(selectedState, walked, resolved.stopped, identity, parsed.answer);
|
|
547
|
-
|
|
793
|
+
const directed = standalone === null ? outcome : withStandaloneGuidance(outcome, standalone);
|
|
794
|
+
if (!directed.ok || !preparedForGate.warnings?.length)
|
|
795
|
+
return directed;
|
|
796
|
+
return {
|
|
797
|
+
...directed,
|
|
798
|
+
value: {
|
|
799
|
+
...directed.value,
|
|
800
|
+
directive: {
|
|
801
|
+
...directed.value.directive,
|
|
802
|
+
next_action: `${preparedForGate.warnings.join("; ")}; ${directed.value.directive.next_action}`,
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function controlFromRaw(raw) {
|
|
808
|
+
try {
|
|
809
|
+
const body = JSON.parse(raw);
|
|
810
|
+
if (!isRecord(body))
|
|
811
|
+
return null;
|
|
812
|
+
return body.choice === PAUSE_LABEL || body.choice === STOP_LABEL ? body.choice : null;
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function checked(state, resolved) {
|
|
819
|
+
const built = directiveFor(state, resolved, []);
|
|
820
|
+
if (!built.ok)
|
|
821
|
+
return { ok: false, failure: built.failure };
|
|
822
|
+
return { ok: true, state, value: { directive: built.directive, advanced: false } };
|
|
823
|
+
}
|
|
824
|
+
async function commitProposalFrom(state, stopped, answer, git, fs, paths) {
|
|
825
|
+
if (stopped.id !== "plan-exec.batch-commit-proposal")
|
|
826
|
+
return { state };
|
|
827
|
+
const batch = (state.batches ?? []).find((entry) => entry.iteration === state.batch_loop?.iteration);
|
|
828
|
+
if (batch === undefined || batch.snapshot === undefined || git === undefined) {
|
|
829
|
+
return {
|
|
830
|
+
failure: {
|
|
831
|
+
code: "PLAN_EXEC_BATCH_COMMIT_UNOBSERVABLE",
|
|
832
|
+
message: "falta la instantánea git o el lector de las fuentes",
|
|
833
|
+
action: "volvé a adquirir la unidad del lote",
|
|
834
|
+
},
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const messages = answer.decisions.messages;
|
|
838
|
+
const declaredPaths = answer.decisions.paths;
|
|
839
|
+
const pathMap = typeof declaredPaths === "object" && declaredPaths !== null && !Array.isArray(declaredPaths)
|
|
840
|
+
? declaredPaths
|
|
841
|
+
: {};
|
|
842
|
+
const inPlace = state.scope?.isolation === "in-place";
|
|
843
|
+
const given = typeof messages === "object" && messages !== null && !Array.isArray(messages)
|
|
844
|
+
? messages
|
|
845
|
+
: {};
|
|
846
|
+
const candidates = await resolveCheckoutCandidates(fs, paths, state.session);
|
|
847
|
+
const sources = [];
|
|
848
|
+
for (const [alias, base] of Object.entries(batch.snapshot).sort(([a], [b]) => a.localeCompare(b))) {
|
|
849
|
+
const root = candidates.find((candidate) => candidate.source === alias)?.root;
|
|
850
|
+
if (root === undefined)
|
|
851
|
+
return {
|
|
852
|
+
failure: {
|
|
853
|
+
code: "PLAN_EXEC_BATCH_COMMIT_UNOBSERVABLE",
|
|
854
|
+
message: `no se puede ubicar la unidad ${alias}`,
|
|
855
|
+
action: "restaurá la unidad de esta sesión",
|
|
856
|
+
},
|
|
857
|
+
};
|
|
858
|
+
const dirty = await git.dirtyPaths(root);
|
|
859
|
+
const changed = dirty
|
|
860
|
+
.filter((entry) => !base.dirty.some((previous) => previous.path === entry.path && previous.digest === entry.digest))
|
|
861
|
+
.map((entry) => entry.path);
|
|
862
|
+
const askedPaths = pathMap[alias];
|
|
863
|
+
if (inPlace &&
|
|
864
|
+
(!Array.isArray(askedPaths) ||
|
|
865
|
+
!askedPaths.length ||
|
|
866
|
+
askedPaths.some((path) => typeof path !== "string" || !path.trim()) ||
|
|
867
|
+
new Set(askedPaths).size !== askedPaths.length))
|
|
868
|
+
return {
|
|
869
|
+
failure: {
|
|
870
|
+
code: "PLAN_EXEC_BATCH_PATHS_REQUIRED",
|
|
871
|
+
message: `${alias}: declarás decisions.paths[${alias}] como lista exacta de rutas de la corrida`,
|
|
872
|
+
action: "indicá sólo rutas de la corrida en este checkout",
|
|
873
|
+
},
|
|
874
|
+
};
|
|
875
|
+
const paths = inPlace ? askedPaths : changed;
|
|
876
|
+
if (inPlace) {
|
|
877
|
+
for (const path of paths) {
|
|
878
|
+
const before = base.dirty.find((entry) => entry.path === path);
|
|
879
|
+
const after = dirty.find((entry) => entry.path === path);
|
|
880
|
+
if (before && after?.digest !== before.digest)
|
|
881
|
+
return {
|
|
882
|
+
failure: {
|
|
883
|
+
code: "PLAN_EXEC_BATCH_SHARED_PATH",
|
|
884
|
+
message: `${alias}: ${path} fue tocada por el usuario y por la corrida`,
|
|
885
|
+
action: "detené el lote sin commitear y acordá la separación de cambios",
|
|
886
|
+
},
|
|
887
|
+
};
|
|
888
|
+
if (before || !after)
|
|
889
|
+
return {
|
|
890
|
+
failure: {
|
|
891
|
+
code: "PLAN_EXEC_BATCH_PATH_NOT_OWNED",
|
|
892
|
+
message: `${alias}: ${path} ya era del usuario o no está sucia`,
|
|
893
|
+
action: "declarás únicamente rutas nuevas de la corrida",
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (paths.length === 0)
|
|
899
|
+
continue;
|
|
900
|
+
const message = given[alias];
|
|
901
|
+
if (typeof message !== "string" ||
|
|
902
|
+
message.trim() !== message ||
|
|
903
|
+
message.length === 0 ||
|
|
904
|
+
/[\r\n]/.test(message) ||
|
|
905
|
+
/\b(?:Co-authored-by|Signed-off-by|Reviewed-by):/i.test(message)) {
|
|
906
|
+
return {
|
|
907
|
+
failure: {
|
|
908
|
+
code: "PLAN_EXEC_BATCH_COMMIT_MESSAGE_INVALID",
|
|
909
|
+
message: `${alias}: el mensaje debe ser una sola línea sin trailers`,
|
|
910
|
+
action: "devolvé decisions.messages por alias con un mensaje de una línea",
|
|
911
|
+
},
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
sources.push({
|
|
915
|
+
alias,
|
|
916
|
+
paths: paths.sort(),
|
|
917
|
+
dirty: dirty.filter((entry) => paths.includes(entry.path)),
|
|
918
|
+
message,
|
|
919
|
+
...(inPlace
|
|
920
|
+
? {
|
|
921
|
+
foreign_paths: dirty
|
|
922
|
+
.filter((entry) => !paths.includes(entry.path))
|
|
923
|
+
.map((entry) => entry.path),
|
|
924
|
+
}
|
|
925
|
+
: {}),
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const digest = semanticDigest({ batch: batch.id, snapshot: batch.snapshot, sources });
|
|
929
|
+
return {
|
|
930
|
+
state: withPlanExecBatchUpdate(state, batch.id, (item) => ({
|
|
931
|
+
...item,
|
|
932
|
+
commit_proposal: { sources, digest },
|
|
933
|
+
})),
|
|
934
|
+
};
|
|
548
935
|
}
|
|
549
936
|
/**
|
|
550
937
|
* The declared fix preview, checked for the three things it has to say.
|
|
@@ -640,10 +1027,7 @@ function routeAnswer(state, stopped, journey, answer) {
|
|
|
640
1027
|
};
|
|
641
1028
|
}
|
|
642
1029
|
const candidate = answer.decisions.route;
|
|
643
|
-
if (!
|
|
644
|
-
!isRecord(candidate.summary) ||
|
|
645
|
-
!isRecord(candidate.basis) ||
|
|
646
|
-
!Array.isArray(candidate.controls)) {
|
|
1030
|
+
if (!routeShape(candidate)) {
|
|
647
1031
|
return {
|
|
648
1032
|
kind: "failure",
|
|
649
1033
|
failure: {
|
|
@@ -668,17 +1052,25 @@ function routeAnswer(state, stopped, journey, answer) {
|
|
|
668
1052
|
const declaredSummary = summary;
|
|
669
1053
|
const basis = candidate.basis;
|
|
670
1054
|
const basisFields = ["intention", "checkout", "conventions", "adopted_decisions"];
|
|
671
|
-
|
|
1055
|
+
const asText = (value) => {
|
|
1056
|
+
if (nonBlank(value))
|
|
1057
|
+
return value.trim();
|
|
1058
|
+
if (Array.isArray(value) && value.length > 0 && value.every(nonBlank))
|
|
1059
|
+
return value.map((entry) => entry.trim()).join("\n");
|
|
1060
|
+
return null;
|
|
1061
|
+
};
|
|
1062
|
+
const invalidBasis = basisFields.find((field) => asText(basis[field]) === null);
|
|
1063
|
+
if (invalidBasis !== undefined) {
|
|
672
1064
|
return {
|
|
673
1065
|
kind: "failure",
|
|
674
1066
|
failure: {
|
|
675
1067
|
code: "FLOW_ROUTE_PROPOSAL_INVALID",
|
|
676
|
-
message:
|
|
1068
|
+
message: `decisions.route.basis.${invalidBasis} espera texto o lista de textos no vacíos; recibió ${Array.isArray(basis[invalidBasis]) ? "lista inválida" : typeof basis[invalidBasis]}`,
|
|
677
1069
|
action: "completá cada campo de decisions.route.basis con una observación concreta",
|
|
678
1070
|
},
|
|
679
1071
|
};
|
|
680
1072
|
}
|
|
681
|
-
const declaredBasis = basis;
|
|
1073
|
+
const declaredBasis = Object.fromEntries(basisFields.map((field) => [field, asText(basis[field])]));
|
|
682
1074
|
const configured = new Map(journey.flatMap((decision) => {
|
|
683
1075
|
const control = routeControlOf(decision);
|
|
684
1076
|
return control === null ? [] : [[decision.id, { decision, control }]];
|
|
@@ -824,15 +1216,26 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
824
1216
|
if (!scopesSources(stopped))
|
|
825
1217
|
return { state };
|
|
826
1218
|
const asked = answer.decisions.sources;
|
|
827
|
-
const sources =
|
|
828
|
-
? asked.
|
|
1219
|
+
const sources = sourceList(asked)
|
|
1220
|
+
? asked.map((entry) => typeof entry === "string"
|
|
1221
|
+
? entry.trim()
|
|
1222
|
+
: isRecord(entry) && typeof entry.alias === "string" && Object.keys(entry).length === 1
|
|
1223
|
+
? entry.alias.trim()
|
|
1224
|
+
: null)
|
|
829
1225
|
: null;
|
|
830
|
-
if (sources
|
|
1226
|
+
if (sources?.some((alias) => alias === null)) {
|
|
1227
|
+
const at = sources.findIndex((alias) => alias === null);
|
|
831
1228
|
return {
|
|
832
|
-
failure: invalidScope(
|
|
1229
|
+
failure: invalidScope(`decisions.sources[${at}] espera un alias de texto o {alias: string}; recibió ${Array.isArray(asked) && asked[at] === null ? "null" : typeof asked[at]}`, "cada fuente es un alias de texto o un objeto {alias} sin otras claves"),
|
|
833
1230
|
};
|
|
834
1231
|
}
|
|
835
|
-
if (
|
|
1232
|
+
if (sources === null || sources.length === 0 || sources.some((alias) => alias?.length === 0)) {
|
|
1233
|
+
return {
|
|
1234
|
+
failure: invalidScope(`decisions.sources espera una lista no vacía de alias de texto o {alias: string}; recibió ${Array.isArray(asked) ? "lista vacía" : typeof asked}`, "devolvé en 'decisions.sources' los alias exactos que el plan afecta, y en 'decisions.plan' su documento"),
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
const aliases = sources;
|
|
1238
|
+
if (new Set(aliases).size !== aliases.length) {
|
|
836
1239
|
return {
|
|
837
1240
|
failure: invalidScope("el scope declara la misma fuente dos veces", "una unidad por alias: declarás cada fuente una sola vez"),
|
|
838
1241
|
};
|
|
@@ -840,7 +1243,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
840
1243
|
const plan = snapshot.plan;
|
|
841
1244
|
if (plan === null || plan.length === 0) {
|
|
842
1245
|
return {
|
|
843
|
-
failure: invalidScope(
|
|
1246
|
+
failure: invalidScope(`decisions.plan espera una ruta de texto no vacía; recibió ${typeof answer.decisions.plan}`, `devolvé en 'decisions.plan' la ruta del plan dentro del workspace (${DEFAULT_CORE_DOCS_CANON.plan}/PPP-plan-<slug>.md)`),
|
|
844
1247
|
};
|
|
845
1248
|
}
|
|
846
1249
|
if (snapshot.plan_error !== null) {
|
|
@@ -853,7 +1256,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
853
1256
|
};
|
|
854
1257
|
}
|
|
855
1258
|
const declared = snapshot.declared;
|
|
856
|
-
if (declared === null &&
|
|
1259
|
+
if (declared === null && aliases.some((alias) => alias !== "workspace")) {
|
|
857
1260
|
return {
|
|
858
1261
|
failure: {
|
|
859
1262
|
code: "FLOW_SCOPE_UNKNOWN_SOURCE",
|
|
@@ -863,7 +1266,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
863
1266
|
};
|
|
864
1267
|
}
|
|
865
1268
|
const declaredAliases = declared ?? [];
|
|
866
|
-
const unknown =
|
|
1269
|
+
const unknown = aliases.filter((alias) => alias !== "workspace" && !declaredAliases.includes(alias));
|
|
867
1270
|
if (unknown.length > 0) {
|
|
868
1271
|
return {
|
|
869
1272
|
failure: {
|
|
@@ -889,12 +1292,19 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
889
1292
|
code: structural.code,
|
|
890
1293
|
message: structural.message,
|
|
891
1294
|
action: planBoundaryAction(structural.code, "execution-entry"),
|
|
1295
|
+
violations: snapshot.boundary_failures.map((failure) => ({
|
|
1296
|
+
code: failure.code,
|
|
1297
|
+
field: failure.line ? `plan:${failure.line}` : "plan",
|
|
1298
|
+
expected: "gramática de fuentes y prueba local del plan",
|
|
1299
|
+
received: failure.message,
|
|
1300
|
+
message: failure.message,
|
|
1301
|
+
})),
|
|
892
1302
|
},
|
|
893
1303
|
};
|
|
894
1304
|
}
|
|
895
1305
|
const declaredByPlan = snapshot.sources;
|
|
896
|
-
const missing = declaredByPlan.filter((alias) => !
|
|
897
|
-
const extra =
|
|
1306
|
+
const missing = declaredByPlan.filter((alias) => !aliases.includes(alias));
|
|
1307
|
+
const extra = aliases.filter((alias) => !declaredByPlan.includes(alias));
|
|
898
1308
|
if (missing.length > 0 || extra.length > 0) {
|
|
899
1309
|
const difference = [
|
|
900
1310
|
...(missing.length > 0 ? [`faltan ${missing.join(", ")}`] : []),
|
|
@@ -908,7 +1318,16 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
908
1318
|
},
|
|
909
1319
|
};
|
|
910
1320
|
}
|
|
911
|
-
return {
|
|
1321
|
+
return {
|
|
1322
|
+
state: withScope(state, {
|
|
1323
|
+
plan,
|
|
1324
|
+
sources: aliases,
|
|
1325
|
+
isolation: snapshot.isolation,
|
|
1326
|
+
...(snapshot.final_validation === null
|
|
1327
|
+
? {}
|
|
1328
|
+
: { final_validation: snapshot.final_validation }),
|
|
1329
|
+
}),
|
|
1330
|
+
};
|
|
912
1331
|
}
|
|
913
1332
|
const SETTLEMENT_OUTCOMES = SETTLEMENT_READINGS.map((reading) => reading.outcome);
|
|
914
1333
|
/** The reading each label of the question stands for — from the row's own table. */
|
|
@@ -1048,7 +1467,7 @@ function ratified(declaration, outcome) {
|
|
|
1048
1467
|
: { note: declaration.note, index: declaration.index, outcome };
|
|
1049
1468
|
}
|
|
1050
1469
|
function readDeclarations(value) {
|
|
1051
|
-
if (!
|
|
1470
|
+
if (!settlementList(value))
|
|
1052
1471
|
return null;
|
|
1053
1472
|
const out = [];
|
|
1054
1473
|
for (const entry of value) {
|
|
@@ -1113,6 +1532,7 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1113
1532
|
}, cost),
|
|
1114
1533
|
};
|
|
1115
1534
|
}
|
|
1535
|
+
const dated = withDecisionDate(state, state.decision_date ?? new Date().toISOString().slice(0, 10));
|
|
1116
1536
|
// The plan of the scope decides WHICH preparation this is. A standalone plan has
|
|
1117
1537
|
// no spec to compose against, so calling `preparePlanExecDecision` for it would
|
|
1118
1538
|
// refuse with `FLOW_DECISION_LINEAGE_INVALID` and leave the run with handoffs as
|
|
@@ -1120,7 +1540,7 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1120
1540
|
const standalone = await standaloneDecisionFor(fs, snapshot.root, plan, candidate);
|
|
1121
1541
|
if (standalone !== null) {
|
|
1122
1542
|
return standalone.ok
|
|
1123
|
-
? { ok: true, state: withDecisionPreparation(
|
|
1543
|
+
? { ok: true, state: withDecisionPreparation(dated, standalone.preparation) }
|
|
1124
1544
|
: {
|
|
1125
1545
|
ok: false,
|
|
1126
1546
|
decision: reject(state, resolved, standalone.failure.message, standalone.failure, cost),
|
|
@@ -1131,23 +1551,27 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1131
1551
|
session: state.session,
|
|
1132
1552
|
plan,
|
|
1133
1553
|
value: candidate,
|
|
1554
|
+
date: dated.decision_date ?? new Date().toISOString().slice(0, 10),
|
|
1134
1555
|
});
|
|
1135
1556
|
if (!prepared.ok) {
|
|
1557
|
+
const refused = reject(dated, resolveBoundary(dated, journeyForRun(dated)), prepared.failure.message, prepared.failure, cost);
|
|
1136
1558
|
return {
|
|
1137
1559
|
ok: false,
|
|
1138
|
-
decision:
|
|
1560
|
+
decision: refused.ok ? { ...refused, persist: true } : refused,
|
|
1139
1561
|
};
|
|
1140
1562
|
}
|
|
1141
1563
|
switch (prepared.kind) {
|
|
1142
1564
|
case "settled":
|
|
1143
1565
|
return {
|
|
1144
1566
|
ok: true,
|
|
1145
|
-
|
|
1567
|
+
...(prepared.warnings === undefined ? {} : { warnings: prepared.warnings }),
|
|
1568
|
+
state: withDecisionPreparation(dated, { kind: "settled", decision: prepared.decision }),
|
|
1146
1569
|
};
|
|
1147
1570
|
case "reused":
|
|
1148
1571
|
return {
|
|
1149
1572
|
ok: true,
|
|
1150
|
-
|
|
1573
|
+
...(prepared.warnings === undefined ? {} : { warnings: prepared.warnings }),
|
|
1574
|
+
state: withDecisionPreparation(dated, {
|
|
1151
1575
|
kind: "reused",
|
|
1152
1576
|
note: prepared.note,
|
|
1153
1577
|
decision: prepared.decision,
|
|
@@ -1157,7 +1581,8 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1157
1581
|
case "prepared":
|
|
1158
1582
|
return {
|
|
1159
1583
|
ok: true,
|
|
1160
|
-
|
|
1584
|
+
...(prepared.warnings === undefined ? {} : { warnings: prepared.warnings }),
|
|
1585
|
+
state: withDecisionPreparation(dated, {
|
|
1161
1586
|
kind: "prepared",
|
|
1162
1587
|
note: prepared.prepared.note,
|
|
1163
1588
|
preview: prepared.prepared.preview,
|
|
@@ -1311,7 +1736,7 @@ function withStandaloneGuidance(decision, record) {
|
|
|
1311
1736
|
* cannot tell them apart, and neither could the table that decides the charge.
|
|
1312
1737
|
*/
|
|
1313
1738
|
function invalidScope(message, action) {
|
|
1314
|
-
return { code: "
|
|
1739
|
+
return { code: "FLOW_SCOPE_SHAPE_INVALID", message, action };
|
|
1315
1740
|
}
|
|
1316
1741
|
/**
|
|
1317
1742
|
* The closure-evidence gate, run BEFORE the preview is sealed.
|
|
@@ -1325,6 +1750,8 @@ function planEvidenceFrom(stopped, answer, observed) {
|
|
|
1325
1750
|
if (proposalContractOf(stopped) === null || answer.artifacts.length === 0)
|
|
1326
1751
|
return null;
|
|
1327
1752
|
const proposed = new Set(answer.artifacts.map((artifact) => artifact.path.trim()));
|
|
1753
|
+
const violations = [];
|
|
1754
|
+
let first = null;
|
|
1328
1755
|
for (const plan of observed) {
|
|
1329
1756
|
if (!proposed.has(plan.path))
|
|
1330
1757
|
continue;
|
|
@@ -1335,15 +1762,20 @@ function planEvidenceFrom(stopped, answer, observed) {
|
|
|
1335
1762
|
// should not learn of the header on the next attempt.
|
|
1336
1763
|
const lineage = plan.failures.find((candidate) => candidate !== failure && candidate.code === "PLAN_LINEAGE_UNSEALED");
|
|
1337
1764
|
const also = lineage === undefined ? "" : ` · además: ${lineage.message}`;
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
action: planBoundaryAction(failure.code, "proposal"),
|
|
1343
|
-
},
|
|
1765
|
+
first ??= {
|
|
1766
|
+
code: failure.code,
|
|
1767
|
+
message: `'${plan.path}': ${failure.message}${also}`,
|
|
1768
|
+
action: planBoundaryAction(failure.code, "proposal"),
|
|
1344
1769
|
};
|
|
1345
|
-
|
|
1346
|
-
|
|
1770
|
+
violations.push(...plan.failures.map((entry) => ({
|
|
1771
|
+
code: entry.code,
|
|
1772
|
+
field: entry.line ? `${plan.path}:${entry.line}` : plan.path,
|
|
1773
|
+
expected: "plan con prueba local y linaje sellado",
|
|
1774
|
+
received: entry.message,
|
|
1775
|
+
message: entry.message,
|
|
1776
|
+
})));
|
|
1777
|
+
}
|
|
1778
|
+
return first === null ? null : { failure: { ...first, violations } };
|
|
1347
1779
|
}
|
|
1348
1780
|
/**
|
|
1349
1781
|
* Seat the proposal an authoring answer just handed over — sealed, not believed.
|
|
@@ -1530,7 +1962,7 @@ function grantOf(state, resolved, answer, granted, journey) {
|
|
|
1530
1962
|
* a payload the boundary's own contract rejects, a decline (a real answer that
|
|
1531
1963
|
* applies nothing), and an execution result that did not earn its transition.
|
|
1532
1964
|
*/
|
|
1533
|
-
function admit(state, resolved, stopped, input, cost, checkouts, scoped) {
|
|
1965
|
+
function admit(state, resolved, stopped, input, cost, checkouts, scoped, preexisting, validationOnlyCurrent) {
|
|
1534
1966
|
const expectedApproval = resolved.kind === "authorization"
|
|
1535
1967
|
? effectApprovalDigest(stopped.id, resolved.authorization?.planned ?? [])
|
|
1536
1968
|
: null;
|
|
@@ -1564,14 +1996,11 @@ function admit(state, resolved, stopped, input, cost, checkouts, scoped) {
|
|
|
1564
1996
|
// consults. A resend, a decline and a pause are not failed tries either, and
|
|
1565
1997
|
// they are classified there rather than being exempted here.
|
|
1566
1998
|
return {
|
|
1567
|
-
decision: reject(state, resolved, parsed.failure.message,
|
|
1999
|
+
decision: reject(state, resolved, parsed.failure.message, parsed.failure, cost),
|
|
1568
2000
|
};
|
|
1569
2001
|
}
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1572
|
-
// stopping ends the run here (`cancelled`), pausing keeps the very same
|
|
1573
|
-
// boundary standing so the run picks it up after compacting (`needs_input`).
|
|
1574
|
-
// Collapsing them would report a paused run as a cancelled one.
|
|
2002
|
+
// Pause leaves the boundary standing; stop places the shared close intention.
|
|
2003
|
+
// The driver runs finalize after this transaction releases the run lock.
|
|
1575
2004
|
if (parsed.answer.choice === PAUSE_LABEL) {
|
|
1576
2005
|
return {
|
|
1577
2006
|
decision: reject(state, resolved, `'${PAUSE_LABEL}': la frontera queda en pie y la corrida retoma acá`, {
|
|
@@ -1582,27 +2011,37 @@ function admit(state, resolved, stopped, input, cost, checkouts, scoped) {
|
|
|
1582
2011
|
};
|
|
1583
2012
|
}
|
|
1584
2013
|
if (parsed.answer.choice === STOP_LABEL) {
|
|
1585
|
-
return {
|
|
1586
|
-
decision: reject(state, resolved, `'${STOP_LABEL}': el recorrido queda detenido en esta frontera`, {
|
|
1587
|
-
code: "FLOW_BOUNDARY_DECLINED",
|
|
1588
|
-
action: "reanudá con 'aw flow advance' cuando quieras retomar esta frontera",
|
|
1589
|
-
outcome: "cancelled",
|
|
1590
|
-
}, cost),
|
|
1591
|
-
};
|
|
2014
|
+
return { decision: closeFromAnswer(state) };
|
|
1592
2015
|
}
|
|
1593
2016
|
// An execution result has to EARN the transition. Anything short of a completed
|
|
1594
2017
|
// run with its evidence and its whole effect keeps the boundary standing: the
|
|
1595
2018
|
// work stays pending, with the recovery the action declared.
|
|
1596
2019
|
if (resolved.kind !== "execution")
|
|
1597
2020
|
return parsed;
|
|
1598
|
-
return earned(state, resolved, stopped, parsed, cost, checkouts, scoped);
|
|
2021
|
+
return earned(state, resolved, stopped, parsed, cost, checkouts, scoped, preexisting, validationOnlyCurrent);
|
|
2022
|
+
}
|
|
2023
|
+
function closeFromAnswer(state) {
|
|
2024
|
+
const closing = closeAtBoundaryState(state);
|
|
2025
|
+
if (!closing.ok)
|
|
2026
|
+
return closing;
|
|
2027
|
+
const advanced = advanceFlowRun({
|
|
2028
|
+
state: closing.state,
|
|
2029
|
+
journey: journeyForRun(closing.state),
|
|
2030
|
+
});
|
|
2031
|
+
if (!advanced.ok)
|
|
2032
|
+
return advanced;
|
|
2033
|
+
return {
|
|
2034
|
+
ok: true,
|
|
2035
|
+
state: advanced.state,
|
|
2036
|
+
value: { directive: advanced.directive, advanced: true },
|
|
2037
|
+
};
|
|
1599
2038
|
}
|
|
1600
2039
|
/** The execution verdict, then — at the phase validation — the batch's own credit. */
|
|
1601
|
-
function earned(state, resolved, stopped, parsed, cost, checkouts, scoped) {
|
|
2040
|
+
function earned(state, resolved, stopped, parsed, cost, checkouts, scoped, preexisting, validationOnlyCurrent) {
|
|
1602
2041
|
// The row's `effects` are the ceiling and the sealed proposal is what really
|
|
1603
2042
|
// happens: demanding an overwrite from a publication that only creates files
|
|
1604
2043
|
// would leave the transition pending for an effect nobody could produce.
|
|
1605
|
-
const verdict = executionVerdict(parsed.answer.result, resolved.action, effectsOfTransition(state, stopped), checkouts);
|
|
2044
|
+
const verdict = executionVerdict(parsed.answer.result, resolved.action, effectsOfTransition(state, stopped), checkouts, preexisting);
|
|
1606
2045
|
if (verdict !== null) {
|
|
1607
2046
|
const trace = declaredTrace(state, stopped, resolved, parsed.answer, verdict);
|
|
1608
2047
|
return {
|
|
@@ -1612,7 +2051,7 @@ function earned(state, resolved, stopped, parsed, cost, checkouts, scoped) {
|
|
|
1612
2051
|
}),
|
|
1613
2052
|
};
|
|
1614
2053
|
}
|
|
1615
|
-
const credited = batchCredit(state, stopped, parsed.answer, resolved, scoped);
|
|
2054
|
+
const credited = batchCredit(state, stopped, parsed.answer, resolved, scoped, validationOnlyCurrent);
|
|
1616
2055
|
if (credited === null)
|
|
1617
2056
|
return parsed;
|
|
1618
2057
|
if (!credited.ok) {
|
|
@@ -1627,7 +2066,7 @@ function earned(state, resolved, stopped, parsed, cost, checkouts, scoped) {
|
|
|
1627
2066
|
* each of its sources, of its own checkout. `null` on every other boundary, and
|
|
1628
2067
|
* on a run with no batch standing, where there is nothing to credit.
|
|
1629
2068
|
*/
|
|
1630
|
-
function batchCredit(state, stopped, answer, resolved, scoped) {
|
|
2069
|
+
function batchCredit(state, stopped, answer, resolved, scoped, validationOnlyCurrent) {
|
|
1631
2070
|
if (stopped.id !== PHASE_VALIDATION || answer.result === null || state.scope === null) {
|
|
1632
2071
|
return null;
|
|
1633
2072
|
}
|
|
@@ -1636,7 +2075,13 @@ function batchCredit(state, stopped, answer, resolved, scoped) {
|
|
|
1636
2075
|
const batch = batches.find((entry) => entry.iteration === iteration);
|
|
1637
2076
|
if (batch === undefined)
|
|
1638
2077
|
return null;
|
|
1639
|
-
const verdict = batchCreditVerdict(answer.result, {
|
|
2078
|
+
const verdict = batchCreditVerdict(answer.result, {
|
|
2079
|
+
batch,
|
|
2080
|
+
batches,
|
|
2081
|
+
sources: state.scope.sources,
|
|
2082
|
+
scoped,
|
|
2083
|
+
validation_only_approved: validationOnlyCurrent && approvedValidationOnly(state, batch),
|
|
2084
|
+
}, resolved.action?.recovery ?? "");
|
|
1640
2085
|
if (!verdict.ok)
|
|
1641
2086
|
return verdict;
|
|
1642
2087
|
return { ok: true, credit: { batch: batch.id, proofs: verdict.credit } };
|
|
@@ -1708,7 +2153,11 @@ function actionDrift(state, resolved, cost) {
|
|
|
1708
2153
|
* was written" are different facts, and only the second one advances a run.
|
|
1709
2154
|
*/
|
|
1710
2155
|
function holdAfterApproval(approved, journey, identity) {
|
|
1711
|
-
const held = advanceFlowRun({
|
|
2156
|
+
const held = advanceFlowRun({
|
|
2157
|
+
state: withAttempt(approved, { ...identity, approval: true }),
|
|
2158
|
+
journey,
|
|
2159
|
+
applied: [],
|
|
2160
|
+
});
|
|
1712
2161
|
if (!held.ok)
|
|
1713
2162
|
return { ok: false, failure: held.failure };
|
|
1714
2163
|
return { ok: true, state: held.state, value: { directive: held.directive, advanced: true } };
|
|
@@ -1721,6 +2170,9 @@ function applyAndAdvance(approved, journey, stopped, identity, answer) {
|
|
|
1721
2170
|
let next = answer.signals.length > 0
|
|
1722
2171
|
? withObservation(approved, observationFor(approved, stopped.id, answer.signals))
|
|
1723
2172
|
: approved;
|
|
2173
|
+
if (stopped.answer_contract === "batch-review" && isBatchReview(answer.decisions.review)) {
|
|
2174
|
+
next = withPlanExecBatchReview(next, answer.decisions.review);
|
|
2175
|
+
}
|
|
1724
2176
|
next = withPlanExecBatchStageForTransition(applyTransition(next, stopped.id, effectsOfTransition(next, stopped)), stopped.id);
|
|
1725
2177
|
next = withAttempt(next, identity);
|
|
1726
2178
|
// The boundary follows the position, always: handing the engine a state whose
|
|
@@ -1774,15 +2226,8 @@ function choiceOutcomeOf(stopped, label) {
|
|
|
1774
2226
|
* the state. A retry is recognized by the attempt ledger, so it cannot create a
|
|
1775
2227
|
* second package or walk the batch toward a commit.
|
|
1776
2228
|
*/
|
|
1777
|
-
function applyAndHandoff(state, journey, stopped, identity, answer, outcome, specPath) {
|
|
1778
|
-
const plan = state.scope?.plan ?? null;
|
|
1779
|
-
if (outcome.destination === "plan-refine" && plan === null) {
|
|
1780
|
-
const resolved = resolveBoundary(state, journey);
|
|
1781
|
-
return reject(state, resolved, "la entrega a plan-refine no tiene el plan que debe reabrir", {
|
|
1782
|
-
code: "FLOW_HANDOFF_PLAN_MISSING",
|
|
1783
|
-
action: "fijá el scope de plan-exec antes de elegir una escalación a plan-refine",
|
|
1784
|
-
}, { journey, identity });
|
|
1785
|
-
}
|
|
2229
|
+
function applyAndHandoff(state, journey, stopped, identity, answer, outcome, specPath, request) {
|
|
2230
|
+
const plan = state.scope?.plan ?? state.plan_exec_entry?.plan ?? null;
|
|
1786
2231
|
const fixPreview = state.fix_preview ?? null;
|
|
1787
2232
|
const packageBody = {
|
|
1788
2233
|
plan,
|
|
@@ -1792,7 +2237,11 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1792
2237
|
// declared preview travels with it — the destination is the one place that
|
|
1793
2238
|
// rewrites it, and rediscovering it there is exactly what the package exists
|
|
1794
2239
|
// to avoid.
|
|
1795
|
-
decisions:
|
|
2240
|
+
decisions: {
|
|
2241
|
+
...answer.decisions,
|
|
2242
|
+
...(fixPreview === null ? {} : { preview: fixPreview }),
|
|
2243
|
+
...(request === null ? {} : { request }),
|
|
2244
|
+
},
|
|
1796
2245
|
selection: answer.choice ?? "",
|
|
1797
2246
|
};
|
|
1798
2247
|
// The handoff to `spec-refine` used to be the bare command while the one to
|
|
@@ -1800,13 +2249,7 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1800
2249
|
// spec again — the escalation package exists precisely so nothing is
|
|
1801
2250
|
// re-derived at the destination. An unreadable plan degrades to the bare
|
|
1802
2251
|
// command: an escalation is never blocked over the convenience of an argument.
|
|
1803
|
-
const command = outcome.destination
|
|
1804
|
-
? `/w:plan-refine ${plan}`
|
|
1805
|
-
: outcome.destination === "spec-refine"
|
|
1806
|
-
? specPath === null
|
|
1807
|
-
? "/w:spec-refine"
|
|
1808
|
-
: `/w:spec-refine ${specPath}`
|
|
1809
|
-
: "/w:spec-new";
|
|
2252
|
+
const command = handoffCommand(outcome.destination, plan, specPath);
|
|
1810
2253
|
let next = withSelectedChoice(state, {
|
|
1811
2254
|
transition: stopped.id,
|
|
1812
2255
|
label: answer.choice ?? "",
|
|
@@ -1814,14 +2257,26 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1814
2257
|
});
|
|
1815
2258
|
next = withPlanExecBatchStageForTransition(applyTransition(next, stopped.id, effectsOfTransition(next, stopped)), stopped.id);
|
|
1816
2259
|
next = withAttempt(next, identity);
|
|
1817
|
-
next = withHandoff(next,
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2260
|
+
next = withHandoff(next, outcome.destination === "plan-refine"
|
|
2261
|
+
? planRefineHandoff(state, answer.decisions, answer.choice ?? "")
|
|
2262
|
+
: {
|
|
2263
|
+
destination: outcome.destination,
|
|
2264
|
+
command,
|
|
2265
|
+
package: packageBody,
|
|
2266
|
+
package_digest: semanticDigest(packageBody),
|
|
2267
|
+
});
|
|
1823
2268
|
next = withBoundary(next, journey[next.applied.length]?.id ?? null);
|
|
1824
|
-
|
|
2269
|
+
if (state.flow === "quick" && outcome.destination === "spec-new") {
|
|
2270
|
+
const closing = closeAtBoundaryState(next);
|
|
2271
|
+
if (!closing.ok)
|
|
2272
|
+
return closing;
|
|
2273
|
+
next = closing.state;
|
|
2274
|
+
}
|
|
2275
|
+
const advanced = advanceFlowRun({
|
|
2276
|
+
state: next,
|
|
2277
|
+
journey: journeyForRun(next),
|
|
2278
|
+
applied: [stepOf(stopped)],
|
|
2279
|
+
});
|
|
1825
2280
|
if (!advanced.ok)
|
|
1826
2281
|
return { ok: false, failure: advanced.failure };
|
|
1827
2282
|
return {
|
|
@@ -1830,6 +2285,13 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1830
2285
|
value: { directive: advanced.directive, advanced: true },
|
|
1831
2286
|
};
|
|
1832
2287
|
}
|
|
2288
|
+
function handoffCommand(destination, plan, spec) {
|
|
2289
|
+
if (destination === "plan-refine")
|
|
2290
|
+
return `/w:plan-refine ${plan}`;
|
|
2291
|
+
if (destination === "spec-refine")
|
|
2292
|
+
return spec === null ? "/w:spec-refine" : `/w:spec-refine ${spec}`;
|
|
2293
|
+
return "/w:spec-new";
|
|
2294
|
+
}
|
|
1833
2295
|
/**
|
|
1834
2296
|
* Whether this submission is a resend, judged by the ledger that already knows
|
|
1835
2297
|
* how.
|
|
@@ -1861,11 +2323,11 @@ function resendCheck(state, resolved, cost) {
|
|
|
1861
2323
|
for (const past of state.attempts) {
|
|
1862
2324
|
const replay = ledger.record(past);
|
|
1863
2325
|
if (!replay.ok)
|
|
1864
|
-
return
|
|
2326
|
+
return reject(state, resolved, replay.failure.message, replay.failure);
|
|
1865
2327
|
}
|
|
1866
2328
|
const verdict = ledger.record(identity);
|
|
1867
2329
|
if (!verdict.ok)
|
|
1868
|
-
return
|
|
2330
|
+
return reject(state, resolved, verdict.failure.message, verdict.failure);
|
|
1869
2331
|
if (verdict.kind === "new")
|
|
1870
2332
|
return null;
|
|
1871
2333
|
// The TWIN's transition, not this submission's: the identity is built over the
|
|
@@ -1873,9 +2335,8 @@ function resendCheck(state, resolved, cost) {
|
|
|
1873
2335
|
// What is being asked is whether the step the twin answered ever moved.
|
|
1874
2336
|
const twin = state.attempts.find((past) => past.invocation_id === identity.invocation_id &&
|
|
1875
2337
|
past.request_digest === identity.request_digest);
|
|
1876
|
-
//
|
|
1877
|
-
//
|
|
1878
|
-
// eventually degrades the boundary instead of leaving the caller looping.
|
|
2338
|
+
// A refused twin gets its real diagnosis again, but the same request cannot
|
|
2339
|
+
// create another row. Only a different insufficient answer spends again.
|
|
1879
2340
|
if (twin !== undefined && twinWasRefused(state, twin))
|
|
1880
2341
|
return null;
|
|
1881
2342
|
return reject(state, resolved, "esta respuesta ya se había aplicado: el recorrido no avanza dos veces", {
|
|
@@ -1907,8 +2368,8 @@ function twinWasRefused(state, twin) {
|
|
|
1907
2368
|
* recoverable — an unbounded loop inside the very mechanism that exists to bound
|
|
1908
2369
|
* one. One place decides, one table says which codes count.
|
|
1909
2370
|
*
|
|
1910
|
-
* `cost` is absent
|
|
1911
|
-
*
|
|
2371
|
+
* `cost` is also absent for mechanical controls and replay failures, which never
|
|
2372
|
+
* charge a boundary even when it is still open.
|
|
1912
2373
|
*/
|
|
1913
2374
|
function reject(state, resolved, message, detail, cost) {
|
|
1914
2375
|
// A refused answer is the one rejection that CHANGES the run: the attempt is
|
|
@@ -1919,14 +2380,20 @@ function reject(state, resolved, message, detail, cost) {
|
|
|
1919
2380
|
const traced = cost?.trace === undefined || restatesLastEvent(state, cost.trace)
|
|
1920
2381
|
? state
|
|
1921
2382
|
: withEvent(state, cost.trace);
|
|
1922
|
-
const after = cost !== undefined &&
|
|
2383
|
+
const after = cost !== undefined &&
|
|
2384
|
+
spendsAttempt(detail.code) &&
|
|
2385
|
+
!state.attempts.some((past) => past.invocation_id === cost.identity.invocation_id &&
|
|
2386
|
+
past.attempt === cost.identity.attempt &&
|
|
2387
|
+
past.request_digest === cost.identity.request_digest)
|
|
2388
|
+
? withAttempt(traced, cost.identity)
|
|
2389
|
+
: traced;
|
|
1923
2390
|
const now = cost === undefined || after === state ? resolved : resolveBoundary(after, cost.journey);
|
|
1924
2391
|
const built = directiveFor(after, now, [], {
|
|
1925
2392
|
// No outcome named ⇒ the boundary decides it: a finished journey reports
|
|
1926
2393
|
// `completed`, an open one `needs_input`. Hardcoding one here would let a
|
|
1927
2394
|
// rejection at the end of a run claim work is still pending.
|
|
1928
2395
|
...(detail.outcome === undefined ? {} : { outcome: detail.outcome }),
|
|
1929
|
-
nextAction: `${message} — ${now.error?.action ?? detail.action}`,
|
|
2396
|
+
nextAction: `${message} — ${detail.code === "FLOW_BOUNDARY_PAUSED" ? detail.action : (now.error?.action ?? detail.action)}`,
|
|
1930
2397
|
});
|
|
1931
2398
|
if (!built.ok)
|
|
1932
2399
|
return { ok: false, failure: built.failure };
|
|
@@ -1936,7 +2403,12 @@ function reject(state, resolved, message, detail, cost) {
|
|
|
1936
2403
|
// longer a way forward, and reporting the payload's error would suggest it is.
|
|
1937
2404
|
const directive = {
|
|
1938
2405
|
...built.directive,
|
|
1939
|
-
error:
|
|
2406
|
+
error: (detail.code === "FLOW_BOUNDARY_PAUSED" ? null : now.error) ?? {
|
|
2407
|
+
code: detail.code,
|
|
2408
|
+
message,
|
|
2409
|
+
action: detail.action,
|
|
2410
|
+
...(detail.violations ? { violations: detail.violations } : {}),
|
|
2411
|
+
},
|
|
1940
2412
|
};
|
|
1941
2413
|
return {
|
|
1942
2414
|
ok: true,
|
|
@@ -1961,14 +2433,9 @@ function attemptIdentity(state, input, seal, transition) {
|
|
|
1961
2433
|
const rowIteration = iterationOf(state, transition);
|
|
1962
2434
|
const prior = state.attempts.filter((past) => past.invocation_id === seal);
|
|
1963
2435
|
const twin = prior.find((past) => past.request_digest === digest);
|
|
1964
|
-
//
|
|
1965
|
-
//
|
|
1966
|
-
//
|
|
1967
|
-
// re-evaluated and its row kept wearing the twin's ordinal — deliberately, so
|
|
1968
|
-
// it still counts toward the cap — so the rows say two while the highest
|
|
1969
|
-
// ordinal says one, and the next answer arrives as attempt 3 where the ledger
|
|
1970
|
-
// accepts only 2. Nobody can answer that boundary, and `recover` reads the
|
|
1971
|
-
// chain as coherent because replaying two identical rows never fails.
|
|
2436
|
+
// A twin reuses its ordinal only for the ledger's resend verdict; reject does
|
|
2437
|
+
// not persist it again. A new answer takes highest + 1, never the row count of
|
|
2438
|
+
// a legacy ledger that may still contain identical twins.
|
|
1972
2439
|
const highest = Math.max(0, ...prior.map((past) => past.attempt));
|
|
1973
2440
|
const attempt = twin?.attempt ?? highest + 1;
|
|
1974
2441
|
const parent = attempt === 1
|