akm-cli 0.9.16 → 0.9.17-alpha.10
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 +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -2,28 +2,27 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* Engine-driven workflow execution — the `akm workflow run`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* every exit path so no child process keeps the event loop open.
|
|
13
|
-
*
|
|
14
|
-
* See docs/architecture/decisions/0011-engine-run-loop-invariants.md for the
|
|
15
|
-
* full design history behind each of these invariants.
|
|
5
|
+
* Engine-driven workflow execution — the `akm workflow run` start/resume/
|
|
6
|
+
* execute path. akm walks the frozen plan (read from `plan_json`, never live
|
|
7
|
+
* source) and dispatches every unit itself; every step advances through
|
|
8
|
+
* `completeWorkflowStep`; one O_EXCL lock file per run keeps a second driver
|
|
9
|
+
* off (a dead holder's lock is reclaimed); gate loops are bounded; and the
|
|
10
|
+
* SDK dispatch registry is drained on every exit so the process can exit.
|
|
11
|
+
* See docs/architecture/decisions/0011-engine-run-loop-invariants.md.
|
|
16
12
|
*/
|
|
17
|
-
import {
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
18
16
|
import { TransientError, UsageError } from "../../core/errors.js";
|
|
19
|
-
import {
|
|
17
|
+
import { releaseLock } from "../../core/file-lock.js";
|
|
18
|
+
import { formatLockHolderPid, tryAcquireRunLock } from "../../core/run-lock.js";
|
|
20
19
|
import { disposeDispatchResources } from "../../integrations/agent/runner-dispatch.js";
|
|
20
|
+
import { resolveStorageLocations } from "../../storage/locations.js";
|
|
21
21
|
import { withWorkflowRunsConnection, withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
|
|
22
|
-
import { assertRunParamsSatisfyPlan } from "../ir/params.js";
|
|
23
22
|
import { computePlanHash } from "../ir/plan-hash.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
23
|
+
import { decodeWorkflowPlan, readRunPlan } from "../runtime/run-plan.js";
|
|
24
|
+
import { abandonWorkflowRun, completeWorkflowStep, getNextWorkflowStep, resumeWorkflowRun, } from "../runtime/runs.js";
|
|
25
|
+
import { loadWorkflowAsset } from "../runtime/workflow-asset-loader.js";
|
|
27
26
|
import { frozenSummaryJudge } from "./frozen-judge.js";
|
|
28
27
|
import { mergeLoweringNotices } from "./lowering-notices.js";
|
|
29
28
|
import { defaultUnitDispatcher, executeStepPlan, } from "./native-executor.js";
|
|
@@ -62,7 +61,13 @@ export async function runWorkflowSteps(options) {
|
|
|
62
61
|
stepsProcessed += result.stepsProcessed;
|
|
63
62
|
const notices = mergeLoweringNotices(...executed.map((step) => step.notices));
|
|
64
63
|
const aggregate = { ...result, executed, stepsProcessed, ...(notices ? { notices } : {}) };
|
|
65
|
-
|
|
64
|
+
// An attempt that executed nothing (an abandoned, undecodable plan) has no
|
|
65
|
+
// failed step for a retry to re-open.
|
|
66
|
+
if (result.run.status !== "failed" ||
|
|
67
|
+
result.aborted ||
|
|
68
|
+
result.gateRejection ||
|
|
69
|
+
result.executed.length === 0 ||
|
|
70
|
+
remainingRetries <= 0) {
|
|
66
71
|
return aggregate;
|
|
67
72
|
}
|
|
68
73
|
if (remainingSteps !== undefined) {
|
|
@@ -86,16 +91,6 @@ async function runWorkflowAttempt(options, liveEvidence) {
|
|
|
86
91
|
parameterFlags: options.parameterFlags,
|
|
87
92
|
newRun: options.newRun,
|
|
88
93
|
});
|
|
89
|
-
// Version/canonical/hash validation precedes every executable mutation,
|
|
90
|
-
// including lease acquisition. Historical rows remain inspectable/abandonable.
|
|
91
|
-
if (!next.done) {
|
|
92
|
-
await withWorkflowRunsRepo((repo) => {
|
|
93
|
-
const row = repo.getRunById(next.run.id);
|
|
94
|
-
if (!row)
|
|
95
|
-
throw new UsageError(`Workflow run ${next.run.id} was not found.`);
|
|
96
|
-
requireExecutableWorkflowPlan(row);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
94
|
// Refuse non-active runs BEFORE any dispatch — completeWorkflowStep would
|
|
100
95
|
// reject the completion anyway, but only after the units already ran (and
|
|
101
96
|
// cost money). Mirror its preflight up front.
|
|
@@ -103,70 +98,68 @@ async function runWorkflowAttempt(options, liveEvidence) {
|
|
|
103
98
|
throw new UsageError(`Workflow run ${next.run.id} is ${next.run.status} and cannot be executed. ` +
|
|
104
99
|
`Use \`akm workflow resume ${next.run.id}\` to reopen it first.`);
|
|
105
100
|
}
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
// between steps inside the loop. A done run takes no lease: nothing will
|
|
111
|
-
// dispatch, and the status re-read below must stay a pure no-op.
|
|
101
|
+
// One driver per run: the per-run lock file is taken BEFORE the plan is
|
|
102
|
+
// read or anything dispatches, so a second `akm workflow run` on a run
|
|
103
|
+
// another process is driving refuses up front (exit 75) instead of racing
|
|
104
|
+
// its spine. A done run takes no lock: nothing will dispatch.
|
|
112
105
|
const runId = next.run.id;
|
|
113
|
-
const
|
|
114
|
-
const leased = !next.done;
|
|
115
|
-
if (leased) {
|
|
116
|
-
await acquireRunLease(runId, leaseHolder);
|
|
117
|
-
}
|
|
118
|
-
// Lease heartbeat (P1 fix): the lease TTL is renewed BETWEEN steps, but a
|
|
119
|
-
// single unit's dispatch can outlive the TTL (the default unit timeout is 10
|
|
120
|
-
// minutes, > the 90s lease). An unheartbeated lease would silently expire
|
|
121
|
-
// mid-dispatch, letting a second `akm workflow run` claim the run and
|
|
122
|
-
// re-dispatch the same units — the two engines clobber each other's journal
|
|
123
|
-
// rows and double-run side effects. A timer INSIDE this invocation renews the
|
|
124
|
-
// lease while dispatch is in flight; it is cleared in the `finally`, so it
|
|
125
|
-
// dies with the process — exactly when the lease SHOULD become claimable
|
|
126
|
-
// after TTL. A renewal that fails (the lease was genuinely stolen after an
|
|
127
|
-
// expiry, e.g. the process was suspended) aborts dispatch and fails the run
|
|
128
|
-
// loudly rather than keep double-driving.
|
|
129
|
-
const heartbeat = leased
|
|
130
|
-
? new LeaseHeartbeat(runId, leaseHolder, options.heartbeatScheduler, options.signal)
|
|
131
|
-
: undefined;
|
|
132
|
-
heartbeat?.start();
|
|
106
|
+
const lock = next.done ? undefined : acquireWorkflowRunLock(runId);
|
|
133
107
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
108
|
+
let plan;
|
|
109
|
+
const warnings = [...(next.startWarnings ?? [])];
|
|
110
|
+
if (!next.done) {
|
|
111
|
+
const row = await withWorkflowRunsRepo((repo) => repo.getRunById(runId));
|
|
112
|
+
if (!row)
|
|
113
|
+
throw new UsageError(`Workflow run ${runId} was not found.`);
|
|
114
|
+
const read = readRunPlan(row);
|
|
115
|
+
if (!read.ok) {
|
|
116
|
+
// A newer akm's run is left untouched for that akm.
|
|
117
|
+
if (read.newer)
|
|
118
|
+
throw new UsageError(read.problem);
|
|
119
|
+
// Otherwise a frozen plan this akm cannot decode is a status change,
|
|
120
|
+
// not an exception: the run is abandoned and the message names how to
|
|
121
|
+
// start afresh.
|
|
122
|
+
const abandoned = await abandonWorkflowRun(runId);
|
|
123
|
+
const message = `${read.problem} The run was abandoned; start a new run with 'akm workflow run ${row.workflow_ref}'.`;
|
|
124
|
+
return {
|
|
125
|
+
run: abandoned.run,
|
|
126
|
+
executed: [],
|
|
127
|
+
stepsProcessed: 0,
|
|
128
|
+
warnings: [...warnings, message],
|
|
129
|
+
...(next.resumed ? { resumed: true } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
plan = read.plan;
|
|
133
|
+
if (!next.autoStarted) {
|
|
134
|
+
const drift = await workflowSourceDriftWarning(runId, row.workflow_ref, plan);
|
|
135
|
+
if (drift)
|
|
136
|
+
warnings.push(drift);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// One state.db connection scope for the whole drive loop (the per-step
|
|
140
|
+
// scopes nest into it); each `openStateDatabase` is not free.
|
|
141
|
+
const result = await withWorkflowRunsConnection(() => driveRun(options, next, plan, liveEvidence));
|
|
142
|
+
// Creation/resume-time notices reach the caller only here: the run row
|
|
143
|
+
// has no warnings column, and a later invocation of the same run must
|
|
144
|
+
// stay silent about a decision it did not make. `driveRun` never sets
|
|
145
|
+
// `warnings` or `resumed` — both are properties of THIS resolution of
|
|
146
|
+
// `target`, not of the run row (#919).
|
|
150
147
|
return {
|
|
151
148
|
...result,
|
|
152
149
|
...(next.resumed ? { resumed: true } : {}),
|
|
153
|
-
...(
|
|
150
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
154
151
|
};
|
|
155
152
|
}
|
|
156
153
|
finally {
|
|
157
|
-
heartbeat?.stop();
|
|
158
154
|
try {
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
repo.releaseEngineLease(runId, leaseHolder);
|
|
162
|
-
});
|
|
163
|
-
}
|
|
155
|
+
if (lock)
|
|
156
|
+
releaseLock(lock);
|
|
164
157
|
}
|
|
165
158
|
finally {
|
|
166
159
|
// Process-lifecycle drain (owner finding 4): release any cached SDK server
|
|
167
160
|
// child processes so a one-shot CLI invocation exits cleanly instead of
|
|
168
|
-
// hanging on the leaked handle. Runs even if
|
|
169
|
-
// a teardown-time
|
|
161
|
+
// hanging on the leaked handle. Runs even if the lock release itself
|
|
162
|
+
// fails; a teardown-time error must not skip dispatch cleanup.
|
|
170
163
|
try {
|
|
171
164
|
await (options.disposeDispatchResources ?? disposeDispatchResources)();
|
|
172
165
|
}
|
|
@@ -176,150 +169,54 @@ async function runWorkflowAttempt(options, liveEvidence) {
|
|
|
176
169
|
}
|
|
177
170
|
}
|
|
178
171
|
}
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
function leaseExpiry() {
|
|
183
|
-
return new Date(Date.now() + RUN_LEASE_TTL_MS).toISOString();
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Atomically claim the run lease or refuse with a TransientError naming the
|
|
187
|
-
* current holder + expiry (#948 addendum — moved off UsageError, exit 75).
|
|
188
|
-
* The single-UPDATE claim in the repository is the arbiter — two racing
|
|
189
|
-
* invocations cannot both win.
|
|
190
|
-
*/
|
|
191
|
-
async function acquireRunLease(runId, holder) {
|
|
192
|
-
await withMaintenanceStartBarrierAsync(() => withWorkflowRunsRepo((repo) => {
|
|
193
|
-
if (repo.acquireEngineLease(runId, holder, leaseExpiry(), new Date().toISOString()))
|
|
194
|
-
return;
|
|
195
|
-
const row = repo.getRunById(runId);
|
|
196
|
-
throw new TransientError(`Workflow run ${runId} is already being driven by engine ${row?.engine_lease_holder ?? "(unknown)"} ` +
|
|
197
|
-
`(run lease expires ${row?.engine_lease_until ?? "(unknown)"}). A second \`akm workflow run\` would race it — ` +
|
|
198
|
-
`wait for that invocation to finish or for the lease to expire.`, "RUN_LEASE_HELD");
|
|
199
|
-
}));
|
|
172
|
+
/** The per-run lock file: `<data dir>/workflow-run-locks/<run id>.lock`, next to `state.db`. */
|
|
173
|
+
export function workflowRunLockPath(runId) {
|
|
174
|
+
return path.join(path.dirname(resolveStorageLocations().stateDb), "workflow-run-locks", `${runId}.lock`);
|
|
200
175
|
}
|
|
201
176
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
177
|
+
* Take the run's O_EXCL lock file, or refuse with `RUN_LEASE_HELD` (exit 75)
|
|
178
|
+
* naming the live holder. A lock whose holder pid is dead is reclaimed by
|
|
179
|
+
* `tryAcquireRunLock` before this refuses, so a crashed engine never wedges
|
|
180
|
+
* a run; nothing here expires by age.
|
|
205
181
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
/** Renew mid-dispatch this often. Well under the TTL so a slow/skipped tick
|
|
216
|
-
* still leaves ample margin before the lease would expire. */
|
|
217
|
-
const HEARTBEAT_INTERVAL_MS = RUN_LEASE_TTL_MS / 3;
|
|
218
|
-
/** Real timer: an unref'd interval so a live heartbeat never keeps the process alive. */
|
|
219
|
-
function defaultHeartbeatScheduler(tick) {
|
|
220
|
-
const id = setInterval(() => void tick(), HEARTBEAT_INTERVAL_MS);
|
|
221
|
-
id.unref?.();
|
|
222
|
-
return () => clearInterval(id);
|
|
182
|
+
function acquireWorkflowRunLock(runId) {
|
|
183
|
+
const result = tryAcquireRunLock(workflowRunLockPath(runId), { label: `workflow run ${runId}` });
|
|
184
|
+
if (result.state === "acquired")
|
|
185
|
+
return result.ownership;
|
|
186
|
+
const since = result.holder.startedAt ? `, since ${result.holder.startedAt}` : "";
|
|
187
|
+
throw new TransientError(`Workflow run ${runId} is already being driven by another akm process ` +
|
|
188
|
+
`(pid ${formatLockHolderPid(result.holder)}${since}). A second \`akm workflow run\` would race it — ` +
|
|
189
|
+
"wait for that invocation to finish.", "RUN_LEASE_HELD");
|
|
223
190
|
}
|
|
224
191
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* the
|
|
228
|
-
*
|
|
229
|
-
* (chained onto the caller's signal) that becomes the effective DISPATCH
|
|
230
|
-
* signal, so a lost lease aborts in-flight dispatch PROMPTLY. After the abort,
|
|
231
|
-
* {@link assertAlive} throws a loud UsageError, so the engine stops instead of
|
|
232
|
-
* continuing to drive a run another engine now owns. No background daemon: the
|
|
233
|
-
* timer is cleared in the caller's `finally` and dies with the process.
|
|
192
|
+
* Resume re-reads the authored workflow source and says so, once, when it no
|
|
193
|
+
* longer matches the bytes the plan was frozen from. The run keeps executing
|
|
194
|
+
* the frozen plan either way — that is the whole safety the freeze buys — and
|
|
195
|
+
* the warning tells the operator a fresh run is what picks up the edit.
|
|
234
196
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
cancel;
|
|
242
|
-
renewing = false;
|
|
243
|
-
/** Set once a renewal failed — the lease was stolen after a genuine expiry. */
|
|
244
|
-
lost = false;
|
|
245
|
-
/** The holder that stole the lease, captured for the loud error. */
|
|
246
|
-
stolenBy = null;
|
|
247
|
-
constructor(runId, holder, scheduler, upstream) {
|
|
248
|
-
this.runId = runId;
|
|
249
|
-
this.holder = holder;
|
|
250
|
-
this.schedule = scheduler ?? defaultHeartbeatScheduler;
|
|
251
|
-
// A caller abort (Ctrl-C, budget) must abort dispatch too; chain it into
|
|
252
|
-
// the effective signal. Distinct from a lost lease: a caller abort does
|
|
253
|
-
// NOT set `lost`, so `assertAlive` stays quiet and the existing graceful
|
|
254
|
-
// break on `options.signal` handles it.
|
|
255
|
-
if (upstream) {
|
|
256
|
-
if (upstream.aborted) {
|
|
257
|
-
this.controller.abort();
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
const onAbort = () => this.controller.abort();
|
|
261
|
-
upstream.addEventListener("abort", onAbort, { once: true });
|
|
262
|
-
this.detachUpstream = () => upstream.removeEventListener("abort", onAbort);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
/** The effective dispatch signal: aborts on a lost lease OR a caller abort. */
|
|
267
|
-
get signal() {
|
|
268
|
-
return this.controller.signal;
|
|
269
|
-
}
|
|
270
|
-
start() {
|
|
271
|
-
this.cancel ??= this.schedule(() => this.tick());
|
|
272
|
-
}
|
|
273
|
-
/** One renewal attempt. A failure marks the lease lost and aborts dispatch. */
|
|
274
|
-
async tick() {
|
|
275
|
-
if (this.lost || this.renewing || this.controller.signal.aborted)
|
|
276
|
-
return;
|
|
277
|
-
this.renewing = true;
|
|
278
|
-
try {
|
|
279
|
-
const renewed = await withWorkflowRunsRepo((repo) => repo.renewEngineLease(this.runId, this.holder, leaseExpiry()));
|
|
280
|
-
if (!renewed) {
|
|
281
|
-
this.stolenBy = await withWorkflowRunsRepo((repo) => repo.getRunById(this.runId)?.engine_lease_holder ?? null);
|
|
282
|
-
this.loseLease();
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
// A renewal that THREW (a DB error / connection failure, or the follow-up
|
|
287
|
-
// getRunById itself throwing) is treated exactly like a stolen lease: we
|
|
288
|
-
// can no longer PROVE we still hold it, so abort in-flight dispatch and let
|
|
289
|
-
// `assertAlive` stop the engine loudly. Swallowing the error here is what
|
|
290
|
-
// keeps the fire-and-forget `void tick()` in the default scheduler from
|
|
291
|
-
// leaking an unhandled promise rejection.
|
|
292
|
-
this.loseLease();
|
|
293
|
-
}
|
|
294
|
-
finally {
|
|
295
|
-
this.renewing = false;
|
|
296
|
-
}
|
|
197
|
+
async function workflowSourceDriftWarning(runId, workflowRef, plan) {
|
|
198
|
+
if (!plan.sourceHash)
|
|
199
|
+
return undefined;
|
|
200
|
+
let sourcePath;
|
|
201
|
+
try {
|
|
202
|
+
sourcePath = (await loadWorkflowAsset(workflowRef)).path;
|
|
297
203
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
this.lost = true;
|
|
303
|
-
this.stop();
|
|
304
|
-
this.controller.abort();
|
|
204
|
+
catch (error) {
|
|
205
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
206
|
+
return (`Workflow run ${runId}: the authored source ${workflowRef} could not be re-read (${detail}); ` +
|
|
207
|
+
"continuing with the frozen plan.");
|
|
305
208
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
* (completing steps, dispatching more units) would double-drive it.
|
|
310
|
-
*/
|
|
311
|
-
assertAlive() {
|
|
312
|
-
if (!this.lost)
|
|
313
|
-
return;
|
|
314
|
-
throw new UsageError(`Workflow run ${this.runId} lost its run lease mid-dispatch (heartbeat renewal failed; lease now held by ` +
|
|
315
|
-
`${this.stolenBy ?? "(nobody)"}). Another engine invocation claimed the run after this one's lease expired — ` +
|
|
316
|
-
`aborting to avoid double-driving it.`);
|
|
209
|
+
let current;
|
|
210
|
+
try {
|
|
211
|
+
current = createHash("sha256").update(fs.readFileSync(sourcePath)).digest("hex");
|
|
317
212
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.cancel = undefined;
|
|
321
|
-
this.detachUpstream?.();
|
|
213
|
+
catch {
|
|
214
|
+
return `Workflow run ${runId}: ${sourcePath} is no longer readable; continuing with the frozen plan.`;
|
|
322
215
|
}
|
|
216
|
+
if (current === plan.sourceHash)
|
|
217
|
+
return undefined;
|
|
218
|
+
return (`Workflow run ${runId}: ${workflowRef} (${sourcePath}) has changed since this run was frozen; ` +
|
|
219
|
+
`continuing with the frozen plan. Start a new run with 'akm workflow run ${workflowRef} --new' to pick up the edit.`);
|
|
323
220
|
}
|
|
324
221
|
/**
|
|
325
222
|
* A terminal run is a pure no-op: do not load or integrity-check its frozen
|
|
@@ -337,31 +234,14 @@ async function completedRunResult(runId) {
|
|
|
337
234
|
function workflowSummaryJudge(options, stepPlan, signal, owner) {
|
|
338
235
|
if (options.summaryJudge !== undefined)
|
|
339
236
|
return options.summaryJudge;
|
|
340
|
-
// The judge dispatches under the
|
|
341
|
-
// identity is threaded in per call by the completion path that journals it.
|
|
342
|
-
// eventSource (gap closed, code review): the judge's dispatch is a "command"
|
|
343
|
-
// request like any other exec/agent/sdk unit, so it goes through the same
|
|
344
|
-
// provenance thread `executeStepSubgraph` uses below — undefined for every
|
|
345
|
-
// non-task caller, byte-identical.
|
|
237
|
+
// The judge dispatches under the real run/step identity and the same event source as units.
|
|
346
238
|
return frozenSummaryJudge(stepPlan.gate.frozenJudge, signal, options.dispatcher ?? defaultUnitDispatcher, owner, options.eventSource);
|
|
347
239
|
}
|
|
348
240
|
/**
|
|
349
|
-
* Seed the
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* against `budget.max_tokens`. Durable result reuse is free.
|
|
354
|
-
*
|
|
355
|
-
* Gate-evaluation rows (`phase = "gate"`, journaled by the completion-gate
|
|
356
|
-
* judge) are EXCLUDED from the seed: the live path never consumes
|
|
357
|
-
* DispatchBudget for a judge call, so counting its journal row on resume
|
|
358
|
-
* would make an interrupted run hit `max_units` (and the lifetime cap)
|
|
359
|
-
* earlier than the identical uninterrupted run — a spurious hard failure
|
|
360
|
-
* that `on_error` cannot soften. The seed must reproduce exactly what live
|
|
361
|
-
* accounting would have accumulated.
|
|
362
|
-
*
|
|
363
|
-
* Attempt rows are append-only, so retries cannot collapse or erase prior
|
|
364
|
-
* dispatch accounting.
|
|
241
|
+
* Seed the run's budget from the append-only attempt journal so a resumed run
|
|
242
|
+
* does not restart it at zero: dispatch attempts count against `max_units`,
|
|
243
|
+
* their tokens against `max_tokens`. Gate-evaluation rows are excluded, as the
|
|
244
|
+
* live path never charges a judge call.
|
|
365
245
|
*/
|
|
366
246
|
async function seedRunAccountingFromJournal(runId) {
|
|
367
247
|
const accounting = await withWorkflowRunsRepo((repo) => repo.getAttemptAccounting(runId));
|
|
@@ -371,22 +251,15 @@ async function seedRunAccountingFromJournal(runId) {
|
|
|
371
251
|
};
|
|
372
252
|
}
|
|
373
253
|
/**
|
|
374
|
-
* The
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
* Reviewer #12: the journaled params row must still satisfy the frozen param
|
|
378
|
-
* schemas before the engine resolves any unit prompt from it, so
|
|
379
|
-
* schema-violating params — post-start corruption — fail loudly BEFORE any
|
|
380
|
-
* unit is dispatched (start already validated the params it stored).
|
|
254
|
+
* The row plan is the sole execution authority. The loader seam may assert an
|
|
255
|
+
* expected plan in tests, but can never replace it.
|
|
381
256
|
*/
|
|
382
|
-
async function loadAuthoritativeRunPlan(options, next) {
|
|
383
|
-
const stored = await loadStoredPlan(next.run.id);
|
|
257
|
+
async function loadAuthoritativeRunPlan(options, next, stored) {
|
|
384
258
|
if (options.loadPlan) {
|
|
385
|
-
const expected =
|
|
259
|
+
const expected = decodeWorkflowPlan(await options.loadPlan(next.run.workflowRef));
|
|
386
260
|
if (computePlanHash(expected) !== computePlanHash(stored))
|
|
387
261
|
throw new UsageError(`Injected workflow plan for run ${next.run.id} differs from its frozen plan.`);
|
|
388
262
|
}
|
|
389
|
-
assertRunParamsSatisfyPlan(next.run.id, stored, next.run.params ?? {});
|
|
390
263
|
return stored;
|
|
391
264
|
}
|
|
392
265
|
/**
|
|
@@ -395,8 +268,8 @@ async function loadAuthoritativeRunPlan(options, next) {
|
|
|
395
268
|
* Returns the re-read spine state so the caller can continue its walk.
|
|
396
269
|
*/
|
|
397
270
|
async function skipUnselectedRouteTarget(input) {
|
|
398
|
-
const { runId, stepId, stepPlan, skipInfo, routeUnselected, executed
|
|
399
|
-
// Cascade
|
|
271
|
+
const { runId, stepId, stepPlan, skipInfo, routeUnselected, executed } = input;
|
|
272
|
+
// Cascade: a skipped step that is ITSELF a router
|
|
400
273
|
// never evaluates its route, so none of its declared targets were
|
|
401
274
|
// selected — mark them all skip-on-reach too (a target another
|
|
402
275
|
// completed router selects stays protected via routeSelected). Without
|
|
@@ -408,27 +281,13 @@ async function skipUnselectedRouteTarget(input) {
|
|
|
408
281
|
? `Skipped by route: step "${skipInfo.router}" was itself skipped, so none of its branch targets run.`
|
|
409
282
|
: `Skipped by route: step "${skipInfo.router}" selected "${skipInfo.selected}".`;
|
|
410
283
|
executed.push({ stepId, ok: true, unitCount: 0, failedUnits: 0, summary: notes });
|
|
411
|
-
await completeWorkflowStep({ runId, stepId, status: "skipped", notes
|
|
284
|
+
await completeWorkflowStep({ runId, stepId, status: "skipped", notes });
|
|
412
285
|
return getNextWorkflowStep(runId);
|
|
413
286
|
}
|
|
414
287
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* (`<step>.gate:l<n>`, complete:false) must resume at loop n+1 with the
|
|
419
|
-
* stored corrective feedback threaded into the unit prompts; without this
|
|
420
|
-
* the engine restarts at loop 1, reuses the rejected loop-1 rows, overwrites
|
|
421
|
-
* `<step>.gate:l1`, and re-judges the stale artifact — breaking journaled
|
|
422
|
-
* replay and making the resumed run diverge from the interrupted one. The rows
|
|
423
|
-
* are re-read per step (NOT the once-at-start budget seed) so a step reached
|
|
424
|
-
* later within THIS same invocation still starts fresh at loop 1.
|
|
425
|
-
*
|
|
426
|
-
* Only the STEP's rows are read (index-backed on `(run_id, step_id)`): both
|
|
427
|
-
* helpers already discard every row carrying a different `step_id`, and gate
|
|
428
|
-
* rows are journaled under the step's own id, so the narrow query returns a
|
|
429
|
-
* superset of what they read. Re-reading the whole run journal here would
|
|
430
|
-
* re-materialize every earlier step's `result_json` — synchronously, blocking
|
|
431
|
-
* the event loop the lease heartbeat and abort handling share — once per step.
|
|
288
|
+
* Seed a step's starting gate loop and feedback from its journaled gate rows,
|
|
289
|
+
* so a run interrupted after a rejection resumes at the next loop with the
|
|
290
|
+
* stored feedback instead of re-judging loop 1. Reads only this step's rows.
|
|
432
291
|
*/
|
|
433
292
|
async function recoverGateLoopState(runId, stepPlan) {
|
|
434
293
|
// A step with no effective completion criteria never reaches a judge
|
|
@@ -443,13 +302,8 @@ async function recoverGateLoopState(runId, stepPlan) {
|
|
|
443
302
|
return { startLoop, seededFeedback: recoverGateFeedback(stepJournal, stepId, startLoop) };
|
|
444
303
|
}
|
|
445
304
|
/**
|
|
446
|
-
* The kinds that
|
|
447
|
-
*
|
|
448
|
-
* outage leave the step unfinished and consume nothing: the next invocation
|
|
449
|
-
* still owes the work. A blocked child is the SAME shape as a judge outage
|
|
450
|
-
* (P3b §3.4) — a gate is a gate for a child workflow too, so it is likewise
|
|
451
|
-
* NOT in this set: `akm workflow resume` is what clears it, never an
|
|
452
|
-
* automatic in-step re-dispatch.
|
|
305
|
+
* The kinds that finished the step — its one `maxSteps` consumption. An abort,
|
|
306
|
+
* a judge outage, and a blocked child consume nothing: the work is still owed.
|
|
453
307
|
*/
|
|
454
308
|
const STEP_FINISHED_KINDS = new Set(["advanced", "failed", "gate-exhausted"]);
|
|
455
309
|
/**
|
|
@@ -459,7 +313,7 @@ const STEP_FINISHED_KINDS = new Set(["advanced", "failed", "gate-exhausted"]);
|
|
|
459
313
|
* native executor.
|
|
460
314
|
*/
|
|
461
315
|
async function executeStepSubgraph(ctx, loop) {
|
|
462
|
-
const { options, next, plan, stepPlan, step, evidence
|
|
316
|
+
const { options, next, plan, stepPlan, step, evidence } = ctx;
|
|
463
317
|
const { gateLoop, gateFeedback, unitsDispatched, tokensUsed } = loop;
|
|
464
318
|
return !stepPlan.root && stepPlan.route
|
|
465
319
|
? {
|
|
@@ -471,42 +325,32 @@ async function executeStepSubgraph(ctx, loop) {
|
|
|
471
325
|
}
|
|
472
326
|
: await executeStepPlan(stepPlan, {
|
|
473
327
|
runId: next.run.id,
|
|
474
|
-
leaseHolder,
|
|
475
328
|
workflowRef: next.run.workflowRef,
|
|
476
329
|
params: next.run.params ?? {},
|
|
477
330
|
evidence,
|
|
478
331
|
unitsDispatched,
|
|
479
332
|
tokensUsed,
|
|
480
|
-
// Budget ceilings ride the FROZEN plan
|
|
333
|
+
// Budget ceilings ride the FROZEN plan: a mid-run
|
|
481
334
|
// asset edit can never loosen or tighten a run's budget.
|
|
482
335
|
...(plan.budget ? { budget: plan.budget } : {}),
|
|
483
336
|
gateLoop,
|
|
484
337
|
...(gateFeedback ? { gateFeedback } : {}),
|
|
485
|
-
// F-1
|
|
338
|
+
// F-1: threaded to an exec unit's child env;
|
|
486
339
|
// undefined for every non-task caller (byte-identical, RunWorkflowOptions doc).
|
|
487
340
|
...(options.eventSource !== undefined ? { eventSource: options.eventSource } : {}),
|
|
488
|
-
|
|
489
|
-
// lease (or a caller abort) aborts in-flight units promptly.
|
|
490
|
-
...(dispatchSignal ? { signal: dispatchSignal } : {}),
|
|
341
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
491
342
|
...(options.dispatcher ? { dispatcher: options.dispatcher } : {}),
|
|
492
343
|
maxConcurrency: Math.min(options.maxConcurrency ?? Number.POSITIVE_INFINITY, plan.execution?.maxConcurrency ?? 1),
|
|
493
344
|
});
|
|
494
345
|
}
|
|
495
346
|
/**
|
|
496
|
-
* Drive
|
|
497
|
-
* the
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* The engine owns only the loop control the shared completion path
|
|
501
|
-
* (`finalizeExecutedStep`) maps onto — retry re-executes; advanced moves on;
|
|
502
|
-
* failure/judge-failure/exhaustion stops this invocation — and returns that
|
|
503
|
-
* decision plus the running budget totals to {@link driveRun}. `ctx.executed`
|
|
504
|
-
* is appended in place (one report per iteration); everything else the caller
|
|
505
|
-
* must observe travels back through {@link StepGateLoopOutcome}.
|
|
347
|
+
* Drive one step's bounded gate loop: a rejection with loops left re-executes
|
|
348
|
+
* the subgraph with the judge's feedback in the unit prompts. Returns the loop
|
|
349
|
+
* decision and running budget totals; `ctx.executed` is appended in place.
|
|
506
350
|
*/
|
|
507
351
|
async function runStepGateLoop(ctx, gate, totals) {
|
|
508
352
|
const { options, next, stepPlan, step, evidence, executed, routeSelected, routeUnselected } = ctx;
|
|
509
|
-
const { summaryJudge
|
|
353
|
+
const { summaryJudge } = ctx;
|
|
510
354
|
const { startLoop, maxLoops } = gate;
|
|
511
355
|
let { unitsDispatched, tokensUsed } = totals;
|
|
512
356
|
let gateFeedback = gate.seededFeedback;
|
|
@@ -518,15 +362,7 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
518
362
|
tokensUsed,
|
|
519
363
|
});
|
|
520
364
|
for (let gateLoop = startLoop; gateLoop <= maxLoops; gateLoop++) {
|
|
521
|
-
// A loop re-execution dispatches a fresh round of units — renew the
|
|
522
|
-
// lease so a long evaluator-optimizer cycle cannot outlive the TTL.
|
|
523
|
-
if (gateLoop > 1)
|
|
524
|
-
await renewRunLease(next.run.id, leaseHolder);
|
|
525
365
|
const result = await executeStepSubgraph(ctx, { gateLoop, gateFeedback, unitsDispatched, tokensUsed });
|
|
526
|
-
// If the heartbeat lost the lease WHILE this step dispatched, another
|
|
527
|
-
// engine now owns the run — stop loudly BEFORE finalizing the step
|
|
528
|
-
// (completeWorkflowStep would race the new owner's spine).
|
|
529
|
-
heartbeat?.assertAlive();
|
|
530
366
|
unitsDispatched = result.unitsDispatched;
|
|
531
367
|
if (result.tokensUsed !== undefined)
|
|
532
368
|
tokensUsed = result.tokensUsed;
|
|
@@ -540,12 +376,7 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
540
376
|
summary: result.summary,
|
|
541
377
|
...(result.notices ? { notices: result.notices } : {}),
|
|
542
378
|
});
|
|
543
|
-
// Route
|
|
544
|
-
// journaling + the bounded-loop rejection contract are the SHARED
|
|
545
|
-
// completion path (`finalizeExecutedStep`): every step advances through
|
|
546
|
-
// that one sequence, whether its units were just dispatched or rehydrated
|
|
547
|
-
// from the journal on resume, so the same frozen plan always promotes the
|
|
548
|
-
// same artifact and advances (or rejects) the spine identically.
|
|
379
|
+
// Route, gate, and advance: the shared completion path, live or resumed.
|
|
549
380
|
let finalize;
|
|
550
381
|
try {
|
|
551
382
|
finalize = await finalizeExecutedStep({
|
|
@@ -563,20 +394,13 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
563
394
|
routeUnselected,
|
|
564
395
|
summaryJudge,
|
|
565
396
|
signal: options.signal,
|
|
566
|
-
// The judge runs under the DISPATCH signal, so the completion path must
|
|
567
|
-
// see it too: an abort delivered there (a lost lease, a caller Ctrl-C)
|
|
568
|
-
// is an interruption, not a verifier outage.
|
|
569
|
-
...(ctx.dispatchSignal ? { dispatchSignal: ctx.dispatchSignal } : {}),
|
|
570
|
-
leaseHolder,
|
|
571
397
|
});
|
|
572
398
|
}
|
|
573
399
|
catch (error) {
|
|
574
|
-
heartbeat?.assertAlive();
|
|
575
400
|
if (options.signal?.aborted)
|
|
576
401
|
return outcome({ kind: "aborted" });
|
|
577
402
|
throw error;
|
|
578
403
|
}
|
|
579
|
-
heartbeat?.assertAlive();
|
|
580
404
|
if (finalize.kind === "retry") {
|
|
581
405
|
// Re-execute the subgraph with the judge/validation feedback threaded
|
|
582
406
|
// into unit prompts — the changed prompt changes each unit's input
|
|
@@ -585,13 +409,8 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
585
409
|
continue;
|
|
586
410
|
}
|
|
587
411
|
if (finalize.kind === "advanced") {
|
|
588
|
-
//
|
|
589
|
-
// some
|
|
590
|
-
// retention, see `referencedStepIds`). `finalize` has already journaled
|
|
591
|
-
// the step (and stamped any route decision onto `result.evidence`), and
|
|
592
|
-
// the persisted row may carry a truncation envelope in place of an
|
|
593
|
-
// over-cap value — the row bound must not change what the very next step
|
|
594
|
-
// reads.
|
|
412
|
+
// Keep the complete artifact for later steps of this invocation, but only
|
|
413
|
+
// when some later reference can read it (`referencedStepIds`).
|
|
595
414
|
if (ctx.liveEvidenceConsumers.has(step.id))
|
|
596
415
|
ctx.liveEvidence.set(step.id, result.evidence);
|
|
597
416
|
// A route-only step's summary IS its decision (finalize surfaces it).
|
|
@@ -609,7 +428,7 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
609
428
|
return outcome({ kind: "judge-failed", judgeFailure: { stepId: step.id, message: finalize.summary } });
|
|
610
429
|
}
|
|
611
430
|
if (finalize.kind === "child-blocked") {
|
|
612
|
-
//
|
|
431
|
+
// a composed child workflow is blocked. Like judge-failed,
|
|
613
432
|
// NO gate loop was consumed and the step does not count against
|
|
614
433
|
// maxSteps. `result.childBlocked` (set by reduceStepOutcomes off the
|
|
615
434
|
// failed unit's live-only childRun field) carries the identity
|
|
@@ -648,28 +467,18 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
648
467
|
// here would mean those two bounds disagree — a bug, not a run outcome.
|
|
649
468
|
throw new Error(`Workflow run ${next.run.id} step "${step.id}" left its gate loop with no terminal outcome (loop bounds disagree).`);
|
|
650
469
|
}
|
|
651
|
-
/** The engine loop proper — runs under the
|
|
652
|
-
async function driveRun(options, initial,
|
|
470
|
+
/** The engine loop proper — runs under the per-run lock `runWorkflowAttempt` holds. */
|
|
471
|
+
async function driveRun(options, initial,
|
|
472
|
+
/** The run row's decoded frozen plan; undefined only for a done run, which drives nothing. */
|
|
473
|
+
storedPlan,
|
|
653
474
|
/**
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
* is rebuilt below — avoiding a re-parse of a row this same invocation just
|
|
657
|
-
* wrote (step artifacts are persisted whole, so the two values agree; this
|
|
658
|
-
* is purely an avoided round trip, not a correctness dependency). A LATER
|
|
659
|
-
* `akm workflow run` starts with an empty map and reads the rows directly.
|
|
660
|
-
*
|
|
661
|
-
* Only steps some OTHER step's references NAME are stored (`referencedStepIds`
|
|
662
|
-
* — the set-time filter): a step nothing downstream reads has no consumer to
|
|
663
|
-
* keep it complete for, so retaining it would buy nothing and cost its bytes
|
|
664
|
-
* for the rest of the invocation.
|
|
475
|
+
* In-memory evidence of steps this call completed that a later step reads,
|
|
476
|
+
* preferred over re-parsing their rows (the values agree).
|
|
665
477
|
*/
|
|
666
478
|
liveEvidence) {
|
|
667
479
|
let next = initial;
|
|
668
|
-
if (initial.done)
|
|
480
|
+
if (initial.done || !storedPlan)
|
|
669
481
|
return completedRunResult(initial.run.id);
|
|
670
|
-
// The effective dispatch signal: the heartbeat's controller (a lost lease or
|
|
671
|
-
// a caller abort aborts it) while leased, else the raw caller signal.
|
|
672
|
-
const dispatchSignal = heartbeat?.signal ?? options.signal;
|
|
673
482
|
const executed = [];
|
|
674
483
|
let gateRejection;
|
|
675
484
|
let judgeFailure;
|
|
@@ -682,7 +491,7 @@ liveEvidence) {
|
|
|
682
491
|
// a route-skipped step consumes NOTHING (no work was dispatched for it).
|
|
683
492
|
let stepsProcessed = 0;
|
|
684
493
|
let { unitsDispatched, tokensUsed } = await seedRunAccountingFromJournal(next.run.id);
|
|
685
|
-
const plan = await loadAuthoritativeRunPlan(options, next);
|
|
494
|
+
const plan = await loadAuthoritativeRunPlan(options, next, storedPlan);
|
|
686
495
|
// Live-evidence retention is decided at SET time, from the frozen plan alone:
|
|
687
496
|
// a completed step's complete artifact is held only while some other step's
|
|
688
497
|
// references can still read it. An exec unit's promoted stdout can be 8 MiB,
|
|
@@ -694,31 +503,17 @@ liveEvidence) {
|
|
|
694
503
|
// (two routers may share a target).
|
|
695
504
|
const routeSelected = new Set();
|
|
696
505
|
const routeUnselected = new Map();
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
// bookkeeping before the spine advances — a re-invoked run (crash, Ctrl-C,
|
|
700
|
-
// maxSteps, gate rejection after the route completed) would otherwise reach
|
|
701
|
-
// the unselected targets with empty in-memory state and execute the wrong
|
|
702
|
-
// branch. Decisions stay pure functions of (frozen plan, params, journaled
|
|
703
|
-
// results) — the addendum determinism bar. A done run skips the seeding:
|
|
704
|
-
// nothing will dispatch, so an unrecoverable prior decision must not
|
|
705
|
-
// block the no-op status return below.
|
|
506
|
+
// Replay journaled route decisions before the spine advances, so a
|
|
507
|
+
// re-invoked run skips the unselected branches. A done run skips this.
|
|
706
508
|
if (!next.done) {
|
|
707
509
|
seedJournaledRouteDecisions(plan, next, routeSelected, routeUnselected);
|
|
708
510
|
}
|
|
709
511
|
while (!next.done && next.step && next.run.status === "active" && stepsProcessed < maxSteps) {
|
|
710
|
-
// A
|
|
711
|
-
// another engine owns the spine now. A caller abort (options.signal) is a
|
|
712
|
-
// graceful break, distinct from a lost lease.
|
|
713
|
-
heartbeat?.assertAlive();
|
|
512
|
+
// A caller abort (options.signal) is a graceful break.
|
|
714
513
|
if (options.signal?.aborted) {
|
|
715
514
|
aborted = true;
|
|
716
515
|
break;
|
|
717
516
|
}
|
|
718
|
-
// Renew the run lease between steps (a fresh 90s window per iteration).
|
|
719
|
-
// Losing it (expired mid-step + claimed by another engine) throws — the
|
|
720
|
-
// new owner drives the spine now.
|
|
721
|
-
await renewRunLease(next.run.id, leaseHolder);
|
|
722
517
|
const step = next.step;
|
|
723
518
|
const stepPlan = plan.steps.find((s) => s.stepId === step.id);
|
|
724
519
|
if (!stepPlan) {
|
|
@@ -735,26 +530,21 @@ liveEvidence) {
|
|
|
735
530
|
skipInfo,
|
|
736
531
|
routeUnselected,
|
|
737
532
|
executed,
|
|
738
|
-
leaseHolder,
|
|
739
533
|
});
|
|
740
534
|
continue;
|
|
741
535
|
}
|
|
742
536
|
const evidence = {};
|
|
743
537
|
for (const s of next.workflow.steps)
|
|
744
538
|
evidence[s.id] = liveEvidence.get(s.id) ?? s.evidence;
|
|
745
|
-
// Bounded gate loop
|
|
539
|
+
// Bounded gate loop: loop 1 is the normal
|
|
746
540
|
// execution; a gate rejection with attempts left re-executes the subgraph
|
|
747
541
|
// with the judge's feedback threaded into unit prompts. The bound comes
|
|
748
542
|
// from the shared derivation, which holds an exec step to a single
|
|
749
543
|
// execution — its argv cannot answer feedback (see effectiveGateMaxLoops).
|
|
750
544
|
const maxLoops = effectiveGateMaxLoops(stepPlan);
|
|
751
545
|
const { startLoop, seededFeedback } = await recoverGateLoopState(next.run.id, stepPlan);
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
// run — reproduce the documented gateRejection outcome from the stored
|
|
755
|
-
// final-loop feedback instead of re-dispatching a spurious extra loop. The
|
|
756
|
-
// l1..l<maxLoops> rows stay untouched and the step stays active, exactly as
|
|
757
|
-
// when the engine first exhausted the gate.
|
|
546
|
+
// Resumed after the final rejection: reproduce the gate-exhausted outcome
|
|
547
|
+
// from the stored feedback instead of running an extra loop.
|
|
758
548
|
if (startLoop > maxLoops) {
|
|
759
549
|
gateRejection = {
|
|
760
550
|
stepId: step.id,
|
|
@@ -770,7 +560,7 @@ liveEvidence) {
|
|
|
770
560
|
// verify. No gate loop is consumed and nothing is dispatched.
|
|
771
561
|
let summaryJudge;
|
|
772
562
|
try {
|
|
773
|
-
summaryJudge = workflowSummaryJudge(options, stepPlan,
|
|
563
|
+
summaryJudge = workflowSummaryJudge(options, stepPlan, options.signal, {
|
|
774
564
|
runId: next.run.id,
|
|
775
565
|
stepId: step.id,
|
|
776
566
|
});
|
|
@@ -784,7 +574,6 @@ liveEvidence) {
|
|
|
784
574
|
runId: next.run.id,
|
|
785
575
|
stepId: step.id,
|
|
786
576
|
cause: `the verification judge could not be resolved from the frozen plan${detail}`,
|
|
787
|
-
leaseHolder,
|
|
788
577
|
});
|
|
789
578
|
executed.push({ stepId: step.id, ok: false, unitCount: 0, failedUnits: 0, summary: notes });
|
|
790
579
|
judgeFailure = { stepId: step.id, message: notes };
|
|
@@ -803,9 +592,6 @@ liveEvidence) {
|
|
|
803
592
|
routeSelected,
|
|
804
593
|
routeUnselected,
|
|
805
594
|
summaryJudge,
|
|
806
|
-
leaseHolder,
|
|
807
|
-
heartbeat,
|
|
808
|
-
dispatchSignal,
|
|
809
595
|
}, { startLoop, maxLoops, seededFeedback }, { unitsDispatched, tokensUsed });
|
|
810
596
|
unitsDispatched = outcome.unitsDispatched;
|
|
811
597
|
tokensUsed = outcome.tokensUsed;
|
|
@@ -840,22 +626,3 @@ liveEvidence) {
|
|
|
840
626
|
...(aborted ? { aborted: true } : {}),
|
|
841
627
|
};
|
|
842
628
|
}
|
|
843
|
-
/**
|
|
844
|
-
* Load the plan a run executes (frozen-plan contract, migration 006):
|
|
845
|
-
*
|
|
846
|
-
* - `plan_json` present → parse it and verify `plan_hash` (sha256 of the
|
|
847
|
-
* canonical JSON). A mismatch means the journaled plan was tampered with
|
|
848
|
-
* or corrupted — fail loudly, never silently recompile. The workflow
|
|
849
|
-
* asset file is NEVER touched on this path.
|
|
850
|
-
* Missing and non-current plans fail validation and are never rebuilt from a
|
|
851
|
-
* mutable source asset.
|
|
852
|
-
*/
|
|
853
|
-
async function loadStoredPlan(runId) {
|
|
854
|
-
const row = await withWorkflowRunsRepo((repo) => {
|
|
855
|
-
const run = repo.getRunById(runId);
|
|
856
|
-
return run;
|
|
857
|
-
});
|
|
858
|
-
if (!row)
|
|
859
|
-
throw new UsageError(`Workflow run ${runId} was not found.`);
|
|
860
|
-
return requireExecutableWorkflowPlan(row);
|
|
861
|
-
}
|