akm-cli 0.9.30 → 0.10.261001-beta
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 +438 -0
- package/README.md +10 -10
- package/ROADMAP.md +112 -0
- package/STABILITY.md +99 -57
- package/dist/api-node.mjs +20 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +45 -0
- package/dist/assets/hints/cli-hints-full.md +15 -29
- package/dist/assets/hints/cli-hints-short.md +2 -2
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/assets/stash-skeleton/README.md +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/cli/shared.js +31 -48
- package/dist/cli/unknown-flags.js +6 -43
- package/dist/cli.js +8 -34
- package/dist/commands/agent/contribute-cli.js +1 -1
- package/dist/commands/completions.js +1 -2
- package/dist/commands/config-cli.js +5 -28
- package/dist/commands/env/env-cli.js +5 -5
- package/dist/commands/env/secret-cli.js +2 -2
- package/dist/commands/feedback-cli.js +75 -49
- package/dist/commands/health/checks.js +41 -16
- package/dist/commands/health/plugin-staleness.js +52 -22
- package/dist/commands/improve/autonomy-gate.js +5 -3
- package/dist/commands/improve/consolidate/chunking.js +3 -10
- package/dist/commands/improve/consolidate/continuity-check.js +4 -4
- package/dist/commands/improve/consolidate/pair-pass.js +29 -10
- package/dist/commands/improve/consolidate.js +10 -65
- package/dist/commands/improve/distill/lesson-update.js +91 -0
- package/dist/commands/improve/distill-guards.js +1 -43
- package/dist/commands/improve/distill.js +148 -21
- package/dist/commands/improve/eligibility.js +14 -1
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/extract-prompt.js +17 -7
- package/dist/commands/improve/extract.js +5 -7
- package/dist/commands/improve/improve-cli.js +125 -118
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/improve.js +134 -52
- package/dist/commands/improve/loop-stages.js +18 -5
- package/dist/commands/improve/planner.js +0 -1
- package/dist/commands/improve/preparation.js +40 -99
- package/dist/commands/improve/proactive-maintenance.js +2 -2
- package/dist/commands/improve/reflect-noise.js +3 -55
- package/dist/commands/improve/reflect.js +29 -34
- package/dist/commands/improve/retrieval-queries.js +37 -0
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/session-asset.js +4 -1
- package/dist/commands/improve/stage.js +23 -12
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +43 -6
- package/dist/commands/proposal/proposal-cli.js +11 -31
- package/dist/commands/proposal/repository.js +20 -14
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -3
- package/dist/commands/read/knowledge.js +3 -3
- package/dist/commands/read/remember-cli.js +0 -13
- package/dist/commands/read/search-cli.js +29 -97
- package/dist/commands/read/search.js +0 -11
- package/dist/commands/read/show.js +0 -19
- package/dist/commands/sources/plugin-upgrade.js +164 -101
- package/dist/commands/sources/sources-cli.js +1 -15
- package/dist/commands/sources/stash-cli.js +6 -14
- package/dist/commands/tasks/tasks-cli.js +4 -115
- package/dist/commands/workflow/plan.js +11 -33
- package/dist/commands/workflow-cli.js +2 -7
- package/dist/core/adapter/adapter-ids.js +1 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
- package/dist/core/adapter/adapters/akm-adapter.js +9 -48
- package/dist/core/adapter/adapters/akm-lint.js +10 -26
- package/dist/core/adapter/adapters/akm-metadata.js +2 -72
- package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
- package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
- package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
- package/dist/core/adapter/recognize-match.js +3 -4
- package/dist/core/common.js +18 -2
- package/dist/core/config/config-schema.js +12 -15
- package/dist/core/config/config-types.js +6 -15
- package/dist/core/config/config-walker.js +6 -23
- package/dist/core/config/config.js +17 -80
- package/dist/core/config/engine-semantics.js +7 -3
- package/dist/core/config/schema/engines.js +7 -7
- package/dist/core/config/schema/improve-processes.js +10 -63
- package/dist/core/config/schema/improve.js +2 -21
- package/dist/core/config/schema/index-config.js +6 -39
- package/dist/core/env-secret-ref.js +2 -45
- package/dist/core/errors.js +1 -2
- package/dist/core/extra-params.js +2 -86
- package/dist/core/file-lock.js +12 -7
- package/dist/core/improve-result.js +13 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/paths.js +4 -0
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/run-lock.js +1 -1
- package/dist/core/subprocess.js +49 -0
- package/dist/core/warn.js +21 -4
- package/dist/core/write-source.js +3 -3
- package/dist/execution/target-ref.js +1 -2
- package/dist/indexer/indexer.js +15 -6
- package/dist/indexer/passes/memory-inference.js +15 -6
- package/dist/indexer/search/db-search.js +49 -36
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/indexer/search/ranking.js +29 -1
- package/dist/indexer/usage/usage-events.js +21 -16
- package/dist/integrations/agent/engine-resolution.js +5 -2
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +41 -98
- package/dist/integrations/agent/request-lowering.js +3 -3
- package/dist/integrations/harnesses/aider/index.js +5 -0
- package/dist/integrations/harnesses/amazonq/index.js +1 -0
- package/dist/integrations/harnesses/claude/config-import.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +5 -0
- package/dist/integrations/harnesses/codex/index.js +1 -0
- package/dist/integrations/harnesses/copilot/index.js +1 -0
- package/dist/integrations/harnesses/gemini/index.js +1 -0
- package/dist/integrations/harnesses/index.js +18 -39
- package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
- package/dist/integrations/harnesses/opencode/config-import.js +121 -52
- package/dist/integrations/harnesses/opencode/index.js +22 -0
- package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
- package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
- package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
- package/dist/integrations/harnesses/opencode/session-log.js +104 -115
- package/dist/integrations/harnesses/opencode/version.js +98 -0
- package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +254 -0
- package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
- package/dist/integrations/harnesses/openhands/index.js +1 -0
- package/dist/integrations/harnesses/pi/index.js +1 -0
- package/dist/integrations/harnesses/shared.js +2 -0
- package/dist/integrations/harnesses/types.js +2 -0
- package/dist/llm/client.js +74 -14
- package/dist/output/cli-hints.js +1 -1
- package/dist/output/context.js +10 -19
- package/dist/output/format-exempt.js +5 -8
- package/dist/output/shapes/helpers.js +1 -22
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/shapes.js +3 -31
- package/dist/output/stdout.js +5 -1
- package/dist/output/text/command-format.js +6 -1
- package/dist/output/text/workflow-format.js +4 -22
- package/dist/scripts/akm-migrate-node.js +15769 -16731
- package/dist/scripts/akm-migrate.js +16339 -17297
- package/dist/setup/detect.js +10 -4
- package/dist/setup/harness-config-import.js +5 -14
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +12 -13
- package/dist/sources/providers/git-stash.js +94 -13
- package/dist/storage/repositories/index-utility-repository.js +2 -32
- package/dist/storage/repositories/index-vec-repository.js +4 -0
- package/dist/storage/repositories/task-history-repository.js +0 -5
- package/dist/storage/repositories/workflow-runs-repository.js +5 -15
- package/dist/tasks/embedded.js +2 -1
- package/dist/tasks/run/run-command-task.js +9 -1
- package/dist/tasks/run/run-workflow-task.js +4 -3
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/run/task-log.js +38 -1
- package/dist/tasks/schedule-gates.js +39 -0
- package/dist/tasks/scheduler-binding.js +0 -16
- package/dist/tasks/scheduler-sync.js +3 -97
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/task-source-v4.js +2 -12
- package/dist/tasks/task-run-reserved-flags.js +2 -31
- package/dist/workflows/authoring/authoring.js +6 -6
- package/dist/workflows/compile.js +8 -65
- package/dist/workflows/exec/child-workflow.js +22 -3
- package/dist/workflows/exec/native-executor.js +12 -147
- package/dist/workflows/exec/run-workflow.js +31 -103
- package/dist/workflows/exec/step-work.js +9 -178
- package/dist/workflows/exec/unit-dispatch.js +0 -10
- package/dist/workflows/freeze/environment.js +3 -44
- package/dist/workflows/freeze/freeze.js +8 -41
- package/dist/workflows/freeze/targets/child-workflow.js +27 -63
- package/dist/workflows/freeze/targets/command.js +2 -16
- package/dist/workflows/freeze/targets/shell.js +0 -2
- package/dist/workflows/freeze/task-bindings.js +2 -28
- package/dist/workflows/ir/params.js +12 -2
- package/dist/workflows/parser.js +82 -232
- package/dist/workflows/plan.js +1 -1
- package/dist/workflows/renderer.js +10 -19
- package/dist/workflows/runtime/run-plan.js +11 -53
- package/dist/workflows/runtime/runs.js +4 -27
- package/dist/workflows/source-files.js +12 -16
- package/dist/workflows/source-semantics.js +1 -93
- package/docs/README.md +43 -42
- package/docs/agents/README.md +15 -0
- package/docs/agents/agent-install.md +14 -0
- package/docs/architecture/0.10-process-rules.md +827 -0
- package/docs/architecture/README.md +67 -0
- package/docs/architecture/adapters.md +258 -0
- package/docs/architecture/akm-architecture-decision-history.md +2055 -0
- package/docs/architecture/akm-core-principles.md +126 -0
- package/docs/architecture/architecture.md +578 -0
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +550 -0
- package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +72 -0
- package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +142 -0
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +189 -0
- package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +97 -0
- package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +108 -0
- package/docs/architecture/decisions/0006-task-source-version-routing.md +124 -0
- package/docs/architecture/decisions/0007-workflow-composition-bounds.md +83 -0
- package/docs/architecture/decisions/0008-task-binding-normalization.md +88 -0
- package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +80 -0
- package/docs/architecture/decisions/0010-driver-protocol-cut.md +36 -0
- package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +125 -0
- package/docs/architecture/decisions/README.md +62 -0
- package/docs/architecture/improvement.md +479 -0
- package/docs/architecture/internals/classification.md +154 -0
- package/docs/architecture/internals/fresh-host-rebuild-runbook.md +40 -0
- package/docs/architecture/internals/functional-contract-patterns.md +340 -0
- package/docs/architecture/internals/health-advisories.md +110 -0
- package/docs/architecture/internals/improve-workflow.md +719 -0
- package/docs/architecture/internals/indexing.md +498 -0
- package/docs/architecture/internals/registry-network-boundary.md +57 -0
- package/docs/architecture/internals/search.md +248 -0
- package/docs/architecture/internals/storage-locations.md +943 -0
- package/docs/architecture/persisted-data-compat.md +68 -0
- package/docs/architecture/reviews/env-secret-access.md +319 -0
- package/docs/architecture/runtime-boundary-design.md +153 -0
- package/docs/architecture/specs/0.9.0-close-out-plan.md +361 -0
- package/docs/architecture/specs/0.9.0-decisions.md +532 -0
- package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +745 -0
- package/docs/architecture/specs/0.9.0-open-items-register.md +135 -0
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +740 -0
- package/docs/architecture/specs/0.9.0-release-surface-review.md +636 -0
- package/docs/architecture/specs/agent-command-engine-model-design.md +513 -0
- package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +501 -0
- package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +291 -0
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +2059 -0
- package/docs/architecture/specs/di-seams-plan.md +539 -0
- package/docs/architecture/specs/driver-protocol-keep-or-cut.md +275 -0
- package/docs/architecture/specs/fact-asset-type.md +142 -0
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +634 -0
- package/docs/architecture/specs/okf-support.md +148 -0
- package/docs/architecture/specs/ref.md +329 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/README.md +72 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +152 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +352 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +300 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +402 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +758 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +645 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +602 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +27 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +27 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +74 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +15 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +132 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +147 -0
- package/docs/architecture/specs/stash-conventions-code-spec.md +275 -0
- package/docs/architecture/specs/stash-organization-conventions.md +377 -0
- package/docs/architecture/specs/task-workflow-format-unification.md +721 -0
- package/docs/architecture/specs/workflow-engine-buy-vs-build.md +231 -0
- package/docs/architecture/specs/workflow-format-unification.md +432 -0
- package/docs/architecture/testing/manual-testing-checklist.md +3411 -0
- package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +924 -0
- package/docs/architecture/testing/task-workflow-manual-tests.md +1720 -0
- package/docs/architecture/testing/testing-workflow.md +446 -0
- package/docs/architecture/workflow-engine.md +223 -0
- package/docs/guides/README.md +33 -0
- package/docs/guides/author-bundles.md +310 -0
- package/docs/guides/author-workflows.md +490 -0
- package/docs/guides/bundles.md +323 -0
- package/docs/guides/capture-knowledge.md +136 -0
- package/docs/guides/claude-code-vs-akm-workflows.md +29 -0
- package/docs/guides/concepts.md +93 -0
- package/docs/guides/discover-and-load.md +158 -0
- package/docs/guides/getting-started.md +240 -0
- package/docs/guides/improve-the-library.md +247 -0
- package/docs/guides/knowledge-management.md +18 -0
- package/docs/guides/recipes/headless-install.md +97 -0
- package/docs/guides/recipes/website-source.md +28 -0
- package/docs/guides/run-workflows.md +279 -0
- package/docs/guides/scheduling.md +131 -0
- package/docs/guides/use-with-any-agent.md +174 -0
- package/docs/guides/wikis.md +81 -0
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/README.md +19 -0
- package/docs/maintainers/curate-workmap.md +335 -0
- package/docs/maintainers/eval.md +3 -0
- package/docs/maintainers/local-development.md +108 -0
- package/docs/maintainers/release-checklist.md +135 -0
- package/docs/migration/README.md +4 -2
- package/docs/migration/release-notes/0.0.13.md +1 -1
- package/docs/migration/release-notes/0.1.0.md +1 -1
- package/docs/migration/release-notes/0.10.0.md +201 -0
- package/docs/migration/release-notes/0.2.0.md +1 -1
- package/docs/migration/release-notes/0.3.0.md +1 -1
- package/docs/migration/release-notes/0.5.0.md +1 -1
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.3.md +2 -2
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +1 -1
- package/docs/migration/release-notes/0.8.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +1 -1
- package/docs/migration/release-notes/0.9.14.md +1 -1
- package/docs/migration/release-notes/0.9.15.md +1 -1
- package/docs/migration/release-notes/0.9.16.md +1 -1
- package/docs/migration/release-notes/0.9.17.md +1 -1
- package/docs/migration/release-notes/0.9.19.md +1 -1
- package/docs/migration/release-notes/0.9.2.md +1 -1
- package/docs/migration/release-notes/README.md +28 -31
- package/docs/migration/v0.5-to-v0.6.md +759 -0
- package/docs/migration/v0.8-to-v0.9.md +1 -1
- package/docs/migration/v0.9-to-v0.10.md +507 -0
- package/docs/plans/0.9.2-agent-command-workflow-plan.md +423 -0
- package/docs/posts/README.md +30 -0
- package/docs/posts/akm-command-tour.md +495 -0
- package/docs/posts/akm-logo-sized.webp +0 -0
- package/docs/posts/belief-aware-memory-12.md +139 -0
- package/docs/posts/config-v2-lessons-16.md +191 -0
- package/docs/posts/improve-loop-autonomous-10.md +225 -0
- package/docs/posts/improve-pipeline-debugging-0803.md +179 -0
- package/docs/posts/intro-part-01.md +76 -0
- package/docs/posts/intro-part-02.md +221 -0
- package/docs/posts/intro-part-03.md +190 -0
- package/docs/posts/intro-part-04.md +221 -0
- package/docs/posts/llm-mode-reflect-13.md +166 -0
- package/docs/posts/openpalm-built-on-akm.md +86 -0
- package/docs/posts/progressive-disclosure-06.md +129 -0
- package/docs/posts/proposal-safety-net-15.md +112 -0
- package/docs/posts/release-0.5.0.md +247 -0
- package/docs/posts/release-0.6.0.md +117 -0
- package/docs/posts/release-0.7.0.md +159 -0
- package/docs/posts/release-0.8.0.md +332 -0
- package/docs/posts/scattered-skills-05.md +100 -0
- package/docs/posts/task-assets-persistent-workflows-11.md +222 -0
- package/docs/posts/team-skills-07.md +198 -0
- package/docs/posts/wiki-agent-knowledge-08.md +223 -0
- package/docs/posts/workflows-vaults-09.md +321 -0
- package/docs/product-surface.md +54 -0
- package/docs/reference/README.md +2 -2
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +166 -0
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +163 -192
- package/docs/reference/configuration.md +101 -20
- package/docs/reference/data-and-telemetry.md +2 -2
- package/docs/reference/env-and-secrets.md +126 -0
- package/docs/reference/memory.md +124 -0
- package/docs/reference/refs.md +156 -0
- package/docs/reference/registry.md +489 -0
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +141 -0
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +23 -16
- package/schemas/akm-config.json +2 -243
- package/schemas/akm-task.json +2 -3
- package/schemas/akm-workflow.json +47 -84
- package/dist/assets/improve-strategies/catchup.json +0 -41
- package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
- package/dist/assets/improve-strategies/quick.json +0 -17
- package/dist/assets/improve-strategies/reflect-distill.json +0 -29
- package/dist/assets/improve-strategies/thorough.json +0 -49
- package/dist/assets/prompts/retrieval-relevance-judge.md +0 -6
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
- package/dist/cli/retired-commands.js +0 -118
- package/dist/commands/improve/retrieval-gate.js +0 -132
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/indexer/feedback/utility-policy.js +0 -75
- package/dist/integrations/harnesses/ids.js +0 -28
- package/dist/tasks/source/task-source-v3-frozen.js +0 -424
- package/dist/tasks/source/task-to-v4.js +0 -863
- package/dist/workflows/exec/worktree.js +0 -372
- package/dist/workflows/freeze/child-output-references.js +0 -83
- package/dist/workflows/freeze/identity.js +0 -22
- package/dist/workflows/freeze/targets/script.js +0 -50
- package/dist/workflows/freeze/targets/task.js +0 -124
- package/dist/workflows/github-yaml.js +0 -506
- package/dist/workflows/runtime/run-outputs.js +0 -80
|
@@ -0,0 +1,1720 @@
|
|
|
1
|
+
# Manual tests — `akm task` and `akm workflow` (0.9.2)
|
|
2
|
+
|
|
3
|
+
This document is a focused manual-QA suite for the `akm task` and `akm
|
|
4
|
+
workflow` subsystems shipped in PR #844 ("Task/workflow final refactor:
|
|
5
|
+
typed task inputs, child workflows, v3 retirement"). It sits alongside
|
|
6
|
+
[`manual-testing-checklist.md`](./manual-testing-checklist.md) (the
|
|
7
|
+
general-purpose runbook, whose §14/§15 give a lighter pass over the same
|
|
8
|
+
commands) as the deeper, field-level reference for this specific feature
|
|
9
|
+
area — use it when validating a task/workflow release or investigating a
|
|
10
|
+
regression in scheduling, typed inputs, child workflows, or the
|
|
11
|
+
task↔workflow seam.
|
|
12
|
+
|
|
13
|
+
**Verified against:** `akm` `0.9.2-alpha.5`, commit `fb7bef3f` (branch
|
|
14
|
+
`release/0.9.2`). Every expected result marked "verified live" below was
|
|
15
|
+
captured by actually running that exact command against that exact commit
|
|
16
|
+
during authoring/review of this document, via `bun src/cli.ts` in an
|
|
17
|
+
isolated sandbox (see [Setup](#setup--one-sandbox-for-every-non-destructive-test)
|
|
18
|
+
below) — not inferred from source or docs. Anything not run live is marked
|
|
19
|
+
`UNVERIFIED` in bold, inline, at the point it applies. **Re-verify the exact
|
|
20
|
+
JSON field names, error strings, and exit codes here before relying on them
|
|
21
|
+
against a later release** — none of this is covered by automated tests
|
|
22
|
+
(that's the point of a manual suite), so it can drift silently.
|
|
23
|
+
|
|
24
|
+
The two defects discovered during that live pass were fixed before stable:
|
|
25
|
+
#846 is now a cross-bundle scheduler-isolation regression, and #847 is now an
|
|
26
|
+
abandon/resume durable-spine regression. Their corrected expectations below
|
|
27
|
+
are pinned by automated integration coverage on the stable candidate; the
|
|
28
|
+
remaining "verified live" labels still refer to the alpha.5 run above.
|
|
29
|
+
|
|
30
|
+
## Read this first — the one safety-critical finding
|
|
31
|
+
|
|
32
|
+
**`akm task sync` (and, to a lesser extent, `akm task add`) write to the
|
|
33
|
+
real, global, per-OS-user scheduler (cron / launchd / schtasks) — no
|
|
34
|
+
`AKM_*_DIR` environment variable isolates it.** Setting `AKM_BUNDLE_DIR`,
|
|
35
|
+
`AKM_DATA_DIR`, etc. isolates task *files* and akm's own state, but the
|
|
36
|
+
scheduler is one shared, host-global resource with no concept of your
|
|
37
|
+
sandbox.
|
|
38
|
+
|
|
39
|
+
Before #846 was fixed, `task sync`'s orphan-removal used bundle-name equality
|
|
40
|
+
without also confirming the installed entry's resolved bundle path. Two
|
|
41
|
+
unregistered bundles with the same directory basename could therefore make
|
|
42
|
+
one bundle inspect another's scheduler entry as an orphan. Stable 0.9.2
|
|
43
|
+
requires both the matching name and matching resolved owner path for this
|
|
44
|
+
primary-bundle case, and refuses to remove an entry whose owner path cannot be
|
|
45
|
+
established. DT-5 below pins that boundary. **There is still no `--dry-run`
|
|
46
|
+
for `task add`/`task sync`, so scheduler tests remain destructive.**
|
|
47
|
+
|
|
48
|
+
Practical rule for every test below that touches `add`/`sync`:
|
|
49
|
+
|
|
50
|
+
1. Never run them on a machine with real akm-managed scheduler entries
|
|
51
|
+
unless you have taken a `crontab -l` (or platform equivalent) backup
|
|
52
|
+
immediately beforehand and can restore it.
|
|
53
|
+
2. Prefer an explicit, collision-proof `--bundle <name>` so evidence and
|
|
54
|
+
cleanup are unambiguous, even though #846 now protects basename collisions.
|
|
55
|
+
3. Prefer a disposable container/VM with an empty scheduler.
|
|
56
|
+
4. Every destructive test below states its own backup/restore commands and
|
|
57
|
+
uses an obviously-throwaway id (`akm-manual-test-*`). Follow them exactly;
|
|
58
|
+
do not substitute `akm task sync` as a "cleanup" step (it can remove
|
|
59
|
+
entries the test didn't create).
|
|
60
|
+
|
|
61
|
+
`akm task doctor` is read-only but **still reads the real OS scheduler**
|
|
62
|
+
regardless of `AKM_*_DIR` isolation — seeing your host's real cron entries
|
|
63
|
+
in `doctor` output during an "isolated" test is expected, not a bug.
|
|
64
|
+
|
|
65
|
+
## Setup — one sandbox for every non-destructive test
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
export AKM_SANDBOX="$(mktemp -d /tmp/akm-task-workflow-qa.XXXXXX)"
|
|
69
|
+
export AKM_BUNDLE_DIR="$AKM_SANDBOX/bundle"
|
|
70
|
+
export AKM_CONFIG_DIR="$AKM_SANDBOX/config"
|
|
71
|
+
export AKM_DATA_DIR="$AKM_SANDBOX/data"
|
|
72
|
+
export AKM_CACHE_DIR="$AKM_SANDBOX/cache"
|
|
73
|
+
export AKM_STATE_DIR="$AKM_SANDBOX/state"
|
|
74
|
+
mkdir -p "$AKM_BUNDLE_DIR"/{tasks,workflows,commands} \
|
|
75
|
+
"$AKM_CONFIG_DIR" "$AKM_DATA_DIR" "$AKM_CACHE_DIR" "$AKM_STATE_DIR"
|
|
76
|
+
cat > "$AKM_CONFIG_DIR/config.json" <<'EOF'
|
|
77
|
+
{"configVersion":"0.9.0","semanticSearchMode":"off","registries":[]}
|
|
78
|
+
EOF
|
|
79
|
+
cd "$AKM_SANDBOX" # workflow list/status scope by cwd — see WF-8
|
|
80
|
+
|
|
81
|
+
# Pick ONE binary and use it consistently for a given test run:
|
|
82
|
+
akm() { bun /home/founder3/code/github/itlackey/akm/src/cli.ts "$@"; } # repo checkout
|
|
83
|
+
# akm() { "$AKM_SANDBOX/npm/bin/akm" "$@"; } # after: npm i -g --prefix "$AKM_SANDBOX/npm" akm-cli@0.9.2
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This hand-written `config.json` is enough for every `task` and `workflow`
|
|
87
|
+
command below — they resolve directly against `AKM_BUNDLE_DIR` without the
|
|
88
|
+
bundle needing to be a registered source. **The one exception is TASK-14**
|
|
89
|
+
(migration), where `migrate status`/`apply` require the bundle to be
|
|
90
|
+
registered via `akm setup` instead; that test gives its own setup variant.
|
|
91
|
+
|
|
92
|
+
**Cleanup for every non-destructive test:** `rm -rf "$AKM_SANDBOX"`. Nothing
|
|
93
|
+
outside it is touched. Destructive tests (final section) have their own,
|
|
94
|
+
separate cleanup that includes restoring the OS scheduler.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Summary table
|
|
99
|
+
|
|
100
|
+
| ID | Name | Destructive | Platform | Live-verified | Expected-to-fail pending |
|
|
101
|
+
|---|---|:---:|---|:---:|---|
|
|
102
|
+
| TASK-1 | Typed input, default provenance | No | Any | Yes | — |
|
|
103
|
+
| TASK-2 | Provenance from explicit flag | No | Any | Yes | — |
|
|
104
|
+
| TASK-3 | Wrong-type input rejected | No | Any | Yes | — |
|
|
105
|
+
| TASK-4 | Missing required input: `explain` lenient, `run` strict | No | Any | Yes | — |
|
|
106
|
+
| TASK-5 | Undeclared input flag rejected | No | Any | Yes | — |
|
|
107
|
+
| TASK-6 | Reserved flag name collision | No | Any | Yes | — |
|
|
108
|
+
| TASK-7 | Secret-shaped default redacted in `explain` | No | Any | Yes | — |
|
|
109
|
+
| TASK-8 | Schedule-binding provenance | No | Any | Yes | — |
|
|
110
|
+
| TASK-9 | Required input w/o default bricks a schedule entry | No | Any | Yes | — |
|
|
111
|
+
| TASK-10 | `run` happy path; inputs validated then discarded | No | Any | Yes | — |
|
|
112
|
+
| TASK-11 | `history` reflects a real run | No | Any | Yes | — |
|
|
113
|
+
| TASK-12 | `run` on nonexistent task id (exit 1, not 2) | No | Any | Yes | — |
|
|
114
|
+
| TASK-13 | Pre-v4 stored task is rejected with a migration hint | No | Any | Yes | — |
|
|
115
|
+
| TASK-14 | v3→v4 migration end to end | No | Any | Yes | — |
|
|
116
|
+
| TASK-15 | `doctor` reports backend/paths | No | Linux verified; macOS/Windows **UNVERIFIED** | Partial | — |
|
|
117
|
+
| TASK-16 | Ineligible-checkout guard fires before any scheduler write | No | Any | Partial | — |
|
|
118
|
+
| WF-1 | Author and run an exec workflow end to end | No | Any | Yes | — |
|
|
119
|
+
| WF-2 | Default `create` template needs a judge engine | No | Any | Yes | — |
|
|
120
|
+
| WF-3 | Child workflow: `plan` expansion + `run` execution | No | Any | Yes | — |
|
|
121
|
+
| WF-4 | `plan` makes zero durable writes | No | Any | Yes | — |
|
|
122
|
+
| WF-5 | Failing step surfaces a clear diagnostic | No | Any | Yes | — |
|
|
123
|
+
| WF-6 | Blocked run → `abandon` → `resume` | No | Any | Alpha.5 bug reproduced; stable regression automated | — |
|
|
124
|
+
| WF-7 | Active (paused) run → `abandon` → `resume` (contrast to WF-6) | No | Any | Yes | — |
|
|
125
|
+
| WF-8 | `list` is cwd-scoped; `status <id>` is not | No | Any | Yes | — |
|
|
126
|
+
| WF-9 | Pre-`irVersion`-5 stored plan is retired | No | Any | Yes | — |
|
|
127
|
+
| WF-10 | Removed `inherit_env` is rejected | No | Any | Yes | — |
|
|
128
|
+
| INT-1 | A task that runs a workflow | No | Any | Yes | — |
|
|
129
|
+
| INT-2 | (removed in 0.10: a workflow step that targets a task) | — | — | — | — |
|
|
130
|
+
| INT-3 | Failure propagation: `task history` vs `workflow status` | No | Any | Yes | — |
|
|
131
|
+
| INT-4 | Identity across the seam: abandon then re-invoke | No | Any | Yes | — |
|
|
132
|
+
| INT-5 | Shipped `improve/*.yml` tasks as a real-world case (`task explain` only) | No | Any | Yes | — |
|
|
133
|
+
| DT-1 | `task add` happy path installs a real cron entry | **Yes** | Linux (cron) | Partial | — |
|
|
134
|
+
| DT-2 | `task add --disabled` writes a disabled binding | **Yes** | Linux (cron) | Partial | — |
|
|
135
|
+
| DT-3 | `task sync` drift reconciliation (shell-target task) | **Yes** | Linux (cron), VM recommended | No — **UNVERIFIED** | — |
|
|
136
|
+
| DT-4 | `task sync` reconciling a workflow-targeting task's schedule | **Yes** | Linux (cron), VM recommended | No — **UNVERIFIED** | — |
|
|
137
|
+
| DT-5 | Cross-bundle name collision cannot make `sync` remove another bundle's entries | **Yes** | Linux (cron), VM required | Automated; manual platform run **UNVERIFIED** | — |
|
|
138
|
+
| DT-6 | macOS launchd equivalents of DT-1..DT-3 | **Yes** | macOS — **PLATFORM-GATED, UNVERIFIED** | No | Issue #770 (no automated coverage) |
|
|
139
|
+
| DT-7 | Windows schtasks equivalents of DT-1..DT-3 | **Yes** | Windows — **PLATFORM-GATED, UNVERIFIED** | No | Issue #770 (no automated coverage) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Tasks
|
|
144
|
+
|
|
145
|
+
Scope: `akm task add|run|explain|history|sync|doctor`, non-destructive
|
|
146
|
+
subset. Destructive/`sync`/`add` scheduler-mutation tests are in
|
|
147
|
+
[Destructive & platform-gated](#destructive--platform-gated-tests) at the
|
|
148
|
+
end.
|
|
149
|
+
|
|
150
|
+
### TASK-1 — Typed input, default provenance
|
|
151
|
+
|
|
152
|
+
**Why it matters:** the most common real path — a task with typed inputs a
|
|
153
|
+
user never overrides. If `explain`'s provenance tracking is wrong here,
|
|
154
|
+
every other provenance claim is suspect too.
|
|
155
|
+
|
|
156
|
+
**Preconditions:** common sandbox above.
|
|
157
|
+
|
|
158
|
+
**Setup:**
|
|
159
|
+
```sh
|
|
160
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-echo.yml" <<'EOF'
|
|
161
|
+
version: 4
|
|
162
|
+
name: Manual test echo
|
|
163
|
+
description: Echoes a greeting for manual QA
|
|
164
|
+
inputs:
|
|
165
|
+
who:
|
|
166
|
+
type: string
|
|
167
|
+
default: world
|
|
168
|
+
loud:
|
|
169
|
+
type: boolean
|
|
170
|
+
default: false
|
|
171
|
+
run: echo "hello $AKM_TASK_INPUTS"
|
|
172
|
+
schedule: "@daily"
|
|
173
|
+
EOF
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Command:** `akm task explain manual-test-echo --format json`
|
|
177
|
+
|
|
178
|
+
**Expected result (verified live):** exit `0`;
|
|
179
|
+
`.suppliedInputs.who == {"value":"world","provenance":"default"}`;
|
|
180
|
+
`.suppliedInputs.loud == {"value":false,"provenance":"default"}`;
|
|
181
|
+
`.inputDeclarations.who.default == "world"`;
|
|
182
|
+
`.schedule[0].cron == "@daily"`, `.schedule[0].enabled == true`.
|
|
183
|
+
|
|
184
|
+
**Cleanup:** keep `manual-test-echo.yml` for TASK-2/TASK-10/TASK-11 if
|
|
185
|
+
running the suite in order, otherwise `rm "$AKM_BUNDLE_DIR/tasks/manual-test-echo.yml"`.
|
|
186
|
+
|
|
187
|
+
### TASK-2 — Provenance from an explicit flag
|
|
188
|
+
|
|
189
|
+
**Why it matters:** provenance is the whole point of `explain` — a user
|
|
190
|
+
debugging "why did my scheduled run use X" needs to trust this field.
|
|
191
|
+
|
|
192
|
+
**Preconditions:** `manual-test-echo.yml` from TASK-1.
|
|
193
|
+
|
|
194
|
+
**Command:** `akm task explain manual-test-echo --who Alice --format json`
|
|
195
|
+
|
|
196
|
+
**Expected result (verified live):** exit `0`;
|
|
197
|
+
`.suppliedInputs.who == {"value":"Alice","provenance":"flag"}`;
|
|
198
|
+
`.suppliedInputs.loud` unchanged (`provenance: "default"`).
|
|
199
|
+
|
|
200
|
+
**Cleanup:** none (read-only).
|
|
201
|
+
|
|
202
|
+
### TASK-3 — Wrong-type input value is rejected
|
|
203
|
+
|
|
204
|
+
**Why it matters:** typed inputs are only useful if bad values are caught
|
|
205
|
+
before reaching a command/script — the core defect-prevention claim of the
|
|
206
|
+
release.
|
|
207
|
+
|
|
208
|
+
**Preconditions:** `manual-test-echo.yml` from TASK-1.
|
|
209
|
+
|
|
210
|
+
**Command:** `akm task explain manual-test-echo --loud notabool`
|
|
211
|
+
|
|
212
|
+
**Expected result (verified live, exact):** exit `2`; stderr:
|
|
213
|
+
```json
|
|
214
|
+
{"ok":false,"error":"Task input \"--loud\" must be boolean.","code":"INPUT_BINDING_INVALID","hint":"Check the step's with: keys against the target's declared inputs."}
|
|
215
|
+
```
|
|
216
|
+
Also run `akm task run manual-test-echo --loud notabool` and confirm the
|
|
217
|
+
same `code`/exit `2` — `run` and `explain` share one validator.
|
|
218
|
+
|
|
219
|
+
**Cleanup:** none.
|
|
220
|
+
|
|
221
|
+
### TASK-4 — Missing `required: true` input: `explain` is lenient, `run` is not
|
|
222
|
+
|
|
223
|
+
**Why it matters:** a real trap — a user who runs `explain` on a
|
|
224
|
+
half-configured task sees no error and may assume it's fine to schedule,
|
|
225
|
+
then discovers at `run` time that it fails.
|
|
226
|
+
|
|
227
|
+
**Setup:**
|
|
228
|
+
```sh
|
|
229
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-required.yml" <<'EOF'
|
|
230
|
+
version: 4
|
|
231
|
+
name: Manual test required input
|
|
232
|
+
inputs:
|
|
233
|
+
ticket:
|
|
234
|
+
type: string
|
|
235
|
+
required: true
|
|
236
|
+
run: echo "ticket=$AKM_TASK_INPUTS"
|
|
237
|
+
EOF
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Commands and expected results (both verified live):**
|
|
241
|
+
1. `akm task explain manual-test-required --format json` → exit `0`,
|
|
242
|
+
`.suppliedInputs == {}` (no error — `ticket` is simply absent).
|
|
243
|
+
2. `akm task run manual-test-required` → exit `2`:
|
|
244
|
+
```json
|
|
245
|
+
{"ok":false,"error":"Task input flags do not satisfy the task's declared input schemas:\n - inputs.ticket: is required","code":"INPUT_BINDING_INVALID"}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Cleanup:** `rm "$AKM_BUNDLE_DIR/tasks/manual-test-required.yml"`.
|
|
249
|
+
|
|
250
|
+
### TASK-5 — Undeclared input flag is rejected
|
|
251
|
+
|
|
252
|
+
**Why it matters:** typo-tolerance — a fat-fingered flag name should get a
|
|
253
|
+
clear rejection, not a silent no-op.
|
|
254
|
+
|
|
255
|
+
**Preconditions:** `manual-test-echo.yml` from TASK-1.
|
|
256
|
+
|
|
257
|
+
**Command:** `akm task explain manual-test-echo --bogus x`
|
|
258
|
+
|
|
259
|
+
**Expected result (verified live, exact):** exit `2`:
|
|
260
|
+
```json
|
|
261
|
+
{"ok":false,"error":"Unknown task input \"--bogus\". Input flags must exactly match a declared task input.","code":"UNKNOWN_FLAG","hint":"Declared inputs: --loud, --who."}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### TASK-6 — Declaring an input that collides with a reserved `task run` flag
|
|
265
|
+
|
|
266
|
+
**Why it matters:** guards against a task input silently shadowing a
|
|
267
|
+
built-in flag (e.g. `bundle`, `format`); the docs say this is rejected at
|
|
268
|
+
parse time.
|
|
269
|
+
|
|
270
|
+
**Setup:**
|
|
271
|
+
```sh
|
|
272
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-reserved.yml" <<'EOF'
|
|
273
|
+
version: 4
|
|
274
|
+
name: Manual test reserved flag collision
|
|
275
|
+
inputs:
|
|
276
|
+
bundle:
|
|
277
|
+
type: string
|
|
278
|
+
default: x
|
|
279
|
+
run: echo hi
|
|
280
|
+
EOF
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Command:** `akm task explain manual-test-reserved`
|
|
284
|
+
|
|
285
|
+
**Expected result (verified live, exact):** exit `2`, `.code ==
|
|
286
|
+
"TASK_SOURCE_INVALID"`, error text is `Invalid task source v4 at
|
|
287
|
+
<path>:5: inputs.bundle collides with akm task run's own --bundle flag;
|
|
288
|
+
declare the input under a different name.`
|
|
289
|
+
|
|
290
|
+
**Cleanup:** `rm "$AKM_BUNDLE_DIR/tasks/manual-test-reserved.yml"`.
|
|
291
|
+
|
|
292
|
+
### TASK-7 — Secret-shaped input default is redacted in `explain`
|
|
293
|
+
|
|
294
|
+
**Why it matters:** `explain` is documented as secret-free — a
|
|
295
|
+
security-conscious user needs to trust this before pasting `explain` output
|
|
296
|
+
into a bug report or chat.
|
|
297
|
+
|
|
298
|
+
**Setup:**
|
|
299
|
+
```sh
|
|
300
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-secret.yml" <<'EOF'
|
|
301
|
+
version: 4
|
|
302
|
+
name: Manual test secret redaction
|
|
303
|
+
inputs:
|
|
304
|
+
api_token:
|
|
305
|
+
type: string
|
|
306
|
+
default: "sk-abcdef123456"
|
|
307
|
+
run: echo "token=$AKM_TASK_INPUTS"
|
|
308
|
+
EOF
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Command:** `akm task explain manual-test-secret`
|
|
312
|
+
|
|
313
|
+
**Expected result (verified live):** exit `0`; the literal value
|
|
314
|
+
`sk-abcdef123456` does **not** appear anywhere in stdout;
|
|
315
|
+
`.inputDeclarations.api_token.default == "<redacted>"` and
|
|
316
|
+
`.redacted == true`; `.suppliedInputs.api_token.value == "<redacted>"` and
|
|
317
|
+
`.redacted == true`. (A heuristic warning about the secret-suggesting name
|
|
318
|
+
is also printed on stderr — expected, not a failure.)
|
|
319
|
+
|
|
320
|
+
Verify outside akm: `grep -c sk-abcdef123456 <(akm task explain manual-test-secret)` must print `0`.
|
|
321
|
+
|
|
322
|
+
**Cleanup:** `rm "$AKM_BUNDLE_DIR/tasks/manual-test-secret.yml"`.
|
|
323
|
+
|
|
324
|
+
### TASK-8 — Schedule-binding provenance is distinct from manual-run provenance
|
|
325
|
+
|
|
326
|
+
**Why it matters:** exercises the third provenance value
|
|
327
|
+
(`schedule-binding`) and the fact that a schedule entry's own literal
|
|
328
|
+
inputs are shown per-entry, not merged into top-level `suppliedInputs`.
|
|
329
|
+
|
|
330
|
+
**Setup:**
|
|
331
|
+
```sh
|
|
332
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-schedbind.yml" <<'EOF'
|
|
333
|
+
version: 4
|
|
334
|
+
name: Manual test schedule-binding provenance
|
|
335
|
+
inputs:
|
|
336
|
+
scope:
|
|
337
|
+
type: string
|
|
338
|
+
enum: [changed, all]
|
|
339
|
+
default: changed
|
|
340
|
+
run: echo "scope=$AKM_TASK_INPUTS"
|
|
341
|
+
schedule:
|
|
342
|
+
- cron: "0 8 * * 1"
|
|
343
|
+
enabled: true
|
|
344
|
+
inputs: { scope: all }
|
|
345
|
+
EOF
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Command:** `akm task explain manual-test-schedbind`
|
|
349
|
+
|
|
350
|
+
**Expected result (verified live, exact):** exit `0`;
|
|
351
|
+
`.suppliedInputs.scope == {"value":"changed","provenance":"default"}`
|
|
352
|
+
(reflecting the bare call);
|
|
353
|
+
`.schedule[0].inputs.scope == {"value":"all","provenance":"schedule-binding"}`;
|
|
354
|
+
`.schedule[0].source == "schedule[0].cron"`.
|
|
355
|
+
|
|
356
|
+
Then `akm task run manual-test-schedbind --scheduled --scope all` → exit `0`
|
|
357
|
+
(the exact invocation tail `sync` would compile for this binding).
|
|
358
|
+
|
|
359
|
+
**Cleanup:** `rm "$AKM_BUNDLE_DIR/tasks/manual-test-schedbind.yml"`.
|
|
360
|
+
|
|
361
|
+
### TASK-9 — Required input with no default bricks the whole schedule entry at parse time
|
|
362
|
+
|
|
363
|
+
**Why it matters:** the sharpest footgun in typed inputs. Adding a
|
|
364
|
+
`required: true` input to an already-scheduled task, without also giving
|
|
365
|
+
the schedule entry a literal value, doesn't fail narrowly — **the whole task
|
|
366
|
+
file fails to parse**, for `explain`, `run`, even with the flag supplied
|
|
367
|
+
manually.
|
|
368
|
+
|
|
369
|
+
**Setup:**
|
|
370
|
+
```sh
|
|
371
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-badsched.yml" <<'EOF'
|
|
372
|
+
version: 4
|
|
373
|
+
name: Manual test bad schedule required input
|
|
374
|
+
inputs:
|
|
375
|
+
ticket:
|
|
376
|
+
type: string
|
|
377
|
+
required: true
|
|
378
|
+
run: echo "ticket=$AKM_TASK_INPUTS"
|
|
379
|
+
schedule: "0 8 * * 1"
|
|
380
|
+
EOF
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Commands and expected (both verified live, identical failure):**
|
|
384
|
+
1. `akm task explain manual-test-badsched`
|
|
385
|
+
2. `akm task run manual-test-badsched --ticket OPS-1` (supplying the flag
|
|
386
|
+
does **not** help)
|
|
387
|
+
|
|
388
|
+
Both: exit `2`, `.code == "TASK_SOURCE_INVALID"`, error text is `Invalid
|
|
389
|
+
task source v4 at <path>:8: schedule does not satisfy the task's declared
|
|
390
|
+
inputs once defaults are applied: inputs.ticket: is required. A scheduled
|
|
391
|
+
run supplies no input flags — give this schedule entry an inputs: value for
|
|
392
|
+
each input named above, or declare a default: on the input instead (a
|
|
393
|
+
required: true input may not carry one).`
|
|
394
|
+
|
|
395
|
+
**Cleanup:** `rm "$AKM_BUNDLE_DIR/tasks/manual-test-badsched.yml"`.
|
|
396
|
+
|
|
397
|
+
### TASK-10 — `run` happy path, and inputs are validated then discarded for non-workflow targets
|
|
398
|
+
|
|
399
|
+
**Why it matters:** for `run:`/`scripts/`/`commands/` targets, input values
|
|
400
|
+
are validated then **discarded** — they do not reach `AKM_TASK_INPUTS` or
|
|
401
|
+
the process environment. A user expecting `--who Bob` to show up in a shell
|
|
402
|
+
script via `$AKM_TASK_INPUTS` will be surprised.
|
|
403
|
+
|
|
404
|
+
**Preconditions:** `manual-test-echo.yml` from TASK-1.
|
|
405
|
+
|
|
406
|
+
**Command:** `akm task run manual-test-echo --who Bob --format json`
|
|
407
|
+
|
|
408
|
+
**Expected result (verified live):** exit `0`; `.result.status ==
|
|
409
|
+
"completed"`, `.result.detail.exitCode == 0`; `.result.log` names a file
|
|
410
|
+
under `$AKM_CACHE_DIR/tasks/logs/manual-test-echo/`.
|
|
411
|
+
|
|
412
|
+
Verify outside akm: `cat "$(akm task run manual-test-echo --who Bob --format json | jq -r .result.log)"`
|
|
413
|
+
prints `hello ` — literally empty after "hello ", **not** "hello Bob". This
|
|
414
|
+
is the falsifiable proof that `--who Bob` was validated and discarded, not
|
|
415
|
+
delivered.
|
|
416
|
+
|
|
417
|
+
### TASK-11 — `history` reflects a real run
|
|
418
|
+
|
|
419
|
+
**Why it matters:** `history` is the only place a user reviews what
|
|
420
|
+
happened after the fact (e.g. debugging a failed scheduled run days later)
|
|
421
|
+
— it must agree with what `run` itself reported.
|
|
422
|
+
|
|
423
|
+
**Preconditions:** run TASK-10 first so at least one history row exists.
|
|
424
|
+
|
|
425
|
+
**Commands:** `akm task history --format json` and `akm task history --id
|
|
426
|
+
manual-test-echo --format json`
|
|
427
|
+
|
|
428
|
+
**Expected result (verified live):** exit `0` both; `.rows[0].id ==
|
|
429
|
+
"manual-test-echo"`, `.rows[0].status == "completed"`,
|
|
430
|
+
`.rows[0].detail.exitCode == 0`, `.rows[0].log` matches TASK-10's log path
|
|
431
|
+
exactly.
|
|
432
|
+
|
|
433
|
+
Also: `akm task history --id does-not-exist` → exit `0`, `.rows == []`
|
|
434
|
+
(empty result, not an error).
|
|
435
|
+
|
|
436
|
+
### TASK-12 — `run` on a nonexistent task id
|
|
437
|
+
|
|
438
|
+
**Why it matters:** distinguishes "usage error" (exit 2) from "resource not
|
|
439
|
+
found" (exit 1) — a real difference for scripts/CI wrapping `akm task run`.
|
|
440
|
+
|
|
441
|
+
**Command:** `akm task run nonexistent-task-xyz`
|
|
442
|
+
|
|
443
|
+
**Expected result (verified live, exact):** exit `1` (not 2);
|
|
444
|
+
```json
|
|
445
|
+
{"ok":false,"error":"Task \"nonexistent-task-xyz\" was not found in the configured \"akm\" component.","code":"ASSET_NOT_FOUND","hint":"Run `akm search <query>` or `akm index` to refresh the index."}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### TASK-13 — A pre-v4 stored task is unusable, and the error names the fix
|
|
449
|
+
|
|
450
|
+
**Why it matters:** the headline real-user-impact change in alpha.5.
|
|
451
|
+
Anyone upgrading from an older akm with existing task files hits this on
|
|
452
|
+
the very next `run`/`sync`.
|
|
453
|
+
|
|
454
|
+
**Setup** (uses the project's own v2 fixture; v3 behaves identically with a
|
|
455
|
+
different version number in the message):
|
|
456
|
+
```sh
|
|
457
|
+
cp /home/founder3/code/github/itlackey/akm/tests/fixtures/manual-qa/bundle/tasks/manual-success.yml \
|
|
458
|
+
"$AKM_BUNDLE_DIR/tasks/manual-success.yml"
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**Commands:** `akm task run manual-success` and `akm task explain
|
|
462
|
+
manual-success`
|
|
463
|
+
|
|
464
|
+
**Expected result (verified live, exact, both commands identical):** exit
|
|
465
|
+
`2`;
|
|
466
|
+
```json
|
|
467
|
+
{"ok":false,"error":"TASK_SCHEMA_VERSION_UNSUPPORTED: Task at <path> uses task schema version 2; this release reads only version 4. Run `akm migrate apply` to convert it.","code":"TASK_SCHEMA_VERSION_UNSUPPORTED","hint":"Run `akm migrate apply --dry-run` to preview the task-v3 to task-source-v4 conversion, then run `akm migrate apply`."}
|
|
468
|
+
```
|
|
469
|
+
The hint says "task-v3 to task-source-v4" even for a v2 file — that's the
|
|
470
|
+
shipped wording (the v2→v3→v4 chain is one hint), not a bug.
|
|
471
|
+
|
|
472
|
+
**Cleanup:** `rm "$AKM_BUNDLE_DIR/tasks/manual-success.yml"`.
|
|
473
|
+
|
|
474
|
+
### TASK-14 — v3→v4 migration end to end: status, dry-run, apply, backup, and post-migration runnability
|
|
475
|
+
|
|
476
|
+
**Why it matters:** TASK-13 shows the wall a real user hits; this shows
|
|
477
|
+
whether `akm migrate` actually gets them through it. The most important
|
|
478
|
+
test in this document for real upgrade risk.
|
|
479
|
+
|
|
480
|
+
**Setup — this test needs the bundle registered as a source**
|
|
481
|
+
(`migrate status`/`apply` scan registered bundle *sources*, not just
|
|
482
|
+
`AKM_BUNDLE_DIR`'s contents — a hand-written `config.json` is NOT enough
|
|
483
|
+
here, unlike every other test in this document):
|
|
484
|
+
```sh
|
|
485
|
+
akm setup --config '{"semanticSearchMode":"off","registries":[]}' \
|
|
486
|
+
--dir "$AKM_BUNDLE_DIR" --no-init --format json
|
|
487
|
+
# NOTE: the scratch/tmp path triggers a "transient directory" refusal in
|
|
488
|
+
# `akm setup` — set AKM_FORCE_SETUP_TMP_STASH=1 first (expected, not a bug).
|
|
489
|
+
|
|
490
|
+
cat > "$AKM_BUNDLE_DIR/tasks/manual-test-v3.yml" <<'EOF'
|
|
491
|
+
version: 3
|
|
492
|
+
name: v3 fixture for migration test
|
|
493
|
+
run: echo "v3 task"
|
|
494
|
+
akm:
|
|
495
|
+
schedule: "0 6 * * *"
|
|
496
|
+
EOF
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Commands and expected results (all verified live):**
|
|
500
|
+
1. `akm migrate status --format json` → exit `0`;
|
|
501
|
+
`.taskV4Migration.files[0].status == "changed"`,
|
|
502
|
+
`.reason == "task-converted"`.
|
|
503
|
+
2. `akm migrate apply --dry-run --format json` → exit `0`; identical
|
|
504
|
+
`status`/`reason` fields; the file on disk is byte-identical to before —
|
|
505
|
+
verify with `diff` against the pre-dry-run content.
|
|
506
|
+
3. `akm migrate apply --format json` → exit `0`; JSON includes
|
|
507
|
+
`.taskV4Applied == 1` and a `.taskV4BackupPath` under
|
|
508
|
+
`$AKM_DATA_DIR/backups/task-v4/<timestamp>-<uuid>/`.
|
|
509
|
+
4. Verify outside akm: `cat "$AKM_BUNDLE_DIR/tasks/manual-test-v3.yml"` now
|
|
510
|
+
shows `version: 4` with the `akm.schedule` value hoisted to a top-level
|
|
511
|
+
`schedule: 0 6 * * *` key; the backup path holds the original v3 bytes.
|
|
512
|
+
5. `akm task explain manual-test-v3 --format json` → exit `0`,
|
|
513
|
+
`.sourceVersion == 4`, `.schedule[0].cron == "0 6 * * *"` — the task is
|
|
514
|
+
now actually runnable, closing the loop from TASK-13.
|
|
515
|
+
|
|
516
|
+
**Cleanup:** `rm -f "$AKM_BUNDLE_DIR/tasks/manual-test-v3.yml"`.
|
|
517
|
+
|
|
518
|
+
### TASK-15 — `doctor` on this platform
|
|
519
|
+
|
|
520
|
+
**Why it matters:** `doctor` is the first-stop diagnostic a user runs when
|
|
521
|
+
scheduling seems broken; its reported backend/paths must match reality.
|
|
522
|
+
|
|
523
|
+
**Command:** `akm task doctor --format json`
|
|
524
|
+
|
|
525
|
+
**Expected result (verified live on Linux/cron):** exit `0`;
|
|
526
|
+
`.backend == "cron"`; `.logDir`/`.historyDir` are under
|
|
527
|
+
`$AKM_CACHE_DIR/tasks/...` (cache-dir isolation applies to logs/history
|
|
528
|
+
even though the scheduler itself isn't isolated); `.akm.eligible == false`
|
|
529
|
+
and `.akm.kind == "checkout"` when run via `bun src/cli.ts` (a repo checkout
|
|
530
|
+
is never scheduler-eligible — see TASK-16); `.remediation == "akm task sync
|
|
531
|
+
--rebind"` is present whenever any bindings are ineligible.
|
|
532
|
+
|
|
533
|
+
Verify outside akm: cross-check `.bindings[].taskIds` against `crontab -l |
|
|
534
|
+
grep -oP '(?<=# akm:task )\S+(?= BEGIN)'` — the two lists should match
|
|
535
|
+
(read-only both sides).
|
|
536
|
+
|
|
537
|
+
**`UNVERIFIED`: the exact `doctor` output shape on macOS (`launchd`) and
|
|
538
|
+
Windows (`schtasks`) backends — only Linux/cron was exercised live for this
|
|
539
|
+
document.** Platform-gated: run the macOS/Windows equivalent on those OSes;
|
|
540
|
+
expect `.backend` to read `"launchd"` or `"schtasks"` respectively (inferred
|
|
541
|
+
from source naming, not observed).
|
|
542
|
+
|
|
543
|
+
### TASK-16 — Ineligible-invocation guard fires before any scheduler write
|
|
544
|
+
|
|
545
|
+
**Why it matters:** the one `add`/`sync`-adjacent test that's genuinely safe
|
|
546
|
+
on a real machine, because it fails *before* touching the scheduler.
|
|
547
|
+
|
|
548
|
+
**Preconditions:** run from a **repo checkout** (`bun src/cli.ts`), not an
|
|
549
|
+
npm-global/standalone install — those are scheduler-eligible and this test
|
|
550
|
+
would not short-circuit the same way (see [DT-1](#dt-1--task-add-happy-path-installs-a-real-cron-entry-linux-cron)
|
|
551
|
+
instead).
|
|
552
|
+
|
|
553
|
+
**Command:** `akm task sync` in the common sandbox, with
|
|
554
|
+
`manual-test-echo.yml` (a `schedule:`-bearing task) present.
|
|
555
|
+
|
|
556
|
+
**Expected result:** exit `2`, `.code == "INVALID_FLAG_VALUE"`, error text
|
|
557
|
+
`Refusing to reconcile native scheduler bindings from an ineligible
|
|
558
|
+
checkout invocation (<argv...>).` (Source:
|
|
559
|
+
`src/commands/tasks/tasks.ts`, `resolveAndValidateSchedulerInvocation`.)
|
|
560
|
+
|
|
561
|
+
**Historical note:** the alpha.5 live run encountered #846's unrelated-entry
|
|
562
|
+
fingerprint guard before this invocation guard. Stable 0.9.2's owner-path
|
|
563
|
+
scoping prevents that cross-bundle detour, so the expected result above is the
|
|
564
|
+
one this test now requires. Its exact wording remains **UNVERIFIED live** in
|
|
565
|
+
this manual suite, although the guard has automated coverage.
|
|
566
|
+
|
|
567
|
+
Verify outside akm: `crontab -l` (or platform equivalent) is byte-identical
|
|
568
|
+
before and after.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Workflows
|
|
573
|
+
|
|
574
|
+
Scope: `akm workflow status|list|create|resume|abandon|run|plan`. Every
|
|
575
|
+
fixture below uses `unit: { exec: { command: [...] } }` shell steps — no
|
|
576
|
+
engine/LLM credentials are needed except WF-6, which needs an engine
|
|
577
|
+
*configured* (not reachable) to force a deterministic judge-infrastructure
|
|
578
|
+
failure.
|
|
579
|
+
|
|
580
|
+
### WF-1 — Author and run an exec workflow end to end
|
|
581
|
+
|
|
582
|
+
**Why it matters:** the single most common real workflow — create a
|
|
583
|
+
document, wire up steps, run it — must work with zero ceremony.
|
|
584
|
+
|
|
585
|
+
**Setup:**
|
|
586
|
+
```sh
|
|
587
|
+
akm workflow create demo --format json
|
|
588
|
+
```
|
|
589
|
+
**Expected:** exit `0`, JSON `{"ok": true, "ref": "bundle//workflows/demo",
|
|
590
|
+
"path": ".../bundle/workflows/demo.md", "bundleDir": "<AKM_SANDBOX>/bundle",
|
|
591
|
+
"shape": "workflow-create", "schemaVersion": 1}`. Note the field is
|
|
592
|
+
**`bundleDir`**, not `stashDir` (renamed this release — a script still
|
|
593
|
+
reading `stashDir` here gets `undefined`).
|
|
594
|
+
|
|
595
|
+
Overwrite `$AKM_BUNDLE_DIR/workflows/demo.md` (the default template needs a
|
|
596
|
+
judge engine — see WF-2):
|
|
597
|
+
```markdown
|
|
598
|
+
---
|
|
599
|
+
type: workflow
|
|
600
|
+
description: Exec-only smoke test workflow (no engine required)
|
|
601
|
+
updated: 2026-01-01
|
|
602
|
+
tags: [example]
|
|
603
|
+
steps:
|
|
604
|
+
- id: first-step
|
|
605
|
+
unit:
|
|
606
|
+
exec:
|
|
607
|
+
command: ["bash", "-lc", "echo hello-first"]
|
|
608
|
+
- id: second-step
|
|
609
|
+
inputs: [steps.first-step.output]
|
|
610
|
+
unit:
|
|
611
|
+
exec:
|
|
612
|
+
command: ["bash", "-lc", "echo hello-second"]
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
# Demo Workflow
|
|
616
|
+
|
|
617
|
+
## first-step
|
|
618
|
+
|
|
619
|
+
Print a greeting.
|
|
620
|
+
|
|
621
|
+
## second-step
|
|
622
|
+
|
|
623
|
+
Print a second greeting, using the first step's artifact as context.
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Command:** `akm workflow run workflows/demo --format json`
|
|
627
|
+
|
|
628
|
+
**Expected result (verified live):** exit `0`; `.run.status ==
|
|
629
|
+
"completed"`; `.run.currentStepId == null`; `.stepsProcessed == 2`,
|
|
630
|
+
`.done == true`; `.executed[0].stepId == "first-step"`,
|
|
631
|
+
`.executed[0].ok == true`; same shape for `"second-step"`. Two advisory
|
|
632
|
+
warnings about missing `output:` schemas print on stderr — non-fatal,
|
|
633
|
+
stdout remains valid JSON.
|
|
634
|
+
|
|
635
|
+
Then `akm workflow status <run-id> --format json`: `.workflow.steps[0]
|
|
636
|
+
.evidence.output == "hello-first"`; `.workflow.steps[1].evidence.output ==
|
|
637
|
+
"hello-second"`.
|
|
638
|
+
|
|
639
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"` (or reuse for WF-4/WF-7/WF-8, which
|
|
640
|
+
reference this fixture).
|
|
641
|
+
|
|
642
|
+
### WF-2 — Default `create` template needs a judge engine (first-run trap)
|
|
643
|
+
|
|
644
|
+
**Why it matters:** `akm workflow create <name>` scaffolds a template whose
|
|
645
|
+
second step ships with a non-empty `### gate` rubric by default. A
|
|
646
|
+
brand-new user who runs `create` then immediately `plan`s/`run`s without
|
|
647
|
+
configuring anything hits an opaque-looking failure.
|
|
648
|
+
|
|
649
|
+
**Preconditions:** fresh sandbox, no `engines`/`workflow.judgeEngine` in
|
|
650
|
+
config.json.
|
|
651
|
+
|
|
652
|
+
**Commands:**
|
|
653
|
+
```sh
|
|
654
|
+
akm workflow create raw --format json
|
|
655
|
+
akm workflow plan workflows/raw --format json
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Expected result (verified live, exact):** `create` exits `0`. `plan`
|
|
659
|
+
(untouched template) **fails**: exit **`78`**;
|
|
660
|
+
```json
|
|
661
|
+
{"ok": false, "error": "This workflow declares completion criteria but no verification engine is configured. Set workflow.judgeEngine to a named LLM or agent engine.", "code": "INVALID_CONFIG_FILE"}
|
|
662
|
+
```
|
|
663
|
+
Note the message says *what* to set but not *where* (`config.json`) or what
|
|
664
|
+
a valid value looks like — a real gap for a scaffolded template's
|
|
665
|
+
out-of-the-box failure, worth flagging in review even though it's not
|
|
666
|
+
incorrect.
|
|
667
|
+
|
|
668
|
+
### WF-3 — Child workflow: `plan` shows expansion, `run` executes it
|
|
669
|
+
|
|
670
|
+
**Why it matters:** child workflows are the headline capability of this
|
|
671
|
+
release. A parent step composing another workflow must show up correctly in
|
|
672
|
+
the frozen plan graph before anything runs, and actually drive the child to
|
|
673
|
+
completion, folding its last step output back into the parent step's
|
|
674
|
+
artifact.
|
|
675
|
+
|
|
676
|
+
**Setup:**
|
|
677
|
+
|
|
678
|
+
`$AKM_BUNDLE_DIR/workflows/child.md`:
|
|
679
|
+
```markdown
|
|
680
|
+
---
|
|
681
|
+
type: workflow
|
|
682
|
+
description: Child workflow composed by parent.md (exec-only, no engine required)
|
|
683
|
+
updated: 2026-01-01
|
|
684
|
+
tags: [example]
|
|
685
|
+
params:
|
|
686
|
+
greeting: { type: string, description: Greeting text bound from the parent step }
|
|
687
|
+
steps:
|
|
688
|
+
- id: greet
|
|
689
|
+
unit:
|
|
690
|
+
exec:
|
|
691
|
+
command: ["bash", "-lc", "echo child-said-hello"]
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
# Child Workflow
|
|
695
|
+
|
|
696
|
+
## greet
|
|
697
|
+
|
|
698
|
+
Print a fixed greeting.
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
`$AKM_BUNDLE_DIR/workflows/parent.md` (child workflows are Experimental):
|
|
702
|
+
```markdown
|
|
703
|
+
---
|
|
704
|
+
type: workflow
|
|
705
|
+
description: Parent composes child
|
|
706
|
+
steps:
|
|
707
|
+
- id: dispatch
|
|
708
|
+
unit:
|
|
709
|
+
workflow: workflows/child
|
|
710
|
+
with:
|
|
711
|
+
greeting: hello-from-parent
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## dispatch
|
|
715
|
+
|
|
716
|
+
Run the child workflow.
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**Part A — `plan` (no writes, no execution):** `akm workflow plan
|
|
720
|
+
workflows/parent --format json`
|
|
721
|
+
|
|
722
|
+
**Expected result (verified live):** exit `0`; `.steps[0].targetKind ==
|
|
723
|
+
"child-workflow"`; `.steps[0].inputBindings == [{"name": "greeting", "kind":
|
|
724
|
+
"literal", "value": "hello-from-parent"}]`; `.steps[0].expansion.via ==
|
|
725
|
+
"child"`, `.expansion.childRef == "bundle//workflows/child"`,
|
|
726
|
+
`.steps[0].expansion.steps` contains the child's own frozen
|
|
727
|
+
graph (one entry, `stepId: "greet"`, `targetKind: "shell"`).
|
|
728
|
+
|
|
729
|
+
**Part B — `run` (actually executes):** `akm workflow run workflows/parent
|
|
730
|
+
--format json`, then `akm workflow status <run-id> --format json`
|
|
731
|
+
|
|
732
|
+
**Expected result (verified live):** `run` exits `0`, `.run.status ==
|
|
733
|
+
"completed"`; `status`'s `.workflow.steps[0].evidence.output == "child-said-hello"` — the
|
|
734
|
+
child's last step output, promoted as the parent step's own artifact; `status` has a top-level **`.children`**
|
|
735
|
+
array (not nested under `.workflow`): one entry, `status: "completed"`,
|
|
736
|
+
`workflowRef: "bundle//workflows/child"`, `spawnedByUnitId:
|
|
737
|
+
"dispatch:solo"`, `stepId: "dispatch"`.
|
|
738
|
+
|
|
739
|
+
**Part C — list visibility:** `akm workflow list --format json` (count N),
|
|
740
|
+
then `akm workflow list --children --format json` (count N+1).
|
|
741
|
+
|
|
742
|
+
**Expected:** the second count is exactly one more — the child run is
|
|
743
|
+
hidden from the default `list` and appears only with `--children`.
|
|
744
|
+
|
|
745
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"`.
|
|
746
|
+
|
|
747
|
+
### WF-4 — `plan` makes zero durable writes
|
|
748
|
+
|
|
749
|
+
**Why it matters:** `plan` is documented as making zero durable writes. If a
|
|
750
|
+
future change accidentally makes it write a run row, it would silently
|
|
751
|
+
pollute `list`/state for every user who reviews a workflow before running
|
|
752
|
+
it — a very common habit.
|
|
753
|
+
|
|
754
|
+
**Preconditions:** fresh sandbox; reuse `demo.md` from WF-1.
|
|
755
|
+
|
|
756
|
+
**Commands:**
|
|
757
|
+
```sh
|
|
758
|
+
akm workflow list --format json # baseline
|
|
759
|
+
sqlite3 "$AKM_DATA_DIR/state.db" "select count(*) from workflow_runs;" # expect 0
|
|
760
|
+
akm workflow plan workflows/demo --format json
|
|
761
|
+
sqlite3 "$AKM_DATA_DIR/state.db" "select count(*) from workflow_runs;" # must still be 0
|
|
762
|
+
akm workflow list --format json # must still be []
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
**Expected result (verified live):** `plan` exits `0`, `.ok == true`,
|
|
766
|
+
`.published == false`. `select count(*)` is `0` both before and after.
|
|
767
|
+
`workflow list` after `plan` is byte-identical to before: `{"runs": [],
|
|
768
|
+
"shape": "workflow-list", "schemaVersion": 1}`.
|
|
769
|
+
|
|
770
|
+
`sqlite3` is required for the row-count check; the `list` before/after diff
|
|
771
|
+
alone is sufficient if unavailable.
|
|
772
|
+
|
|
773
|
+
### WF-5 — A failing step surfaces a clear diagnostic
|
|
774
|
+
|
|
775
|
+
**Why it matters:** most real workflow authoring involves a step failing.
|
|
776
|
+
The failure needs to be loud, specific, and land in a stable place
|
|
777
|
+
(`status`), not just flash by in `run`'s stdout.
|
|
778
|
+
|
|
779
|
+
**Setup:**
|
|
780
|
+
|
|
781
|
+
`$AKM_BUNDLE_DIR/workflows/fails.md`:
|
|
782
|
+
```markdown
|
|
783
|
+
---
|
|
784
|
+
type: workflow
|
|
785
|
+
description: Exec-only workflow whose second step always fails (no engine required)
|
|
786
|
+
updated: 2026-01-01
|
|
787
|
+
tags: [example]
|
|
788
|
+
steps:
|
|
789
|
+
- id: ok-step
|
|
790
|
+
unit:
|
|
791
|
+
exec:
|
|
792
|
+
command: ["bash", "-lc", "echo ok"]
|
|
793
|
+
- id: fail-step
|
|
794
|
+
inputs: [steps.ok-step.output]
|
|
795
|
+
unit:
|
|
796
|
+
exec:
|
|
797
|
+
command: ["bash", "-lc", "echo boom 1>&2; exit 1"]
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
# Fails Workflow
|
|
801
|
+
|
|
802
|
+
## ok-step
|
|
803
|
+
|
|
804
|
+
Print ok.
|
|
805
|
+
|
|
806
|
+
## fail-step
|
|
807
|
+
|
|
808
|
+
Always exits 1.
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
**Command:** `akm workflow run workflows/fails --format json`
|
|
812
|
+
|
|
813
|
+
**Expected result (verified live, exact):** exit **`1`** (not 0, not 2 — a
|
|
814
|
+
run-outcome failure, distinct from a usage error); `.run.status ==
|
|
815
|
+
"failed"`, `.run.currentStepId == "fail-step"`; `.executed[1].ok == false`,
|
|
816
|
+
`.executed[1].failedUnits == 1`; `.executed[1].summary` contains the exact
|
|
817
|
+
substring `fail-step:solo (non_zero_exit)` and the captured stderr text
|
|
818
|
+
`boom`.
|
|
819
|
+
|
|
820
|
+
Then `akm workflow status <run-id> --units --format json`:
|
|
821
|
+
`.workflow.steps[1].status == "failed"`;
|
|
822
|
+
`.workflow.steps[1].evidence.units[0].failureReason == "non_zero_exit"`.
|
|
823
|
+
|
|
824
|
+
### WF-6 — Blocked run: both direct `resume` and `abandon` → `resume` work (#847 regression)
|
|
825
|
+
|
|
826
|
+
**Why it matters:** `akm workflow abandon --help` promises "mark it failed
|
|
827
|
+
so it stops counting as active (resume can reopen it)". Alpha.5 violated that
|
|
828
|
+
promise for a run blocked by a judge-infrastructure failure: abandon left the
|
|
829
|
+
step blocked, and resume rejected that honest failed-run spine as corrupt.
|
|
830
|
+
Stable 0.9.2 accepts the three legitimate failed-run current-step states and
|
|
831
|
+
normalizes each back to pending on resume. This test keeps the exact #847
|
|
832
|
+
reproduction as a regression.
|
|
833
|
+
|
|
834
|
+
**How "blocked" is produced without any real LLM/API key:** a workflow with
|
|
835
|
+
a non-empty `### gate` requires `workflow.judgeEngine` to be *configured*
|
|
836
|
+
(present in `config.json`) before it can freeze — nothing requires it to be
|
|
837
|
+
*reachable*. Pointing it at a closed local port makes every gate evaluation
|
|
838
|
+
throw a network error, which is a judge-infrastructure failure (not a
|
|
839
|
+
verdict), and that path blocks the step. No credentials, no network,
|
|
840
|
+
reproduces every time.
|
|
841
|
+
|
|
842
|
+
**Setup:**
|
|
843
|
+
```sh
|
|
844
|
+
cat > "$AKM_CONFIG_DIR/config.json" <<'EOF'
|
|
845
|
+
{
|
|
846
|
+
"configVersion": "0.9.0",
|
|
847
|
+
"semanticSearchMode": "off",
|
|
848
|
+
"registries": [],
|
|
849
|
+
"engines": {
|
|
850
|
+
"unreachable": { "kind": "llm", "endpoint": "http://127.0.0.1:1/v1/chat/completions", "model": "does-not-matter" }
|
|
851
|
+
},
|
|
852
|
+
"workflow": { "judgeEngine": "unreachable" }
|
|
853
|
+
}
|
|
854
|
+
EOF
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
`$AKM_BUNDLE_DIR/workflows/gated.md`:
|
|
858
|
+
```markdown
|
|
859
|
+
---
|
|
860
|
+
type: workflow
|
|
861
|
+
description: Exec step with a gate rubric, judged by an unreachable engine (simulates judge-infrastructure failure -> blocked)
|
|
862
|
+
updated: 2026-01-01
|
|
863
|
+
tags: [example]
|
|
864
|
+
steps:
|
|
865
|
+
- id: produce
|
|
866
|
+
unit:
|
|
867
|
+
exec:
|
|
868
|
+
command: ["bash", "-lc", "echo produced"]
|
|
869
|
+
gate: { max_loops: 1 }
|
|
870
|
+
---
|
|
871
|
+
|
|
872
|
+
# Gated Workflow
|
|
873
|
+
|
|
874
|
+
## produce
|
|
875
|
+
|
|
876
|
+
Print the word "produced".
|
|
877
|
+
|
|
878
|
+
### gate
|
|
879
|
+
|
|
880
|
+
- The artifact is exactly "produced".
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
**Part A — plain resume works (contrast case):**
|
|
884
|
+
```sh
|
|
885
|
+
akm workflow run workflows/gated --format json --timeout 30s
|
|
886
|
+
```
|
|
887
|
+
**Expected (verified live):** exit `1`; `.run.status == "blocked"`;
|
|
888
|
+
`.workflow.steps[0].notes` contains `could not be verified: the
|
|
889
|
+
verification judge failed (Network error:` and names the exact resume
|
|
890
|
+
command.
|
|
891
|
+
|
|
892
|
+
```sh
|
|
893
|
+
akm workflow resume <run-id> --format json
|
|
894
|
+
```
|
|
895
|
+
**Expected (verified live):** exit `0`; `.run.status` becomes `"active"`;
|
|
896
|
+
`.workflow.steps[0].status` becomes `"pending"` (evidence cleared — the
|
|
897
|
+
next `run` re-evaluates the gate, not the exec unit).
|
|
898
|
+
|
|
899
|
+
**Part B — abandon then resume.** Start a **fresh** run (do not
|
|
900
|
+
reuse Part A's run — re-running against the ref resumes the existing active
|
|
901
|
+
run rather than starting a new one; see the note at the end of this test):
|
|
902
|
+
```sh
|
|
903
|
+
akm workflow run workflows/gated --format json --timeout 30s # capture the NEW run id
|
|
904
|
+
akm workflow abandon <run-id> --format json
|
|
905
|
+
akm workflow resume <run-id> --format json
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**Expected result (stable regression, automated):**
|
|
909
|
+
- `abandon` succeeds, exit `0`. `.run.status` becomes `"failed"`; the current
|
|
910
|
+
step honestly remains `"blocked"` until a resume is requested.
|
|
911
|
+
- `resume` succeeds, exit `0`. `.run.status` becomes `"active"` and
|
|
912
|
+
`.workflow.steps[0].status` becomes `"pending"`, with the blocked notes and
|
|
913
|
+
evidence cleared so the gate can be evaluated again.
|
|
914
|
+
- No corrupt-spine error is emitted. A subsequent `run <run-id>` re-enters
|
|
915
|
+
the current step normally.
|
|
916
|
+
|
|
917
|
+
**Note on `run <ref>` semantics:** `akm workflow run workflows/<name>` does
|
|
918
|
+
not always start a brand-new run — if an **active** run already exists for
|
|
919
|
+
that ref in the current scope, it resumes that run instead, consistent with
|
|
920
|
+
`run --help` ("Start **or resume** a workflow...") but easy to miss.
|
|
921
|
+
|
|
922
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"`.
|
|
923
|
+
|
|
924
|
+
### WF-7 — Active (paused) run → `abandon` → `resume` round-trips cleanly (companion to WF-6)
|
|
925
|
+
|
|
926
|
+
**Why it matters:** pins the other legitimate abandoned-run spine: an active
|
|
927
|
+
run leaves its current step pending, while WF-6 leaves it blocked. Resume must
|
|
928
|
+
normalize both without weakening corruption detection for impossible shapes.
|
|
929
|
+
This is a deliberate contrast pair with WF-6, not a duplicate — keep both.
|
|
930
|
+
|
|
931
|
+
**Preconditions:** fresh sandbox, no `engines` config needed. Use `demo.md`
|
|
932
|
+
from WF-1.
|
|
933
|
+
|
|
934
|
+
**Commands:**
|
|
935
|
+
```sh
|
|
936
|
+
akm workflow run workflows/demo --max-steps=1 --format json
|
|
937
|
+
# capture run id; .run.status is "active", .run.currentStepId "second-step"
|
|
938
|
+
akm workflow abandon <run-id> --format json
|
|
939
|
+
akm workflow resume <run-id> --format json
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
**Expected result (verified live):**
|
|
943
|
+
- After `run --max-steps=1`: `.run.status == "active"`,
|
|
944
|
+
`.workflow.steps[0].status == "completed"`,
|
|
945
|
+
`.workflow.steps[1].status == "pending"`.
|
|
946
|
+
- `abandon` succeeds, exit `0`, `.run.status` becomes `"failed"`,
|
|
947
|
+
`.workflow.steps[1].status` **stays `"pending"`** — this is the case that
|
|
948
|
+
does *not* produce the WF-6 bug, because the step status already agrees
|
|
949
|
+
with the run's new status.
|
|
950
|
+
- `resume` succeeds, exit **`0`**, `.run.status` becomes `"active"` again,
|
|
951
|
+
`.workflow.steps[1].status` stays `"pending"`. No corrupt-spine error.
|
|
952
|
+
|
|
953
|
+
### WF-8 — Scope semantics: `list` is cwd-scoped by default; `--all-scopes`
|
|
954
|
+
and the cross-scope start warning (#942)
|
|
955
|
+
|
|
956
|
+
**Why it matters:** `list`/`status --help` say refs/runs "resolve within
|
|
957
|
+
the current scope" — easy to read past. A user who runs a workflow from one
|
|
958
|
+
directory, `cd`s elsewhere, then runs `akm workflow list` and sees
|
|
959
|
+
`{"runs": []}` could plausibly conclude the run vanished — this is exactly
|
|
960
|
+
what happened in production: a scheduled task's cwd (`$HOME`) and a human
|
|
961
|
+
shell's cwd hashed to different scope keys, each `akm workflow run`
|
|
962
|
+
independently believed no run was active, and each started one. Scope is
|
|
963
|
+
still the **working directory**, hashed into `scopeKey` — not the bundle,
|
|
964
|
+
not the config dir — but the envelope now says which scope it searched, and
|
|
965
|
+
`--all-scopes` can search every scope.
|
|
966
|
+
|
|
967
|
+
**Setup:** fresh sandbox at `$AKM_SANDBOX`, plus `mkdir -p
|
|
968
|
+
"$AKM_SANDBOX-elsewhere"`. Use `demo.md` from WF-1.
|
|
969
|
+
|
|
970
|
+
**Commands:**
|
|
971
|
+
```sh
|
|
972
|
+
cd "$AKM_SANDBOX"
|
|
973
|
+
akm workflow run workflows/demo --format json # note RUNID
|
|
974
|
+
akm workflow list --format json # from $AKM_SANDBOX
|
|
975
|
+
|
|
976
|
+
cd "$AKM_SANDBOX-elsewhere"
|
|
977
|
+
akm workflow list --format json # same env vars, different cwd
|
|
978
|
+
akm workflow list --all-scopes --format json # same env vars, different cwd
|
|
979
|
+
akm workflow status "$RUNID" --format json # by id, from elsewhere
|
|
980
|
+
akm workflow run workflows/demo --format json # same ref, from elsewhere
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
**Expected result (verified live):**
|
|
984
|
+
- `list` from `$AKM_SANDBOX` returns `.runs` length **1** and a top-level
|
|
985
|
+
`.scopeKey` starting `"dir:v1:..."` — the scope it searched, which every
|
|
986
|
+
returned run's own `.scopeKey` also matches.
|
|
987
|
+
- `list` from the elsewhere directory (identical `AKM_*` env vars, only
|
|
988
|
+
`cwd` differs) returns **`{"runs": [], "scopeKey": "dir:v1:..."}`**
|
|
989
|
+
(a *different* hash than the first call), exit `0` — not an error, and no
|
|
990
|
+
longer indistinguishable from "nothing anywhere": the envelope names the
|
|
991
|
+
scope that came up empty.
|
|
992
|
+
- `list --all-scopes` from the elsewhere directory returns `.runs` length
|
|
993
|
+
**1** (the run started in `$AKM_SANDBOX`) and a top-level `.scopeKey` of
|
|
994
|
+
**`null`** — "every scope" — while the run's own `.scopeKey` field still
|
|
995
|
+
names where it actually started.
|
|
996
|
+
- `status $RUNID` from elsewhere **still succeeds** and returns the full
|
|
997
|
+
run — status-by-id is not scope-filtered the way `list` is.
|
|
998
|
+
- `run workflows/demo` from elsewhere **starts a second, independent run**
|
|
999
|
+
(the scope-local concurrency guard is unchanged — a deliberate
|
|
1000
|
+
per-project partition, see `storage-locations.md`) but its envelope now
|
|
1001
|
+
carries a `warnings[]` entry naming `$RUNID` and `$AKM_SANDBOX`'s scope
|
|
1002
|
+
key, with the same `akm workflow run <id>` / `akm workflow abandon <id>`
|
|
1003
|
+
remedy the same-scope guard already gives — so the operator sees the
|
|
1004
|
+
other run instead of two runs silently drifting.
|
|
1005
|
+
|
|
1006
|
+
### WF-9 — Migration: a pre-`irVersion`-5 stored plan is retired
|
|
1007
|
+
|
|
1008
|
+
**Why it matters:** every user upgrading from 0.9.1 or an early 0.9.2 alpha
|
|
1009
|
+
with an in-flight workflow run hits this. Requires the `sqlite3` CLI (or any
|
|
1010
|
+
SQLite client) to hand-edit one column of one row.
|
|
1011
|
+
|
|
1012
|
+
**Setup:**
|
|
1013
|
+
|
|
1014
|
+
`$AKM_BUNDLE_DIR/workflows/retiring.md`:
|
|
1015
|
+
```markdown
|
|
1016
|
+
---
|
|
1017
|
+
type: workflow
|
|
1018
|
+
description: Exec-only two-step workflow used for the pre-irVersion-5 retirement test
|
|
1019
|
+
updated: 2026-01-01
|
|
1020
|
+
tags: [example]
|
|
1021
|
+
steps:
|
|
1022
|
+
- id: first-step
|
|
1023
|
+
unit:
|
|
1024
|
+
exec:
|
|
1025
|
+
command: ["bash", "-lc", "echo hello-first"]
|
|
1026
|
+
- id: second-step
|
|
1027
|
+
inputs: [steps.first-step.output]
|
|
1028
|
+
unit:
|
|
1029
|
+
exec:
|
|
1030
|
+
command: ["bash", "-lc", "echo hello-second"]
|
|
1031
|
+
---
|
|
1032
|
+
|
|
1033
|
+
# Retiring Workflow
|
|
1034
|
+
|
|
1035
|
+
## first-step
|
|
1036
|
+
|
|
1037
|
+
Print a greeting.
|
|
1038
|
+
|
|
1039
|
+
## second-step
|
|
1040
|
+
|
|
1041
|
+
Print a second greeting.
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
**Commands:**
|
|
1045
|
+
```sh
|
|
1046
|
+
OUT=$(akm workflow run workflows/retiring --max-steps=1 --format json)
|
|
1047
|
+
RUNID=$(echo "$OUT" | python3 -c "import json,sys; print(json.load(sys.stdin)['run']['id'])")
|
|
1048
|
+
sqlite3 "$AKM_DATA_DIR/state.db" "UPDATE workflow_runs SET plan_ir_version = 4 WHERE id = '$RUNID';"
|
|
1049
|
+
akm workflow status "$RUNID" --format json # read-only surfaces must still work
|
|
1050
|
+
akm workflow resume "$RUNID" --format json # execution surfaces must fail closed
|
|
1051
|
+
akm workflow abandon "$RUNID" --format json # abandon must still work
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**Expected result (verified live, every line confirmed):**
|
|
1055
|
+
- Before tampering: `.run.status == "active"`.
|
|
1056
|
+
- After tampering, `status` reports `.run.planIrVersion == 4`,
|
|
1057
|
+
`.run.executionSupport == "unsupported-version"`, `.run.status`
|
|
1058
|
+
unchanged (`"active"`) — reading the run does not itself fail.
|
|
1059
|
+
- `resume` fails, exit **`2`**:
|
|
1060
|
+
```json
|
|
1061
|
+
{"ok": false, "error": "Workflow run <RUNID> was frozen as workflow plan irVersion 4; pre-irVersion-5 plans cannot execute after the 0.9.2 upgrade. Complete them before upgrading, or run 'akm workflow abandon <RUNID>' and start a new run from the authored workflow. 'akm workflow status' and 'akm workflow list' still work on this run.", "code": "WORKFLOW_IR_VERSION_UNSUPPORTED", "hint": "Abandon the run with `akm workflow abandon <id>`, then start it again from the workflow source — a frozen plan this akm cannot execute is not re-executable in place."}
|
|
1062
|
+
```
|
|
1063
|
+
This message names the run id twice, the exact `abandon` command, and
|
|
1064
|
+
states the only path forward — the strongest error message found across
|
|
1065
|
+
this whole document; treat it as the bar WF-6's corrupt-spine error and
|
|
1066
|
+
WF-10's `inherit_env` rejection should be held to.
|
|
1067
|
+
- `abandon` **succeeds**, exit `0`, `.run.status` becomes `"failed"` —
|
|
1068
|
+
unlike WF-6's blocked-abandon case, this does not corrupt the step spine
|
|
1069
|
+
(the step here is `"pending"`, a status abandon's `"failed"` reconciles
|
|
1070
|
+
with cleanly).
|
|
1071
|
+
|
|
1072
|
+
### WF-10 — Migration: removed `inherit_env` is rejected
|
|
1073
|
+
|
|
1074
|
+
**Why it matters:** `inherit_env: true` was breaking-removed in
|
|
1075
|
+
`0.9.2-alpha.1` in favor of `exec.pass_env`. Anyone porting an older
|
|
1076
|
+
exec-unit workflow forward will paste this key in and needs an unambiguous
|
|
1077
|
+
signal.
|
|
1078
|
+
|
|
1079
|
+
**Setup:**
|
|
1080
|
+
|
|
1081
|
+
`$AKM_BUNDLE_DIR/workflows/inherit-env.md`:
|
|
1082
|
+
```markdown
|
|
1083
|
+
---
|
|
1084
|
+
type: workflow
|
|
1085
|
+
description: Exec step authored with the removed inherit_env option
|
|
1086
|
+
updated: 2026-01-01
|
|
1087
|
+
tags: [example]
|
|
1088
|
+
steps:
|
|
1089
|
+
- id: step1
|
|
1090
|
+
unit:
|
|
1091
|
+
exec:
|
|
1092
|
+
command: ["bash", "-lc", "echo hi"]
|
|
1093
|
+
inherit_env: true
|
|
1094
|
+
---
|
|
1095
|
+
|
|
1096
|
+
# Inherit Env Workflow
|
|
1097
|
+
|
|
1098
|
+
## step1
|
|
1099
|
+
|
|
1100
|
+
Print hi; this step's exec unit illegally authors `inherit_env: true`.
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
**Commands:** `akm workflow plan workflows/inherit-env --format json` and
|
|
1104
|
+
`akm workflow run workflows/inherit-env --format json`
|
|
1105
|
+
|
|
1106
|
+
**Expected result (verified live, identical for both):** exit **`2`**;
|
|
1107
|
+
```json
|
|
1108
|
+
{"ok": false, "error": "Workflow source has 1 error(s):\n <path>/workflows/inherit-env.md:11 — Unknown Step \"step1\" \"exec\" key \"inherit_env\". Allowed keys: command, cwd, pass_env.", "code": "WORKFLOW_SOURCE_INVALID", "hint": "Run `akm lint` to see the failing source location, or `akm workflow plan <ref>` to compile it without writing."}
|
|
1109
|
+
```
|
|
1110
|
+
This is a generic "unknown field" rejection, not a dedicated migration
|
|
1111
|
+
message — it lists `pass_env` among allowed keys (a usable hint for someone
|
|
1112
|
+
who already knows the replacement) but never says "`inherit_env` was
|
|
1113
|
+
removed" or points at
|
|
1114
|
+
[`docs/migration/v0.9.1-to-v0.9.2.md#workflow-cutover`](../../migration/v0.9.1-to-v0.9.2.md#workflow-cutover).
|
|
1115
|
+
Compare against WF-9's message, which names the concept, reason, and exact
|
|
1116
|
+
fix — worth a documentation-quality discussion, not necessarily a blocking
|
|
1117
|
+
bug.
|
|
1118
|
+
|
|
1119
|
+
**Positive control:** replace `inherit_env: true` with `pass_env: [PATH]`
|
|
1120
|
+
in the same fixture and re-run `plan`. Expected (verified live): `.ok ==
|
|
1121
|
+
true`, and the frozen step's `.environment` array gains `{"kind":
|
|
1122
|
+
"pass-through", "name": "PATH"}`.
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
## Integration (task ↔ workflow seam)
|
|
1127
|
+
|
|
1128
|
+
Scope: only scenarios where `akm task` and `akm workflow` genuinely meet —
|
|
1129
|
+
a task whose target is a workflow, a workflow step that targets a task, and
|
|
1130
|
+
the shared identity/failure machinery. The destructive `task sync` seam
|
|
1131
|
+
tests (a workflow-targeting task's schedule, and the #846 cross-bundle
|
|
1132
|
+
regression) are in [Destructive & platform-gated](#destructive--platform-gated-tests).
|
|
1133
|
+
|
|
1134
|
+
### INT-1 — A task that runs a workflow (the headline case)
|
|
1135
|
+
|
|
1136
|
+
**Why it matters:** `akm task` is documented as scheduling "recurring
|
|
1137
|
+
commands, prompts, and workflows" — a task whose target IS a workflow is
|
|
1138
|
+
the one thing that makes `task` and `workflow` the same feature area
|
|
1139
|
+
instead of two unrelated CLIs.
|
|
1140
|
+
|
|
1141
|
+
**Destructive:** No — uses `task run` directly (verified to make zero
|
|
1142
|
+
scheduler writes).
|
|
1143
|
+
|
|
1144
|
+
**Setup:**
|
|
1145
|
+
```sh
|
|
1146
|
+
cat > "$AKM_BUNDLE_DIR/workflows/leaf.md" <<'EOF'
|
|
1147
|
+
---
|
|
1148
|
+
type: workflow
|
|
1149
|
+
description: Manual test leaf workflow
|
|
1150
|
+
steps:
|
|
1151
|
+
- id: work
|
|
1152
|
+
unit:
|
|
1153
|
+
exec:
|
|
1154
|
+
command: ["printf", "leaf-ok"]
|
|
1155
|
+
---
|
|
1156
|
+
|
|
1157
|
+
## work
|
|
1158
|
+
|
|
1159
|
+
Print leaf-ok.
|
|
1160
|
+
EOF
|
|
1161
|
+
|
|
1162
|
+
cat > "$AKM_BUNDLE_DIR/tasks/akm-manual-test-wf-task.yml" <<'EOF'
|
|
1163
|
+
version: 4
|
|
1164
|
+
name: Manual test task -> workflow
|
|
1165
|
+
uses: workflows/leaf
|
|
1166
|
+
schedule:
|
|
1167
|
+
- cron: "0 3 * * *"
|
|
1168
|
+
enabled: false
|
|
1169
|
+
EOF
|
|
1170
|
+
akm index
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
**Steps and expected results (all verified live):**
|
|
1174
|
+
1. `akm task explain akm-manual-test-wf-task --format json` → exit `0`,
|
|
1175
|
+
`.target == {"kind":"workflow","ref":"workflows/leaf"}`.
|
|
1176
|
+
2. `akm task run akm-manual-test-wf-task --format json` (record
|
|
1177
|
+
`.result.detail.runId`) → exit `0`, `.ok == true`, `.result.status ==
|
|
1178
|
+
"completed"`, `.result.target == {"kind":"workflow","ref":"bundle//workflows/leaf"}`,
|
|
1179
|
+
`.result.detail.runId` is a UUID.
|
|
1180
|
+
3. `akm workflow list --format json` → `.runs[]` contains an entry with
|
|
1181
|
+
`.id` equal to step 2's runId, `.workflowRef ==
|
|
1182
|
+
"bundle//workflows/leaf"`, `.status == "completed"`.
|
|
1183
|
+
4. `akm workflow status <runId> --format json` → `.run.status ==
|
|
1184
|
+
"completed"`, `.workflow.steps[0].id == "work"`, `.status ==
|
|
1185
|
+
"completed"`, `.evidence.output == "leaf-ok"`.
|
|
1186
|
+
5. `akm task history --id akm-manual-test-wf-task --format json` →
|
|
1187
|
+
`.rows[0].detail.runId` equals step 2's runId, `.rows[0].target.ref ==
|
|
1188
|
+
"bundle//workflows/leaf"`.
|
|
1189
|
+
|
|
1190
|
+
**Cross-check outside akm:** `crontab -l | grep -c akm-manual-test-wf-task`
|
|
1191
|
+
before and after step 2 — both must print `0`. `task run` never touches the
|
|
1192
|
+
OS scheduler.
|
|
1193
|
+
|
|
1194
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"` (no scheduler entries were ever
|
|
1195
|
+
created).
|
|
1196
|
+
|
|
1197
|
+
### INT-2 — (removed in 0.10)
|
|
1198
|
+
|
|
1199
|
+
A workflow step that targets a task (`uses: tasks/<ref>`) went with the
|
|
1200
|
+
GitHub-shaped YAML workflow format. A task runs a workflow (INT-1); a workflow
|
|
1201
|
+
step runs a child workflow (WF-3), not a task.
|
|
1202
|
+
|
|
1203
|
+
### INT-3 — Failure propagation: `task history` vs `workflow status`
|
|
1204
|
+
|
|
1205
|
+
**Why it matters:** operability. When a scheduled task's workflow fails at
|
|
1206
|
+
3am, does the on-call person get the answer from `task history` (what a
|
|
1207
|
+
cron failure notification points at), or must they already know to pivot to
|
|
1208
|
+
`workflow status <runId>`?
|
|
1209
|
+
|
|
1210
|
+
**Destructive:** No (`task run` only).
|
|
1211
|
+
|
|
1212
|
+
**Setup:**
|
|
1213
|
+
```sh
|
|
1214
|
+
cat > "$AKM_BUNDLE_DIR/workflows/fail-leaf.md" <<'EOF'
|
|
1215
|
+
---
|
|
1216
|
+
type: workflow
|
|
1217
|
+
steps:
|
|
1218
|
+
- id: boom
|
|
1219
|
+
unit:
|
|
1220
|
+
exec:
|
|
1221
|
+
command: ["sh", "-c", "exit 1"]
|
|
1222
|
+
---
|
|
1223
|
+
|
|
1224
|
+
## boom
|
|
1225
|
+
|
|
1226
|
+
Fail.
|
|
1227
|
+
EOF
|
|
1228
|
+
|
|
1229
|
+
cat > "$AKM_BUNDLE_DIR/tasks/akm-manual-test-fail-task.yml" <<'EOF'
|
|
1230
|
+
version: 4
|
|
1231
|
+
name: Manual test task -> failing workflow
|
|
1232
|
+
uses: workflows/fail-leaf
|
|
1233
|
+
schedule:
|
|
1234
|
+
- cron: "0 3 * * *"
|
|
1235
|
+
enabled: false
|
|
1236
|
+
EOF
|
|
1237
|
+
akm index
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
**Steps and expected (verified live) results:**
|
|
1241
|
+
1. `akm task run akm-manual-test-fail-task --format json` → exit **`1`**,
|
|
1242
|
+
`.ok == false`, `.result.status == "failed"`, `.result.target ==
|
|
1243
|
+
{"kind":"workflow","ref":"bundle//workflows/fail-leaf"}`.
|
|
1244
|
+
**`.result.detail` is exactly `{"runId": "<uuid>"}` — no `error`/`message`
|
|
1245
|
+
field at all.** (`run-workflow-task.ts` only attaches `detail.error` for
|
|
1246
|
+
a thrown error, gate rejection, or timeout — a workflow that reaches
|
|
1247
|
+
`status: "failed"` through ordinary step failure sets none of those.)
|
|
1248
|
+
2. The task's own log file (path from step 1's `.result.log`) contains only:
|
|
1249
|
+
```
|
|
1250
|
+
[akm task] task=akm-manual-test-fail-task kind=workflow ref=bundle//workflows/fail-leaf
|
|
1251
|
+
run_id=<uuid> status=failed
|
|
1252
|
+
workflow_title=fail-leaf
|
|
1253
|
+
```
|
|
1254
|
+
No mention of which step failed or why.
|
|
1255
|
+
3. `akm task history --id akm-manual-test-fail-task --format json` → same
|
|
1256
|
+
shape as step 1's `.result` — still no reason.
|
|
1257
|
+
4. `akm workflow status <runId> --format json` → `.workflow.steps[0].notes`
|
|
1258
|
+
contains the actual reason: `Executed 1 unit(s) for step "boom" via
|
|
1259
|
+
workflow orchestration: 0 succeeded, 1 failed. Failures: boom:solo
|
|
1260
|
+
(non_zero_exit). First failure diagnostic (boom:solo): exec unit
|
|
1261
|
+
"boom:solo" ran "/usr/bin/sh" (2 arguments) and it exited 1`.
|
|
1262
|
+
|
|
1263
|
+
**Conclusion (falsifiable, and it falsifies the "should"):** a normal
|
|
1264
|
+
workflow-side failure is legible ONLY from `workflow status`, never from
|
|
1265
|
+
`task history`/the task log/the task's own JSON result. This is a real,
|
|
1266
|
+
reproducible operability gap, not a hypothetical.
|
|
1267
|
+
|
|
1268
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"`.
|
|
1269
|
+
|
|
1270
|
+
### INT-4 — Identity/ownership across the seam: abandon then re-invoke
|
|
1271
|
+
|
|
1272
|
+
**Why it matters:** if a task's scheduled invocation drives a workflow run
|
|
1273
|
+
and a human intervenes with `workflow abandon` (e.g. because it's stuck),
|
|
1274
|
+
the next scheduled firing must do something predictable.
|
|
1275
|
+
|
|
1276
|
+
**Destructive:** No (`task run`/`workflow abandon` only, isolated state DB).
|
|
1277
|
+
|
|
1278
|
+
**Setup:**
|
|
1279
|
+
```sh
|
|
1280
|
+
cat > "$AKM_BUNDLE_DIR/workflows/two-step.md" <<'EOF'
|
|
1281
|
+
---
|
|
1282
|
+
type: workflow
|
|
1283
|
+
steps:
|
|
1284
|
+
- id: first
|
|
1285
|
+
unit:
|
|
1286
|
+
exec:
|
|
1287
|
+
command: ["printf", "step-one-ok"]
|
|
1288
|
+
- id: second
|
|
1289
|
+
unit:
|
|
1290
|
+
exec:
|
|
1291
|
+
command: ["printf", "step-two-ok"]
|
|
1292
|
+
---
|
|
1293
|
+
|
|
1294
|
+
## first
|
|
1295
|
+
|
|
1296
|
+
Print step-one-ok.
|
|
1297
|
+
|
|
1298
|
+
## second
|
|
1299
|
+
|
|
1300
|
+
Print step-two-ok.
|
|
1301
|
+
EOF
|
|
1302
|
+
|
|
1303
|
+
cat > "$AKM_BUNDLE_DIR/tasks/akm-manual-test-abandon-task.yml" <<'EOF'
|
|
1304
|
+
version: 4
|
|
1305
|
+
name: Manual test task -> two-step workflow (stops after 1 step)
|
|
1306
|
+
uses: workflows/two-step
|
|
1307
|
+
maxSteps: 1
|
|
1308
|
+
schedule:
|
|
1309
|
+
- cron: "0 3 * * *"
|
|
1310
|
+
enabled: false
|
|
1311
|
+
EOF
|
|
1312
|
+
akm index
|
|
1313
|
+
```
|
|
1314
|
+
(`maxSteps: 1` lets a "scheduled" invocation stop mid-run, so there's
|
|
1315
|
+
something to abandon.)
|
|
1316
|
+
|
|
1317
|
+
**Steps and expected results (all verified live):**
|
|
1318
|
+
1. `akm task run akm-manual-test-abandon-task --format json` (first
|
|
1319
|
+
firing) → `.result.status == "active"`, exit `0` (a paused run is not a
|
|
1320
|
+
failure), `.result.detail.runId` = **runA**. `akm workflow status runA`
|
|
1321
|
+
confirms `.run.status == "active"`, step `"first"` is `"completed"`,
|
|
1322
|
+
`"second"` is `"pending"`.
|
|
1323
|
+
2. `akm workflow abandon runA --format json` → exit `0`, `.run.status ==
|
|
1324
|
+
"failed"`.
|
|
1325
|
+
3. `akm task run akm-manual-test-abandon-task --format json` (second
|
|
1326
|
+
firing, as if the scheduler fired it again) → **a brand-new run id,
|
|
1327
|
+
"runB" != "runA"**, `.result.detail.runId == runB`, `.result.status ==
|
|
1328
|
+
"active"` again. The task log shows step `"first"` executing again from
|
|
1329
|
+
scratch — it is not resuming runA's position.
|
|
1330
|
+
4. `akm workflow list --format json` → both `runA` (`"failed"`) and `runB`
|
|
1331
|
+
(`"active"`) present as separate rows for the same `workflowRef`.
|
|
1332
|
+
|
|
1333
|
+
**Conclusion (falsifiable and confirmed):** the next scheduled invocation
|
|
1334
|
+
starts fresh, never resumes and never errors — a direct consequence of
|
|
1335
|
+
`getActiveRunRowForScope`'s SQL filter
|
|
1336
|
+
(`src/storage/repositories/workflow-runs-repository.ts`) only ever
|
|
1337
|
+
attaching to `status = 'active'` rows. One nuance: this auto-attach path is
|
|
1338
|
+
also `AND parent_run_id IS NULL`, so it applies to a top-level run a *task*
|
|
1339
|
+
starts (INT-1) but not to a run started as a **child** of a parent workflow
|
|
1340
|
+
(WF-3) — a child run is only ever driven by its parent. Abandoning a child
|
|
1341
|
+
run's semantics from the *task* side is **UNVERIFIED** — not exercised
|
|
1342
|
+
here, plausibly a second interesting gap (a task can only ever directly own
|
|
1343
|
+
a top-level run).
|
|
1344
|
+
|
|
1345
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"`.
|
|
1346
|
+
|
|
1347
|
+
### INT-5 — The shipped `improve` tasks as a real-world case
|
|
1348
|
+
|
|
1349
|
+
**Why it matters:** `src/assets/tasks/improve/*.yml` are framed as "real
|
|
1350
|
+
examples of tasks that drive akm's own workflows." Testing only synthetic
|
|
1351
|
+
fixtures never catches a real schema drift between what `akm task add`
|
|
1352
|
+
authors and what these shipped files contain.
|
|
1353
|
+
|
|
1354
|
+
**Finding that reshapes this test:** none of the shipped
|
|
1355
|
+
`src/assets/tasks/improve/*.yml` (or `src/assets/tasks/core/*.yml`) files
|
|
1356
|
+
actually target an `akm workflow` asset. Every one is `run: akm <command>
|
|
1357
|
+
...` — a shell target. Verified live for `akm-improve-consolidate.yml`:
|
|
1358
|
+
`.target == {"kind":"shell"}`. So the literal task→workflow seam (INT-1)
|
|
1359
|
+
has **no shipped real-world example** — "workflow" in the PR/brief is used in
|
|
1360
|
+
the English sense (the `improve` pipeline is a multi-step process), not the
|
|
1361
|
+
`akm workflow` asset sense.
|
|
1362
|
+
|
|
1363
|
+
**Destructive:** No (`task explain` only).
|
|
1364
|
+
|
|
1365
|
+
**Setup:**
|
|
1366
|
+
```sh
|
|
1367
|
+
cp /home/founder3/code/github/itlackey/akm/src/assets/tasks/improve/akm-improve-consolidate.yml \
|
|
1368
|
+
"$AKM_BUNDLE_DIR/tasks/akm-improve-consolidate.yml"
|
|
1369
|
+
akm index
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
**Steps and expected results (verified live):**
|
|
1373
|
+
1. `akm task explain akm-improve-consolidate --format json` → exit `0`,
|
|
1374
|
+
`.target.kind == "shell"`, `.description == "Consolidation-only
|
|
1375
|
+
pass (every 4h at :20)"`, `.schedule[0] == {"ordinal":0,"cron":"20 */4 * * *","enabled":false,"source":"schedule[0].cron","inputs":{}}`
|
|
1376
|
+
— the real shipped file describes a schedule but cannot activate itself;
|
|
1377
|
+
this host's `scheduler.enabled` list does not name it.
|
|
1378
|
+
|
|
1379
|
+
**Do not** `task run akm-improve-consolidate` directly — it would invoke the
|
|
1380
|
+
real `akm improve --strategy consolidate` pipeline against a live agent
|
|
1381
|
+
engine, which is slow, may require credentials, and is out of scope for a seam
|
|
1382
|
+
test.
|
|
1383
|
+
|
|
1384
|
+
**Cleanup:** `rm -rf "$AKM_SANDBOX"`.
|
|
1385
|
+
|
|
1386
|
+
---
|
|
1387
|
+
|
|
1388
|
+
## Destructive & platform-gated tests
|
|
1389
|
+
|
|
1390
|
+
**Everything in this section writes to the real OS scheduler (cron /
|
|
1391
|
+
launchd / schtasks). Re-read [Read this first](#read-this-first--the-one-safety-critical-finding)
|
|
1392
|
+
before running any of it.** Mandatory for every test below:
|
|
1393
|
+
|
|
1394
|
+
1. Back up first: `crontab -l > ~/akm-crontab-backup-$(date +%s).txt` (or
|
|
1395
|
+
the platform equivalent). If this shows entries you don't recognize as
|
|
1396
|
+
your own throwaway fixtures, **stop** — you are not on a disposable
|
|
1397
|
+
scheduler.
|
|
1398
|
+
2. Use an obviously-throwaway id/name (`akm-manual-test-<unix-timestamp>`
|
|
1399
|
+
or similarly namespaced) so a stray leftover is identifiable later.
|
|
1400
|
+
3. Restore explicitly at the end: `crontab <backup-file>`, then verify with
|
|
1401
|
+
`crontab -l`.
|
|
1402
|
+
4. **Never use `akm task sync` as a cleanup step** — it performs a broad
|
|
1403
|
+
reconcile, while cleanup should remove only the exact test fixture. DT-5
|
|
1404
|
+
separately proves #846's owner-path isolation. Clean up by hand.
|
|
1405
|
+
5. Prefer a disposable OS account, VM, or container over a real machine.
|
|
1406
|
+
|
|
1407
|
+
`add` writes only its own binding (narrower blast radius); `sync` does a
|
|
1408
|
+
full reconcile and can attempt to remove unrelated real entries.
|
|
1409
|
+
|
|
1410
|
+
### DT-1 — `task add` happy path installs a real cron entry [Linux: cron]
|
|
1411
|
+
|
|
1412
|
+
**Setup:** `export TID="akm-manual-test-$(date +%s)"`; back up crontab per
|
|
1413
|
+
above.
|
|
1414
|
+
|
|
1415
|
+
**Command:**
|
|
1416
|
+
```sh
|
|
1417
|
+
akm task add "$TID" --schedule "@daily" --command "echo akm-manual-test" --bundle "$AKM_BUNDLE_DIR"
|
|
1418
|
+
```
|
|
1419
|
+
Note: from a repo checkout this still requires `--rebind` (see TASK-16) —
|
|
1420
|
+
from an npm-global/standalone install it should succeed without it.
|
|
1421
|
+
|
|
1422
|
+
**`UNVERIFIED`: the exact success JSON shape.** This command was not
|
|
1423
|
+
executed live in review of this document (the coding-agent sandbox's own
|
|
1424
|
+
destructive-operation classifier refused all `task add` invocations —
|
|
1425
|
+
itself corroborating evidence the command is correctly recognized as
|
|
1426
|
+
touching the real system). Confirm on a disposable VM/container before
|
|
1427
|
+
relying on the shape below.
|
|
1428
|
+
|
|
1429
|
+
**Expected:** exit `0`; a task YAML at `$AKM_BUNDLE_DIR/tasks/$TID.yml`
|
|
1430
|
+
with `version: 4`.
|
|
1431
|
+
|
|
1432
|
+
**Verify OUTSIDE akm (mandatory — the whole point of the test):**
|
|
1433
|
+
```sh
|
|
1434
|
+
crontab -l | grep -A2 "# akm:task $TID BEGIN"
|
|
1435
|
+
```
|
|
1436
|
+
Expected format (pattern only, confirmed live against this machine's
|
|
1437
|
+
existing real entries):
|
|
1438
|
+
```
|
|
1439
|
+
# akm:task <TID> BEGIN
|
|
1440
|
+
<cron-expr> AKM_BUNDLE_DIR=<working stash> <absolute-node-or-bun-path> <absolute-akm-launcher> task run <TID> --bundle <bundle> --scheduled > <logDir>/<TID>.log 2>&1
|
|
1441
|
+
# akm:task <TID> END
|
|
1442
|
+
```
|
|
1443
|
+
(Rows written by 0.9.0 – 0.9.17-alpha.6 carry `--scheduler-context <path>`
|
|
1444
|
+
after the launcher instead of the inline `AKM_BUNDLE_DIR=`.)
|
|
1445
|
+
A disabled binding's line is prefixed `# akm:disabled ` instead of being a
|
|
1446
|
+
live cron line.
|
|
1447
|
+
|
|
1448
|
+
**Cleanup (do NOT use `akm task sync`):**
|
|
1449
|
+
```sh
|
|
1450
|
+
crontab -l | sed "/# akm:task $TID BEGIN/,/# akm:task $TID END/d" | crontab -
|
|
1451
|
+
rm -f "$AKM_BUNDLE_DIR/tasks/$TID.yml"
|
|
1452
|
+
crontab -l | grep -c "$TID" # must print 0
|
|
1453
|
+
```
|
|
1454
|
+
|
|
1455
|
+
### DT-2 — `task add --disabled` writes a disabled binding [Linux: cron]
|
|
1456
|
+
|
|
1457
|
+
Same as DT-1 with `--disabled` added. Expected difference: the generated
|
|
1458
|
+
task YAML has `schedule: [{cron: "@daily", enabled: false}]`; the crontab
|
|
1459
|
+
line is written as `# akm:disabled ...`, matching real disabled entries
|
|
1460
|
+
observed on this host. Verify and clean up identically to DT-1.
|
|
1461
|
+
**UNVERIFIED for the same reason as DT-1.**
|
|
1462
|
+
|
|
1463
|
+
### DT-3 — `task sync` drift reconciliation, shell-target task [Linux: cron; VM/disposable-account strongly recommended]
|
|
1464
|
+
|
|
1465
|
+
**Why it matters:** the actual "scheduler and bundle disagree" scenario —
|
|
1466
|
+
a high-risk mutation path that also exercises #846's owner-path scoping.
|
|
1467
|
+
|
|
1468
|
+
**Mandatory precondition:**
|
|
1469
|
+
```sh
|
|
1470
|
+
crontab -l > "$AKM_SANDBOX/crontab-backup-$(date +%Y%m%d-%H%M%S).txt" 2>&1
|
|
1471
|
+
# If this shows real akm:task entries you don't recognize, STOP.
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
**Setup:** install a throwaway task via DT-1 (`$TID`), then edit its cron
|
|
1475
|
+
schedule by hand in `crontab -e` (change the minute field) without touching
|
|
1476
|
+
the YAML — manufacturing drift between the scheduler and the bundle.
|
|
1477
|
+
|
|
1478
|
+
**Command:** `akm task sync --bundle "$AKM_BUNDLE_DIR"`
|
|
1479
|
+
|
|
1480
|
+
**Expected:** `sync` detects the drifted binding and rewrites it to match
|
|
1481
|
+
the YAML's declared schedule (the YAML is the source of truth). Exit `0`.
|
|
1482
|
+
**`UNVERIFIED`: the exact JSON field name reporting "which entries
|
|
1483
|
+
changed"** — not executed live in review of this document; inspect
|
|
1484
|
+
`finalizeSchedulerSyncPlan`'s result shape in `src/commands/tasks/tasks.ts`
|
|
1485
|
+
before relying on a specific field name, or run once in a container and
|
|
1486
|
+
record the real output.
|
|
1487
|
+
|
|
1488
|
+
**Verify outside akm:** `crontab -l | grep -A2 "# akm:task $TID BEGIN"`
|
|
1489
|
+
shows the schedule restored to the YAML's value, not the hand-edited one.
|
|
1490
|
+
|
|
1491
|
+
**Cleanup:** same manual crontab-block removal as DT-1, plus delete the
|
|
1492
|
+
YAML, plus restore the mandatory backup.
|
|
1493
|
+
|
|
1494
|
+
### DT-4 — `task sync` reconciling a workflow-targeting task's schedule [Linux: cron; VM/disposable-account strongly recommended]
|
|
1495
|
+
|
|
1496
|
+
**Why it matters:** `task sync`'s own one-line help ("preflight and
|
|
1497
|
+
reconcile a bundle's task/workflow schedules") is the only place in the CLI
|
|
1498
|
+
that names both features together. This is DT-3's counterpart for a task
|
|
1499
|
+
whose target is a workflow rather than a shell command — not a duplicate:
|
|
1500
|
+
the target kind is different and both paths need independent coverage.
|
|
1501
|
+
|
|
1502
|
+
**Mandatory precondition:** `crontab -l > "$AKM_SANDBOX/crontab-backup-$(date +%Y%m%d-%H%M%S).txt" 2>&1` — stop if it shows unrecognized real entries.
|
|
1503
|
+
|
|
1504
|
+
**Setup** (disposable container/VM with an empty crontab only; use a
|
|
1505
|
+
collision-proof bundle name so the resulting scheduler evidence is easy to
|
|
1506
|
+
identify and clean up):
|
|
1507
|
+
```sh
|
|
1508
|
+
export SEAM_BUNDLE_NAME="akm-manual-test-seam-$(date +%s)"
|
|
1509
|
+
cat > "$AKM_BUNDLE_DIR/workflows/leaf.md" <<'EOF'
|
|
1510
|
+
---
|
|
1511
|
+
type: workflow
|
|
1512
|
+
description: Manual test leaf workflow
|
|
1513
|
+
steps:
|
|
1514
|
+
- id: work
|
|
1515
|
+
unit:
|
|
1516
|
+
exec:
|
|
1517
|
+
command: ["printf", "leaf-ok"]
|
|
1518
|
+
---
|
|
1519
|
+
|
|
1520
|
+
## work
|
|
1521
|
+
|
|
1522
|
+
Print leaf-ok.
|
|
1523
|
+
EOF
|
|
1524
|
+
cat > "$AKM_BUNDLE_DIR/tasks/akm-manual-test-drift.yml" <<'EOF'
|
|
1525
|
+
version: 4
|
|
1526
|
+
name: Manual test drift task -> workflow
|
|
1527
|
+
uses: workflows/leaf
|
|
1528
|
+
schedule:
|
|
1529
|
+
- cron: "0 3 * * *"
|
|
1530
|
+
enabled: true
|
|
1531
|
+
EOF
|
|
1532
|
+
akm index
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
**Steps and expected results:**
|
|
1536
|
+
1. `akm task sync --bundle "$SEAM_BUNDLE_NAME"` (first install) → expected
|
|
1537
|
+
exit `0`, `.installed` contains `"akm-manual-test-drift"`, `.updated ==
|
|
1538
|
+
[]`, `.removed == []`. Cross-check: `crontab -l | grep -A1 "akm:task
|
|
1539
|
+
akm-manual-test-drift BEGIN"` shows `0 3 * * *`, `--bundle
|
|
1540
|
+
"$SEAM_BUNDLE_NAME"`, and `task run akm-manual-test-drift --scheduled`.
|
|
1541
|
+
2. Simulate OS-side drift without touching the bundle file:
|
|
1542
|
+
`crontab -l | sed 's/^0 3 \* \* \*\(.*akm-manual-test-drift.*\)/0 4 * * *\1/' | crontab -`.
|
|
1543
|
+
Cross-check the entry now shows `0 4 * * *`.
|
|
1544
|
+
3. `akm task sync --bundle "$SEAM_BUNDLE_NAME"` again → expected exit `0`,
|
|
1545
|
+
`.updated` contains `"akm-manual-test-drift"` (the bundle's `.yml` wins),
|
|
1546
|
+
`.installed == []`, `.removed == []`. Cross-check the entry shows `0 3 *
|
|
1547
|
+
* *` again.
|
|
1548
|
+
4. `rm "$AKM_BUNDLE_DIR/tasks/akm-manual-test-drift.yml" && akm task sync
|
|
1549
|
+
--bundle "$SEAM_BUNDLE_NAME"` (the product's documented removal
|
|
1550
|
+
mechanism — there is no `task remove` subcommand;
|
|
1551
|
+
`docs/reference/tasks.md:391`: *"Delete the `.yml` source and sync to
|
|
1552
|
+
remove its derived binding(s)."*) → expected exit `0`, `.removed`
|
|
1553
|
+
contains `"akm-manual-test-drift"`. Cross-check: `crontab -l | grep -c
|
|
1554
|
+
akm-manual-test-drift` prints `0`.
|
|
1555
|
+
|
|
1556
|
+
**`UNVERIFIED` — this scenario's step-by-step outputs were designed from
|
|
1557
|
+
traced source (`belongsToBundle`, `src/tasks/scheduler-sync.ts`) but not
|
|
1558
|
+
executed live: the coding-agent sandbox's destructive-action classifier
|
|
1559
|
+
refused every `task sync` call attempted during review, which is itself a
|
|
1560
|
+
useful signal about how this surface should be treated.** Confirm the exact
|
|
1561
|
+
`.installed`/`.updated`/`.removed` values on a real disposable scheduler
|
|
1562
|
+
before relying on this test.
|
|
1563
|
+
|
|
1564
|
+
**Cleanup:** if step 4 was reached the crontab is already clean; regardless,
|
|
1565
|
+
restore the mandatory backup and verify with `crontab -l`.
|
|
1566
|
+
|
|
1567
|
+
### DT-5 — Cross-bundle name collision cannot make `sync` remove another bundle's entries (#846 regression) [Linux: cron; disposable VM/container REQUIRED]
|
|
1568
|
+
|
|
1569
|
+
**Why it matters:** alpha.5 could inspect and remove an unrelated scheduler
|
|
1570
|
+
entry when two primary bundles derived the same basename. Stable 0.9.2
|
|
1571
|
+
requires a name match *and* a resolved owner-path match before an entry is in
|
|
1572
|
+
scope, and refuses ownership when the path cannot be established. (Since
|
|
1573
|
+
0.9.17-alpha.7 every row names the path inline as `AKM_BUNDLE_DIR=`; an
|
|
1574
|
+
older row names it in its `--scheduler-context` descriptor, and is refused
|
|
1575
|
+
when that file is missing or unreadable.) This test reproduces the
|
|
1576
|
+
original collision safely and proves task A survives bundle B's sync.
|
|
1577
|
+
|
|
1578
|
+
**`UNVERIFIED` — even this fully self-contained, two-throwaway-bundle
|
|
1579
|
+
reproduction was refused by the coding-agent sandbox's destructive-action
|
|
1580
|
+
classifier before any `task sync` call executed during this document's
|
|
1581
|
+
review.** This test is designed from traced source, not run live. Whoever
|
|
1582
|
+
runs it for real needs an environment without that guardrail — a
|
|
1583
|
+
disposable VM/container is exactly that, and is required here regardless.
|
|
1584
|
+
|
|
1585
|
+
**Mandatory precondition:** `crontab -l > "$AKM_SANDBOX/crontab-backup-$(date +%Y%m%d-%H%M%S).txt" 2>&1`.
|
|
1586
|
+
|
|
1587
|
+
**Setup — two throwaway bundles that deliberately collide by name:**
|
|
1588
|
+
```sh
|
|
1589
|
+
export BASE=/tmp/akm-seam-collide-$(date +%s)
|
|
1590
|
+
mkdir -p "$BASE/a/bundle"/{tasks,workflows} "$BASE/a"/{config,data,cache,state}
|
|
1591
|
+
mkdir -p "$BASE/b-collide/bundle"/{tasks,workflows} "$BASE/b-collide"/{config,data,cache,state}
|
|
1592
|
+
for d in a b-collide; do
|
|
1593
|
+
cat > "$BASE/$d/config/config.json" <<'EOF'
|
|
1594
|
+
{"configVersion":"0.9.0","semanticSearchMode":"off","registries":[]}
|
|
1595
|
+
EOF
|
|
1596
|
+
done
|
|
1597
|
+
# Both bundle directories are literally named "bundle" — same basename means
|
|
1598
|
+
# deriveBundleId (src/core/write-source.ts:1254) derives the SAME name for
|
|
1599
|
+
# both, because de-dup only checks each bundle's OWN config.json, never the
|
|
1600
|
+
# other bundle's. This is the collision, reproduced on purpose.
|
|
1601
|
+
|
|
1602
|
+
cat > "$BASE/a/bundle/tasks/akm-manual-test-collide-a.yml" <<'EOF'
|
|
1603
|
+
version: 4
|
|
1604
|
+
name: Collision test task A (should survive if scoping is correct)
|
|
1605
|
+
run: printf collide-a-ok
|
|
1606
|
+
schedule:
|
|
1607
|
+
- cron: "1 1 * * *"
|
|
1608
|
+
enabled: true
|
|
1609
|
+
EOF
|
|
1610
|
+
|
|
1611
|
+
cat > "$BASE/b-collide/bundle/tasks/akm-manual-test-collide-b.yml" <<'EOF'
|
|
1612
|
+
version: 4
|
|
1613
|
+
name: Collision test task B (does not declare task A)
|
|
1614
|
+
run: printf collide-b-ok
|
|
1615
|
+
schedule:
|
|
1616
|
+
- cron: "2 2 * * *"
|
|
1617
|
+
enabled: true
|
|
1618
|
+
EOF
|
|
1619
|
+
```
|
|
1620
|
+
|
|
1621
|
+
**Steps and expected results:**
|
|
1622
|
+
1. Install bundle A with **no** `--bundle` flag (name comes from the
|
|
1623
|
+
directory-basename default):
|
|
1624
|
+
```sh
|
|
1625
|
+
export AKM_BUNDLE_DIR="$BASE/a/bundle" AKM_CONFIG_DIR="$BASE/a/config" \
|
|
1626
|
+
AKM_DATA_DIR="$BASE/a/data" AKM_CACHE_DIR="$BASE/a/cache" AKM_STATE_DIR="$BASE/a/state"
|
|
1627
|
+
akm index && akm task sync
|
|
1628
|
+
```
|
|
1629
|
+
Expected: exit `0`, `.installed` contains `"akm-manual-test-collide-a"`.
|
|
1630
|
+
Cross-check: `crontab -l | grep akm-manual-test-collide-a` shows `1 1 *
|
|
1631
|
+
* *`.
|
|
1632
|
+
2. Install bundle B (the colliding one), same pattern, no `--bundle` flag:
|
|
1633
|
+
```sh
|
|
1634
|
+
export AKM_BUNDLE_DIR="$BASE/b-collide/bundle" AKM_CONFIG_DIR="$BASE/b-collide/config" \
|
|
1635
|
+
AKM_DATA_DIR="$BASE/b-collide/data" AKM_CACHE_DIR="$BASE/b-collide/cache" AKM_STATE_DIR="$BASE/b-collide/state"
|
|
1636
|
+
akm index && akm task sync
|
|
1637
|
+
```
|
|
1638
|
+
Expected: `.removed == []` for task A and `.installed` contains
|
|
1639
|
+
`"akm-manual-test-collide-b"`. Cross-check: `crontab -l | grep -c
|
|
1640
|
+
akm-manual-test-collide-a` prints `1`, and its line still shows `1 1 * *
|
|
1641
|
+
*`. **This is the falsifiable core of the regression:** a removal of task A
|
|
1642
|
+
is a release-blocking recurrence of #846.
|
|
1643
|
+
3. **Negative control**, proving scoping DOES work when names differ.
|
|
1644
|
+
Reinstall task A (repeat step 1), then repeat step 2 with a
|
|
1645
|
+
non-colliding directory name, e.g. `"$BASE/b-safe/bundle-safe"`.
|
|
1646
|
+
Expected: `.removed == []`, task A's entry is still untouched. This proves
|
|
1647
|
+
the non-colliding case stays unchanged alongside the collision fix.
|
|
1648
|
+
|
|
1649
|
+
**Cleanup:** `crontab "$AKM_SANDBOX/crontab-backup-"*.txt` to restore, then
|
|
1650
|
+
verify with `crontab -l` that only the original entries remain. `rm -rf
|
|
1651
|
+
"$BASE"`.
|
|
1652
|
+
|
|
1653
|
+
### DT-6 — macOS launchd equivalents of DT-1..DT-3 [PLATFORM-GATED — cannot run on this Linux machine]
|
|
1654
|
+
|
|
1655
|
+
Everything above assumed `backend == "cron"`. On macOS, `akm task doctor`
|
|
1656
|
+
should report `.backend == "launchd"` (inferred from source, not observed).
|
|
1657
|
+
Re-run DT-1–DT-3 there with:
|
|
1658
|
+
- Verify outside akm: `launchctl list | grep <TID>` and inspect the
|
|
1659
|
+
generated plist under whatever path `doctor`'s `logDir`-equivalent
|
|
1660
|
+
reports on that host.
|
|
1661
|
+
- Cleanup: `launchctl bootout`/`unload` the job by label before deleting
|
|
1662
|
+
the plist and the task YAML — do not rely on `akm task sync` for
|
|
1663
|
+
cleanup, same rationale as DT-1.
|
|
1664
|
+
|
|
1665
|
+
**Entirely UNVERIFIED — confirm before shipping.** Per issue #770 this
|
|
1666
|
+
path has no automated coverage, which is exactly why it's high-risk manual
|
|
1667
|
+
surface.
|
|
1668
|
+
|
|
1669
|
+
### DT-7 — Windows schtasks equivalents of DT-1..DT-3 [PLATFORM-GATED — cannot run on this Linux machine]
|
|
1670
|
+
|
|
1671
|
+
Same structure as DT-6 but for `schtasks`. Verify outside akm with
|
|
1672
|
+
`schtasks /query /tn <TID> /v /fo list`. Cleanup with `schtasks /delete /tn
|
|
1673
|
+
<TID> /f` before deleting the task YAML. **Entirely UNVERIFIED** — not
|
|
1674
|
+
executed, platform absent here; per #770 this is uncovered by automation.
|
|
1675
|
+
|
|
1676
|
+
---
|
|
1677
|
+
|
|
1678
|
+
## Corrections made during adversarial review
|
|
1679
|
+
|
|
1680
|
+
This document was assembled from three independently-authored drafts and
|
|
1681
|
+
adversarially spot-checked against a live build before merging. Two
|
|
1682
|
+
specific corrections from that review:
|
|
1683
|
+
|
|
1684
|
+
1. **The claim "`akm workflow --help` USAGE omits `plan`" does not
|
|
1685
|
+
reproduce on commit `fb7bef3f`.** Live-checked against both `bun
|
|
1686
|
+
src/cli.ts workflow --help` and a freshly rebuilt `dist/akm workflow
|
|
1687
|
+
--help`: both list `status|list|create|resume|abandon|run|plan` in the
|
|
1688
|
+
USAGE line and describe `plan` in the COMMANDS table. One source draft's
|
|
1689
|
+
claim to the contrary appears to have been made against a stale `dist/`
|
|
1690
|
+
build before it noticed and rebuilt (the same draft separately warns
|
|
1691
|
+
"the checked-out `dist/` was stale... lacked `workflow plan`"). The
|
|
1692
|
+
correct, still-true finding kept in this document instead is: `akm task
|
|
1693
|
+
--help` genuinely has no `remove` subcommand (`add|run|explain|history|
|
|
1694
|
+
sync|doctor` only) — confirmed live.
|
|
1695
|
+
|
|
1696
|
+
Live alpha.5 spot-checks performed (in addition to the two above): TASK-3,
|
|
1697
|
+
TASK-6, TASK-9, TASK-12, TASK-13, WF-2, WF-5 (incl. `status --units`),
|
|
1698
|
+
WF-3's `.children` shape, WF-6/#847's original reproduction, WF-9's
|
|
1699
|
+
sqlite table/column names and exact error message, and INT-1 end to end. All
|
|
1700
|
+
matched their source draft's claimed exact fields, error codes, and exit codes
|
|
1701
|
+
with no further discrepancies found. Stable 0.9.2 changes WF-6 and DT-5 to
|
|
1702
|
+
the fixed regression expectations documented above; both are now automated.
|
|
1703
|
+
No test was found using
|
|
1704
|
+
non-falsifiable language ("works correctly", "runs successfully", "no
|
|
1705
|
+
errors"); the three source drafts were already disciplined about naming
|
|
1706
|
+
exact exit codes, JSON fields, and error strings. No true duplicate tests
|
|
1707
|
+
were found across the three drafts — the deliberate contrast pairs (WF-6 vs
|
|
1708
|
+
WF-7; DT-3 vs DT-4) were kept, and no accidental duplication existed thanks
|
|
1709
|
+
to the drafts' explicit scope carve-outs.
|
|
1710
|
+
|
|
1711
|
+
## A note on the existing bundle checklist
|
|
1712
|
+
|
|
1713
|
+
The AKM bundle asset `knowledge/projects/akm/akm-manual-testing-checklist`
|
|
1714
|
+
(frontmatter `updated: 2026-05-17`) has stale Tasks (§5) and Workflows (§7)
|
|
1715
|
+
sections: it references `akm tasks list` (the command is `task`, singular
|
|
1716
|
+
— `akm tasks list` does not exist) and `akm workflow next` (no such
|
|
1717
|
+
subcommand exists in 0.9.2; the current surface is `status|list|create|
|
|
1718
|
+
resume|abandon|run|plan`). Recommend replacing those two sections with a
|
|
1719
|
+
pointer to this document rather than trying to patch them in place. This
|
|
1720
|
+
document does not edit that bundle asset — only its author/owner should.
|