akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +756 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +2 -7
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +0 -17
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +363 -679
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +87 -406
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -21
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/config/retired-experimental-keys-shim.js +0 -62
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -78
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
import { withImmediateTransaction } from "./sqlite-transaction.js";
|
|
5
|
+
/**
|
|
6
|
+
* Reject a `MIGRATIONS` array containing a duplicate `id`.
|
|
7
|
+
*
|
|
8
|
+
* A compiled-in registry can't diverge at runtime, so this is a dev-time
|
|
9
|
+
* invariant, not a per-open guard: each consumer (state.db, logs.db) calls it
|
|
10
|
+
* once on its own array at module load, and `tests/storage/sqlite-migrations.test.ts`
|
|
11
|
+
* pins the duplicate-detection behavior directly. {@link inspectMigrationLedger}
|
|
12
|
+
* does NOT call this — re-scanning the same compiled-in array on every DB open
|
|
13
|
+
* added no safety over the module-load check, only repeated O(n) cost.
|
|
14
|
+
*/
|
|
15
|
+
export function assertMigrationRegistry(migrations) {
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
for (const migration of migrations) {
|
|
18
|
+
if (seen.has(migration.id))
|
|
19
|
+
throw new Error(`Migration registry contains duplicate ID ${migration.id}.`);
|
|
20
|
+
seen.add(migration.id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function migrationLedgerExists(db) {
|
|
24
|
+
return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'").get();
|
|
25
|
+
}
|
|
26
|
+
/** Inspect the database's applied IDs against the exact ordered registry prefix. */
|
|
27
|
+
function inspectLedgerAgainst(db, registryIds) {
|
|
28
|
+
if (!migrationLedgerExists(db))
|
|
29
|
+
return { status: registryIds.length === 0 ? "current" : "old", migrationIds: [] };
|
|
30
|
+
const rows = db.prepare("SELECT id FROM schema_migrations ORDER BY rowid").all();
|
|
31
|
+
const migrationIds = rows.map((row) => row.id);
|
|
32
|
+
for (const [index, row] of rows.entries()) {
|
|
33
|
+
const expectedId = registryIds[index];
|
|
34
|
+
// Every id this binary knows matched in order and the ledger carries more:
|
|
35
|
+
// the database was migrated by a newer akm. Nothing here is applicable —
|
|
36
|
+
// this binary's whole registry is already applied — so this is version
|
|
37
|
+
// skew, not divergence.
|
|
38
|
+
if (!expectedId) {
|
|
39
|
+
return {
|
|
40
|
+
status: "newer",
|
|
41
|
+
migrationIds,
|
|
42
|
+
detail: `applied migration ID${rows.length - registryIds.length === 1 ? "" : "s"} ${migrationIds
|
|
43
|
+
.slice(registryIds.length)
|
|
44
|
+
.join(", ")} unknown to this akm`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// A mismatch at a position this binary has a migration for is divergence,
|
|
48
|
+
// whether or not the id is one this binary knows later: this binary's
|
|
49
|
+
// migration at `index` was never applied, and something else was.
|
|
50
|
+
if (row.id !== expectedId) {
|
|
51
|
+
return {
|
|
52
|
+
status: "inconsistent",
|
|
53
|
+
migrationIds,
|
|
54
|
+
detail: `migration ledger is not an exact ordered prefix at position ${index + 1} (found '${row.id}', expected '${expectedId}'). ` +
|
|
55
|
+
`Applied, in order: [${migrationIds.join(", ")}]. This akm's expected order: [${registryIds.join(", ")}].`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
status: rows.length === registryIds.length ? "current" : "old",
|
|
61
|
+
migrationIds,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function inspectMigrationLedger(db, migrations) {
|
|
65
|
+
return inspectLedgerAgainst(db, migrations.map((migration) => migration.id));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Reject only a ledger this binary cannot reason about at all.
|
|
69
|
+
*
|
|
70
|
+
* A `newer` ledger — an exact ordered prefix of this binary's registry plus
|
|
71
|
+
* migrations a later akm added — is NOT rejected. Two akm versions sharing one
|
|
72
|
+
* data directory is a supported deployment (a bundled CLI alongside a newer
|
|
73
|
+
* global install), and refusing the open bricked the older one for every
|
|
74
|
+
* command while protecting nothing: its entire registry is already applied, so
|
|
75
|
+
* it has no pending migration to run. Callers that want to tell an operator
|
|
76
|
+
* about the skew read {@link MigrationLedgerState.status}.
|
|
77
|
+
*
|
|
78
|
+
* An `inconsistent` ledger is different: this binary has a migration that was
|
|
79
|
+
* never applied and something else was applied in its place, so running the
|
|
80
|
+
* pending set could conflict with schema it cannot see. That still refuses.
|
|
81
|
+
*/
|
|
82
|
+
export function assertMigrationLedger(db, migrations) {
|
|
83
|
+
const state = inspectMigrationLedger(db, migrations);
|
|
84
|
+
if (state.status === "inconsistent") {
|
|
85
|
+
throw new Error(`Refusing a database whose migrations are not an exact ordered prefix: ${state.detail} ` +
|
|
86
|
+
"Applying this binary's missing migration now could run it against a schema a later migration already " +
|
|
87
|
+
"changed underneath it, which is a real risk of producing a wrong schema — not something akm can guess " +
|
|
88
|
+
"its way out of safely. This usually means the database was migrated by an incompatible akm build or " +
|
|
89
|
+
"fork, or schema_migrations was edited by hand. Restore this file from a backup taken before the " +
|
|
90
|
+
"divergence, or — if there is no backup and the data is not needed — delete it and let akm rebuild it " +
|
|
91
|
+
"from scratch (a derived index.db regenerates from your sources on the next 'akm index'; state.db loses " +
|
|
92
|
+
"durable history such as improve/proposal state and must be treated as a last resort).");
|
|
93
|
+
}
|
|
94
|
+
return state;
|
|
95
|
+
}
|
|
96
|
+
/** Create the migrations ledger table if it does not exist. */
|
|
97
|
+
export function ensureMigrationsTable(db) {
|
|
98
|
+
db.exec(`
|
|
99
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
102
|
+
);
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
/** The registry entries not yet recorded in the ledger, in order. Throws on a divergent ledger. */
|
|
106
|
+
export function pendingMigrations(db, migrations) {
|
|
107
|
+
return migrations.slice(assertMigrationLedger(db, migrations).migrationIds.length);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Apply every pending migration in one `BEGIN IMMEDIATE` transaction.
|
|
111
|
+
*
|
|
112
|
+
* A database with nothing pending is only read, never write-locked. Otherwise
|
|
113
|
+
* the write lock is taken up front — a second process bootstrapping the same
|
|
114
|
+
* database WAITS for the first to commit instead of racing it — and the
|
|
115
|
+
* pending set is re-read under that lock, so the process that lost the race
|
|
116
|
+
* finds nothing left to do rather than re-running DDL. Each migration's ledger
|
|
117
|
+
* row is inserted right after its SQL inside the same transaction: a failing
|
|
118
|
+
* migration rolls back every migration this call applied, and their ledger
|
|
119
|
+
* rows with them. Contention that outlasts every BEGIN retry surfaces as
|
|
120
|
+
* `TransientError("STATE_DB_CONTENDED")` (`../sqlite-transaction`). Returns the
|
|
121
|
+
* IDs this call applied, in order — empty when another process got there first.
|
|
122
|
+
*/
|
|
123
|
+
export function runMigrations(db, migrations) {
|
|
124
|
+
if (pendingMigrations(db, migrations).length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
return withImmediateTransaction(db, () => {
|
|
127
|
+
ensureMigrationsTable(db);
|
|
128
|
+
const applied = [];
|
|
129
|
+
for (const migration of pendingMigrations(db, migrations)) {
|
|
130
|
+
db.exec(migration.up);
|
|
131
|
+
db.prepare("INSERT INTO schema_migrations (id) VALUES (?)").run(migration.id);
|
|
132
|
+
applied.push(migration.id);
|
|
133
|
+
}
|
|
134
|
+
return applied;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -95,17 +95,19 @@ export function isNetworkFilesystem(fsType) {
|
|
|
95
95
|
return false;
|
|
96
96
|
return NETWORK_FS_MAGICS.has(fsType);
|
|
97
97
|
}
|
|
98
|
-
/**
|
|
98
|
+
/** How long a statement waits for a lock before failing with SQLITE_BUSY. */
|
|
99
|
+
export const SQLITE_BUSY_TIMEOUT_MS = 30_000;
|
|
99
100
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* the
|
|
105
|
-
*
|
|
106
|
-
* fallback and `AKM_SQLITE_JOURNAL_MODE` can select, where readers do block.
|
|
101
|
+
* The standard set for a READ-ONLY handle: just `busy_timeout`. A read-only
|
|
102
|
+
* connection cannot run the rest (journal_mode and foreign_keys are write
|
|
103
|
+
* operations), but SQLite's default timeout of 0 makes reads fail INSTANTLY
|
|
104
|
+
* under writer contention — which matters in the DELETE/TRUNCATE journal modes
|
|
105
|
+
* the network-filesystem fallback and `AKM_SQLITE_JOURNAL_MODE` can select,
|
|
106
|
+
* where readers do block.
|
|
107
107
|
*/
|
|
108
|
-
export
|
|
108
|
+
export function applyReadonlyPragmas(db) {
|
|
109
|
+
db.exec(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`);
|
|
110
|
+
}
|
|
109
111
|
/**
|
|
110
112
|
* Apply AKM's standard opening PRAGMAs to `db`, in order:
|
|
111
113
|
* 1. `journal_mode` = the configured mode (with WAL→DELETE network-FS fallback)
|
|
@@ -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
|
+
}
|
|
@@ -2,42 +2,49 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* state.db integrity + reclaimable-space probes (R0,
|
|
5
|
+
* state.db integrity + reclaimable-space probes (R0), and the VACUUM pass
|
|
6
|
+
* state.db and index.db share.
|
|
6
7
|
*
|
|
7
8
|
* `akm health`'s `state-db-integrity` check (src/commands/health/checks.ts)
|
|
8
9
|
* is a pure projection like every other check, so the actual IO lives here:
|
|
9
10
|
* a read-only `PRAGMA quick_check` and a read-only freelist/page-count read.
|
|
10
|
-
* Both open their own short-lived connection via the plain
|
|
11
|
-
* opener
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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.
|
|
14
16
|
*
|
|
15
|
-
* {@link
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* and never throws — a locked/busy
|
|
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.
|
|
20
24
|
*
|
|
21
25
|
* @module storage/state-db-integrity
|
|
22
26
|
*/
|
|
23
27
|
import { appendEvent } from "../core/events.js";
|
|
24
28
|
import { openDatabase } from "./database.js";
|
|
25
|
-
import {
|
|
29
|
+
import { applyReadonlyPragmas } from "./sqlite-pragmas.js";
|
|
26
30
|
/** How many corruption errors `PRAGMA quick_check` collects before it stops scanning and returns. */
|
|
27
31
|
const QUICK_CHECK_ERROR_LIMIT = 10;
|
|
28
|
-
/**
|
|
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
|
+
*/
|
|
29
37
|
export const STATE_DB_FREELIST_WARN_RATIO = 0.5;
|
|
30
|
-
/** Event appended by {@link
|
|
38
|
+
/** Event appended by {@link vacuumIfReclaimable} after a successful VACUUM of state.db. */
|
|
31
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";
|
|
32
42
|
function firstColumn(row) {
|
|
33
43
|
return row === undefined ? undefined : Object.values(row)[0];
|
|
34
44
|
}
|
|
35
45
|
function openReadonlyStateDb(dbPath) {
|
|
36
46
|
const db = openDatabase(dbPath, { readonly: true, create: false });
|
|
37
|
-
|
|
38
|
-
// but busy_timeout is legal — see openReadonlyExistingDatabase's identical
|
|
39
|
-
// rationale in src/storage/repositories/index-connection.ts.
|
|
40
|
-
db.exec(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`);
|
|
47
|
+
applyReadonlyPragmas(db);
|
|
41
48
|
return db;
|
|
42
49
|
}
|
|
43
50
|
/**
|
|
@@ -89,21 +96,21 @@ export function getStateDbFreelistInfo(dbPath) {
|
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
/**
|
|
92
|
-
* VACUUM `db` when `freelist.ratio` exceeds
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* database (another writer holds the
|
|
97
|
-
* `reason: "busy"` rather than raised, since
|
|
98
|
-
* maintenance and must not fail the
|
|
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.
|
|
99
106
|
*
|
|
100
107
|
* The event is appended via `appendEvent` (not a direct `insertEvent` on
|
|
101
108
|
* `db`) so it honors the caller's `EventsContext` — `readOnly` suppresses
|
|
102
109
|
* the write and an injected `now` is used for `ts` — the same as every
|
|
103
110
|
* other event `runRetentionPurgePass` appends in this callback.
|
|
104
111
|
*/
|
|
105
|
-
export function
|
|
106
|
-
if (freelist.ratio <= STATE_DB_FREELIST_WARN_RATIO) {
|
|
112
|
+
export function vacuumIfReclaimable(db, freelist, vacuum, eventsCtx) {
|
|
113
|
+
if (!vacuum.force && freelist.ratio <= STATE_DB_FREELIST_WARN_RATIO) {
|
|
107
114
|
return { ran: false, reason: "below-threshold", pagesBefore: freelist.pageCount };
|
|
108
115
|
}
|
|
109
116
|
try {
|
|
@@ -116,7 +123,7 @@ export function vacuumStateDbIfReclaimable(db, freelist, eventsCtx) {
|
|
|
116
123
|
}
|
|
117
124
|
const pagesAfter = Number(firstColumn(db.prepare("PRAGMA page_count").get()) ?? 0);
|
|
118
125
|
appendEvent({
|
|
119
|
-
eventType:
|
|
126
|
+
eventType: vacuum.eventType,
|
|
120
127
|
metadata: { pagesBefore: freelist.pageCount, pagesAfter, freelistRatioBefore: freelist.ratio },
|
|
121
128
|
}, eventsCtx);
|
|
122
129
|
return { ran: true, pagesBefore: freelist.pageCount, pagesAfter };
|