akm-cli 0.9.16 → 0.9.17-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* `BEGIN IMMEDIATE` transactions and the one SQLite contention classifier.
|
|
6
|
+
*
|
|
7
|
+
* `db.transaction()` is DEFERRED by default on both Bun and better-sqlite3,
|
|
8
|
+
* which means two writers can both perform stale preflight reads and only race
|
|
9
|
+
* when they finally attempt the write. Proposal creation, queue mutation and
|
|
10
|
+
* schema migration need the write lock BEFORE those reads so concurrent
|
|
11
|
+
* processes serialize on the live state rather than clobbering each other.
|
|
12
|
+
*
|
|
13
|
+
* Every open already carries a 30 s `busy_timeout` (`sqlite-pragmas.ts`), so a
|
|
14
|
+
* single blocked statement waits before failing. {@link beginImmediateTransaction}
|
|
15
|
+
* additionally retries `BEGIN IMMEDIATE` itself ({@link WITH_IMMEDIATE_TX_MAX_ATTEMPTS}
|
|
16
|
+
* attempts) for the rarer case of two writers racing the BEGIN statement
|
|
17
|
+
* back-to-back. If every attempt is still contention-shaped
|
|
18
|
+
* ({@link isSqliteContentionError} — SQLITE_BUSY/LOCKED, the matching message
|
|
19
|
+
* text, or the phantom-BEGIN marker), the exhaustion throw is reclassified
|
|
20
|
+
* into `TransientError("STATE_DB_CONTENDED")` (exit 75, #948 addendum) instead
|
|
21
|
+
* of surfacing the raw driver text: another akm process (an unrelated
|
|
22
|
+
* `improve`, `workflow run`, or task run — not necessarily contending for the
|
|
23
|
+
* same row) is writing the database right now. A genuinely unrelated error
|
|
24
|
+
* (real corruption, a body-thrown failure) is never reclassified and rethrows
|
|
25
|
+
* exactly as raised.
|
|
26
|
+
*
|
|
27
|
+
* This is the single place SQLITE_BUSY/LOCKED becomes exit 75 for state.db:
|
|
28
|
+
* `openStateDatabase`'s migration transaction, every repository write and the
|
|
29
|
+
* shared migration runner (`engines/sqlite-migrations.ts`) all go through it.
|
|
30
|
+
* `core/state-db.ts` re-exports these helpers for its callers.
|
|
31
|
+
*
|
|
32
|
+
* @module storage/sqlite-transaction
|
|
33
|
+
*/
|
|
34
|
+
import { TransientError } from "../core/errors.js";
|
|
35
|
+
import { sleepSync } from "../runtime.js";
|
|
36
|
+
/**
|
|
37
|
+
* Whether `err` is one of the SQLite conditions a concurrent-writer race can
|
|
38
|
+
* throw that are transient — the statement did NOT corrupt anything, another
|
|
39
|
+
* writer just holds the lock right now — and therefore safe to retry or
|
|
40
|
+
* reclassify as ordinary contention rather than a genuine failure:
|
|
41
|
+
* - `SQLITE_BUSY` / `SQLITE_LOCKED` (either driver's `.code`).
|
|
42
|
+
* - "database is locked" / "database table is locked" message text.
|
|
43
|
+
* - the phantom-BEGIN marker synthesized below when `BEGIN IMMEDIATE`
|
|
44
|
+
* returns without actually opening a transaction.
|
|
45
|
+
*
|
|
46
|
+
* This is the single shared classifier for "is this ordinary contention"
|
|
47
|
+
* (#948): `reclassifyIndexDbContention` (indexer) and the workflow-runs
|
|
48
|
+
* repository's lease-contention classifier (which additionally matches a
|
|
49
|
+
* couple of corruption-shaped texts specific to its own narrower cross-process
|
|
50
|
+
* race and keeps its own live-lease confirmation before reclassifying) both
|
|
51
|
+
* delegate to it. Matching this set alone is never sufficient to declare
|
|
52
|
+
* something DEFINITELY contention when a caller needs corroborating evidence
|
|
53
|
+
* (see the lease path); for `beginImmediateTransaction`'s own exhaustion case
|
|
54
|
+
* there is no such evidence available, so the five 30 s `busy_timeout` waits
|
|
55
|
+
* already spent stand as the evidence instead.
|
|
56
|
+
*/
|
|
57
|
+
export function isSqliteContentionError(err) {
|
|
58
|
+
const code = err?.code;
|
|
59
|
+
if (code === "SQLITE_BUSY" || code === "SQLITE_LOCKED")
|
|
60
|
+
return true;
|
|
61
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
62
|
+
return (msg.includes("database is locked") ||
|
|
63
|
+
msg.includes("database table is locked") ||
|
|
64
|
+
// Phantom BEGIN (see below) — synthesized when BEGIN IMMEDIATE returns
|
|
65
|
+
// without opening a transaction. Safe to retry: fn() has not run.
|
|
66
|
+
msg.includes("did not open a transaction"));
|
|
67
|
+
}
|
|
68
|
+
const WITH_IMMEDIATE_TX_MAX_ATTEMPTS = 5;
|
|
69
|
+
/**
|
|
70
|
+
* Reclassify an exhausted-retry BEGIN failure that is still contention-shaped
|
|
71
|
+
* (#948) into a `TransientError("STATE_DB_CONTENDED")`, the same class as a
|
|
72
|
+
* held workflow run lock (`RUN_LEASE_HELD`): the driver text is accurate but unhelpful (`{"ok":false,"error":"database is
|
|
73
|
+
* locked"}`, exit 70/INTERNAL) — this instead reads as a retryable-shortly
|
|
74
|
+
* signal (exit 75, sysexits EX_TEMPFAIL) with the original error preserved as
|
|
75
|
+
* `cause` for `--verbose`/debugging. A genuinely unrelated error (not
|
|
76
|
+
* contention-shaped) is rethrown exactly as raised, never reclassified.
|
|
77
|
+
*/
|
|
78
|
+
function throwBeginFailure(err) {
|
|
79
|
+
if (isSqliteContentionError(err)) {
|
|
80
|
+
const contended = new TransientError("akm's state database is busy (another akm process is writing it); retry shortly.", "STATE_DB_CONTENDED");
|
|
81
|
+
contended.cause = err;
|
|
82
|
+
throw contended;
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Open, but deliberately do not finish, an immediate transaction.
|
|
88
|
+
*
|
|
89
|
+
* This is the split-phase counterpart to {@link withImmediateTransaction} for
|
|
90
|
+
* the source-update coordinator: index finalization must mutate state.db in a
|
|
91
|
+
* transaction that remains pending until content, lockfile, and index
|
|
92
|
+
* publication have all succeeded. The caller that asked for this split phase
|
|
93
|
+
* owns the matching COMMIT/ROLLBACK.
|
|
94
|
+
*
|
|
95
|
+
* Only a contention-shaped BEGIN failure is retried. "cannot start a
|
|
96
|
+
* transaction within a transaction" is deliberately NOT: it means a
|
|
97
|
+
* transaction is already open on this connection (a re-entrant call — handled
|
|
98
|
+
* by the entry guard in {@link withImmediateTransaction}), and "retrying" it
|
|
99
|
+
* with a ROLLBACK would destroy the caller's transaction (issue #686).
|
|
100
|
+
*/
|
|
101
|
+
export function beginImmediateTransaction(db) {
|
|
102
|
+
if (db.inTransaction) {
|
|
103
|
+
throw new Error("beginImmediateTransaction requires a connection with no active transaction");
|
|
104
|
+
}
|
|
105
|
+
let lastBeginErr;
|
|
106
|
+
for (let attempt = 1; attempt <= WITH_IMMEDIATE_TX_MAX_ATTEMPTS; attempt++) {
|
|
107
|
+
try {
|
|
108
|
+
db.exec("BEGIN IMMEDIATE");
|
|
109
|
+
if (!db.inTransaction) {
|
|
110
|
+
throw new Error("BEGIN IMMEDIATE did not open a transaction (phantom contention state)");
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
lastBeginErr = err;
|
|
116
|
+
if (isSqliteContentionError(err) && attempt < WITH_IMMEDIATE_TX_MAX_ATTEMPTS) {
|
|
117
|
+
if (db.inTransaction) {
|
|
118
|
+
try {
|
|
119
|
+
db.exec("ROLLBACK");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Transaction already gone — safe to retry BEGIN.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
sleepSync(2 ** (attempt - 1));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
throwBeginFailure(err);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throwBeginFailure(lastBeginErr);
|
|
132
|
+
}
|
|
133
|
+
/** Run `fn` inside a `BEGIN IMMEDIATE` transaction, joining one that is already open on `db`. */
|
|
134
|
+
export function withImmediateTransaction(db, fn) {
|
|
135
|
+
// Re-entrancy guard (issue #686): if a transaction is already open on this
|
|
136
|
+
// connection (e.g. a nested withImmediateTransaction call inside an outer
|
|
137
|
+
// frame's fn), join it — run fn directly with no BEGIN/COMMIT/ROLLBACK of
|
|
138
|
+
// our own. Without this, the nested BEGIN throws "cannot start a transaction
|
|
139
|
+
// within a transaction", which the old retry path answered with an
|
|
140
|
+
// unconditional ROLLBACK — destroying the OUTER transaction and leaving its
|
|
141
|
+
// COMMIT to fail with "cannot commit - no transaction is active".
|
|
142
|
+
if (db.inTransaction) {
|
|
143
|
+
return fn();
|
|
144
|
+
}
|
|
145
|
+
beginImmediateTransaction(db);
|
|
146
|
+
try {
|
|
147
|
+
const result = fn();
|
|
148
|
+
if (!db.inTransaction) {
|
|
149
|
+
// The transaction we opened vanished while fn() ran (e.g. an
|
|
150
|
+
// auto-rollback or a stray ROLLBACK inside fn). fn's writes may have
|
|
151
|
+
// escaped serialization, so retrying is unsafe — fail loudly instead of
|
|
152
|
+
// letting COMMIT throw the opaque "cannot commit - no transaction is
|
|
153
|
+
// active" SQLiteError.
|
|
154
|
+
throw new Error("withImmediateTransaction invariant violated: transaction opened by BEGIN IMMEDIATE was no longer active after the transaction body ran; refusing to COMMIT (writes may have escaped serialization)");
|
|
155
|
+
}
|
|
156
|
+
db.exec("COMMIT");
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (db.inTransaction) {
|
|
161
|
+
try {
|
|
162
|
+
db.exec("ROLLBACK");
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Ignore rollback failures so the original error is preserved.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* state.db integrity + reclaimable-space probes (R0), and the VACUUM pass
|
|
6
|
+
* state.db and index.db share.
|
|
7
|
+
*
|
|
8
|
+
* `akm health`'s `state-db-integrity` check (src/commands/health/checks.ts)
|
|
9
|
+
* is a pure projection like every other check, so the actual IO lives here:
|
|
10
|
+
* a read-only `PRAGMA quick_check` and a read-only freelist/page-count read.
|
|
11
|
+
* Both open their own short-lived read-only connection via the plain
|
|
12
|
+
* {@link openDatabase} opener rather than `openStateDatabase`
|
|
13
|
+
* (src/core/state-db.ts), since a corrupt database must not need a clean
|
|
14
|
+
* migration-ledger read, let alone a migration, just to report itself as
|
|
15
|
+
* corrupt.
|
|
16
|
+
*
|
|
17
|
+
* {@link vacuumIfReclaimable} is the VACUUM step: given an already-open
|
|
18
|
+
* read-write connection (VACUUM cannot run inside a transaction, and a
|
|
19
|
+
* read-only handle cannot run it at all) and a freelist reading, it VACUUMs
|
|
20
|
+
* when asked to or when the freelist ratio crosses
|
|
21
|
+
* {@link STATE_DB_FREELIST_WARN_RATIO}, and never throws — a locked/busy
|
|
22
|
+
* database is reported, not raised. improve's post-purge pass runs it on
|
|
23
|
+
* state.db, and `akm index` on index.db.
|
|
24
|
+
*
|
|
25
|
+
* @module storage/state-db-integrity
|
|
26
|
+
*/
|
|
27
|
+
import { appendEvent } from "../core/events.js";
|
|
28
|
+
import { openDatabase } from "./database.js";
|
|
29
|
+
import { applyReadonlyPragmas } from "./sqlite-pragmas.js";
|
|
30
|
+
/** How many corruption errors `PRAGMA quick_check` collects before it stops scanning and returns. */
|
|
31
|
+
const QUICK_CHECK_ERROR_LIMIT = 10;
|
|
32
|
+
/**
|
|
33
|
+
* Above this fraction of free pages, `state-db-integrity` warns, and
|
|
34
|
+
* {@link vacuumIfReclaimable} compacts state.db (after improve's retention
|
|
35
|
+
* purge) or index.db (at the end of `akm index`).
|
|
36
|
+
*/
|
|
37
|
+
export const STATE_DB_FREELIST_WARN_RATIO = 0.5;
|
|
38
|
+
/** Event appended by {@link vacuumIfReclaimable} after a successful VACUUM of state.db. */
|
|
39
|
+
export const STATE_DB_VACUUMED_EVENT = "state_db_vacuumed";
|
|
40
|
+
/** Event appended by {@link vacuumIfReclaimable} after a successful VACUUM of index.db. */
|
|
41
|
+
export const INDEX_DB_VACUUMED_EVENT = "index_db_vacuumed";
|
|
42
|
+
function firstColumn(row) {
|
|
43
|
+
return row === undefined ? undefined : Object.values(row)[0];
|
|
44
|
+
}
|
|
45
|
+
function openReadonlyStateDb(dbPath) {
|
|
46
|
+
const db = openDatabase(dbPath, { readonly: true, create: false });
|
|
47
|
+
applyReadonlyPragmas(db);
|
|
48
|
+
return db;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run `PRAGMA quick_check(N)` against `dbPath` read-only. Sub-second on a
|
|
52
|
+
* healthy multi-hundred-MB file; on a corrupt one, `N` bounds how many errors
|
|
53
|
+
* SQLite collects before it stops scanning, which keeps the check's runtime
|
|
54
|
+
* bounded even against a badly corrupt file.
|
|
55
|
+
*/
|
|
56
|
+
export function runStateDbQuickCheck(dbPath) {
|
|
57
|
+
let db;
|
|
58
|
+
try {
|
|
59
|
+
db = openReadonlyStateDb(dbPath);
|
|
60
|
+
const rows = db.prepare(`PRAGMA quick_check(${QUICK_CHECK_ERROR_LIMIT})`).all();
|
|
61
|
+
const lines = rows.map((row) => String(firstColumn(row)));
|
|
62
|
+
const ok = lines.length === 1 && lines[0] === "ok";
|
|
63
|
+
return { ok, lines };
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return { ok: false, lines: [], error: err instanceof Error ? err.message : String(err) };
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
db?.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read `PRAGMA freelist_count` / `PRAGMA page_count` off an already-open
|
|
74
|
+
* connection. Shared by {@link getStateDbFreelistInfo} (which opens its own
|
|
75
|
+
* read-only handle) and the post-purge VACUUM call site, which must read the
|
|
76
|
+
* freelist off the same read-write connection the purge just used rather
|
|
77
|
+
* than open a second one.
|
|
78
|
+
*/
|
|
79
|
+
export function readFreelistInfo(db) {
|
|
80
|
+
const freelistCount = Number(firstColumn(db.prepare("PRAGMA freelist_count").get()) ?? 0);
|
|
81
|
+
const pageCount = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
|
|
82
|
+
return { freelistCount, pageCount, ratio: pageCount > 0 ? freelistCount / pageCount : 0 };
|
|
83
|
+
}
|
|
84
|
+
/** Read `PRAGMA freelist_count` / `PRAGMA page_count` — how much of state.db is reclaimable by VACUUM. */
|
|
85
|
+
export function getStateDbFreelistInfo(dbPath) {
|
|
86
|
+
let db;
|
|
87
|
+
try {
|
|
88
|
+
db = openReadonlyStateDb(dbPath);
|
|
89
|
+
return readFreelistInfo(db);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return { freelistCount: 0, pageCount: 0, ratio: 0, error: err instanceof Error ? err.message : String(err) };
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
db?.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* VACUUM `db` when `vacuum.force` is set or `freelist.ratio` exceeds
|
|
100
|
+
* {@link STATE_DB_FREELIST_WARN_RATIO}, appending `vacuum.eventType` with
|
|
101
|
+
* the pages before/after. Runs on the caller's read-write connection — for
|
|
102
|
+
* state.db right after the retention purge, for index.db at the end of an
|
|
103
|
+
* index run. Never throws: a locked/busy database (another writer holds the
|
|
104
|
+
* file right now) is reported via `reason: "busy"` rather than raised, since
|
|
105
|
+
* this is opportunistic maintenance and must not fail the pass it follows.
|
|
106
|
+
*
|
|
107
|
+
* The event is appended via `appendEvent` (not a direct `insertEvent` on
|
|
108
|
+
* `db`) so it honors the caller's `EventsContext` — `readOnly` suppresses
|
|
109
|
+
* the write and an injected `now` is used for `ts` — the same as every
|
|
110
|
+
* other event `runRetentionPurgePass` appends in this callback.
|
|
111
|
+
*/
|
|
112
|
+
export function vacuumIfReclaimable(db, freelist, vacuum, eventsCtx) {
|
|
113
|
+
if (!vacuum.force && freelist.ratio <= STATE_DB_FREELIST_WARN_RATIO) {
|
|
114
|
+
return { ran: false, reason: "below-threshold", pagesBefore: freelist.pageCount };
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
db.exec("VACUUM");
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
121
|
+
const busy = /busy|locked/i.test(message);
|
|
122
|
+
return { ran: false, reason: busy ? "busy" : "error", pagesBefore: freelist.pageCount, error: message };
|
|
123
|
+
}
|
|
124
|
+
const pagesAfter = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
|
|
125
|
+
appendEvent({
|
|
126
|
+
eventType: vacuum.eventType,
|
|
127
|
+
metadata: { pagesBefore: freelist.pageCount, pagesAfter, freelistRatioBefore: freelist.ratio },
|
|
128
|
+
}, eventsCtx);
|
|
129
|
+
return { ran: true, pagesBefore: freelist.pageCount, pagesAfter };
|
|
130
|
+
}
|
|
@@ -1,88 +1,160 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The host's scheduling choice.
|
|
6
|
+
*
|
|
7
|
+
* `scheduler.enabled` in config.json is the list of fully-qualified refs
|
|
8
|
+
* (`bundle//tasks/x`, `bundle//workflows/y`) this host installs in its native
|
|
9
|
+
* scheduler. Installing a bundle never schedules anything: a task only gets a
|
|
10
|
+
* native row once its ref is on this list (`akm task enable`, `akm setup`),
|
|
11
|
+
* and only bundles that are active on this host are eligible.
|
|
12
|
+
*
|
|
13
|
+
* A config that predates the list (`scheduler.enabled` absent) is read as
|
|
14
|
+
* "keep what is installed": `akm task sync` takes the akm-written rows already
|
|
15
|
+
* in the native scheduler as the host's choice and writes the list once. A
|
|
16
|
+
* 0.9.17-alpha `{kind, ref, sourceId}` entry is read as its `ref`.
|
|
17
|
+
*/
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { assetPathForName, stashDirFor } from "../core/asset/asset-placement.js";
|
|
21
|
+
import { bundleRefToString, makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
22
|
+
import { typeNameFromConceptId } from "../core/asset/resolve-ref.js";
|
|
23
|
+
import { mutateConfig, schedulerSourceIdFor } from "../core/config/config.js";
|
|
24
|
+
import { bundleComponentConfig, bundleContentRoots, isBundleEnabled } from "../core/config/config-sources.js";
|
|
9
25
|
import { UsageError } from "../core/errors.js";
|
|
10
|
-
|
|
26
|
+
import { warnOnce } from "../core/warn.js";
|
|
27
|
+
export function canonicalSchedulerRef(ref) {
|
|
11
28
|
const parsed = parseBundleRef(ref);
|
|
12
29
|
if (!parsed.bundle || parsed.fragment !== undefined || bundleRefToString(parsed) !== ref) {
|
|
13
|
-
throw new UsageError(`
|
|
30
|
+
throw new UsageError(`Scheduling requires a canonical fully-qualified ref, got ${JSON.stringify(ref)}.`, "INVALID_FLAG_VALUE");
|
|
14
31
|
}
|
|
15
32
|
return ref;
|
|
16
33
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Refs this host schedules, or `undefined` when the host has never chosen
|
|
36
|
+
* (the config predates the list). An entry that is not a canonical ref is
|
|
37
|
+
* skipped with a warning, never fatal.
|
|
38
|
+
*/
|
|
39
|
+
export function schedulerEnabledRefs(config) {
|
|
40
|
+
const enabled = config.scheduler?.enabled;
|
|
41
|
+
if (enabled === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
const refs = new Set();
|
|
44
|
+
for (const ref of enabled) {
|
|
45
|
+
try {
|
|
46
|
+
refs.add(canonicalSchedulerRef(ref));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
warnOnce(`scheduler:enabled:${ref}`, `Ignoring scheduler.enabled entry ${JSON.stringify(ref)}: not a canonical ref.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Object.freeze([...refs]);
|
|
31
53
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return schedulerActivations(config).filter((activation) => {
|
|
35
|
-
const bundle = parseBundleRef(activation.ref).bundle;
|
|
36
|
-
return activation.sourceId === schedulerActivationSourceId(config, bundle);
|
|
37
|
-
});
|
|
54
|
+
export function isSchedulerRefEnabled(config, ref) {
|
|
55
|
+
return (schedulerEnabledRefs(config) ?? []).includes(canonicalSchedulerRef(ref));
|
|
38
56
|
}
|
|
39
|
-
/** Pure lifecycle helper used when a bundle is removed or replaced. */
|
|
40
57
|
export function revokeSchedulerActivationsForBundle(config, bundleId) {
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
58
|
+
const current = config.scheduler?.enabled;
|
|
59
|
+
if (current === undefined)
|
|
60
|
+
return config;
|
|
61
|
+
const enabled = current.filter((ref) => bundleOf(ref) !== bundleId);
|
|
62
|
+
if (enabled.length === current.length)
|
|
43
63
|
return config;
|
|
44
64
|
return { ...config, scheduler: { ...config.scheduler, enabled } };
|
|
45
65
|
}
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
/** Whether refs from this bundle may be scheduled here: configured and enabled, or the implicit `AKM_BUNDLE_DIR` stash. */
|
|
67
|
+
export function isSchedulerBundleActive(config, bundleId) {
|
|
68
|
+
return schedulerSourceIdFor(config, bundleId) !== undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Add or remove one ref on this host's list. Callers must have made the host's
|
|
72
|
+
* choice explicit first (see `initializeSchedulerChoice` in
|
|
73
|
+
* `commands/tasks/tasks.ts`): on a config without the list this writes just
|
|
74
|
+
* the one ref, which is the whole choice from then on.
|
|
75
|
+
*/
|
|
76
|
+
export function setSchedulerRefEnabled(ref, enabled) {
|
|
77
|
+
const canonicalRef = canonicalSchedulerRef(ref);
|
|
48
78
|
const result = mutateConfig((current) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const sourceId = enabled ? schedulerActivationSourceId(current, bundle) : undefined;
|
|
53
|
-
if (enabled && !sourceId) {
|
|
54
|
-
throw new UsageError(`Cannot enable scheduled execution from inactive or unconfigured bundle ${JSON.stringify(bundle)}.`, "INVALID_FLAG_VALUE");
|
|
79
|
+
const existing = current.scheduler?.enabled ?? [];
|
|
80
|
+
if (enabled && !isSchedulerBundleActive(current, bundleOf(canonicalRef) ?? "")) {
|
|
81
|
+
throw new UsageError(`Cannot enable scheduled execution from inactive or unconfigured bundle ${JSON.stringify(bundleOf(canonicalRef))}.`, "INVALID_FLAG_VALUE");
|
|
55
82
|
}
|
|
56
|
-
|
|
57
|
-
if (present === enabled)
|
|
83
|
+
if (current.scheduler?.enabled !== undefined && existing.includes(canonicalRef) === enabled)
|
|
58
84
|
return current;
|
|
59
|
-
const next =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
: existing.filter((activation) => activation.kind !== kind || activation.ref !== canonicalRef);
|
|
65
|
-
next.sort((left, right) => left.ref.localeCompare(right.ref) || left.kind.localeCompare(right.kind));
|
|
66
|
-
return {
|
|
67
|
-
...current,
|
|
68
|
-
scheduler: {
|
|
69
|
-
...current.scheduler,
|
|
70
|
-
enabled: next,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
85
|
+
const next = existing.filter((entry) => entry !== canonicalRef);
|
|
86
|
+
if (enabled)
|
|
87
|
+
next.push(canonicalRef);
|
|
88
|
+
next.sort((left, right) => left.localeCompare(right));
|
|
89
|
+
return { ...current, scheduler: { ...current.scheduler, enabled: next } };
|
|
73
90
|
});
|
|
74
91
|
return { config: result.config, changed: result.written, ref: canonicalRef };
|
|
75
92
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The refs behind the akm-written rows currently installed in the native
|
|
95
|
+
* scheduler: what this host was running before it had a `scheduler.enabled`
|
|
96
|
+
* list. Rows the backend proves disabled, rows for bundles that are not
|
|
97
|
+
* active here, and rows whose task file is gone are left out.
|
|
98
|
+
*/
|
|
99
|
+
export function enabledRefsFromInstalled(installed, config) {
|
|
100
|
+
const refs = new Set();
|
|
101
|
+
for (const entry of installed) {
|
|
102
|
+
const ref = installedEntryRef(entry, config);
|
|
103
|
+
if (ref !== undefined)
|
|
104
|
+
refs.add(ref);
|
|
105
|
+
}
|
|
106
|
+
return Object.freeze([...refs].sort((left, right) => left.localeCompare(right)));
|
|
107
|
+
}
|
|
108
|
+
function installedEntryRef(entry, config) {
|
|
109
|
+
if (entry.enabled === false || !entry.invocation)
|
|
110
|
+
return undefined;
|
|
111
|
+
const invocation = entry.invocation;
|
|
112
|
+
try {
|
|
113
|
+
if (invocation[0] === "workflow" && invocation[1] === "run" && invocation.length === 3 && invocation[2]) {
|
|
114
|
+
const ref = canonicalSchedulerRef(invocation[2]);
|
|
115
|
+
const parsed = parseBundleRef(ref);
|
|
116
|
+
if (!parsed.bundle || !isBundleEnabled(config, parsed.bundle))
|
|
117
|
+
return undefined;
|
|
118
|
+
return hasBackingFile(config, parsed.bundle, parsed.conceptId) ? ref : undefined;
|
|
119
|
+
}
|
|
120
|
+
if (invocation[0] !== "task" || invocation[1] !== "run" || !invocation[2])
|
|
121
|
+
return undefined;
|
|
122
|
+
const bundleIndex = invocation.indexOf("--bundle", 3);
|
|
123
|
+
const bundle = bundleIndex === -1 ? config.defaultBundle : invocation[bundleIndex + 1];
|
|
124
|
+
if (!bundle || !isBundleEnabled(config, bundle))
|
|
125
|
+
return undefined;
|
|
126
|
+
const bundleConfig = config.bundles?.[bundle];
|
|
127
|
+
const adapter = bundleConfig ? (bundleComponentConfig(bundleConfig)?.adapter ?? "akm") : "akm";
|
|
128
|
+
const conceptId = adapter === "akm-task" ? invocation[2] : `tasks/${invocation[2]}`;
|
|
129
|
+
if (!hasBackingFile(config, bundle, conceptId))
|
|
130
|
+
return undefined;
|
|
131
|
+
return canonicalSchedulerRef(makeBundleRef(bundle, conceptId));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
81
134
|
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function hasBackingFile(config, bundle, conceptId) {
|
|
138
|
+
const parts = typeNameFromConceptId(conceptId);
|
|
139
|
+
if (!parts)
|
|
140
|
+
return true;
|
|
141
|
+
const typeDir = stashDirFor(parts.type);
|
|
142
|
+
if (!typeDir)
|
|
143
|
+
return true;
|
|
144
|
+
const contentRoot = bundleContentRoots(config).find((entry) => entry.id === bundle)?.contentRoot;
|
|
145
|
+
if (!contentRoot)
|
|
146
|
+
return true;
|
|
147
|
+
try {
|
|
148
|
+
const target = assetPathForName(parts.type, path.join(contentRoot, typeDir), parts.name);
|
|
149
|
+
return fs.existsSync(target) && fs.statSync(target).isFile();
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function bundleOf(ref) {
|
|
82
156
|
try {
|
|
83
|
-
|
|
84
|
-
const implicitId = deriveBundleId(undefined, root, new Set(Object.keys(config.bundles ?? {})));
|
|
85
|
-
return implicitId === bundleId ? filesystemBundleSourceId(root) : undefined;
|
|
157
|
+
return parseBundleRef(ref).bundle;
|
|
86
158
|
}
|
|
87
159
|
catch {
|
|
88
160
|
return undefined;
|