@tacuchi/agent-workflow-cli 25.7.0 → 25.9.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 +110 -0
- package/README.md +5 -1
- package/dist/adapters/git-cli.js +311 -24
- 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 +124 -43
- package/dist/application/capability/design-handler.js.map +1 -1
- package/dist/application/capability/dispatcher.js +4 -1
- 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 +26 -10
- package/dist/application/capability/readiness.js.map +1 -1
- package/dist/application/capability/wrapper.js +10 -3
- package/dist/application/capability/wrapper.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 +171 -27
- 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 +4 -1
- 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 +10 -2
- package/dist/application/doctor/provider-mcps.js.map +1 -1
- package/dist/application/doctor/provider-skills.js +25 -0
- 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 +47 -3
- 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/doctor/skills-toml-migrate.js +76 -0
- package/dist/application/doctor/skills-toml-migrate.js.map +1 -0
- 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 +241 -30
- 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 +110 -8
- 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 +155 -0
- package/dist/application/flow/close-at-boundary.js.map +1 -0
- 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 +49 -5
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +361 -80
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/internal-drive.js +47 -19
- 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 +159 -0
- package/dist/application/flow/reopen-run.js.map +1 -0
- package/dist/application/flow/run-journey.js +11 -8
- package/dist/application/flow/run-journey.js.map +1 -1
- package/dist/application/flow/run-projection.js +16 -5
- package/dist/application/flow/run-projection.js.map +1 -1
- package/dist/application/flow/run-state-service.js +3 -1
- package/dist/application/flow/run-state-service.js.map +1 -1
- package/dist/application/flow/submit.js +731 -154
- 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-sql-mutation-guard.js +147 -12
- package/dist/application/hook-sql-mutation-guard.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 +2 -0
- 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 +121 -48
- 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/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 +35 -11
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/install-targets.js +73 -2
- package/dist/application/self/install-targets.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/uninstall.js +22 -8
- 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 +51 -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 +190 -20
- 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 +1 -1
- 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 +97 -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 +86 -0
- package/dist/cli/ascii.js.map +1 -0
- 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 -2
- package/dist/cli/commands/amend.js.map +1 -1
- package/dist/cli/commands/capability.js +26 -0
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/check-branch.js +1 -0
- package/dist/cli/commands/check-branch.js.map +1 -1
- package/dist/cli/commands/checkpoint-read.js +1 -0
- package/dist/cli/commands/checkpoint-read.js.map +1 -1
- package/dist/cli/commands/checkpoint-write.js +4 -1
- package/dist/cli/commands/checkpoint-write.js.map +1 -1
- package/dist/cli/commands/claims.js +1 -0
- package/dist/cli/commands/claims.js.map +1 -1
- package/dist/cli/commands/code-scan.js +1 -0
- package/dist/cli/commands/code-scan.js.map +1 -1
- package/dist/cli/commands/context-budget.js +1 -0
- package/dist/cli/commands/context-budget.js.map +1 -1
- package/dist/cli/commands/context-plan.js +1 -0
- package/dist/cli/commands/context-plan.js.map +1 -1
- package/dist/cli/commands/cut-intent.js +1 -0
- package/dist/cli/commands/cut-intent.js.map +1 -1
- package/dist/cli/commands/designs.js +15 -5
- package/dist/cli/commands/designs.js.map +1 -1
- package/dist/cli/commands/dev-only.js +22 -5
- 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 +9 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/export.js +89 -16
- package/dist/cli/commands/export.js.map +1 -1
- package/dist/cli/commands/fix-git.js +89 -13
- package/dist/cli/commands/fix-git.js.map +1 -1
- package/dist/cli/commands/flow.js +129 -13
- package/dist/cli/commands/flow.js.map +1 -1
- package/dist/cli/commands/generate-launch.js +15 -2
- package/dist/cli/commands/generate-launch.js.map +1 -1
- package/dist/cli/commands/git-flow.js +1 -0
- package/dist/cli/commands/git-flow.js.map +1 -1
- package/dist/cli/commands/history-update.js +7 -9
- 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 +26 -5
- package/dist/cli/commands/hook.js.map +1 -1
- package/dist/cli/commands/host-doctor.js +1 -0
- package/dist/cli/commands/host-doctor.js.map +1 -1
- package/dist/cli/commands/host-memory.js +2 -9
- package/dist/cli/commands/host-memory.js.map +1 -1
- package/dist/cli/commands/index.js +18 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +30 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/merge-state.js +3 -2
- package/dist/cli/commands/merge-state.js.map +1 -1
- package/dist/cli/commands/multiroot.js +16 -0
- package/dist/cli/commands/multiroot.js.map +1 -1
- package/dist/cli/commands/persist.js +7 -5
- package/dist/cli/commands/persist.js.map +1 -1
- package/dist/cli/commands/plan.js +1 -0
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/plugin-cache.js +4 -0
- package/dist/cli/commands/plugin-cache.js.map +1 -1
- package/dist/cli/commands/plugin-doctor.js +3 -0
- package/dist/cli/commands/plugin-doctor.js.map +1 -1
- package/dist/cli/commands/project-md-upsert.js +8 -3
- package/dist/cli/commands/project-md-upsert.js.map +1 -1
- package/dist/cli/commands/release-data.js +12 -0
- package/dist/cli/commands/release-data.js.map +1 -1
- package/dist/cli/commands/release-pass.js +13 -3
- package/dist/cli/commands/release-pass.js.map +1 -1
- package/dist/cli/commands/remove-source.js +1 -0
- package/dist/cli/commands/remove-source.js.map +1 -1
- package/dist/cli/commands/reseal.js +1 -0
- package/dist/cli/commands/reseal.js.map +1 -1
- package/dist/cli/commands/resume-summary.js +5 -0
- package/dist/cli/commands/resume-summary.js.map +1 -1
- package/dist/cli/commands/resume.js +13 -3
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/retirement.js +1 -0
- package/dist/cli/commands/retirement.js.map +1 -1
- package/dist/cli/commands/self.js +39 -1
- package/dist/cli/commands/self.js.map +1 -1
- package/dist/cli/commands/session-artifacts.js +1 -0
- package/dist/cli/commands/session-artifacts.js.map +1 -1
- package/dist/cli/commands/session-close.js +185 -31
- package/dist/cli/commands/session-close.js.map +1 -1
- package/dist/cli/commands/session-create.js +4 -1
- 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/session-resume.js +1 -0
- package/dist/cli/commands/session-resume.js.map +1 -1
- package/dist/cli/commands/sessions.js +16 -2
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/set-branch.js +29 -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/settle.js +1 -0
- package/dist/cli/commands/settle.js.map +1 -1
- package/dist/cli/commands/skill-index.js +1 -0
- package/dist/cli/commands/skill-index.js.map +1 -1
- package/dist/cli/commands/skills.js +1 -0
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/commands/sources.js +5 -0
- package/dist/cli/commands/sources.js.map +1 -1
- package/dist/cli/commands/stack.js +1 -0
- package/dist/cli/commands/stack.js.map +1 -1
- package/dist/cli/commands/status.js +36 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/tool.js +5 -21
- package/dist/cli/commands/tool.js.map +1 -1
- package/dist/cli/commands/unknown-flags.js +68 -16
- package/dist/cli/commands/unknown-flags.js.map +1 -1
- package/dist/cli/commands/visibility.js +2 -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 +51 -2
- package/dist/cli/commands/workspace-init.js.map +1 -1
- package/dist/cli/commands/workspace-migrate.js +31 -14
- 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 +5 -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 +108 -14
- 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 -471
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output-mode.js +11 -2
- package/dist/cli/output-mode.js.map +1 -1
- package/dist/cli/parser.js +18 -5
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/render.js +30 -1
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/tui/app.js +1 -1
- package/dist/cli/tui/app.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/recommended-skills.js +2 -2
- package/dist/cli/tui/data/recommended-skills.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/dist-manifest.json +4117 -0
- 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 +28 -2
- 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 +273 -130
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +330 -44
- 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 +169 -9
- package/dist/domain/flow/execution-result.js.map +1 -1
- package/dist/domain/flow/route.js.map +1 -1
- package/dist/domain/flow/rules.js +5 -6
- package/dist/domain/flow/rules.js.map +1 -1
- package/dist/domain/flow/run-state.js +531 -47
- 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 +22 -6
- 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 +2 -0
- package/dist/domain/skills.js.map +1 -1
- package/dist/domain/sql-lexer.js +15 -2
- 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 +5 -4
- package/skills/w/SKILL.md +3 -1
- 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/export-diagrams/EXPORT.md +3 -3
- 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 +45 -21
- package/skills/w/harness/HARNESS.md +5 -3
- package/skills/w/hooks/hooks.template.json +6 -4
- package/skills/w/loops/CHASSIS.md +12 -6
- package/skills/w/loops/CODE-POLICIES.md +7 -4
- 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 +6 -5
- 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/design/CONTRACT.md +16 -7
- package/skills/w/roles/design/ROLE.md +4 -2
- package/skills/w/roles/git/ROLE.md +16 -9
- package/skills/w/roles/research/ROLE.md +1 -1
- package/skills/w/roles/sql/ROLE.md +16 -14
- package/skills/w/schemas/design/ui-screen.v1.schema.json +9 -0
|
@@ -25,32 +25,88 @@ import { authorizeEffects, touchesTheWorld, } from "../../domain/capability/effe
|
|
|
25
25
|
import { AttemptLedger } from "../../domain/capability/protocol.js";
|
|
26
26
|
import { DEFAULT_CORE_DOCS_CANON, coreDocumentKindForPath } from "../../domain/docs-canon.js";
|
|
27
27
|
import { claimedSeal, parseFlowAnswer, spendsAttempt, } from "../../domain/flow/answer.js";
|
|
28
|
-
import { FIX_PREVIEW_TRANSITION, SETTLEMENT_READINGS, actionOf, alternativesOf, approvalGrantOf, effectsOf, internalActionOf, isRouteEvaluation, proposalContractOf, publishApprovalOf, routeControlOf, scopesSources, } from "../../domain/flow/authority.js";
|
|
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
|
-
import { executionVerdict } from "../../domain/flow/execution-result.js";
|
|
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,
|
|
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
|
-
import { observeCheckout, withObservedCheckouts } from "./checkout-observation.js";
|
|
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
|
-
import { applyUnderLock, locateRun } from "./run-state-service.js";
|
|
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,15 +176,78 @@ 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,
|
|
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),
|
|
129
191
|
baselines: plans.baselines,
|
|
130
192
|
};
|
|
131
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* The scoped fingerprints a phase validation is judged against, read before the
|
|
196
|
+
* lock like every other observation. The run read here only decides WHETHER to
|
|
197
|
+
* measure: the decision under the lock re-reads it, and a run that moved in
|
|
198
|
+
* between answers a different seal and is refused as stale anyway.
|
|
199
|
+
*/
|
|
200
|
+
async function observeBatchSources(fs, paths, session, git) {
|
|
201
|
+
if (git === undefined)
|
|
202
|
+
return null;
|
|
203
|
+
// With a reader present, "not measured" is an empty map and never `null`: the
|
|
204
|
+
// judgment then finds every source unobservable and refuses, instead of
|
|
205
|
+
// skipping the one comparison the phase validation exists for.
|
|
206
|
+
const read = await readRun(fs, locateRun(paths, session));
|
|
207
|
+
if (!read.ok || read.state.boundary !== PHASE_VALIDATION || read.state.scope === null) {
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
const observed = await observeScopedFingerprints(fs, git, paths, session, read.state.scope.sources);
|
|
211
|
+
return observed.ok ? observed.base : {};
|
|
212
|
+
}
|
|
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
|
+
}
|
|
132
251
|
/**
|
|
133
252
|
* Judge the plan bytes a proposal is carrying, and derive the seal each one gets.
|
|
134
253
|
*
|
|
@@ -209,8 +328,10 @@ async function observeScope(fs, paths, raw) {
|
|
|
209
328
|
declared: null,
|
|
210
329
|
plan: null,
|
|
211
330
|
sources: null,
|
|
331
|
+
final_validation: null,
|
|
212
332
|
boundary_failures: null,
|
|
213
333
|
plan_error: null,
|
|
334
|
+
isolation: "unit",
|
|
214
335
|
};
|
|
215
336
|
let parsed;
|
|
216
337
|
try {
|
|
@@ -231,7 +352,7 @@ async function observeScope(fs, paths, raw) {
|
|
|
231
352
|
const named = decisions.plan;
|
|
232
353
|
const plan = typeof named === "string" ? named.trim() : null;
|
|
233
354
|
if (plan === null || plan.length === 0 || !checkSafeRelativePath(plan).ok) {
|
|
234
|
-
return { ...empty, declared, plan };
|
|
355
|
+
return { ...empty, declared, plan, isolation: block?.edit_mode ?? "unit" };
|
|
235
356
|
}
|
|
236
357
|
const canon = await resolveCoreDocsCanon(fs, paths);
|
|
237
358
|
if (!canon.ok)
|
|
@@ -255,8 +376,12 @@ async function observeScope(fs, paths, raw) {
|
|
|
255
376
|
declared,
|
|
256
377
|
plan,
|
|
257
378
|
sources: sourceAliasesOfPlan(text),
|
|
379
|
+
final_validation: resolveFinalValidation(text, sourceAliasesOfPlan(text), await readSourcePipelines(fs, paths)),
|
|
258
380
|
boundary_failures: planGrammarAtEntry(text, declared),
|
|
259
381
|
plan_error: null,
|
|
382
|
+
isolation: /^> Aislamiento: unidad\s*$/m.test(text.split(/^## /m, 1)[0] ?? "")
|
|
383
|
+
? "unit"
|
|
384
|
+
: (block?.edit_mode ?? "unit"),
|
|
260
385
|
};
|
|
261
386
|
}
|
|
262
387
|
/**
|
|
@@ -268,7 +393,7 @@ async function observeScope(fs, paths, raw) {
|
|
|
268
393
|
*/
|
|
269
394
|
async function observeCheckouts(fs, paths, session, git) {
|
|
270
395
|
if (git === undefined)
|
|
271
|
-
return null;
|
|
396
|
+
return { states: null, failure: null };
|
|
272
397
|
// The SAME resolution the directive published, from the same function: two
|
|
273
398
|
// callers computing this apart is what made a rejection claim the tree moved
|
|
274
399
|
// when all that differed was the directory each side measured.
|
|
@@ -277,11 +402,24 @@ async function observeCheckouts(fs, paths, session, git) {
|
|
|
277
402
|
for (const candidate of candidates) {
|
|
278
403
|
// An unreadable checkout is deliberately absent from the eligible set: a proof
|
|
279
404
|
// against it fails closed instead of being treated as a clean tree.
|
|
280
|
-
|
|
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
|
+
}
|
|
281
419
|
if (state !== null)
|
|
282
420
|
states.push(state);
|
|
283
421
|
}
|
|
284
|
-
return states;
|
|
422
|
+
return { states, failure: null };
|
|
285
423
|
}
|
|
286
424
|
/**
|
|
287
425
|
* The alias→root pairs worth publishing: the ones actually OBSERVED.
|
|
@@ -348,11 +486,25 @@ async function observeDestinations(fs, paths, raw) {
|
|
|
348
486
|
return snapshot;
|
|
349
487
|
}
|
|
350
488
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: resend, drift, admission, scope, sealing, preview, grant and handoff checks are one intentional precedence contract.
|
|
351
|
-
async function decide(fs, paths, state, input, snapshot) {
|
|
489
|
+
async function decide(fs, paths, state, input, snapshot, checkOnly = false) {
|
|
352
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
|
+
}
|
|
353
504
|
const incoherent = checkAgainstJourney(state, journey);
|
|
354
|
-
if (incoherent !== null)
|
|
355
|
-
return
|
|
505
|
+
if (incoherent !== null) {
|
|
506
|
+
return reject(state, resolveBoundary(state, journey), incoherent.message, incoherent);
|
|
507
|
+
}
|
|
356
508
|
const resolved = resolveBoundary(state, journey);
|
|
357
509
|
if (resolved.stopped === null) {
|
|
358
510
|
return reject(state, resolved, "el recorrido ya terminó: no hay frontera que responder", {
|
|
@@ -385,6 +537,16 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
385
537
|
action: "corré 'aw flow advance': el CLI corre una vez más la operación interna y, si reconoce su efecto, avanza sin cobrar",
|
|
386
538
|
});
|
|
387
539
|
}
|
|
540
|
+
// An internal action is credited only by the driver that ran it: that is what
|
|
541
|
+
// leaves its `executed` event in the trace. A result from outside, however well
|
|
542
|
+
// formed, is a claim nobody verified — and applying it used to publish nothing
|
|
543
|
+
// while marking the step done.
|
|
544
|
+
if (resolved.kind === "execution" && internalActionOf(resolved.stopped) !== null) {
|
|
545
|
+
return reject(state, resolved, `'${resolved.stopped.id}' la ejecuta el CLI: un resultado externo no la acredita`, {
|
|
546
|
+
code: "FLOW_INTERNAL_ACTION_EXTERNAL_RESULT",
|
|
547
|
+
action: `corré 'aw flow advance --session ${state.session}': el CLI corre la operación y la acredita; si la corrida no sale de esta frontera, '${restartInvocation(state.session)}' la destraba`,
|
|
548
|
+
}, cost);
|
|
549
|
+
}
|
|
388
550
|
// The action the run is waiting on is the one that was EMITTED, and the seal
|
|
389
551
|
// would already refuse a result about any other. What the persisted digest adds
|
|
390
552
|
// is the diagnosis: when the two differ, the invocation changed underneath a run
|
|
@@ -393,8 +555,11 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
393
555
|
const drifted = actionDrift(state, resolved, cost);
|
|
394
556
|
if (drifted !== null)
|
|
395
557
|
return drifted;
|
|
558
|
+
if (resolved.kind === "execution" && snapshot.checkoutFailure !== null) {
|
|
559
|
+
return reject(state, resolved, snapshot.checkoutFailure.message, snapshot.checkoutFailure, cost);
|
|
560
|
+
}
|
|
396
561
|
// 2 · The boundary in force decides what is admissible.
|
|
397
|
-
const admissible = admit(state, resolved, resolved.stopped, input, cost, snapshot.checkouts);
|
|
562
|
+
const admissible = admit(state, resolved, resolved.stopped, input, cost, snapshot.checkouts, snapshot.scoped, snapshot.preexisting, snapshot.validation_only_current);
|
|
398
563
|
if ("decision" in admissible)
|
|
399
564
|
return admissible.decision;
|
|
400
565
|
const parsed = admissible;
|
|
@@ -408,19 +573,73 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
408
573
|
if (route.kind === "adjust") {
|
|
409
574
|
return adjustRouteProposal(state, journey);
|
|
410
575
|
}
|
|
411
|
-
|
|
576
|
+
if (checkOnly && resolved.kind === "execution")
|
|
577
|
+
return checked(state, resolved);
|
|
578
|
+
const accepted = route.kind === "accept" ? withRouteDecisions(state, route.decisions) : state;
|
|
579
|
+
// Sealed with the transition it earned, in the same state publication: a batch
|
|
580
|
+
// is never seen validated without the proofs that validated it.
|
|
581
|
+
let routed = parsed.credit === undefined
|
|
582
|
+
? accepted
|
|
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
|
+
}
|
|
412
602
|
// The registry, not the Spanish consequence text, owns what a selected
|
|
413
603
|
// alternative does. A handoff must stop before any later plan-exec row can
|
|
414
604
|
// mark work, validate or commit; a decision selection is durably recorded
|
|
415
605
|
// even while its registration is handled by the decision bridge below.
|
|
416
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
|
+
}
|
|
417
634
|
if (selected?.kind === "handoff") {
|
|
418
635
|
// Only `spec-refine` needs a document the run never declared: the plan's
|
|
419
636
|
// scope names the plan, and the spec is one hop away through its lineage.
|
|
420
637
|
const specPath = selected.destination === "spec-refine"
|
|
421
638
|
? await specPathOfScopedPlan(fs, paths, snapshot.root, state.scope?.plan ?? null)
|
|
422
639
|
: null;
|
|
423
|
-
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);
|
|
424
643
|
}
|
|
425
644
|
// The fix preview is checked HERE and nowhere else: it is a declaration, so no
|
|
426
645
|
// seal, no destination and no evidence would ever notice its absence, and the
|
|
@@ -438,27 +657,63 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
438
657
|
// preview showed. Neither ever applies the step by itself.
|
|
439
658
|
const scoped = scopeFrom(routed, resolved.stopped, parsed.answer, snapshot.scope);
|
|
440
659
|
if ("failure" in scoped) {
|
|
441
|
-
|
|
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);
|
|
442
681
|
}
|
|
443
682
|
// The closure-evidence gate runs over the proposed bytes BEFORE the seal: a
|
|
444
683
|
// plan whose validation names no observable check of the checkout is refused
|
|
445
684
|
// while it is being written, not when somebody tries to execute it.
|
|
446
685
|
const evidence = planEvidenceFrom(resolved.stopped, parsed.answer, snapshot.plans);
|
|
447
686
|
if (evidence !== null) {
|
|
448
|
-
return reject(state, resolved, evidence.failure.message,
|
|
687
|
+
return reject(state, resolved, evidence.failure.message, evidence.failure, cost);
|
|
449
688
|
}
|
|
450
|
-
const sealed = sealFrom(
|
|
689
|
+
const sealed = sealFrom(batchProposal.state, resolved.stopped, parsed.answer, snapshot.destinations, snapshot.baselines);
|
|
451
690
|
if ("failure" in sealed) {
|
|
452
|
-
return reject(state, resolved, sealed.failure.message,
|
|
691
|
+
return reject(state, resolved, sealed.failure.message, sealed.failure, cost);
|
|
453
692
|
}
|
|
454
693
|
const preparedForGate = await prepareDecisionForGate(fs, sealed.state, snapshot, parsed.answer, resolved, cost);
|
|
455
694
|
if (!preparedForGate.ok)
|
|
456
695
|
return preparedForGate.decision;
|
|
696
|
+
if (checkOnly)
|
|
697
|
+
return checked(state, resolved);
|
|
457
698
|
const settlement = recordSettlement(preparedForGate.state, parsed.answer, resolved, cost);
|
|
458
699
|
if (!settlement.ok)
|
|
459
700
|
return settlement.decision;
|
|
460
701
|
const granted = resolved.kind === "authorization" ? (resolved.authorization?.planned ?? []) : [];
|
|
461
|
-
|
|
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
|
+
}
|
|
462
717
|
// All pure scope/proposal checks have now passed. A durable decision is a
|
|
463
718
|
// material write, so it must never land when a later validation of the same
|
|
464
719
|
// answer would refuse the boundary and leave its cursor standing.
|
|
@@ -481,6 +736,29 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
481
736
|
outcome: selected,
|
|
482
737
|
})
|
|
483
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
|
+
}
|
|
484
762
|
if (registered?.ok && registered.reconciliation !== null) {
|
|
485
763
|
selectedState = withContinuation(selectedState, registered.reconciliation);
|
|
486
764
|
}
|
|
@@ -500,12 +778,160 @@ async function decide(fs, paths, state, input, snapshot) {
|
|
|
500
778
|
// cuya respuesta se tiraba —así que `Compactar` ahí volvía a una pregunta que
|
|
501
779
|
// ya nadie podía mostrar.
|
|
502
780
|
if (preview.preview !== null) {
|
|
503
|
-
selectedState = withFixPreview(selectedState,
|
|
781
|
+
selectedState = withFixPreview(selectedState, {
|
|
782
|
+
...preview.preview,
|
|
783
|
+
...(snapshot.quickCheckouts === null ? {} : { checkouts: snapshot.quickCheckouts }),
|
|
784
|
+
});
|
|
504
785
|
}
|
|
786
|
+
selectedState = withQuickCheckouts(selectedState, snapshot.quickCheckouts);
|
|
787
|
+
// A redraft recorded by `grantOf` inserted rows after this boundary, so the
|
|
788
|
+
// transition is applied against the journey as it now reads.
|
|
789
|
+
const walked = journeyForRun(selectedState);
|
|
505
790
|
const outcome = holds
|
|
506
|
-
? holdAfterApproval(selectedState,
|
|
507
|
-
: applyAndAdvance(selectedState,
|
|
508
|
-
|
|
791
|
+
? holdAfterApproval(selectedState, walked, identity)
|
|
792
|
+
: applyAndAdvance(selectedState, walked, resolved.stopped, identity, parsed.answer);
|
|
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
|
+
};
|
|
509
935
|
}
|
|
510
936
|
/**
|
|
511
937
|
* The declared fix preview, checked for the three things it has to say.
|
|
@@ -601,10 +1027,7 @@ function routeAnswer(state, stopped, journey, answer) {
|
|
|
601
1027
|
};
|
|
602
1028
|
}
|
|
603
1029
|
const candidate = answer.decisions.route;
|
|
604
|
-
if (!
|
|
605
|
-
!isRecord(candidate.summary) ||
|
|
606
|
-
!isRecord(candidate.basis) ||
|
|
607
|
-
!Array.isArray(candidate.controls)) {
|
|
1030
|
+
if (!routeShape(candidate)) {
|
|
608
1031
|
return {
|
|
609
1032
|
kind: "failure",
|
|
610
1033
|
failure: {
|
|
@@ -629,17 +1052,25 @@ function routeAnswer(state, stopped, journey, answer) {
|
|
|
629
1052
|
const declaredSummary = summary;
|
|
630
1053
|
const basis = candidate.basis;
|
|
631
1054
|
const basisFields = ["intention", "checkout", "conventions", "adopted_decisions"];
|
|
632
|
-
|
|
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) {
|
|
633
1064
|
return {
|
|
634
1065
|
kind: "failure",
|
|
635
1066
|
failure: {
|
|
636
1067
|
code: "FLOW_ROUTE_PROPOSAL_INVALID",
|
|
637
|
-
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]}`,
|
|
638
1069
|
action: "completá cada campo de decisions.route.basis con una observación concreta",
|
|
639
1070
|
},
|
|
640
1071
|
};
|
|
641
1072
|
}
|
|
642
|
-
const declaredBasis = basis;
|
|
1073
|
+
const declaredBasis = Object.fromEntries(basisFields.map((field) => [field, asText(basis[field])]));
|
|
643
1074
|
const configured = new Map(journey.flatMap((decision) => {
|
|
644
1075
|
const control = routeControlOf(decision);
|
|
645
1076
|
return control === null ? [] : [[decision.id, { decision, control }]];
|
|
@@ -679,17 +1110,28 @@ function routeAnswer(state, stopped, journey, answer) {
|
|
|
679
1110
|
},
|
|
680
1111
|
};
|
|
681
1112
|
}
|
|
1113
|
+
const { decision, control } = configuredControl;
|
|
1114
|
+
const consequence = control.consequences[raw.disposition];
|
|
1115
|
+
if (consequence === undefined) {
|
|
1116
|
+
return {
|
|
1117
|
+
kind: "failure",
|
|
1118
|
+
failure: {
|
|
1119
|
+
code: "FLOW_ROUTE_HARD_GATE",
|
|
1120
|
+
message: `'${raw.transition}' no admite '${raw.disposition}': el registro no deja que una ruta la saltee`,
|
|
1121
|
+
action: "declará apply o substitute para ese control; la evidencia que acredita el paso no se omite",
|
|
1122
|
+
},
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
682
1125
|
const substitution = routeSubstitution(raw.disposition, raw.substitution);
|
|
683
1126
|
if ("failure" in substitution)
|
|
684
1127
|
return { kind: "failure", failure: substitution.failure };
|
|
685
|
-
const { decision, control } = configuredControl;
|
|
686
1128
|
controls.push({
|
|
687
1129
|
transition: decision.id,
|
|
688
1130
|
title: decision.title,
|
|
689
1131
|
disposition: raw.disposition,
|
|
690
1132
|
recommendation: control.recommendation,
|
|
691
1133
|
alternatives: control.consequences,
|
|
692
|
-
consequence
|
|
1134
|
+
consequence,
|
|
693
1135
|
risk: control.risk,
|
|
694
1136
|
reason: raw.reason.trim(),
|
|
695
1137
|
substitution: substitution.value,
|
|
@@ -774,15 +1216,26 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
774
1216
|
if (!scopesSources(stopped))
|
|
775
1217
|
return { state };
|
|
776
1218
|
const asked = answer.decisions.sources;
|
|
777
|
-
const sources =
|
|
778
|
-
? 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)
|
|
779
1225
|
: null;
|
|
780
|
-
if (sources
|
|
1226
|
+
if (sources?.some((alias) => alias === null)) {
|
|
1227
|
+
const at = sources.findIndex((alias) => alias === null);
|
|
781
1228
|
return {
|
|
782
|
-
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"),
|
|
783
1230
|
};
|
|
784
1231
|
}
|
|
785
|
-
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) {
|
|
786
1239
|
return {
|
|
787
1240
|
failure: invalidScope("el scope declara la misma fuente dos veces", "una unidad por alias: declarás cada fuente una sola vez"),
|
|
788
1241
|
};
|
|
@@ -790,7 +1243,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
790
1243
|
const plan = snapshot.plan;
|
|
791
1244
|
if (plan === null || plan.length === 0) {
|
|
792
1245
|
return {
|
|
793
|
-
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)`),
|
|
794
1247
|
};
|
|
795
1248
|
}
|
|
796
1249
|
if (snapshot.plan_error !== null) {
|
|
@@ -803,7 +1256,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
803
1256
|
};
|
|
804
1257
|
}
|
|
805
1258
|
const declared = snapshot.declared;
|
|
806
|
-
if (declared === null &&
|
|
1259
|
+
if (declared === null && aliases.some((alias) => alias !== "workspace")) {
|
|
807
1260
|
return {
|
|
808
1261
|
failure: {
|
|
809
1262
|
code: "FLOW_SCOPE_UNKNOWN_SOURCE",
|
|
@@ -813,7 +1266,7 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
813
1266
|
};
|
|
814
1267
|
}
|
|
815
1268
|
const declaredAliases = declared ?? [];
|
|
816
|
-
const unknown =
|
|
1269
|
+
const unknown = aliases.filter((alias) => alias !== "workspace" && !declaredAliases.includes(alias));
|
|
817
1270
|
if (unknown.length > 0) {
|
|
818
1271
|
return {
|
|
819
1272
|
failure: {
|
|
@@ -839,12 +1292,19 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
839
1292
|
code: structural.code,
|
|
840
1293
|
message: structural.message,
|
|
841
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
|
+
})),
|
|
842
1302
|
},
|
|
843
1303
|
};
|
|
844
1304
|
}
|
|
845
1305
|
const declaredByPlan = snapshot.sources;
|
|
846
|
-
const missing = declaredByPlan.filter((alias) => !
|
|
847
|
-
const extra =
|
|
1306
|
+
const missing = declaredByPlan.filter((alias) => !aliases.includes(alias));
|
|
1307
|
+
const extra = aliases.filter((alias) => !declaredByPlan.includes(alias));
|
|
848
1308
|
if (missing.length > 0 || extra.length > 0) {
|
|
849
1309
|
const difference = [
|
|
850
1310
|
...(missing.length > 0 ? [`faltan ${missing.join(", ")}`] : []),
|
|
@@ -858,7 +1318,16 @@ function scopeFrom(state, stopped, answer, snapshot) {
|
|
|
858
1318
|
},
|
|
859
1319
|
};
|
|
860
1320
|
}
|
|
861
|
-
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
|
+
};
|
|
862
1331
|
}
|
|
863
1332
|
const SETTLEMENT_OUTCOMES = SETTLEMENT_READINGS.map((reading) => reading.outcome);
|
|
864
1333
|
/** The reading each label of the question stands for — from the row's own table. */
|
|
@@ -998,7 +1467,7 @@ function ratified(declaration, outcome) {
|
|
|
998
1467
|
: { note: declaration.note, index: declaration.index, outcome };
|
|
999
1468
|
}
|
|
1000
1469
|
function readDeclarations(value) {
|
|
1001
|
-
if (!
|
|
1470
|
+
if (!settlementList(value))
|
|
1002
1471
|
return null;
|
|
1003
1472
|
const out = [];
|
|
1004
1473
|
for (const entry of value) {
|
|
@@ -1063,6 +1532,7 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1063
1532
|
}, cost),
|
|
1064
1533
|
};
|
|
1065
1534
|
}
|
|
1535
|
+
const dated = withDecisionDate(state, state.decision_date ?? new Date().toISOString().slice(0, 10));
|
|
1066
1536
|
// The plan of the scope decides WHICH preparation this is. A standalone plan has
|
|
1067
1537
|
// no spec to compose against, so calling `preparePlanExecDecision` for it would
|
|
1068
1538
|
// refuse with `FLOW_DECISION_LINEAGE_INVALID` and leave the run with handoffs as
|
|
@@ -1070,7 +1540,7 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1070
1540
|
const standalone = await standaloneDecisionFor(fs, snapshot.root, plan, candidate);
|
|
1071
1541
|
if (standalone !== null) {
|
|
1072
1542
|
return standalone.ok
|
|
1073
|
-
? { ok: true, state: withDecisionPreparation(
|
|
1543
|
+
? { ok: true, state: withDecisionPreparation(dated, standalone.preparation) }
|
|
1074
1544
|
: {
|
|
1075
1545
|
ok: false,
|
|
1076
1546
|
decision: reject(state, resolved, standalone.failure.message, standalone.failure, cost),
|
|
@@ -1081,23 +1551,27 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1081
1551
|
session: state.session,
|
|
1082
1552
|
plan,
|
|
1083
1553
|
value: candidate,
|
|
1554
|
+
date: dated.decision_date ?? new Date().toISOString().slice(0, 10),
|
|
1084
1555
|
});
|
|
1085
1556
|
if (!prepared.ok) {
|
|
1557
|
+
const refused = reject(dated, resolveBoundary(dated, journeyForRun(dated)), prepared.failure.message, prepared.failure, cost);
|
|
1086
1558
|
return {
|
|
1087
1559
|
ok: false,
|
|
1088
|
-
decision:
|
|
1560
|
+
decision: refused.ok ? { ...refused, persist: true } : refused,
|
|
1089
1561
|
};
|
|
1090
1562
|
}
|
|
1091
1563
|
switch (prepared.kind) {
|
|
1092
1564
|
case "settled":
|
|
1093
1565
|
return {
|
|
1094
1566
|
ok: true,
|
|
1095
|
-
|
|
1567
|
+
...(prepared.warnings === undefined ? {} : { warnings: prepared.warnings }),
|
|
1568
|
+
state: withDecisionPreparation(dated, { kind: "settled", decision: prepared.decision }),
|
|
1096
1569
|
};
|
|
1097
1570
|
case "reused":
|
|
1098
1571
|
return {
|
|
1099
1572
|
ok: true,
|
|
1100
|
-
|
|
1573
|
+
...(prepared.warnings === undefined ? {} : { warnings: prepared.warnings }),
|
|
1574
|
+
state: withDecisionPreparation(dated, {
|
|
1101
1575
|
kind: "reused",
|
|
1102
1576
|
note: prepared.note,
|
|
1103
1577
|
decision: prepared.decision,
|
|
@@ -1107,7 +1581,8 @@ async function prepareDecisionForGate(fs, state, snapshot, answer, resolved, cos
|
|
|
1107
1581
|
case "prepared":
|
|
1108
1582
|
return {
|
|
1109
1583
|
ok: true,
|
|
1110
|
-
|
|
1584
|
+
...(prepared.warnings === undefined ? {} : { warnings: prepared.warnings }),
|
|
1585
|
+
state: withDecisionPreparation(dated, {
|
|
1111
1586
|
kind: "prepared",
|
|
1112
1587
|
note: prepared.prepared.note,
|
|
1113
1588
|
preview: prepared.prepared.preview,
|
|
@@ -1216,11 +1691,11 @@ async function registerSelectedDecision(fs, paths, state, snapshot, resolved, co
|
|
|
1216
1691
|
* run that merely advanced would be indistinguishable from one that never decided.
|
|
1217
1692
|
*/
|
|
1218
1693
|
function standaloneDecisionEvent(state, transition, record) {
|
|
1219
|
-
const
|
|
1694
|
+
const rowIteration = iterationOf(state, transition);
|
|
1220
1695
|
return {
|
|
1221
1696
|
kind: "executed",
|
|
1222
1697
|
transition,
|
|
1223
|
-
...
|
|
1698
|
+
...rowIteration,
|
|
1224
1699
|
operation: "plan-exec.standalone-decision",
|
|
1225
1700
|
summary: record.decision,
|
|
1226
1701
|
output_digest: record.digest,
|
|
@@ -1261,7 +1736,7 @@ function withStandaloneGuidance(decision, record) {
|
|
|
1261
1736
|
* cannot tell them apart, and neither could the table that decides the charge.
|
|
1262
1737
|
*/
|
|
1263
1738
|
function invalidScope(message, action) {
|
|
1264
|
-
return { code: "
|
|
1739
|
+
return { code: "FLOW_SCOPE_SHAPE_INVALID", message, action };
|
|
1265
1740
|
}
|
|
1266
1741
|
/**
|
|
1267
1742
|
* The closure-evidence gate, run BEFORE the preview is sealed.
|
|
@@ -1275,6 +1750,8 @@ function planEvidenceFrom(stopped, answer, observed) {
|
|
|
1275
1750
|
if (proposalContractOf(stopped) === null || answer.artifacts.length === 0)
|
|
1276
1751
|
return null;
|
|
1277
1752
|
const proposed = new Set(answer.artifacts.map((artifact) => artifact.path.trim()));
|
|
1753
|
+
const violations = [];
|
|
1754
|
+
let first = null;
|
|
1278
1755
|
for (const plan of observed) {
|
|
1279
1756
|
if (!proposed.has(plan.path))
|
|
1280
1757
|
continue;
|
|
@@ -1285,15 +1762,20 @@ function planEvidenceFrom(stopped, answer, observed) {
|
|
|
1285
1762
|
// should not learn of the header on the next attempt.
|
|
1286
1763
|
const lineage = plan.failures.find((candidate) => candidate !== failure && candidate.code === "PLAN_LINEAGE_UNSEALED");
|
|
1287
1764
|
const also = lineage === undefined ? "" : ` · además: ${lineage.message}`;
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
action: planBoundaryAction(failure.code, "proposal"),
|
|
1293
|
-
},
|
|
1765
|
+
first ??= {
|
|
1766
|
+
code: failure.code,
|
|
1767
|
+
message: `'${plan.path}': ${failure.message}${also}`,
|
|
1768
|
+
action: planBoundaryAction(failure.code, "proposal"),
|
|
1294
1769
|
};
|
|
1295
|
-
|
|
1296
|
-
|
|
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 } };
|
|
1297
1779
|
}
|
|
1298
1780
|
/**
|
|
1299
1781
|
* Seat the proposal an authoring answer just handed over — sealed, not believed.
|
|
@@ -1396,6 +1878,26 @@ function sealFrom(state, stopped, answer, snapshot, baselines) {
|
|
|
1396
1878
|
})),
|
|
1397
1879
|
};
|
|
1398
1880
|
}
|
|
1881
|
+
/**
|
|
1882
|
+
* The redraft a confirmation row's alternative asked for, or `null`.
|
|
1883
|
+
*
|
|
1884
|
+
* It unseats any standing proposal and records a reentry from the flow's
|
|
1885
|
+
* authoring row, so the run walks the redraft and its gate again instead of
|
|
1886
|
+
* carrying on to a publication with nothing to publish and a finalize that
|
|
1887
|
+
* closes the session. Independent of whether a proposal is seated: a proposal
|
|
1888
|
+
* the run gave up on is exactly when redrafting matters most.
|
|
1889
|
+
*/
|
|
1890
|
+
function redraftOf(state, stopped, answer, journey) {
|
|
1891
|
+
const redraft = reentryOf(stopped);
|
|
1892
|
+
if (redraft === null || answer.choice !== redraft.label)
|
|
1893
|
+
return null;
|
|
1894
|
+
return withReentry(withProposal(state, null), {
|
|
1895
|
+
kind: "refine",
|
|
1896
|
+
transition: stopped.id,
|
|
1897
|
+
occurrence: occurrenceAt(journey, state.applied.length),
|
|
1898
|
+
from: redraft.from,
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1399
1901
|
/**
|
|
1400
1902
|
* The grant this answer produced, scoped to what it was given over.
|
|
1401
1903
|
*
|
|
@@ -1410,8 +1912,13 @@ function sealFrom(state, stopped, answer, snapshot, baselines) {
|
|
|
1410
1912
|
*/
|
|
1411
1913
|
function grantOf(state, resolved, answer, granted, journey) {
|
|
1412
1914
|
const stopped = resolved.stopped;
|
|
1915
|
+
if (stopped === null)
|
|
1916
|
+
return state;
|
|
1917
|
+
const redrafted = redraftOf(state, stopped, answer, journey);
|
|
1918
|
+
if (redrafted !== null)
|
|
1919
|
+
return redrafted;
|
|
1413
1920
|
const proposal = state.proposal;
|
|
1414
|
-
const approve =
|
|
1921
|
+
const approve = publishApprovalOf(stopped);
|
|
1415
1922
|
if (approve !== null && proposal !== null) {
|
|
1416
1923
|
if (answer.choice === approve) {
|
|
1417
1924
|
return withApproval(state, {
|
|
@@ -1420,13 +1927,11 @@ function grantOf(state, resolved, answer, granted, journey) {
|
|
|
1420
1927
|
classes: [...proposal.requires_approval],
|
|
1421
1928
|
});
|
|
1422
1929
|
}
|
|
1423
|
-
// Declining unseats the proposal
|
|
1424
|
-
// nothing: the publication downstream finds nothing to publish and is skipped
|
|
1425
|
-
// saying so, instead of stopping to ask for an authorization over bytes the
|
|
1930
|
+
// Declining unseats the proposal: no authorization is asked over bytes the
|
|
1426
1931
|
// person just turned down.
|
|
1427
1932
|
return withProposal(state, null);
|
|
1428
1933
|
}
|
|
1429
|
-
const link =
|
|
1934
|
+
const link = approvalGrantOf(stopped);
|
|
1430
1935
|
if (link !== null && answer.choice === link.approve) {
|
|
1431
1936
|
const target = journey.find((row) => row.id === link.transition);
|
|
1432
1937
|
// A link to a transition this journey does not walk grants nothing: the
|
|
@@ -1457,7 +1962,7 @@ function grantOf(state, resolved, answer, granted, journey) {
|
|
|
1457
1962
|
* a payload the boundary's own contract rejects, a decline (a real answer that
|
|
1458
1963
|
* applies nothing), and an execution result that did not earn its transition.
|
|
1459
1964
|
*/
|
|
1460
|
-
function admit(state, resolved, stopped, input, cost, checkouts) {
|
|
1965
|
+
function admit(state, resolved, stopped, input, cost, checkouts, scoped, preexisting, validationOnlyCurrent) {
|
|
1461
1966
|
const expectedApproval = resolved.kind === "authorization"
|
|
1462
1967
|
? effectApprovalDigest(stopped.id, resolved.authorization?.planned ?? [])
|
|
1463
1968
|
: null;
|
|
@@ -1491,14 +1996,11 @@ function admit(state, resolved, stopped, input, cost, checkouts) {
|
|
|
1491
1996
|
// consults. A resend, a decline and a pause are not failed tries either, and
|
|
1492
1997
|
// they are classified there rather than being exempted here.
|
|
1493
1998
|
return {
|
|
1494
|
-
decision: reject(state, resolved, parsed.failure.message,
|
|
1999
|
+
decision: reject(state, resolved, parsed.failure.message, parsed.failure, cost),
|
|
1495
2000
|
};
|
|
1496
2001
|
}
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
// stopping ends the run here (`cancelled`), pausing keeps the very same
|
|
1500
|
-
// boundary standing so the run picks it up after compacting (`needs_input`).
|
|
1501
|
-
// 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.
|
|
1502
2004
|
if (parsed.answer.choice === PAUSE_LABEL) {
|
|
1503
2005
|
return {
|
|
1504
2006
|
decision: reject(state, resolved, `'${PAUSE_LABEL}': la frontera queda en pie y la corrida retoma acá`, {
|
|
@@ -1509,33 +2011,80 @@ function admit(state, resolved, stopped, input, cost, checkouts) {
|
|
|
1509
2011
|
};
|
|
1510
2012
|
}
|
|
1511
2013
|
if (parsed.answer.choice === STOP_LABEL) {
|
|
1512
|
-
return {
|
|
1513
|
-
decision: reject(state, resolved, `'${STOP_LABEL}': el recorrido queda detenido en esta frontera`, {
|
|
1514
|
-
code: "FLOW_BOUNDARY_DECLINED",
|
|
1515
|
-
action: "reanudá con 'aw flow advance' cuando quieras retomar esta frontera",
|
|
1516
|
-
outcome: "cancelled",
|
|
1517
|
-
}, cost),
|
|
1518
|
-
};
|
|
2014
|
+
return { decision: closeFromAnswer(state) };
|
|
1519
2015
|
}
|
|
1520
2016
|
// An execution result has to EARN the transition. Anything short of a completed
|
|
1521
2017
|
// run with its evidence and its whole effect keeps the boundary standing: the
|
|
1522
2018
|
// work stays pending, with the recovery the action declared.
|
|
1523
|
-
if (resolved.kind
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
2019
|
+
if (resolved.kind !== "execution")
|
|
2020
|
+
return parsed;
|
|
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
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
/** The execution verdict, then — at the phase validation — the batch's own credit. */
|
|
2040
|
+
function earned(state, resolved, stopped, parsed, cost, checkouts, scoped, preexisting, validationOnlyCurrent) {
|
|
2041
|
+
// The row's `effects` are the ceiling and the sealed proposal is what really
|
|
2042
|
+
// happens: demanding an overwrite from a publication that only creates files
|
|
2043
|
+
// would leave the transition pending for an effect nobody could produce.
|
|
2044
|
+
const verdict = executionVerdict(parsed.answer.result, resolved.action, effectsOfTransition(state, stopped), checkouts, preexisting);
|
|
2045
|
+
if (verdict !== null) {
|
|
2046
|
+
const trace = declaredTrace(state, stopped, resolved, parsed.answer, verdict);
|
|
2047
|
+
return {
|
|
2048
|
+
decision: reject(state, resolved, verdict.message, verdict.detail, {
|
|
2049
|
+
...cost,
|
|
2050
|
+
...(trace === null ? {} : { trace }),
|
|
2051
|
+
}),
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
const credited = batchCredit(state, stopped, parsed.answer, resolved, scoped, validationOnlyCurrent);
|
|
2055
|
+
if (credited === null)
|
|
2056
|
+
return parsed;
|
|
2057
|
+
if (!credited.ok) {
|
|
2058
|
+
return {
|
|
2059
|
+
decision: reject(state, resolved, credited.refusal.message, credited.refusal.detail, cost),
|
|
2060
|
+
};
|
|
1537
2061
|
}
|
|
1538
|
-
return parsed;
|
|
2062
|
+
return { ...parsed, credit: credited.credit };
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* The phase validation's own judgment: the batch it closes must bring a proof of
|
|
2066
|
+
* each of its sources, of its own checkout. `null` on every other boundary, and
|
|
2067
|
+
* on a run with no batch standing, where there is nothing to credit.
|
|
2068
|
+
*/
|
|
2069
|
+
function batchCredit(state, stopped, answer, resolved, scoped, validationOnlyCurrent) {
|
|
2070
|
+
if (stopped.id !== PHASE_VALIDATION || answer.result === null || state.scope === null) {
|
|
2071
|
+
return null;
|
|
2072
|
+
}
|
|
2073
|
+
const iteration = state.batch_loop?.iteration ?? null;
|
|
2074
|
+
const batches = state.batches ?? [];
|
|
2075
|
+
const batch = batches.find((entry) => entry.iteration === iteration);
|
|
2076
|
+
if (batch === undefined)
|
|
2077
|
+
return null;
|
|
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 ?? "");
|
|
2085
|
+
if (!verdict.ok)
|
|
2086
|
+
return verdict;
|
|
2087
|
+
return { ok: true, credit: { batch: batch.id, proofs: verdict.credit } };
|
|
1539
2088
|
}
|
|
1540
2089
|
/**
|
|
1541
2090
|
* The material fact a REFUSED external result still leaves behind — or `null`.
|
|
@@ -1557,11 +2106,11 @@ function declaredTrace(state, stopped, resolved, answer, verdict) {
|
|
|
1557
2106
|
if (!touchesTheWorld(applied) && verdict.detail.code !== "FLOW_EFFECT_PARTIAL")
|
|
1558
2107
|
return null;
|
|
1559
2108
|
const invocation = resolved.action?.invocation;
|
|
1560
|
-
const
|
|
2109
|
+
const rowIteration = iterationOf(state, stopped.id);
|
|
1561
2110
|
return {
|
|
1562
2111
|
kind: "failed",
|
|
1563
2112
|
transition: stopped.id,
|
|
1564
|
-
...
|
|
2113
|
+
...rowIteration,
|
|
1565
2114
|
// What was really run, named the way whoever ran it would recognize it: an
|
|
1566
2115
|
// external execution has no internal operation id to quote.
|
|
1567
2116
|
operation: invocation === undefined ? stopped.id : [invocation.program, ...invocation.args].join(" "),
|
|
@@ -1604,7 +2153,11 @@ function actionDrift(state, resolved, cost) {
|
|
|
1604
2153
|
* was written" are different facts, and only the second one advances a run.
|
|
1605
2154
|
*/
|
|
1606
2155
|
function holdAfterApproval(approved, journey, identity) {
|
|
1607
|
-
const held = advanceFlowRun({
|
|
2156
|
+
const held = advanceFlowRun({
|
|
2157
|
+
state: withAttempt(approved, { ...identity, approval: true }),
|
|
2158
|
+
journey,
|
|
2159
|
+
applied: [],
|
|
2160
|
+
});
|
|
1608
2161
|
if (!held.ok)
|
|
1609
2162
|
return { ok: false, failure: held.failure };
|
|
1610
2163
|
return { ok: true, state: held.state, value: { directive: held.directive, advanced: true } };
|
|
@@ -1617,14 +2170,10 @@ function applyAndAdvance(approved, journey, stopped, identity, answer) {
|
|
|
1617
2170
|
let next = answer.signals.length > 0
|
|
1618
2171
|
? withObservation(approved, observationFor(approved, stopped.id, answer.signals))
|
|
1619
2172
|
: approved;
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
// path — no internal executor, so the caller ran the write and returned its
|
|
1623
|
-
// result — and leaving the proposal seated would keep a preview of bytes that
|
|
1624
|
-
// are already on disk standing in front of the next boundary.
|
|
1625
|
-
if (internalActionOf(stopped)?.operation === "proposal.publish") {
|
|
1626
|
-
next = withProposal(next, null);
|
|
2173
|
+
if (stopped.answer_contract === "batch-review" && isBatchReview(answer.decisions.review)) {
|
|
2174
|
+
next = withPlanExecBatchReview(next, answer.decisions.review);
|
|
1627
2175
|
}
|
|
2176
|
+
next = withPlanExecBatchStageForTransition(applyTransition(next, stopped.id, effectsOfTransition(next, stopped)), stopped.id);
|
|
1628
2177
|
next = withAttempt(next, identity);
|
|
1629
2178
|
// The boundary follows the position, always: handing the engine a state whose
|
|
1630
2179
|
// boundary still names the transition just applied is exactly the incoherence
|
|
@@ -1677,15 +2226,8 @@ function choiceOutcomeOf(stopped, label) {
|
|
|
1677
2226
|
* the state. A retry is recognized by the attempt ledger, so it cannot create a
|
|
1678
2227
|
* second package or walk the batch toward a commit.
|
|
1679
2228
|
*/
|
|
1680
|
-
function applyAndHandoff(state, journey, stopped, identity, answer, outcome, specPath) {
|
|
1681
|
-
const plan = state.scope?.plan ?? null;
|
|
1682
|
-
if (outcome.destination === "plan-refine" && plan === null) {
|
|
1683
|
-
const resolved = resolveBoundary(state, journey);
|
|
1684
|
-
return reject(state, resolved, "la entrega a plan-refine no tiene el plan que debe reabrir", {
|
|
1685
|
-
code: "FLOW_HANDOFF_PLAN_MISSING",
|
|
1686
|
-
action: "fijá el scope de plan-exec antes de elegir una escalación a plan-refine",
|
|
1687
|
-
}, { journey, identity });
|
|
1688
|
-
}
|
|
2229
|
+
function applyAndHandoff(state, journey, stopped, identity, answer, outcome, specPath, request) {
|
|
2230
|
+
const plan = state.scope?.plan ?? state.plan_exec_entry?.plan ?? null;
|
|
1689
2231
|
const fixPreview = state.fix_preview ?? null;
|
|
1690
2232
|
const packageBody = {
|
|
1691
2233
|
plan,
|
|
@@ -1695,7 +2237,11 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1695
2237
|
// declared preview travels with it — the destination is the one place that
|
|
1696
2238
|
// rewrites it, and rediscovering it there is exactly what the package exists
|
|
1697
2239
|
// to avoid.
|
|
1698
|
-
decisions:
|
|
2240
|
+
decisions: {
|
|
2241
|
+
...answer.decisions,
|
|
2242
|
+
...(fixPreview === null ? {} : { preview: fixPreview }),
|
|
2243
|
+
...(request === null ? {} : { request }),
|
|
2244
|
+
},
|
|
1699
2245
|
selection: answer.choice ?? "",
|
|
1700
2246
|
};
|
|
1701
2247
|
// The handoff to `spec-refine` used to be the bare command while the one to
|
|
@@ -1703,13 +2249,7 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1703
2249
|
// spec again — the escalation package exists precisely so nothing is
|
|
1704
2250
|
// re-derived at the destination. An unreadable plan degrades to the bare
|
|
1705
2251
|
// command: an escalation is never blocked over the convenience of an argument.
|
|
1706
|
-
const command = outcome.destination
|
|
1707
|
-
? `/w:plan-refine ${plan}`
|
|
1708
|
-
: outcome.destination === "spec-refine"
|
|
1709
|
-
? specPath === null
|
|
1710
|
-
? "/w:spec-refine"
|
|
1711
|
-
: `/w:spec-refine ${specPath}`
|
|
1712
|
-
: "/w:spec-new";
|
|
2252
|
+
const command = handoffCommand(outcome.destination, plan, specPath);
|
|
1713
2253
|
let next = withSelectedChoice(state, {
|
|
1714
2254
|
transition: stopped.id,
|
|
1715
2255
|
label: answer.choice ?? "",
|
|
@@ -1717,14 +2257,26 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1717
2257
|
});
|
|
1718
2258
|
next = withPlanExecBatchStageForTransition(applyTransition(next, stopped.id, effectsOfTransition(next, stopped)), stopped.id);
|
|
1719
2259
|
next = withAttempt(next, identity);
|
|
1720
|
-
next = withHandoff(next,
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
+
});
|
|
1726
2268
|
next = withBoundary(next, journey[next.applied.length]?.id ?? null);
|
|
1727
|
-
|
|
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
|
+
});
|
|
1728
2280
|
if (!advanced.ok)
|
|
1729
2281
|
return { ok: false, failure: advanced.failure };
|
|
1730
2282
|
return {
|
|
@@ -1733,6 +2285,13 @@ function applyAndHandoff(state, journey, stopped, identity, answer, outcome, spe
|
|
|
1733
2285
|
value: { directive: advanced.directive, advanced: true },
|
|
1734
2286
|
};
|
|
1735
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
|
+
}
|
|
1736
2295
|
/**
|
|
1737
2296
|
* Whether this submission is a resend, judged by the ledger that already knows
|
|
1738
2297
|
* how.
|
|
@@ -1764,11 +2323,11 @@ function resendCheck(state, resolved, cost) {
|
|
|
1764
2323
|
for (const past of state.attempts) {
|
|
1765
2324
|
const replay = ledger.record(past);
|
|
1766
2325
|
if (!replay.ok)
|
|
1767
|
-
return
|
|
2326
|
+
return reject(state, resolved, replay.failure.message, replay.failure);
|
|
1768
2327
|
}
|
|
1769
2328
|
const verdict = ledger.record(identity);
|
|
1770
2329
|
if (!verdict.ok)
|
|
1771
|
-
return
|
|
2330
|
+
return reject(state, resolved, verdict.failure.message, verdict.failure);
|
|
1772
2331
|
if (verdict.kind === "new")
|
|
1773
2332
|
return null;
|
|
1774
2333
|
// The TWIN's transition, not this submission's: the identity is built over the
|
|
@@ -1776,10 +2335,9 @@ function resendCheck(state, resolved, cost) {
|
|
|
1776
2335
|
// What is being asked is whether the step the twin answered ever moved.
|
|
1777
2336
|
const twin = state.attempts.find((past) => past.invocation_id === identity.invocation_id &&
|
|
1778
2337
|
past.request_digest === identity.request_digest);
|
|
1779
|
-
//
|
|
1780
|
-
//
|
|
1781
|
-
|
|
1782
|
-
if (twin !== undefined && !state.applied.includes(twin.transition))
|
|
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.
|
|
2340
|
+
if (twin !== undefined && twinWasRefused(state, twin))
|
|
1783
2341
|
return null;
|
|
1784
2342
|
return reject(state, resolved, "esta respuesta ya se había aplicado: el recorrido no avanza dos veces", {
|
|
1785
2343
|
code: "FLOW_ANSWER_RESENT",
|
|
@@ -1787,6 +2345,19 @@ function resendCheck(state, resolved, cost) {
|
|
|
1787
2345
|
outcome: "completed",
|
|
1788
2346
|
}, cost);
|
|
1789
2347
|
}
|
|
2348
|
+
/**
|
|
2349
|
+
* Whether the attempt a resend matches never advanced anything.
|
|
2350
|
+
*
|
|
2351
|
+
* Its transition missing from `applied` says so. So does its transition being
|
|
2352
|
+
* the boundary still standing at the same iteration: a repeated row (a batch or
|
|
2353
|
+
* a redraft copy) has its id in `applied` from an earlier walk, and that earlier
|
|
2354
|
+
* walk is not this one.
|
|
2355
|
+
*/
|
|
2356
|
+
function twinWasRefused(state, twin) {
|
|
2357
|
+
if (!state.applied.includes(twin.transition))
|
|
2358
|
+
return true;
|
|
2359
|
+
return (twin.transition === state.boundary && sameIteration(twin, iterationOf(state, twin.transition)));
|
|
2360
|
+
}
|
|
1790
2361
|
/**
|
|
1791
2362
|
* A rejection, expressed as the recalculated directive.
|
|
1792
2363
|
*
|
|
@@ -1797,8 +2368,8 @@ function resendCheck(state, resolved, cost) {
|
|
|
1797
2368
|
* recoverable — an unbounded loop inside the very mechanism that exists to bound
|
|
1798
2369
|
* one. One place decides, one table says which codes count.
|
|
1799
2370
|
*
|
|
1800
|
-
* `cost` is absent
|
|
1801
|
-
*
|
|
2371
|
+
* `cost` is also absent for mechanical controls and replay failures, which never
|
|
2372
|
+
* charge a boundary even when it is still open.
|
|
1802
2373
|
*/
|
|
1803
2374
|
function reject(state, resolved, message, detail, cost) {
|
|
1804
2375
|
// A refused answer is the one rejection that CHANGES the run: the attempt is
|
|
@@ -1809,14 +2380,20 @@ function reject(state, resolved, message, detail, cost) {
|
|
|
1809
2380
|
const traced = cost?.trace === undefined || restatesLastEvent(state, cost.trace)
|
|
1810
2381
|
? state
|
|
1811
2382
|
: withEvent(state, cost.trace);
|
|
1812
|
-
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;
|
|
1813
2390
|
const now = cost === undefined || after === state ? resolved : resolveBoundary(after, cost.journey);
|
|
1814
2391
|
const built = directiveFor(after, now, [], {
|
|
1815
2392
|
// No outcome named ⇒ the boundary decides it: a finished journey reports
|
|
1816
2393
|
// `completed`, an open one `needs_input`. Hardcoding one here would let a
|
|
1817
2394
|
// rejection at the end of a run claim work is still pending.
|
|
1818
2395
|
...(detail.outcome === undefined ? {} : { outcome: detail.outcome }),
|
|
1819
|
-
nextAction: `${message} — ${now.error?.action ?? detail.action}`,
|
|
2396
|
+
nextAction: `${message} — ${detail.code === "FLOW_BOUNDARY_PAUSED" ? detail.action : (now.error?.action ?? detail.action)}`,
|
|
1820
2397
|
});
|
|
1821
2398
|
if (!built.ok)
|
|
1822
2399
|
return { ok: false, failure: built.failure };
|
|
@@ -1826,7 +2403,12 @@ function reject(state, resolved, message, detail, cost) {
|
|
|
1826
2403
|
// longer a way forward, and reporting the payload's error would suggest it is.
|
|
1827
2404
|
const directive = {
|
|
1828
2405
|
...built.directive,
|
|
1829
|
-
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
|
+
},
|
|
1830
2412
|
};
|
|
1831
2413
|
return {
|
|
1832
2414
|
ok: true,
|
|
@@ -1848,17 +2430,12 @@ function reject(state, resolved, message, detail, cost) {
|
|
|
1848
2430
|
*/
|
|
1849
2431
|
function attemptIdentity(state, input, seal, transition) {
|
|
1850
2432
|
const digest = semanticDigest({ payload: input.raw, approval: input.approval });
|
|
1851
|
-
const
|
|
2433
|
+
const rowIteration = iterationOf(state, transition);
|
|
1852
2434
|
const prior = state.attempts.filter((past) => past.invocation_id === seal);
|
|
1853
2435
|
const twin = prior.find((past) => past.request_digest === digest);
|
|
1854
|
-
//
|
|
1855
|
-
//
|
|
1856
|
-
//
|
|
1857
|
-
// re-evaluated and its row kept wearing the twin's ordinal — deliberately, so
|
|
1858
|
-
// it still counts toward the cap — so the rows say two while the highest
|
|
1859
|
-
// ordinal says one, and the next answer arrives as attempt 3 where the ledger
|
|
1860
|
-
// accepts only 2. Nobody can answer that boundary, and `recover` reads the
|
|
1861
|
-
// 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.
|
|
1862
2439
|
const highest = Math.max(0, ...prior.map((past) => past.attempt));
|
|
1863
2440
|
const attempt = twin?.attempt ?? highest + 1;
|
|
1864
2441
|
const parent = attempt === 1
|
|
@@ -1872,18 +2449,18 @@ function attemptIdentity(state, input, seal, transition) {
|
|
|
1872
2449
|
// The seal moves with the state; the transition does not while the run
|
|
1873
2450
|
// stands there. It is what the cap counts over.
|
|
1874
2451
|
transition,
|
|
1875
|
-
...
|
|
2452
|
+
...rowIteration,
|
|
1876
2453
|
};
|
|
1877
2454
|
}
|
|
1878
2455
|
function isRecord(value) {
|
|
1879
2456
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1880
2457
|
}
|
|
1881
2458
|
function observationFor(state, transition, signals) {
|
|
1882
|
-
const
|
|
2459
|
+
const rowIteration = iterationOf(state, transition);
|
|
1883
2460
|
return {
|
|
1884
2461
|
transition,
|
|
1885
2462
|
signals,
|
|
1886
|
-
...
|
|
2463
|
+
...rowIteration,
|
|
1887
2464
|
};
|
|
1888
2465
|
}
|
|
1889
2466
|
//# sourceMappingURL=submit.js.map
|