akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4
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 +756 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- 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 +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- 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 +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- 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 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- 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 +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- 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/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- 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 +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- 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-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- 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 +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- 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/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- 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 +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -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 +2 -7
- 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/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.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 +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- 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 +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- 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/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 +0 -17
- 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 +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- 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 +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- 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 +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- 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/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +363 -679
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- 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 +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- 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 +87 -406
- 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 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- 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 -21
- 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/config/retired-experimental-keys-shim.js +0 -62
- 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/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- 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/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/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 -78
- 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,19 +2,13 @@
|
|
|
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
|
-
* Shared step semantics
|
|
6
|
-
* decisions,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* helpers are PURE (no clock, no IO, no journal read); the gate-evaluation
|
|
11
|
-
* journaling functions are the one deliberate exception. This module never
|
|
12
|
-
* dispatches a unit and never writes step rows.
|
|
13
|
-
*
|
|
14
|
-
* See docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md for
|
|
15
|
-
* the full purity-contract design history.
|
|
5
|
+
* Shared step semantics: the one implementation of a step's orchestration
|
|
6
|
+
* decisions, used by the engine on both a fresh run and a resume, so the same
|
|
7
|
+
* frozen plan produces byte-identical unit graphs. Pure except the
|
|
8
|
+
* gate-evaluation journaling; never dispatches and never writes step rows.
|
|
9
|
+
* See docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md.
|
|
16
10
|
*/
|
|
17
|
-
import { createHash
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
18
12
|
import unitPreambleTemplate from "../../assets/prompts/workflow-unit-preamble.md" with { type: "text" };
|
|
19
13
|
import { UsageError } from "../../core/errors.js";
|
|
20
14
|
import { validateJsonSchemaSubset } from "../../core/json-schema.js";
|
|
@@ -25,40 +19,18 @@ import { canonicalJson } from "../ir/plan-hash.js";
|
|
|
25
19
|
import { parseReference, resolveReferenceString, } from "../program/expressions.js";
|
|
26
20
|
import { clip, WORKFLOW_UNIT_DIAGNOSTIC_CLIP } from "../resource-limits.js";
|
|
27
21
|
import { completeWorkflowStep } from "../runtime/runs.js";
|
|
28
|
-
import { GATE_EVALUATION_PHASE } from "../runtime/unit-phases.js";
|
|
29
22
|
import { parseJudgeVerdict } from "../validate-summary.js";
|
|
30
23
|
import { gateNodeId } from "./frozen-judge.js";
|
|
31
24
|
import { enqueueUnitWrite } from "./unit-writer.js";
|
|
32
25
|
/** How much raw unit output is retained in step evidence (full text lives on the unit row). */
|
|
33
26
|
const EVIDENCE_TEXT_CLIP = 2_000;
|
|
34
|
-
/** How much artifact JSON the completion-criteria judge receives
|
|
27
|
+
/** How much artifact JSON the completion-criteria judge receives. */
|
|
35
28
|
const GATE_ARTIFACT_CLIP = 4_000;
|
|
36
29
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* ids/hashes/prompts — the invariant resume/replay relies on to recognize the
|
|
42
|
-
* units an earlier run already journaled.
|
|
43
|
-
*
|
|
44
|
-
* Whole-list failures (missing subgraph, unresolvable / non-array `over`,
|
|
45
|
-
* null or duplicate fan-out items) return `{ ok: false }`. Per-unit resolution
|
|
46
|
-
* cannot fail in the shared source IR — prose is never scanned for references,
|
|
47
|
-
* and everything that CAN fail (map.over / route.input / inputs:) resolves
|
|
48
|
-
* once per step, failing the whole list above.
|
|
49
|
-
*/
|
|
50
|
-
/**
|
|
51
|
-
* Validate a fan-out item list BEFORE any identity/dispatch work: expansion
|
|
52
|
-
* within the resource limit, no null/undefined items, no canonical duplicates.
|
|
53
|
-
* Returns the failure message, or undefined when the list is dispatchable.
|
|
54
|
-
*
|
|
55
|
-
* Null items: producer garbage — there is nothing to hand the unit as its work
|
|
56
|
-
* item. The pre-unification format rejected them incidentally (substituting
|
|
57
|
-
* `${{ item }}` failed); with items attached as context instead of spliced,
|
|
58
|
-
* nothing later would stop a unit from being dispatched with "Item: null", so
|
|
59
|
-
* the rejection is explicit here. Duplicates: content-derived unit identity
|
|
60
|
-
* makes canonical duplicates collide on id — an authoring error caught
|
|
61
|
-
* deterministically, before dispatch.
|
|
30
|
+
* Validate a fan-out item list before any identity/dispatch work: no
|
|
31
|
+
* null/undefined items (there would be nothing to hand the unit) and no
|
|
32
|
+
* canonical duplicates (content-derived unit ids would collide). Returns the
|
|
33
|
+
* failure message, or undefined when the list is dispatchable.
|
|
62
34
|
*/
|
|
63
35
|
function validateFanOutItems(stepId, items) {
|
|
64
36
|
const nullIndex = items.findIndex((item) => item === null || item === undefined);
|
|
@@ -69,17 +41,9 @@ function validateFanOutItems(stepId, items) {
|
|
|
69
41
|
return undefined;
|
|
70
42
|
}
|
|
71
43
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* The first occurrence of a given canonical value keeps the byte-identical id
|
|
76
|
-
* {@link unitIdFor} always produced for it — so a plan with no duplicates (the
|
|
77
|
-
* overwhelming common case) is completely unaffected, and no prior journal
|
|
78
|
-
* entry is ever invalidated by this change. Only the SECOND and later
|
|
79
|
-
* occurrences gain a `#<n>` suffix (`#2`, `#3`, …), computed purely from each
|
|
80
|
-
* item's position in `items` — deterministic across a fresh run and a
|
|
81
|
-
* resumed one, since both call this from the same place in
|
|
82
|
-
* {@link computeStepWorkList} over the same resolved list.
|
|
44
|
+
* Unit ids for a fan-out list: the first occurrence of a canonical value keeps
|
|
45
|
+
* {@link unitIdFor}'s id; later occurrences gain `#2`, `#3`, … by position, so
|
|
46
|
+
* a fresh run and a resume derive the same ids.
|
|
83
47
|
*/
|
|
84
48
|
function occurrenceSuffixedUnitIds(nodeId, items) {
|
|
85
49
|
const occurrenceByCanonical = new Map();
|
|
@@ -91,35 +55,15 @@ function occurrenceSuffixedUnitIds(nodeId, items) {
|
|
|
91
55
|
return occurrence === 1 ? base : `${base}#${occurrence}`;
|
|
92
56
|
});
|
|
93
57
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Resolve one whole-value reference (`inputs[]`, `map.over`, `route.input`).
|
|
96
|
-
*
|
|
97
|
-
* Every step artifact is now persisted whole (issue C), so this is a thin
|
|
98
|
-
* wrapper: source adapters may retain GitHub's whole-value `${{ ... }}`
|
|
99
|
-
* spelling, and this work-list seam unwraps only an exact whole-value
|
|
100
|
-
* wrapper — it never interpolates prose.
|
|
101
|
-
*/
|
|
58
|
+
/** Resolve one whole-value reference (`inputs[]`, `map.over`, `route.input`, a binding's `from`). */
|
|
102
59
|
function resolveStepReference(reference, scope) {
|
|
103
|
-
|
|
104
|
-
// The source IR owns GitHub's whole-value spelling; this work-list seam
|
|
105
|
-
// unwraps only an exact whole-value wrapper and never interpolates prose.
|
|
106
|
-
const exactWrapper = /^\$\{\{\s*([^{}]+?)\s*\}\}$/.exec(reference);
|
|
107
|
-
const canonicalReference = exactWrapper?.[1] ?? reference;
|
|
108
|
-
return resolveReferenceString(canonicalReference, scope);
|
|
60
|
+
return resolveReferenceString(reference, scope);
|
|
109
61
|
}
|
|
110
62
|
/**
|
|
111
|
-
*
|
|
112
|
-
* (
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* `src/workflows/freeze/task-bindings.ts`), so it is never re-validated. A
|
|
116
|
-
* `{kind:"reference"}` resolves via the SAME {@link resolveStepReference}
|
|
117
|
-
* every other whole-value position uses, then validates the resolved value
|
|
118
|
-
* against the binding's own frozen `schema` — a mismatch (or a reference that
|
|
119
|
-
* fails to resolve at all) fails the WHOLE step here, before
|
|
120
|
-
* `reserveUnitAttempt` is ever called by the native executor. Absent
|
|
121
|
-
* `bindings` (the overwhelmingly common case — no `with:` on this step's
|
|
122
|
-
* target) resolves trivially to `{}` with no scope access at all.
|
|
63
|
+
* Resolve a composing step's frozen `inputBindings` before any attempt: a
|
|
64
|
+
* literal passes through (checked at freeze); a reference resolves like every
|
|
65
|
+
* other whole-value position and is validated against its frozen schema. A
|
|
66
|
+
* failure fails the whole step. No bindings resolve to `{}`.
|
|
123
67
|
*/
|
|
124
68
|
function resolveTaskInputBindings(bindings, stepId, scope) {
|
|
125
69
|
if (!bindings || bindings.length === 0)
|
|
@@ -155,6 +99,13 @@ function resolveTaskInputBindings(bindings, stepId, scope) {
|
|
|
155
99
|
}
|
|
156
100
|
return { ok: true, values };
|
|
157
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Compute a step's work list purely from the frozen plan and its inputs:
|
|
104
|
+
* resolve the fan-out list, derive content-derived unit ids, assemble each
|
|
105
|
+
* unit's prompt, and hash its input. Same inputs give byte-identical
|
|
106
|
+
* ids/hashes/prompts — what resume relies on to recognize journaled units.
|
|
107
|
+
* Every reference resolves once per step, so failures fail the whole list.
|
|
108
|
+
*/
|
|
158
109
|
export function computeStepWorkList(plan, input) {
|
|
159
110
|
const root = plan.root;
|
|
160
111
|
// Route-only steps (YAML `route:`) carry no execution subgraph.
|
|
@@ -185,15 +136,7 @@ export function computeStepWorkList(plan, input) {
|
|
|
185
136
|
}
|
|
186
137
|
resolvedInputs.push({ reference, value: resolved.value });
|
|
187
138
|
}
|
|
188
|
-
//
|
|
189
|
-
// `inputBindings` (spec §3.6, B-31..B-34): a `{kind:"literal"}` passes
|
|
190
|
-
// through unchanged (its schema was already checked at freeze, B-34); a
|
|
191
|
-
// `{kind:"reference"}` resolves against this SAME scope, then its resolved
|
|
192
|
-
// value is validated against the binding's own frozen `schema` — a
|
|
193
|
-
// mismatch fails the WHOLE step here, before `reserveUnitAttempt` is ever
|
|
194
|
-
// reached (B-32). This runs for every target kind (command/shell/script);
|
|
195
|
-
// Lane B's delivery consumes the result via `StepWorkUnitContext.taskInputs`
|
|
196
|
-
// / `taskInputsJson` below.
|
|
139
|
+
// A composing step's frozen `inputBindings`, resolved against this scope for every target kind.
|
|
197
140
|
const taskInputsResolution = resolveTaskInputBindings(template.frozenTarget.inputBindings, plan.stepId, scope);
|
|
198
141
|
if (!taskInputsResolution.ok)
|
|
199
142
|
return taskInputsResolution;
|
|
@@ -235,31 +178,14 @@ export function computeStepWorkList(plan, input) {
|
|
|
235
178
|
const target = template.frozenTarget;
|
|
236
179
|
const frozenExec = target.kind === "shell" || target.kind === "script" ? target.exec : undefined;
|
|
237
180
|
const runner = target.kind === "command" ? target.runner.kind : "exec";
|
|
238
|
-
// Taken
|
|
239
|
-
//
|
|
240
|
-
// (`
|
|
241
|
-
// `defaults.timeout` → `engines.<name>.timeoutMs` → the engine-kind default,
|
|
242
|
-
// `DEFAULT_LLM_TIMEOUT_MS` / `DEFAULT_AGENT_TIMEOUT_MS`). A frozen `null`
|
|
243
|
-
// means genuinely unbounded and is honored as such: it is reached either by an
|
|
244
|
-
// author writing `timeout: none` — an explicit, documented opt-out that a
|
|
245
|
-
// silent cap here would break — or by `DEFAULT_AGENT_TIMEOUT_MS`, which is
|
|
246
|
-
// itself `null` because agent harnesses own their own lifetime. The frozen IR
|
|
247
|
-
// collapses both to `timeoutMs: null`, so this layer could not tell them apart
|
|
248
|
-
// even if it wanted to; anything that should bound a unit belongs in
|
|
249
|
-
// `effectiveTimeout`, not here.
|
|
250
|
-
// An exec unit's budget is frozen on its exec spec (there is no engine to
|
|
251
|
-
// inherit one from); `ir/freeze.ts` resolved it once from unit `timeout:` →
|
|
252
|
-
// `defaults.timeout` → DEFAULT_EXEC_TIMEOUT_MS.
|
|
181
|
+
// Taken verbatim from the frozen plan, resolved once at freeze (an exec
|
|
182
|
+
// unit's on its exec spec). A frozen `null` means genuinely unbounded
|
|
183
|
+
// (`timeout: none`, or an agent harness that owns its own lifetime).
|
|
253
184
|
const timeoutMs = target.kind === "command"
|
|
254
185
|
? (target.runner.timeoutMs ?? null)
|
|
255
186
|
: target.kind === "child-workflow"
|
|
256
|
-
? // A child-workflow target carries no exec spec of its own
|
|
257
|
-
//
|
|
258
|
-
// unconditionally — the child executor (child-workflow.ts,
|
|
259
|
-
// reached from native-executor.ts's dispatch seam, P3b §3.2) is
|
|
260
|
-
// what actually drives a child-workflow unit, not this line, so
|
|
261
|
-
// `null` only needs to be a value this layer can carry, never one
|
|
262
|
-
// an engine acts on.
|
|
187
|
+
? // A child-workflow target carries no exec spec of its own.
|
|
188
|
+
// A child-workflow unit is driven by child-workflow.ts, never by this value.
|
|
263
189
|
null
|
|
264
190
|
: target.exec.timeoutMs;
|
|
265
191
|
// Step-constant exec context: `AKM_PARAMS` / `AKM_INPUTS` depend only on
|
|
@@ -271,7 +197,7 @@ export function computeStepWorkList(plan, input) {
|
|
|
271
197
|
const execInputsJson = frozenExec && resolvedInputs.length > 0
|
|
272
198
|
? (canonicalJson(Object.fromEntries(resolvedInputs.map((entry) => [entry.reference, entry.value]))) ?? "{}")
|
|
273
199
|
: undefined;
|
|
274
|
-
// P2b Lane A2
|
|
200
|
+
// P2b Lane A2: the resolved effective task-composition inputs,
|
|
275
201
|
// serialized ONCE here (mirrors execParamsJson/execInputsJson above) —
|
|
276
202
|
// Lane B's delivery (buildUnitPrompt's "## Task inputs" block,
|
|
277
203
|
// buildExecContextEnv's AKM_TASK_INPUTS) reads both back per unit.
|
|
@@ -298,33 +224,17 @@ export function computeStepWorkList(plan, input) {
|
|
|
298
224
|
list: { template, reducer, isFanOut, ...(concurrency !== undefined ? { concurrency } : {}), items, units },
|
|
299
225
|
};
|
|
300
226
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Build ONE unit of the step's work list: its journal id, its assembled prompt,
|
|
303
|
-
* its exec context env (exec units only), and its canonical input hash.
|
|
304
|
-
*
|
|
305
|
-
* Extracted from {@link computeStepWorkList} verbatim — same inputs, same
|
|
306
|
-
* bytes. It is a separate named pass only because the step-level resolution
|
|
307
|
-
* (inputs, fan-out items, runner, timeout) and the per-unit instantiation are
|
|
308
|
-
* two different jobs, and keeping them in one function had grown it past the
|
|
309
|
-
* repo's 220-line function bar.
|
|
310
|
-
*/
|
|
227
|
+
/** Build one unit of the step's work list: journal id, prompt, exec context env, and input hash. */
|
|
311
228
|
function buildStepWorkUnit(ctx, unitId, item, index) {
|
|
312
229
|
const { plan, input, template, isFanOut, resolvedInputs, target, frozenExec, taskInputs } = ctx;
|
|
313
230
|
// Gate loops (>= 2) journal under `<unitId>~l<loop>` so loop 1's rows are
|
|
314
231
|
// never clobbered; the content-derived identity (and the prompt's
|
|
315
232
|
// {{UNIT_ID}}) stays the base id.
|
|
316
233
|
const journalBaseId = ctx.gateLoop > 1 ? `${unitId}~l${ctx.gateLoop}` : unitId;
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
// An EXEC unit gets NO prompt: there is no model to read one, the exec
|
|
323
|
-
// dispatch branch returns before ever touching `request.prompt`, and the input
|
|
324
|
-
// hash is built from `template.instructions`, not from the assembled string.
|
|
325
|
-
// Its context reaches the child through {@link buildExecContextEnv} instead —
|
|
326
|
-
// attached as environment, never spliced into argv, which is the argv-array
|
|
327
|
-
// analogue of "data is attached context, not string splices".
|
|
234
|
+
// Every unit receives the run params, its item + index if it is a map unit,
|
|
235
|
+
// and its step's `inputs:` artifacts as attached context; instructions are
|
|
236
|
+
// never interpolated. An exec unit gets no prompt: its context reaches the
|
|
237
|
+
// child as environment ({@link buildExecContextEnv}), never spliced into argv.
|
|
328
238
|
const prompt = frozenExec
|
|
329
239
|
? ""
|
|
330
240
|
: buildUnitPrompt({
|
|
@@ -334,7 +244,7 @@ function buildStepWorkUnit(ctx, unitId, item, index) {
|
|
|
334
244
|
params: input.params,
|
|
335
245
|
...(isFanOut ? { item, itemIndex: index } : {}),
|
|
336
246
|
...(resolvedInputs.length > 0 ? { inputs: resolvedInputs } : {}),
|
|
337
|
-
// P2b Lane B
|
|
247
|
+
// P2b Lane B: the composed task's resolved
|
|
338
248
|
// `inputBindings`, when non-empty — see StepWorkUnitContext.taskInputs.
|
|
339
249
|
...(taskInputs && Object.keys(taskInputs).length > 0 ? { taskInputs } : {}),
|
|
340
250
|
...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
|
|
@@ -359,7 +269,7 @@ function buildStepWorkUnit(ctx, unitId, item, index) {
|
|
|
359
269
|
...(template.retry ? { retry: template.retry } : {}),
|
|
360
270
|
onError: template.onError,
|
|
361
271
|
...(template.isolation ? { isolation: template.isolation } : {}),
|
|
362
|
-
//
|
|
272
|
+
// the SAME resolved `with:` bindings `taskInputs` already
|
|
363
273
|
// carries, exposed under the name `child-workflow.ts`'s drive contract
|
|
364
274
|
// reads. Absent (never `{}`) when the step binds nothing.
|
|
365
275
|
...(taskInputs && Object.keys(taskInputs).length > 0 ? { childParams: taskInputs } : {}),
|
|
@@ -368,30 +278,11 @@ function buildStepWorkUnit(ctx, unitId, item, index) {
|
|
|
368
278
|
};
|
|
369
279
|
}
|
|
370
280
|
/**
|
|
371
|
-
* The `AKM_*` context environment an exec unit's child receives
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* — as attached environment, exactly as they reach an engine unit as attached
|
|
377
|
-
* prompt context. Values are canonical JSON so a command can parse them.
|
|
378
|
-
*
|
|
379
|
-
* These are applied on top of the resolved `env:` bindings in the child, so an
|
|
380
|
-
* engine-authored context variable can never be shadowed by a binding. Params
|
|
381
|
-
* are DECLARED NON-SECRET (`exec/param-secrets.ts` explains why: they are in
|
|
382
|
-
* every unit prompt and in the input hash, so they cannot be redacted);
|
|
383
|
-
* secrets belong in `env:` bindings, which reach the child by name.
|
|
384
|
-
*
|
|
385
|
-
* SIZE is not bounded here, on purpose. A workflow artifact has no bound
|
|
386
|
-
* comparable to an OS environment entry, so `AKM_INPUTS` (and `AKM_PARAMS` /
|
|
387
|
-
* `AKM_ITEM` / `AKM_TASK_INPUTS`) can serialize past what `execve` accepts and
|
|
388
|
-
* make PROCESS CREATION
|
|
389
|
-
* fail with a bare `E2BIG`. The check belongs at the spawn boundary, where the
|
|
390
|
-
* failure can be journaled as a unit outcome with an actionable message naming
|
|
391
|
-
* the variable: `checkExecContextSize` in `exec/exec-unit.ts`, against
|
|
392
|
-
* `execContextLimits()` for the platform the run is actually on (a Linux run is
|
|
393
|
-
* checked against Linux's ceiling, not against the smallest supported one).
|
|
394
|
-
* This function stays PURE and total.
|
|
281
|
+
* The `AKM_*` context environment an exec unit's child receives: how a fan-out
|
|
282
|
+
* item, the run params, and the step's `inputs:` artifacts reach a frozen argv
|
|
283
|
+
* (as canonical JSON). Applied over the resolved `env:` bindings so a binding
|
|
284
|
+
* cannot shadow it. Size is checked at the spawn boundary
|
|
285
|
+
* (`checkExecContextSize`, exec-unit.ts), where an E2BIG can be reported by name.
|
|
395
286
|
*/
|
|
396
287
|
function buildExecContextEnv(args) {
|
|
397
288
|
const { ctx, unitId, item, index } = args;
|
|
@@ -409,36 +300,17 @@ function buildExecContextEnv(args) {
|
|
|
409
300
|
}
|
|
410
301
|
if (ctx.execInputsJson !== undefined)
|
|
411
302
|
env.AKM_INPUTS = ctx.execInputsJson;
|
|
412
|
-
//
|
|
413
|
-
// composed task's effective `inputBindings` as canonical JSON — never one
|
|
414
|
-
// var per input. Absent when the frozen target carries no `inputBindings`
|
|
415
|
-
// or every resolved value is empty. Sizing is enforced by the SAME generic
|
|
416
|
-
// `checkExecContextSize` loop as every other `AKM_*` entry (exec-unit.ts) —
|
|
417
|
-
// no change there, the roster is just longer by one name (B-37).
|
|
303
|
+
// One variable carrying the resolved `inputBindings` as canonical JSON; absent when nothing is bound.
|
|
418
304
|
if (ctx.taskInputsJson !== undefined)
|
|
419
305
|
env.AKM_TASK_INPUTS = ctx.taskInputsJson;
|
|
420
306
|
return env;
|
|
421
307
|
}
|
|
422
308
|
/**
|
|
423
|
-
* The
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* `gateFeedback` is included conditionally (a gate retry is a materially
|
|
429
|
-
* different ask). `taskInputs` is likewise included conditionally (R-R15,
|
|
430
|
-
* `hashVersion` 7): a reference binding's RESOLVED value reaches the unit's
|
|
431
|
-
* prompt / `AKM_TASK_INPUTS` / `childParams`, so a changed upstream value is a
|
|
432
|
-
* materially different ask even though the binding's authored shape inside
|
|
433
|
-
* `frozenTarget` is unchanged — hashing it makes a resume whose journaled
|
|
434
|
-
* upstream output was altered fail loudly as replay divergence instead of
|
|
435
|
-
* silently reusing the stale row. The key is absent for a unit whose target
|
|
436
|
-
* carries no `inputBindings`, so a binding-free unit's preimage keeps the same
|
|
437
|
-
* shape it had (only the version fields moved 6 → 7). This is the ONE place a
|
|
438
|
-
* unit's inputHash is computed.
|
|
439
|
-
*
|
|
440
|
-
* See docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md for
|
|
441
|
-
* the full field-by-field inclusion/exclusion rationale (reviewer finding #1).
|
|
309
|
+
* The unit's `input_hash`: every input that changes what the backend is asked
|
|
310
|
+
* to do (names, never secret values). Informational — resume reuses a
|
|
311
|
+
* completed row by unit id. `gateFeedback`/`taskInputs` are included only when
|
|
312
|
+
* present so the loop-1, binding-free preimage keeps its `hashVersion` 7 shape.
|
|
313
|
+
* See docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md.
|
|
442
314
|
*/
|
|
443
315
|
function computeUnitInputHash(ctx, item) {
|
|
444
316
|
return createHash("sha256")
|
|
@@ -462,13 +334,10 @@ function computeUnitInputHash(ctx, item) {
|
|
|
462
334
|
.digest("hex");
|
|
463
335
|
}
|
|
464
336
|
/**
|
|
465
|
-
* Assemble the final prompt: engine preamble (
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* unification, spec §2.3) — data reaches the unit as attached context, not
|
|
470
|
-
* string splices; only the ENGINE's own preamble placeholders are substituted
|
|
471
|
-
* here.
|
|
337
|
+
* Assemble the final prompt: the engine preamble (params, item/index, input
|
|
338
|
+
* artifacts as JSON context) + the step's byte-exact instructions (+ gate
|
|
339
|
+
* feedback on a loop, + schema directive). Only the preamble's own
|
|
340
|
+
* placeholders are substituted.
|
|
472
341
|
*/
|
|
473
342
|
export function buildUnitPrompt(input) {
|
|
474
343
|
const { runId, stepId, unitId, params, itemIndex, item, inputs, taskInputs, gateFeedback, schema, instructions } = input;
|
|
@@ -489,12 +358,7 @@ export function buildUnitPrompt(input) {
|
|
|
489
358
|
const inputsBlock = inputs && inputs.length > 0
|
|
490
359
|
? `\n\n## Declared inputs\n${inputs.map((i) => `### ${i.reference}\n${safeJson(i.value)}`).join("\n\n")}`
|
|
491
360
|
: "";
|
|
492
|
-
//
|
|
493
|
-
// `inputBindings`, as a structured fenced JSON block — the same "attached
|
|
494
|
-
// context, never a splice" mechanism as itemBlock/inputsBlock above.
|
|
495
|
-
// `canonicalInputJson` (sorted keys) matches the AKM_TASK_INPUTS env var's
|
|
496
|
-
// own serialization, so the effective-inputs value reads identically on
|
|
497
|
-
// every delivery surface. Absent (or empty) appends nothing (B-39).
|
|
361
|
+
// The resolved `inputBindings` as a fenced JSON block, serialized exactly like AKM_TASK_INPUTS.
|
|
498
362
|
const taskInputsBlock = taskInputs && Object.keys(taskInputs).length > 0
|
|
499
363
|
? `\n\n## Task inputs\nThe composed task's declared inputs resolved to:\n\`\`\`json\n${canonicalInputJson(taskInputs)}\n\`\`\``
|
|
500
364
|
: "";
|
|
@@ -556,17 +420,9 @@ function stepTemplate(stepPlan) {
|
|
|
556
420
|
return root.kind === "map" ? root.template : root;
|
|
557
421
|
}
|
|
558
422
|
/**
|
|
559
|
-
* The step ids
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
* scanned (workflow-format-unification, spec §2.3) — so a step outside this set
|
|
563
|
-
* has no in-plan consumer and nothing needs to hold its artifact in memory once
|
|
564
|
-
* it is journaled.
|
|
565
|
-
*
|
|
566
|
-
* Derived from the plan alone: O(plan), independent of run state, and stable
|
|
567
|
-
* across the retry and gate loops (a retry re-opens one failed step, and a
|
|
568
|
-
* looping step has not advanced, so neither can turn an unreferenced producer
|
|
569
|
-
* into a referenced one mid-invocation).
|
|
423
|
+
* The step ids another step can still read (named by `inputs[]`, `map.over`,
|
|
424
|
+
* or `route.input` — the whole reference surface). A step outside this set
|
|
425
|
+
* need not keep its artifact in memory once journaled. Derived from the plan alone.
|
|
570
426
|
*/
|
|
571
427
|
export function referencedStepIds(plan) {
|
|
572
428
|
const referenced = new Set();
|
|
@@ -586,8 +442,8 @@ export function referencedStepIds(plan) {
|
|
|
586
442
|
return referenced;
|
|
587
443
|
}
|
|
588
444
|
/**
|
|
589
|
-
* Typed artifacts
|
|
590
|
-
* `
|
|
445
|
+
* Typed artifacts: validate the promoted step artifact against
|
|
446
|
+
* `WorkflowPlanStep.outputSchema`. Returns the step-failure summary (validation
|
|
591
447
|
* errors included) on mismatch, undefined when valid or when no schema is
|
|
592
448
|
* declared.
|
|
593
449
|
*/
|
|
@@ -601,14 +457,9 @@ export function validateStepArtifact(plan, evidence) {
|
|
|
601
457
|
`${errors.join("; ")}.`);
|
|
602
458
|
}
|
|
603
459
|
/**
|
|
604
|
-
* Warn-only check of each successful unit's
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
* field `outputSchema`), after which nothing else ever compares the returned
|
|
608
|
-
* text to it. Unlike {@link validateStepArtifact} this never fails the step:
|
|
609
|
-
* a harness that DID honor the schema already returned a compliant `result`
|
|
610
|
-
* (this re-check then finds nothing), and one that could not is exactly the
|
|
611
|
-
* case this exists to surface — the run continues either way.
|
|
460
|
+
* Warn-only check of each successful unit's value against its declared
|
|
461
|
+
* `unit.output` schema — the field a harness without structured output drops
|
|
462
|
+
* during lowering. Never fails the step (unlike {@link validateStepArtifact}).
|
|
612
463
|
*/
|
|
613
464
|
export function unitSchemaWarning(plan, units) {
|
|
614
465
|
const schema = stepTemplate(plan)?.schema;
|
|
@@ -657,17 +508,9 @@ function unitOutputValue(unit) {
|
|
|
657
508
|
return unit.text ?? null;
|
|
658
509
|
}
|
|
659
510
|
export function buildEvidence(units, reducer, isFanOut) {
|
|
660
|
-
// Per-unit evidence
|
|
661
|
-
// and a
|
|
662
|
-
//
|
|
663
|
-
// - a SUCCESS keeps its promoted contribution (structured `result` or clipped
|
|
664
|
-
// `text`) — the reuse path rehydrates exactly these from the unit row;
|
|
665
|
-
// - a FAILURE keeps only its `failureReason` (the durable, journaled failure
|
|
666
|
-
// vocabulary). The in-memory dispatch diagnostic (`error`) and any residual
|
|
667
|
-
// `text` on a failed unit are NOT persisted here: they do not survive a
|
|
668
|
-
// restart, so persisting them on the live-dispatch path alone would make
|
|
669
|
-
// the durable graph depend on WHEN it was built. The full raw text/reason
|
|
670
|
-
// still lives on the unit row for diagnostics; this is the shared graph.
|
|
511
|
+
// Per-unit evidence carries only what the journal can reproduce, so a fresh
|
|
512
|
+
// run and a resume agree byte-for-byte: a success keeps its `result`/clipped
|
|
513
|
+
// `text`, a failure only its `failureReason` (diagnostics stay on the unit row).
|
|
671
514
|
const collected = units.map((u) => u.ok
|
|
672
515
|
? {
|
|
673
516
|
unitId: u.unitId,
|
|
@@ -712,32 +555,17 @@ export function buildEvidence(units, reducer, isFanOut) {
|
|
|
712
555
|
else {
|
|
713
556
|
const winner = ranked[0].value;
|
|
714
557
|
evidence.vote = { winner, votes: ranked[0].count, total: units.length };
|
|
715
|
-
// An empty free-text
|
|
716
|
-
//
|
|
717
|
-
// under JSON serialization: a LIVE run then saw `output` absent (and fell
|
|
718
|
-
// back to the whole evidence envelope), while a RESUMED run rehydrated the
|
|
719
|
-
// same step from the journal and produced a different artifact — with the
|
|
720
|
-
// raw envelope exposed as `steps.<id>.output`. Normalize to an explicit
|
|
721
|
-
// empty string so both paths promote the same value.
|
|
558
|
+
// An empty free-text winner is `undefined`; normalize to "" so a live run
|
|
559
|
+
// and a resume promote the same `output` key.
|
|
722
560
|
evidence.output = winner === undefined ? "" : winner;
|
|
723
561
|
}
|
|
724
562
|
}
|
|
725
563
|
return evidence;
|
|
726
564
|
}
|
|
727
565
|
/**
|
|
728
|
-
* The
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
* failed; for an exec unit the reason it failed is on stderr, and the summary is
|
|
732
|
-
* what `akm workflow run` prints and what the failed step row keeps as its
|
|
733
|
-
* notes. Bounded on both axes: ONE unit (a 10 000-wide fan-out must not turn its
|
|
734
|
-
* summary into a log) clipped to {@link WORKFLOW_UNIT_DIAGNOSTIC_CLIP} — the same
|
|
735
|
-
* bound the journal and `status --units` use.
|
|
736
|
-
*
|
|
737
|
-
* Reproducible on both surfaces: a live dispatch carries the diagnostic as
|
|
738
|
-
* `error`; a unit rehydrated from the journal carries it as `text` (the column
|
|
739
|
-
* `journaledUnitResultJson` wrote it to), so the fallback below composes the
|
|
740
|
-
* SAME summary from either.
|
|
566
|
+
* The first failed unit's diagnostic (e.g. an exec unit's stderr), clipped to
|
|
567
|
+
* {@link WORKFLOW_UNIT_DIAGNOSTIC_CLIP}, for the step summary. A live outcome
|
|
568
|
+
* carries it as `error`, a rehydrated one as `text`; both give the same summary.
|
|
741
569
|
*/
|
|
742
570
|
function firstFailureDiagnostic(failed) {
|
|
743
571
|
const first = failed.find((u) => (u.error ?? u.text)?.trim());
|
|
@@ -747,13 +575,9 @@ function firstFailureDiagnostic(failed) {
|
|
|
747
575
|
return ` First failure diagnostic (${first.unitId}): ${clip(diagnostic, WORKFLOW_UNIT_DIAGNOSTIC_CLIP)}`;
|
|
748
576
|
}
|
|
749
577
|
/**
|
|
750
|
-
* Reduce a step's terminal unit outcomes into the promoted artifact
|
|
751
|
-
* verdict
|
|
752
|
-
*
|
|
753
|
-
* {@link buildEvidence}), the vote-tie failure, and the typed-artifact schema
|
|
754
|
-
* validation (fail-fast, errors in the summary, `artifactSchemaFailure` marker).
|
|
755
|
-
* Callers own dispatch-specific concerns (replay-divergence, budget) BEFORE
|
|
756
|
-
* calling this; those never occur on the report path (units are journaled).
|
|
578
|
+
* Reduce a step's terminal unit outcomes into the promoted artifact and step
|
|
579
|
+
* verdict: the `on_error` policy, the reducer, the vote-tie failure, and the
|
|
580
|
+
* typed-artifact schema check (`artifactSchemaFailure` marks a retryable one).
|
|
757
581
|
*/
|
|
758
582
|
export function reduceStepOutcomes(plan, reducer, isFanOut, onError, units) {
|
|
759
583
|
const failed = units.filter((u) => !u.ok);
|
|
@@ -784,12 +608,8 @@ export function reduceStepOutcomes(plan, reducer, isFanOut, onError, units) {
|
|
|
784
608
|
if (schemaWarning !== undefined)
|
|
785
609
|
summary += ` ${schemaWarning}`;
|
|
786
610
|
}
|
|
787
|
-
//
|
|
788
|
-
//
|
|
789
|
-
// driveChildWorkflowUnit). Surfaced here, unconditionally on the unit
|
|
790
|
-
// list, so `finalizeExecutedStep` can check it before deciding whether
|
|
791
|
-
// this step's failure is retryable — an `onError: "continue"` step that
|
|
792
|
-
// tolerates the failure (`ok` stays true) never reaches that check at all.
|
|
611
|
+
// A blocked child workflow (the failed unit's live-only `childRun`) is
|
|
612
|
+
// surfaced so `finalizeExecutedStep` blocks the step instead of retrying.
|
|
793
613
|
const blockedChildUnit = failed.find((u) => u.failureReason === "child_workflow_blocked" && u.childRun !== undefined);
|
|
794
614
|
const childBlocked = blockedChildUnit?.childRun
|
|
795
615
|
? {
|
|
@@ -808,18 +628,9 @@ export function reduceStepOutcomes(plan, reducer, isFanOut, onError, units) {
|
|
|
808
628
|
};
|
|
809
629
|
}
|
|
810
630
|
/**
|
|
811
|
-
* The
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
* reducer, `null` for `vote` (references into a missing winner fail loudly at
|
|
815
|
-
* resolution rather than silently reading the envelope). Even the degenerate
|
|
816
|
-
* artifact must honor the step's declared `outputSchema` before it can complete.
|
|
817
|
-
*
|
|
818
|
-
* Used by native dispatch (`executeStepPlan`'s `items.length === 0` branch): a
|
|
819
|
-
* zero-unit step can never be advanced by a unit completion, so it is promoted
|
|
820
|
-
* here instead. Deliberately does NOT run the reducer/vote-tie logic: an empty
|
|
821
|
-
* step has no successful results to count, and a vote-tie "failure" would
|
|
822
|
-
* diverge from the engine's long-standing empty-list semantics.
|
|
631
|
+
* The outcome of a step whose fan-out list is empty: no units dispatch and the
|
|
632
|
+
* artifact is `[]` (collect) or `null` (vote), still checked against the step's
|
|
633
|
+
* `outputSchema`. The reducer/vote-tie logic does not run.
|
|
823
634
|
*/
|
|
824
635
|
export function reduceEmptyStep(plan, reducer) {
|
|
825
636
|
const evidence = { units: [], itemCount: 0, output: reducer === "collect" ? [] : null };
|
|
@@ -833,16 +644,9 @@ export function reduceEmptyStep(plan, reducer) {
|
|
|
833
644
|
};
|
|
834
645
|
}
|
|
835
646
|
/**
|
|
836
|
-
* Rehydrate a journaled unit row into
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
* journal yields the same outcome the live dispatch produced. A completed row's
|
|
840
|
-
* text unit journals its output as a JSON string; a schema unit journals the
|
|
841
|
-
* validated structure. A failed row carries its `failure_reason` plus whatever
|
|
842
|
-
* `journaledUnitResultJson` (native-executor.ts) wrote to `result_json` —
|
|
843
|
-
* surfaced as `text`, its historical meaning. {@link firstFailureDiagnostic} is
|
|
844
|
-
* the one consumer that wants it as a diagnostic and falls back to `text`, so
|
|
845
|
-
* the step summary stays the same on both surfaces.
|
|
647
|
+
* Rehydrate a journaled unit row into the {@link UnitOutcome} its live dispatch
|
|
648
|
+
* produced: a completed row's JSON text or structured result, or a failed row's
|
|
649
|
+
* `failure_reason` with its journaled diagnostic as `text`.
|
|
846
650
|
*/
|
|
847
651
|
export function unitOutcomeFromRow(unitId, row, hasSchema) {
|
|
848
652
|
let parsed;
|
|
@@ -879,37 +683,19 @@ export function unitOutcomeFromRow(unitId, row, hasSchema) {
|
|
|
879
683
|
export { canonicalJson };
|
|
880
684
|
// ── Gate-feedback recovery (PURE) ────────────────────────────────────────────
|
|
881
685
|
//
|
|
882
|
-
// A gate rejection
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
// every unit id and input hash in it) matches the one the original run built.
|
|
888
|
-
// `native-executor.test.ts` asserts the round-trip identity.
|
|
889
|
-
// GATE_EVALUATION_PHASE moved to ../runtime/unit-phases.ts (leaf) so
|
|
890
|
-
// unit-checkin can key on it without closing the exec ↔ runtime cycle.
|
|
686
|
+
// A gate rejection journals `{ complete: false, missing, feedback }` under
|
|
687
|
+
// `<stepId>.gate:l<loop>`, byte-identical to what the next loop's prompts
|
|
688
|
+
// carry, so a resume rebuilds the same loop-N work list.
|
|
689
|
+
/** `phase` marker on gate-evaluation unit rows (dispatch rows journal `phase: null`). */
|
|
690
|
+
const GATE_EVALUATION_PHASE = "gate";
|
|
891
691
|
/** The unit id of a step's gate-evaluation row for a given 1-based loop. */
|
|
892
692
|
export function gateUnitId(stepId, loop) {
|
|
893
693
|
return `${stepId}.gate:l${loop}`;
|
|
894
694
|
}
|
|
895
695
|
/**
|
|
896
|
-
* How many times a step's subgraph may run under its
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
* A gate loop only earns its re-dispatch when the subgraph can ANSWER the
|
|
900
|
-
* judge: an engine unit reads the rejection feedback in its prompt and produces
|
|
901
|
-
* different work. An `exec` unit cannot. Its argv is frozen and never
|
|
902
|
-
* interpolated, {@link buildExecContextEnv} exposes no feedback variable, and
|
|
903
|
-
* the default dispatcher drops feedback for exec — so a second loop re-runs the
|
|
904
|
-
* BYTE-IDENTICAL command for a verdict that cannot change, which for a deploy /
|
|
905
|
-
* publish / migrate command means performing the side effect twice. The same
|
|
906
|
-
* reasoning already pins exec structured output to a single attempt and makes
|
|
907
|
-
* `exec_capture_incomplete` non-retryable (`native-executor.ts`).
|
|
908
|
-
*
|
|
909
|
-
* So an exec step's gate still EVALUATES — the verdict can still fail the step
|
|
910
|
-
* — but it never loops: a rejection lands on the gate-exhausted terminal
|
|
911
|
-
* instead of re-dispatching. An authored `gate.max_loops` on an engine step is
|
|
912
|
-
* untouched.
|
|
696
|
+
* How many times a step's subgraph may run under its gate. An exec step never
|
|
697
|
+
* loops: its frozen argv cannot read the judge's feedback, so a second loop
|
|
698
|
+
* would only repeat the command's side effects. Its gate still evaluates.
|
|
913
699
|
*/
|
|
914
700
|
export function effectiveGateMaxLoops(stepPlan) {
|
|
915
701
|
const declared = Math.max(1, stepPlan.gate.maxLoops ?? 1);
|
|
@@ -917,17 +703,9 @@ export function effectiveGateMaxLoops(stepPlan) {
|
|
|
917
703
|
return target && target.kind !== "command" ? 1 : declared;
|
|
918
704
|
}
|
|
919
705
|
/**
|
|
920
|
-
* The gate loop the engine is about to
|
|
921
|
-
*
|
|
922
|
-
* (
|
|
923
|
-
* A passed gate would have advanced the spine, so an active step never has a
|
|
924
|
-
* `complete: true` row as its latest gate evaluation.
|
|
925
|
-
*
|
|
926
|
-
* Reviewer #17: a gate row that EXISTS but cannot be parsed (or carries an
|
|
927
|
-
* invalid verdict shape) is CORRUPTION — {@link parseGateVerdict} throws loudly
|
|
928
|
-
* rather than letting `gateRowRejected` swallow the parse error, which would
|
|
929
|
-
* silently drop the loop back to 1 and re-dispatch work whose gate outcome is
|
|
930
|
-
* unknown.
|
|
706
|
+
* The gate loop the engine is about to run for an active step: one past the
|
|
707
|
+
* highest journaled rejected loop (loop 1 when none). An unparseable gate row
|
|
708
|
+
* throws ({@link parseGateVerdict}) rather than silently restarting at loop 1.
|
|
931
709
|
*/
|
|
932
710
|
export function activeGateLoop(rows, stepId) {
|
|
933
711
|
let maxRejectedLoop = 0;
|
|
@@ -944,14 +722,8 @@ export function activeGateLoop(rows, stepId) {
|
|
|
944
722
|
return maxRejectedLoop + 1;
|
|
945
723
|
}
|
|
946
724
|
/**
|
|
947
|
-
*
|
|
948
|
-
* `
|
|
949
|
-
* (`<stepId>.gate:l<loop-1>`). Loop 1 (or a missing/passed/errored previous row)
|
|
950
|
-
* has no feedback. Pure — the journal rows are passed in.
|
|
951
|
-
*
|
|
952
|
-
* Reviewer #17: a PRESENT previous gate row that cannot be parsed fails LOUDLY
|
|
953
|
-
* (via {@link parseGateVerdict}) instead of returning undefined — a corrupt row
|
|
954
|
-
* must not make an in-loop step look like loop 1 with no recovered feedback.
|
|
725
|
+
* The `{ feedback, missing }` the previous loop's rejection journaled, which
|
|
726
|
+
* `loop`'s prompts carry; none for loop 1. An unparseable previous row throws.
|
|
955
727
|
*/
|
|
956
728
|
export function recoverGateFeedback(rows, stepId, loop) {
|
|
957
729
|
if (loop <= 1)
|
|
@@ -972,16 +744,9 @@ function gateLoopOf(unitId, stepId) {
|
|
|
972
744
|
return Number.isInteger(n) && n >= 1 ? n : undefined;
|
|
973
745
|
}
|
|
974
746
|
/**
|
|
975
|
-
* Classify a gate
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
* if completion itself throws after judge invocation, and a `running` row has no
|
|
979
|
-
* verdict yet) and classifies as `empty`. But a PRESENT `result_json` that does
|
|
980
|
-
* not parse as JSON, or parses to
|
|
981
|
-
* anything other than an object with a boolean `complete` field, is corruption —
|
|
982
|
-
* a truncated or hand-edited row — and MUST NOT be silently treated as absent
|
|
983
|
-
* (which would reset an active step's gate loop to 1 and re-dispatch work whose
|
|
984
|
-
* completion outcome is unknown). We refuse to guess.
|
|
747
|
+
* Classify a gate row's journaled verdict. A NULL `result_json` (in flight, or
|
|
748
|
+
* a completion error) is `empty`; a present value that is not `{ complete:
|
|
749
|
+
* boolean }` throws rather than resetting the gate loop to 1.
|
|
985
750
|
*/
|
|
986
751
|
function parseGateVerdict(row) {
|
|
987
752
|
if (row.result_json === null)
|
|
@@ -1016,8 +781,6 @@ function gateCorruptionMessage(row, why) {
|
|
|
1016
781
|
/** Insert the gate-evaluation unit row (running) just before the judge runs. */
|
|
1017
782
|
export async function journalGateEvaluationStart(gate) {
|
|
1018
783
|
const unitId = gateUnitId(gate.stepId, gate.loop);
|
|
1019
|
-
const now = new Date().toISOString();
|
|
1020
|
-
const claimHolder = gate.claimHolder ?? `direct:${randomUUID()}`;
|
|
1021
784
|
const reserved = await enqueueUnitWrite(() => withWorkflowRunsRepo((repo) => repo.reserveUnitAttempt({
|
|
1022
785
|
runId: gate.runId,
|
|
1023
786
|
unitId,
|
|
@@ -1028,25 +791,15 @@ export async function journalGateEvaluationStart(gate) {
|
|
|
1028
791
|
engine: gate.engine,
|
|
1029
792
|
model: gate.model,
|
|
1030
793
|
inputHash: gate.inputHash,
|
|
1031
|
-
|
|
1032
|
-
claimExpiresAt: new Date(Date.parse(now) + 90_000).toISOString(),
|
|
1033
|
-
now,
|
|
1034
|
-
leaseMode: gate.claimHolder === undefined ? "direct" : "engine",
|
|
794
|
+
now: new Date().toISOString(),
|
|
1035
795
|
})));
|
|
1036
|
-
|
|
1037
|
-
throw new UsageError(`Gate ${unitId} has a live durable attempt held by another engine.`);
|
|
1038
|
-
}
|
|
1039
|
-
return { ...gate, claimHolder, durableAttempt: reserved.attempt };
|
|
796
|
+
return { ...gate, durableAttempt: reserved.attempt };
|
|
1040
797
|
}
|
|
1041
798
|
/**
|
|
1042
|
-
* Finish the gate-evaluation
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1045
|
-
*
|
|
1046
|
-
* judge ran) journals a failed row with NO verdict (`result_json` NULL) —
|
|
1047
|
-
* `errored` takes precedence over any synthesized fail-closed rejection, so
|
|
1048
|
-
* `activeGateLoop`/`recoverGateFeedback` never mistake a judge outage for an
|
|
1049
|
-
* honest rejection and burn a gate loop on resume.
|
|
799
|
+
* Finish the gate-evaluation row: a rejection journals `{ complete: false,
|
|
800
|
+
* missing, feedback }`, a pass `{ complete: true, missing: [] }`. An errored
|
|
801
|
+
* evaluation journals a failed row with no verdict, so a judge outage never
|
|
802
|
+
* burns a gate loop on resume.
|
|
1050
803
|
*/
|
|
1051
804
|
export async function journalGateEvaluationFinish(gate, errored, rejection) {
|
|
1052
805
|
const unitId = gateUnitId(gate.stepId, gate.loop);
|
|
@@ -1065,7 +818,6 @@ export async function journalGateEvaluationFinish(gate, errored, rejection) {
|
|
|
1065
818
|
unitId,
|
|
1066
819
|
attempt: durableAttempt.attempt,
|
|
1067
820
|
dispatchId: durableAttempt.dispatch_id,
|
|
1068
|
-
claimHolder: durableAttempt.claim_holder,
|
|
1069
821
|
status,
|
|
1070
822
|
resultJson: verdict ? JSON.stringify(verdict) : null,
|
|
1071
823
|
tokens: gate.tokens ?? null,
|
|
@@ -1074,7 +826,7 @@ export async function journalGateEvaluationFinish(gate, errored, rejection) {
|
|
|
1074
826
|
});
|
|
1075
827
|
}));
|
|
1076
828
|
if (!finished) {
|
|
1077
|
-
throw new UsageError(`Gate ${unitId}
|
|
829
|
+
throw new UsageError(`Gate ${unitId} was already finished; refusing a duplicate terminal write.`);
|
|
1078
830
|
}
|
|
1079
831
|
}
|
|
1080
832
|
/**
|
|
@@ -1162,15 +914,7 @@ function journaledRouteSelection(evidence) {
|
|
|
1162
914
|
function routeTargets(route) {
|
|
1163
915
|
return new Set([...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])]);
|
|
1164
916
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Reviewer #7: a journaled route decision must name a target the route actually
|
|
1167
|
-
* DECLARES (`when` branch or `default`). Corrupted or hand-edited evidence can
|
|
1168
|
-
* otherwise mark a non-existent step as `selected` — which unselects and skips
|
|
1169
|
-
* every REAL branch target, silently steering the run down a phantom branch.
|
|
1170
|
-
* `evaluateRoute` can only ever produce a declared target, so a stored value
|
|
1171
|
-
* outside that set is provably tampered evidence: fail loudly rather than seed a
|
|
1172
|
-
* bogus skip set.
|
|
1173
|
-
*/
|
|
917
|
+
/** A journaled route decision must name a target the route declares; anything else fails loudly. */
|
|
1174
918
|
function assertRouteTargetDeclared(route, stepId, selected, runId) {
|
|
1175
919
|
const targets = routeTargets(route);
|
|
1176
920
|
if (!targets.has(selected)) {
|
|
@@ -1205,7 +949,7 @@ export function seedJournaledRouteDecisions(plan, state, routeSelected, routeUns
|
|
|
1205
949
|
continue;
|
|
1206
950
|
let selected = journaledRouteSelection(stepState.evidence);
|
|
1207
951
|
if (selected !== undefined) {
|
|
1208
|
-
//
|
|
952
|
+
// a stored decision must name a declared target — a bogus one
|
|
1209
953
|
// (tampered/hand-edited evidence) fails loudly rather than seeding a skip
|
|
1210
954
|
// set that buries the real branches.
|
|
1211
955
|
assertRouteTargetDeclared(stepPlan.route, stepPlan.stepId, selected, state.run.id);
|
|
@@ -1255,7 +999,6 @@ export async function blockStepForJudgeFailure(input) {
|
|
|
1255
999
|
status: "blocked",
|
|
1256
1000
|
notes,
|
|
1257
1001
|
...(input.evidence !== undefined ? { evidence: input.evidence } : {}),
|
|
1258
|
-
...(input.leaseHolder !== undefined ? { leaseHolder: input.leaseHolder } : {}),
|
|
1259
1002
|
});
|
|
1260
1003
|
return notes;
|
|
1261
1004
|
}
|
|
@@ -1266,19 +1009,10 @@ async function blockFinalizedStep(input, cause) {
|
|
|
1266
1009
|
stepId: input.stepId,
|
|
1267
1010
|
cause,
|
|
1268
1011
|
evidence: input.result.evidence,
|
|
1269
|
-
...(input.leaseHolder !== undefined ? { leaseHolder: input.leaseHolder } : {}),
|
|
1270
1012
|
});
|
|
1271
1013
|
return { kind: "judge-failed", summary };
|
|
1272
1014
|
}
|
|
1273
|
-
/**
|
|
1274
|
-
* §3.4's exact `blockStepForChildWorkflow` notes — the ONE place the
|
|
1275
|
-
* blocked-child resume sequence is worded, mirroring {@link judgeFailureNotes}.
|
|
1276
|
-
* Two properties this wording pins (each its own test): the CHILD is resumed
|
|
1277
|
-
* FIRST, and the PARENT's own re-drive is what advances it (the child drive
|
|
1278
|
-
* never calls `resumeWorkflowRun` itself, row A-22); the notes name the child
|
|
1279
|
-
* run id and both commands verbatim, so the text renderer needs no change
|
|
1280
|
-
* (B-N15 — Lane A touches no output module).
|
|
1281
|
-
*/
|
|
1015
|
+
/** The blocked-child resume notes: resume the child first, then re-drive the parent. */
|
|
1282
1016
|
function childWorkflowBlockedNotes(runId, stepId, childRunId, childRef, childStepId) {
|
|
1283
1017
|
return (`Step "${stepId}" composes child workflow run ${childRunId} (${childRef}), ` +
|
|
1284
1018
|
`which is blocked at its own step "${childStepId ?? "(unknown)"}". Nothing in this run advances ` +
|
|
@@ -1287,14 +1021,7 @@ function childWorkflowBlockedNotes(runId, stepId, childRunId, childRef, childSte
|
|
|
1287
1021
|
`\`akm workflow resume ${runId}\` and \`akm workflow run ${runId}\` to ` +
|
|
1288
1022
|
`continue: re-driving the parent drives the resumed child.`);
|
|
1289
1023
|
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Complete a step `blocked` because the child workflow it composes is
|
|
1292
|
-
* blocked, and return the notes written (P3b §3.4). Sits beside
|
|
1293
|
-
* {@link blockStepForJudgeFailure} — the SAME shape of "infrastructure-like"
|
|
1294
|
-
* block: the step is completed `blocked`, and `akm workflow resume` is what
|
|
1295
|
-
* clears it (of the CHILD first, then the parent) rather than an automatic
|
|
1296
|
-
* in-step re-dispatch.
|
|
1297
|
-
*/
|
|
1024
|
+
/** Complete a step `blocked` because its child workflow is blocked; `akm workflow resume` clears it. */
|
|
1298
1025
|
export async function blockStepForChildWorkflow(input) {
|
|
1299
1026
|
const notes = childWorkflowBlockedNotes(input.runId, input.stepId, input.childRunId, input.childRef, input.childStepId);
|
|
1300
1027
|
await completeWorkflowStep({
|
|
@@ -1303,7 +1030,6 @@ export async function blockStepForChildWorkflow(input) {
|
|
|
1303
1030
|
status: "blocked",
|
|
1304
1031
|
notes,
|
|
1305
1032
|
...(input.evidence !== undefined ? { evidence: input.evidence } : {}),
|
|
1306
|
-
...(input.leaseHolder !== undefined ? { leaseHolder: input.leaseHolder } : {}),
|
|
1307
1033
|
});
|
|
1308
1034
|
return notes;
|
|
1309
1035
|
}
|
|
@@ -1316,38 +1042,25 @@ async function blockFinalizedStepForChildWorkflow(input, childBlocked) {
|
|
|
1316
1042
|
childRef: childBlocked.childRef,
|
|
1317
1043
|
childStepId: childBlocked.childStepId,
|
|
1318
1044
|
evidence: input.result.evidence,
|
|
1319
|
-
...(input.leaseHolder !== undefined ? { leaseHolder: input.leaseHolder } : {}),
|
|
1320
1045
|
});
|
|
1321
1046
|
return { kind: "child-blocked", summary };
|
|
1322
1047
|
}
|
|
1323
1048
|
/**
|
|
1324
|
-
* Perform
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1329
|
-
* -
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* row; a rejection with loops remaining returns `retry` (feedback threaded
|
|
1335
|
-
* into the next loop), a rejection with none returns `gate-exhausted`, a pass
|
|
1336
|
-
* returns `advanced`;
|
|
1337
|
-
* - a judge INFRASTRUCTURE failure (missing judge, thrown judge call, or a
|
|
1338
|
-
* malformed verdict) is NOT a verdict: it consumes no gate loop and blocks
|
|
1339
|
-
* the step for `akm workflow resume` (`judge-failed`) instead of feeding
|
|
1340
|
-
* the bounded loop's re-dispatch.
|
|
1341
|
-
*
|
|
1342
|
-
* Every DB advance goes through {@link completeWorkflowStep} — the gate spine is
|
|
1343
|
-
* never bypassed. Behavior is byte-identical to the engine's former inline loop
|
|
1344
|
-
* body (its tests prove it).
|
|
1049
|
+
* Perform one completion attempt for an executed step:
|
|
1050
|
+
* - a hard unit failure fails the step (a retryable artifact-schema mismatch
|
|
1051
|
+
* with loops left returns `retry` without a gate row);
|
|
1052
|
+
* - a route decision is evaluated, journaled on the evidence, and applied to
|
|
1053
|
+
* the skip bookkeeping; an unroutable value fails the step;
|
|
1054
|
+
* - the gate judges a summary built from the promoted artifact: a rejection
|
|
1055
|
+
* returns `retry` (loops left) or `gate-exhausted`, a pass `advanced`;
|
|
1056
|
+
* - a judge infrastructure failure is not a verdict: it blocks the step for
|
|
1057
|
+
* `akm workflow resume` (`judge-failed`) without consuming a loop.
|
|
1058
|
+
* Every advance goes through {@link completeWorkflowStep}.
|
|
1345
1059
|
*/
|
|
1346
1060
|
export async function finalizeExecutedStep(input) {
|
|
1347
1061
|
const { runId, workflowRef, stepId, stepPlan, completionCriteria, gateLoop, loopsRemaining, result } = input;
|
|
1348
|
-
const lease = input.leaseHolder !== undefined ? { leaseHolder: input.leaseHolder } : {};
|
|
1349
1062
|
if (!result.ok) {
|
|
1350
|
-
//
|
|
1063
|
+
// a composed child workflow that blocked is never fed into the
|
|
1351
1064
|
// bounded gate loop — a gate is a gate for a child workflow too. Checked
|
|
1352
1065
|
// FIRST, before the artifactSchemaFailure retry branch below.
|
|
1353
1066
|
if (result.childBlocked) {
|
|
@@ -1365,7 +1078,6 @@ export async function finalizeExecutedStep(input) {
|
|
|
1365
1078
|
status: "failed",
|
|
1366
1079
|
notes: result.summary,
|
|
1367
1080
|
evidence: result.evidence,
|
|
1368
|
-
...lease,
|
|
1369
1081
|
});
|
|
1370
1082
|
return { kind: "failed", summary: result.summary };
|
|
1371
1083
|
}
|
|
@@ -1391,7 +1103,7 @@ export async function finalizeExecutedStep(input) {
|
|
|
1391
1103
|
const decision = evaluateRoute(stepPlan.route, scope);
|
|
1392
1104
|
if (!decision.ok) {
|
|
1393
1105
|
const notes = `Step "${stepId}" route failed: ${decision.error}`;
|
|
1394
|
-
await completeWorkflowStep({ runId, stepId, status: "failed", notes, evidence: result.evidence
|
|
1106
|
+
await completeWorkflowStep({ runId, stepId, status: "failed", notes, evidence: result.evidence });
|
|
1395
1107
|
return { kind: "failed", summary: notes, routeFailure: true };
|
|
1396
1108
|
}
|
|
1397
1109
|
applyRouteDecision(stepPlan.route, stepId, decision.selected, input.routeSelected, input.routeUnselected);
|
|
@@ -1446,7 +1158,6 @@ export async function finalizeExecutedStep(input) {
|
|
|
1446
1158
|
prompt,
|
|
1447
1159
|
}))
|
|
1448
1160
|
.digest("hex"),
|
|
1449
|
-
...(input.leaseHolder !== undefined ? { claimHolder: input.leaseHolder } : {}),
|
|
1450
1161
|
};
|
|
1451
1162
|
gateUnit = await journalGateEvaluationStart(gateUnit);
|
|
1452
1163
|
}
|
|
@@ -1486,23 +1197,10 @@ export async function finalizeExecutedStep(input) {
|
|
|
1486
1197
|
return raw;
|
|
1487
1198
|
}
|
|
1488
1199
|
: null;
|
|
1489
|
-
//
|
|
1490
|
-
//
|
|
1491
|
-
//
|
|
1492
|
-
// validateStepSummary — `judgeFailure` records it). The remaining
|
|
1493
|
-
// window is `completeWorkflowStep` throwing AFTER the judge ran — a stolen
|
|
1494
|
-
// lease, a concurrent state change, a DB error — which would otherwise skip the
|
|
1495
|
-
// finish and strand the gate row in `running`. Finish it as an errored row (the
|
|
1496
|
-
// observed outcome: the completion did not succeed), then re-propagate.
|
|
1497
|
-
//
|
|
1498
|
-
// The signal handed down is the DISPATCH signal (the judge call runs under
|
|
1499
|
-
// it), not just the caller's: the interruption guard inside the completion
|
|
1500
|
-
// path rethrows an abort instead of classifying it as a judge outage, and a
|
|
1501
|
-
// lost lease aborting mid-judge is an interruption — recording it as a
|
|
1502
|
-
// verifier failure would blame infrastructure and durably block a step whose
|
|
1503
|
-
// gate simply never finished evaluating.
|
|
1200
|
+
// Once the judge runs, its `running` gate row must be finished on every
|
|
1201
|
+
// exit: if `completeWorkflowStep` throws afterwards, finish it as errored,
|
|
1202
|
+
// then re-propagate.
|
|
1504
1203
|
let completion;
|
|
1505
|
-
const completionSignal = input.dispatchSignal ?? input.signal;
|
|
1506
1204
|
try {
|
|
1507
1205
|
completion = await completeWorkflowStep({
|
|
1508
1206
|
runId,
|
|
@@ -1511,8 +1209,7 @@ export async function finalizeExecutedStep(input) {
|
|
|
1511
1209
|
summary,
|
|
1512
1210
|
evidence: result.evidence,
|
|
1513
1211
|
summaryJudge,
|
|
1514
|
-
...(
|
|
1515
|
-
...lease,
|
|
1212
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
1516
1213
|
});
|
|
1517
1214
|
}
|
|
1518
1215
|
catch (err) {
|