akm-cli 0.9.1 → 0.9.2-alpha.1
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 +88 -0
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract.js +521 -342
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +10 -0
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +835 -152
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +83 -34
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +21 -20
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +65 -34
- package/dist/indexer/indexer.js +443 -222
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/scan/doc-to-entry.js +7 -9
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +98 -22
- package/dist/indexer/search/search-fields.js +1 -1
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +28 -32
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19122 -52218
- package/dist/scripts/akm-migrate.js +19064 -51567
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +167 -207
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +10 -10
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +71 -342
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +1 -1
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +153 -27
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -216
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
package/dist/core/events.js
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
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
|
+
* Append-only events stream — backed by state.db (#204, Phase 3).
|
|
6
|
+
*
|
|
7
|
+
* Every mutating CLI verb funnels through `appendEvent` so external
|
|
8
|
+
* observers (sync, replication, audit, dashboards) can react to stash
|
|
9
|
+
* changes. Events are stored in the `events` table in `state.db`
|
|
10
|
+
* (SQLite, WAL mode) instead of a flat `events.jsonl` file.
|
|
11
|
+
*
|
|
12
|
+
* The helper is the only thing in akm that writes to the events table. It
|
|
13
|
+
* accepts an injectable `dbPath` (via `EventsContext`) so tests can pin a
|
|
14
|
+
* tmpdir without any global mutation.
|
|
15
|
+
*
|
|
16
|
+
* Format (each EventEnvelope):
|
|
17
|
+
* { "schemaVersion": 1, "id": <number>, "ts": "<ISO>",
|
|
18
|
+
* "eventType": "<verb>", "ref"?: "<asset-ref>", ... }
|
|
19
|
+
*
|
|
20
|
+
* - `id` is a monotonic SQLite AUTOINCREMENT rowid. Callers can persist it
|
|
21
|
+
* as a durable cursor for `--since` resumption. The public API surfaces this
|
|
22
|
+
* as the opaque `nextOffset` number.
|
|
23
|
+
* - `ts` is ISO-8601 (UTC, millisecond precision).
|
|
24
|
+
*/
|
|
25
|
+
import fs from "node:fs";
|
|
26
|
+
import { openDatabase } from "../storage/database.js";
|
|
4
27
|
import { insertEvent, readStateEvents } from "../storage/repositories/events-repository.js";
|
|
5
28
|
import { rethrowIfTestIsolationError } from "./errors.js";
|
|
6
29
|
import { getStateDbPath, openStateDatabase, withStateDb } from "./state-db.js";
|
|
7
30
|
import { borrowScopedStateDb } from "./state-db-scope.js";
|
|
31
|
+
import { isReadOnlyFilesystemError } from "./system-error.js";
|
|
8
32
|
import { error } from "./warn.js";
|
|
9
33
|
/**
|
|
10
34
|
* Resolve the state.db path from context:
|
|
@@ -39,6 +63,8 @@ function resolveNow(ctx) {
|
|
|
39
63
|
* connection.
|
|
40
64
|
*/
|
|
41
65
|
export function appendEvent(input, ctx) {
|
|
66
|
+
if (ctx?.readOnly)
|
|
67
|
+
return;
|
|
42
68
|
const now = resolveNow(ctx);
|
|
43
69
|
const ts = new Date(now()).toISOString();
|
|
44
70
|
const row = { eventType: input.eventType, ts, ref: input.ref, metadata: input.metadata };
|
|
@@ -68,31 +94,40 @@ export function appendEvent(input, ctx) {
|
|
|
68
94
|
catch (err) {
|
|
69
95
|
// Never mask the bun-test isolation guard as a silent "events failed".
|
|
70
96
|
rethrowIfTestIsolationError(err);
|
|
97
|
+
// Read-only sandboxes can serve an existing index but cannot create the
|
|
98
|
+
// maintenance lock or state DB used by best-effort usage tracking. That is
|
|
99
|
+
// expected for a read verb; unrelated storage failures remain visible.
|
|
100
|
+
if (isReadOnlyFilesystemError(err))
|
|
101
|
+
return;
|
|
71
102
|
// Best-effort: events stream failures must not break the mutating verb.
|
|
72
103
|
// Surface once to stderr so operators can diagnose.
|
|
73
104
|
error(`akm: appendEvent failed: ${String(err)}`);
|
|
74
105
|
}
|
|
75
106
|
}
|
|
76
|
-
/**
|
|
77
|
-
* 0.9.0 breaking change (owner ruling 12): `akm sync` used to persist
|
|
78
|
-
* `eventType: "save"`; it now writes `"sync"` instead (matching the command
|
|
79
|
-
* name). Existing `state.db` rows — and any user script running
|
|
80
|
-
* `akm log --type save` — still carry the old spelling. Rather than
|
|
81
|
-
* rewriting historical rows (a migration users never asked for, on data we
|
|
82
|
-
* don't get to touch at rest), reads treat the two names as synonyms: asking
|
|
83
|
-
* for either "save" or "sync" returns rows written under both names. Only
|
|
84
|
-
* the WRITE path (sources-cli.ts's `runSyncBody`) changed.
|
|
85
|
-
*/
|
|
86
|
-
const SAVE_SYNC_EVENT_TYPE_ALIASES = new Set(["save", "sync"]);
|
|
87
107
|
/**
|
|
88
108
|
* Read all events matching the filter. Returns a `nextOffset` that callers
|
|
89
109
|
* can persist between processes for monotonic resumption.
|
|
90
110
|
*/
|
|
91
111
|
export function readEvents(options = {}, ctx) {
|
|
112
|
+
if (ctx?.readOnlySnapshotUnavailable)
|
|
113
|
+
return { events: [], nextOffset: 0 };
|
|
92
114
|
const dbPath = resolveDbPath(ctx);
|
|
93
115
|
let db;
|
|
116
|
+
let ownsDb = false;
|
|
94
117
|
try {
|
|
95
|
-
|
|
118
|
+
if (ctx?.db) {
|
|
119
|
+
db = ctx.db;
|
|
120
|
+
}
|
|
121
|
+
else if (ctx?.readOnly) {
|
|
122
|
+
if (!fs.existsSync(dbPath))
|
|
123
|
+
return { events: [], nextOffset: 0 };
|
|
124
|
+
db = openDatabase(dbPath, { readonly: true, create: false });
|
|
125
|
+
ownsDb = true;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
db = openStateDatabase(dbPath);
|
|
129
|
+
ownsDb = true;
|
|
130
|
+
}
|
|
96
131
|
}
|
|
97
132
|
catch (err) {
|
|
98
133
|
// Never mask the bun-test isolation guard as "no events".
|
|
@@ -101,12 +136,7 @@ export function readEvents(options = {}, ctx) {
|
|
|
101
136
|
return { events: [], nextOffset: 0 };
|
|
102
137
|
}
|
|
103
138
|
try {
|
|
104
|
-
//
|
|
105
|
-
// ?` match (see SAVE_SYNC_EVENT_TYPE_ALIASES above), so widen the SQL
|
|
106
|
-
// filter to "no type filter" for that one case and apply the alias match
|
|
107
|
-
// client-side alongside the existing tag post-filter below.
|
|
108
|
-
const typeIsAliased = options.type !== undefined && SAVE_SYNC_EVENT_TYPE_ALIASES.has(options.type);
|
|
109
|
-
// D-38: a JS-side post-filter (the type alias above, or the tag filters
|
|
139
|
+
// D-38: a JS-side post-filter (the tag filters
|
|
110
140
|
// below) runs AFTER the SQL read, so a SQL-level LIMIT applied before it
|
|
111
141
|
// could drop rows the post-filter would have kept out anyway, silently
|
|
112
142
|
// returning fewer than `limit` (or the wrong — oldest-in-the-SQL-window —
|
|
@@ -114,21 +144,26 @@ export function readEvents(options = {}, ctx) {
|
|
|
114
144
|
// downstream can shrink the result further; otherwise read unbounded (the
|
|
115
145
|
// pre-existing behavior) and apply `limit` ourselves, below, AFTER the
|
|
116
146
|
// post-filter runs.
|
|
117
|
-
const needsPostFilter =
|
|
118
|
-
(options.excludeTags?.length ?? 0) > 0 ||
|
|
119
|
-
(options.includeTags?.length ?? 0) > 0 ||
|
|
120
|
-
options.runId !== undefined;
|
|
147
|
+
const needsPostFilter = (options.excludeTags?.length ?? 0) > 0 || (options.includeTags?.length ?? 0) > 0 || options.runId !== undefined;
|
|
121
148
|
const pushLimitToSql = options.limit !== undefined && !needsPostFilter;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
let rawEvents;
|
|
150
|
+
let nextId;
|
|
151
|
+
try {
|
|
152
|
+
({ events: rawEvents, nextId } = readStateEvents(db, {
|
|
153
|
+
sinceId: options.sinceOffset,
|
|
154
|
+
since: options.since,
|
|
155
|
+
type: options.type,
|
|
156
|
+
ref: options.ref,
|
|
157
|
+
...(pushLimitToSql ? { limit: options.limit } : {}),
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (ctx?.readOnly && error instanceof Error && /no such table:/i.test(error.message)) {
|
|
162
|
+
return { events: [], nextOffset: options.sinceOffset ?? 0 };
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
129
166
|
const filtered = rawEvents.filter((envelope) => {
|
|
130
|
-
if (typeIsAliased && !SAVE_SYNC_EVENT_TYPE_ALIASES.has(envelope.eventType))
|
|
131
|
-
return false;
|
|
132
167
|
if (options.runId !== undefined && envelope.metadata?.runId !== options.runId)
|
|
133
168
|
return false;
|
|
134
169
|
// Apply tag filters after the indexed state.db read.
|
|
@@ -148,6 +183,7 @@ export function readEvents(options = {}, ctx) {
|
|
|
148
183
|
return { events, nextOffset: nextId };
|
|
149
184
|
}
|
|
150
185
|
finally {
|
|
151
|
-
|
|
186
|
+
if (ownsDb)
|
|
187
|
+
db.close();
|
|
152
188
|
}
|
|
153
189
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
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
|
+
import { cloneExecutionJsonObject } from "../execution/json.js";
|
|
4
5
|
const COMMON_FIELDS = [
|
|
5
6
|
"schemaVersion",
|
|
6
7
|
"ok",
|
|
7
8
|
"scope",
|
|
8
9
|
"dryRun",
|
|
10
|
+
"notices",
|
|
9
11
|
"skipped",
|
|
10
12
|
"guidance",
|
|
11
13
|
"memorySummary",
|
|
12
14
|
"memoryCleanup",
|
|
13
15
|
"cyclesRun",
|
|
14
16
|
"plannedRefs",
|
|
17
|
+
"plan",
|
|
15
18
|
"actions",
|
|
16
19
|
"distillSkipped",
|
|
17
20
|
"validationFailures",
|
|
@@ -55,6 +58,308 @@ function requireExactFields(value, allowed) {
|
|
|
55
58
|
if (unknown.length > 0)
|
|
56
59
|
fail(`unknown field${unknown.length === 1 ? "" : "s"}: ${unknown.sort().join(", ")}`);
|
|
57
60
|
}
|
|
61
|
+
function requireNumber(value, field, path) {
|
|
62
|
+
if (typeof value[field] !== "number" || !Number.isFinite(value[field]))
|
|
63
|
+
fail(`${path}.${field} must be a number`);
|
|
64
|
+
}
|
|
65
|
+
function requireCount(value, field, path) {
|
|
66
|
+
requireNumber(value, field, path);
|
|
67
|
+
if (!Number.isSafeInteger(value[field]) || value[field] < 0) {
|
|
68
|
+
fail(`${path}.${field} must be a non-negative integer`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function validateLoweringNotices(value) {
|
|
72
|
+
if (!Array.isArray(value))
|
|
73
|
+
fail("notices must be an array");
|
|
74
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
75
|
+
const notice = value[index];
|
|
76
|
+
if (!isRecord(notice))
|
|
77
|
+
fail(`notices[${index}] must be an object`);
|
|
78
|
+
requireExactFields(notice, new Set(["code", "severity", "adapter", "field", "message", "details"]));
|
|
79
|
+
for (const field of ["code", "adapter", "message"]) {
|
|
80
|
+
if (typeof notice[field] !== "string")
|
|
81
|
+
fail(`notices[${index}].${field} must be a string`);
|
|
82
|
+
}
|
|
83
|
+
if (notice.severity !== "info" && notice.severity !== "warning") {
|
|
84
|
+
fail(`notices[${index}].severity is invalid`);
|
|
85
|
+
}
|
|
86
|
+
if (notice.field !== undefined && notice.field !== null && typeof notice.field !== "string") {
|
|
87
|
+
fail(`notices[${index}].field must be a string or null`);
|
|
88
|
+
}
|
|
89
|
+
if (notice.details !== undefined && notice.details !== null) {
|
|
90
|
+
try {
|
|
91
|
+
cloneExecutionJsonObject(notice.details, `notices[${index}].details`);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
fail(`notices[${index}].details must be a JSON object or null`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function validateConsolidationPlan(value) {
|
|
100
|
+
if (!isRecord(value))
|
|
101
|
+
fail("plan.consolidation must be an object");
|
|
102
|
+
requireExactFields(value, new Set([
|
|
103
|
+
"configured",
|
|
104
|
+
"effective",
|
|
105
|
+
"poolSize",
|
|
106
|
+
"candidatePoolSize",
|
|
107
|
+
"gates",
|
|
108
|
+
"wouldRun",
|
|
109
|
+
"reason",
|
|
110
|
+
"estimatedChunks",
|
|
111
|
+
]));
|
|
112
|
+
if (!isRecord(value.configured))
|
|
113
|
+
fail("plan.consolidation.configured must be an object");
|
|
114
|
+
requireExactFields(value.configured, new Set(["enabled", "minPoolSize", "limit", "maxChunkSize", "incrementalSince"]));
|
|
115
|
+
if (value.configured.enabled !== undefined && typeof value.configured.enabled !== "boolean") {
|
|
116
|
+
fail("plan.consolidation.configured.enabled must be a boolean");
|
|
117
|
+
}
|
|
118
|
+
for (const field of ["minPoolSize", "limit", "maxChunkSize"]) {
|
|
119
|
+
if (value.configured[field] !== undefined && typeof value.configured[field] !== "number") {
|
|
120
|
+
fail(`plan.consolidation.configured.${field} must be a number`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (value.configured.incrementalSince !== undefined && typeof value.configured.incrementalSince !== "string") {
|
|
124
|
+
fail("plan.consolidation.configured.incrementalSince must be a string");
|
|
125
|
+
}
|
|
126
|
+
if (!isRecord(value.effective))
|
|
127
|
+
fail("plan.consolidation.effective must be an object");
|
|
128
|
+
requireExactFields(value.effective, new Set(["enabled", "minPoolSize", "limit", "chunkSize"]));
|
|
129
|
+
if (typeof value.effective.enabled !== "boolean")
|
|
130
|
+
fail("plan.consolidation.effective.enabled must be a boolean");
|
|
131
|
+
requireCount(value.effective, "minPoolSize", "plan.consolidation.effective");
|
|
132
|
+
requireCount(value.effective, "chunkSize", "plan.consolidation.effective");
|
|
133
|
+
if (value.effective.limit !== undefined && typeof value.effective.limit !== "number") {
|
|
134
|
+
fail("plan.consolidation.effective.limit must be a number");
|
|
135
|
+
}
|
|
136
|
+
requireCount(value, "poolSize", "plan.consolidation");
|
|
137
|
+
requireCount(value, "candidatePoolSize", "plan.consolidation");
|
|
138
|
+
requireCount(value, "estimatedChunks", "plan.consolidation");
|
|
139
|
+
if (typeof value.wouldRun !== "boolean")
|
|
140
|
+
fail("plan.consolidation.wouldRun must be a boolean");
|
|
141
|
+
if (typeof value.reason !== "string")
|
|
142
|
+
fail("plan.consolidation.reason must be a string");
|
|
143
|
+
if (!isRecord(value.gates))
|
|
144
|
+
fail("plan.consolidation.gates must be an object");
|
|
145
|
+
requireExactFields(value.gates, new Set(["profile", "minimumPool", "delta"]));
|
|
146
|
+
for (const gateName of ["profile", "minimumPool", "delta"]) {
|
|
147
|
+
const gate = value.gates[gateName];
|
|
148
|
+
if (!isRecord(gate))
|
|
149
|
+
fail(`plan.consolidation.gates.${gateName} must be an object`);
|
|
150
|
+
requireExactFields(gate, new Set(["passed", "reason"]));
|
|
151
|
+
if (typeof gate.passed !== "boolean" || typeof gate.reason !== "string") {
|
|
152
|
+
fail(`plan.consolidation.gates.${gateName} must contain boolean passed and string reason`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function validateProactivePlan(value) {
|
|
157
|
+
if (!isRecord(value))
|
|
158
|
+
fail("plan.proactive must be an object");
|
|
159
|
+
requireExactFields(value, new Set(["configured", "effective", "candidatePool", "dueTotal", "neverReflected", "selected", "selectedRefs"]));
|
|
160
|
+
if (!isRecord(value.configured))
|
|
161
|
+
fail("plan.proactive.configured must be an object");
|
|
162
|
+
requireExactFields(value.configured, new Set(["dueDays", "maxPerRun", "limit"]));
|
|
163
|
+
for (const field of ["dueDays", "maxPerRun", "limit"]) {
|
|
164
|
+
if (value.configured[field] !== undefined && typeof value.configured[field] !== "number") {
|
|
165
|
+
fail(`plan.proactive.configured.${field} must be a number`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!isRecord(value.effective))
|
|
169
|
+
fail("plan.proactive.effective must be an object");
|
|
170
|
+
requireExactFields(value.effective, new Set(["dueDays", "maxPerRun"]));
|
|
171
|
+
requireNumber(value.effective, "dueDays", "plan.proactive.effective");
|
|
172
|
+
requireNumber(value.effective, "maxPerRun", "plan.proactive.effective");
|
|
173
|
+
for (const field of ["candidatePool", "dueTotal", "neverReflected", "selected"]) {
|
|
174
|
+
requireCount(value, field, "plan.proactive");
|
|
175
|
+
}
|
|
176
|
+
if (!Array.isArray(value.selectedRefs) || value.selectedRefs.some((ref) => typeof ref !== "string")) {
|
|
177
|
+
fail("plan.proactive.selectedRefs must be an array of strings");
|
|
178
|
+
}
|
|
179
|
+
if (value.selected !== value.selectedRefs.length) {
|
|
180
|
+
fail("plan.proactive.selected must equal plan.proactive.selectedRefs.length");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function validateImprovePlan(value, dryRun, plannedRefNames) {
|
|
184
|
+
if (!isRecord(value))
|
|
185
|
+
fail("plan must be an object");
|
|
186
|
+
requireExactFields(value, new Set([
|
|
187
|
+
"mode",
|
|
188
|
+
"dispatch",
|
|
189
|
+
"snapshot",
|
|
190
|
+
"candidates",
|
|
191
|
+
"limits",
|
|
192
|
+
"gates",
|
|
193
|
+
"effectiveRefs",
|
|
194
|
+
"proactive",
|
|
195
|
+
"consolidation",
|
|
196
|
+
"stages",
|
|
197
|
+
"triage",
|
|
198
|
+
]));
|
|
199
|
+
if (value.mode !== "estimate" && value.mode !== "execution")
|
|
200
|
+
fail('plan.mode must be "estimate" or "execution"');
|
|
201
|
+
if (typeof value.dispatch !== "boolean")
|
|
202
|
+
fail("plan.dispatch must be a boolean");
|
|
203
|
+
if ((value.mode === "estimate" && value.dispatch) || (value.mode === "execution" && !value.dispatch)) {
|
|
204
|
+
fail("plan.dispatch must be false for estimate mode and true for execution mode");
|
|
205
|
+
}
|
|
206
|
+
if (dryRun && (value.mode !== "estimate" || value.dispatch !== false)) {
|
|
207
|
+
fail("dryRun=true requires estimate mode with dispatch=false");
|
|
208
|
+
}
|
|
209
|
+
if (!dryRun && (value.mode !== "execution" || value.dispatch !== true)) {
|
|
210
|
+
fail("dryRun=false requires execution mode with dispatch=true");
|
|
211
|
+
}
|
|
212
|
+
if (!isRecord(value.snapshot))
|
|
213
|
+
fail("plan.snapshot must be an object");
|
|
214
|
+
requireExactFields(value.snapshot, new Set(["status", "reason"]));
|
|
215
|
+
if (typeof value.snapshot.status !== "string" ||
|
|
216
|
+
!new Set(["ready", "missing", "incompatible", "unknown"]).has(value.snapshot.status)) {
|
|
217
|
+
fail("plan.snapshot.status is invalid");
|
|
218
|
+
}
|
|
219
|
+
if (typeof value.snapshot.reason !== "string")
|
|
220
|
+
fail("plan.snapshot.reason must be a string");
|
|
221
|
+
if (!isRecord(value.candidates))
|
|
222
|
+
fail("plan.candidates must be an object");
|
|
223
|
+
requireExactFields(value.candidates, new Set(["rawInScope", "selected", "effective"]));
|
|
224
|
+
for (const field of ["rawInScope", "selected", "effective"]) {
|
|
225
|
+
requireCount(value.candidates, field, "plan.candidates");
|
|
226
|
+
}
|
|
227
|
+
if (value.candidates.rawInScope < value.candidates.selected) {
|
|
228
|
+
fail("plan.candidates.rawInScope cannot be less than plan.candidates.selected");
|
|
229
|
+
}
|
|
230
|
+
if (value.candidates.selected < value.candidates.effective) {
|
|
231
|
+
fail("plan.candidates.selected cannot be less than plan.candidates.effective");
|
|
232
|
+
}
|
|
233
|
+
if (!isRecord(value.limits))
|
|
234
|
+
fail("plan.limits must be an object");
|
|
235
|
+
requireExactFields(value.limits, new Set(["configured", "effective", "additiveReplayAllowance", "totalCeiling"]));
|
|
236
|
+
if (!isRecord(value.limits.configured))
|
|
237
|
+
fail("plan.limits.configured must be an object");
|
|
238
|
+
requireExactFields(value.limits.configured, new Set(["cli", "profile", "reflect"]));
|
|
239
|
+
for (const field of ["cli", "profile", "reflect"]) {
|
|
240
|
+
if (value.limits.configured[field] !== undefined)
|
|
241
|
+
requireCount(value.limits.configured, field, "plan.limits.configured");
|
|
242
|
+
}
|
|
243
|
+
if (value.limits.effective !== undefined)
|
|
244
|
+
requireCount(value.limits, "effective", "plan.limits");
|
|
245
|
+
requireCount(value.limits, "additiveReplayAllowance", "plan.limits");
|
|
246
|
+
if (value.limits.totalCeiling !== undefined)
|
|
247
|
+
requireCount(value.limits, "totalCeiling", "plan.limits");
|
|
248
|
+
if (value.limits.effective === undefined && value.limits.totalCeiling !== undefined) {
|
|
249
|
+
fail("plan.limits.totalCeiling must be omitted when plan.limits.effective is unbounded");
|
|
250
|
+
}
|
|
251
|
+
if (value.limits.effective !== undefined &&
|
|
252
|
+
value.limits.totalCeiling !== value.limits.effective + value.limits.additiveReplayAllowance) {
|
|
253
|
+
fail("plan.limits.totalCeiling must equal plan.limits.effective + plan.limits.additiveReplayAllowance");
|
|
254
|
+
}
|
|
255
|
+
const gateNames = new Set(["profile", "cleanup", "validation", "signal", "disk", "limit"]);
|
|
256
|
+
if (!Array.isArray(value.gates))
|
|
257
|
+
fail("plan.gates must be an array");
|
|
258
|
+
const gateRemovedByName = new Map();
|
|
259
|
+
for (const gate of value.gates) {
|
|
260
|
+
if (!isRecord(gate))
|
|
261
|
+
fail("plan.gates entries must be objects");
|
|
262
|
+
requireExactFields(gate, new Set(["name", "removed", "reason"]));
|
|
263
|
+
if (typeof gate.name !== "string" || !gateNames.has(gate.name))
|
|
264
|
+
fail("plan.gates.name is invalid");
|
|
265
|
+
requireCount(gate, "removed", "plan.gates entry");
|
|
266
|
+
if (typeof gate.reason !== "string")
|
|
267
|
+
fail("plan.gates.reason must be a string");
|
|
268
|
+
if (gateRemovedByName.has(gate.name))
|
|
269
|
+
fail(`plan.gates must contain exactly one ${gate.name} gate`);
|
|
270
|
+
gateRemovedByName.set(gate.name, gate.removed);
|
|
271
|
+
}
|
|
272
|
+
for (const gateName of gateNames) {
|
|
273
|
+
if (!gateRemovedByName.has(gateName))
|
|
274
|
+
fail(`plan.gates must contain exactly one ${gateName} gate`);
|
|
275
|
+
}
|
|
276
|
+
const preLimitRemoved = [...gateRemovedByName]
|
|
277
|
+
.filter(([gateName]) => gateName !== "limit")
|
|
278
|
+
.reduce((total, [, removed]) => total + removed, 0);
|
|
279
|
+
if (preLimitRemoved !== value.candidates.rawInScope - value.candidates.selected) {
|
|
280
|
+
fail("plan pre-limit gate removals must equal plan.candidates.rawInScope - plan.candidates.selected");
|
|
281
|
+
}
|
|
282
|
+
if (gateRemovedByName.get("limit") !==
|
|
283
|
+
value.candidates.selected - value.candidates.effective) {
|
|
284
|
+
fail("plan limit removal must equal plan.candidates.selected - plan.candidates.effective");
|
|
285
|
+
}
|
|
286
|
+
const lanes = new Set([
|
|
287
|
+
"scope",
|
|
288
|
+
"signal-delta",
|
|
289
|
+
"proactive",
|
|
290
|
+
"high-salience",
|
|
291
|
+
"distill-only",
|
|
292
|
+
"forgetting-safety",
|
|
293
|
+
"replay",
|
|
294
|
+
"unknown",
|
|
295
|
+
]);
|
|
296
|
+
const reasons = new Set(["scope-ref", "scope-type", "memory-cleanup", "strategy_filtered_all_passes"]);
|
|
297
|
+
if (!Array.isArray(value.effectiveRefs))
|
|
298
|
+
fail("plan.effectiveRefs must be an array");
|
|
299
|
+
for (const entry of value.effectiveRefs) {
|
|
300
|
+
if (!isRecord(entry))
|
|
301
|
+
fail("plan.effectiveRefs entries must be objects");
|
|
302
|
+
requireExactFields(entry, new Set(["ref", "lane", "reason"]));
|
|
303
|
+
if (typeof entry.ref !== "string")
|
|
304
|
+
fail("plan.effectiveRefs.ref must be a string");
|
|
305
|
+
if (typeof entry.lane !== "string" || !lanes.has(entry.lane))
|
|
306
|
+
fail("plan.effectiveRefs.lane is invalid");
|
|
307
|
+
if (typeof entry.reason !== "string" || !reasons.has(entry.reason))
|
|
308
|
+
fail("plan.effectiveRefs.reason is invalid");
|
|
309
|
+
}
|
|
310
|
+
if (value.candidates.effective !== value.effectiveRefs.length) {
|
|
311
|
+
fail("plan.candidates.effective must equal plan.effectiveRefs.length");
|
|
312
|
+
}
|
|
313
|
+
const effectiveRefNames = value.effectiveRefs.map((entry) => entry.ref);
|
|
314
|
+
if (new Set(effectiveRefNames).size !== effectiveRefNames.length) {
|
|
315
|
+
fail("plan.effectiveRefs must not contain duplicate refs");
|
|
316
|
+
}
|
|
317
|
+
if (plannedRefNames.length !== effectiveRefNames.length ||
|
|
318
|
+
plannedRefNames.some((ref, index) => ref !== effectiveRefNames[index])) {
|
|
319
|
+
fail("plannedRefs must contain the same refs in the same order as plan.effectiveRefs");
|
|
320
|
+
}
|
|
321
|
+
const replayCount = value.effectiveRefs.filter((entry) => entry.lane === "replay").length;
|
|
322
|
+
const ordinaryCount = value.effectiveRefs.length - replayCount;
|
|
323
|
+
if (replayCount > value.limits.additiveReplayAllowance) {
|
|
324
|
+
fail("plan replay refs cannot exceed plan.limits.additiveReplayAllowance");
|
|
325
|
+
}
|
|
326
|
+
if (value.limits.effective !== undefined && ordinaryCount > value.limits.effective) {
|
|
327
|
+
fail("plan ordinary refs cannot exceed plan.limits.effective");
|
|
328
|
+
}
|
|
329
|
+
if (value.limits.totalCeiling !== undefined && value.effectiveRefs.length > value.limits.totalCeiling) {
|
|
330
|
+
fail("plan.effectiveRefs cannot exceed plan.limits.totalCeiling");
|
|
331
|
+
}
|
|
332
|
+
if (value.proactive !== undefined)
|
|
333
|
+
validateProactivePlan(value.proactive);
|
|
334
|
+
validateConsolidationPlan(value.consolidation);
|
|
335
|
+
const stageNames = new Set(["consolidation", "extract", "graph-extraction", "memory-inference"]);
|
|
336
|
+
if (!Array.isArray(value.stages))
|
|
337
|
+
fail("plan.stages must be an array");
|
|
338
|
+
for (const stage of value.stages) {
|
|
339
|
+
if (!isRecord(stage))
|
|
340
|
+
fail("plan.stages entries must be objects");
|
|
341
|
+
requireExactFields(stage, new Set(["name", "wouldRun", "reason"]));
|
|
342
|
+
if (typeof stage.name !== "string" || !stageNames.has(stage.name))
|
|
343
|
+
fail("plan.stages.name is invalid");
|
|
344
|
+
if (typeof stage.wouldRun !== "boolean" || typeof stage.reason !== "string") {
|
|
345
|
+
fail("plan.stages entries must contain boolean wouldRun and string reason");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!isRecord(value.triage))
|
|
349
|
+
fail("plan.triage must be an object");
|
|
350
|
+
requireExactFields(value.triage, new Set(["enabled", "configuredMode", "mode", "maxAcceptsPerRun", "maxDiffLines"]));
|
|
351
|
+
if (typeof value.triage.enabled !== "boolean")
|
|
352
|
+
fail("plan.triage.enabled must be a boolean");
|
|
353
|
+
for (const field of ["configuredMode", "mode"]) {
|
|
354
|
+
if (value.triage[field] !== "queue" && value.triage[field] !== "promote") {
|
|
355
|
+
fail(`plan.triage.${field} must be queue or promote`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
requireCount(value.triage, "maxAcceptsPerRun", "plan.triage");
|
|
359
|
+
if (value.triage.maxDiffLines !== undefined && typeof value.triage.maxDiffLines !== "number") {
|
|
360
|
+
fail("plan.triage.maxDiffLines must be a number");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
58
363
|
function validateCommon(value) {
|
|
59
364
|
if (typeof value.ok !== "boolean")
|
|
60
365
|
fail("ok must be a boolean");
|
|
@@ -62,6 +367,15 @@ function validateCommon(value) {
|
|
|
62
367
|
fail("dryRun must be a boolean");
|
|
63
368
|
if (!Array.isArray(value.plannedRefs))
|
|
64
369
|
fail("plannedRefs must be an array");
|
|
370
|
+
if (value.notices !== undefined)
|
|
371
|
+
validateLoweringNotices(value.notices);
|
|
372
|
+
const plannedRefNames = value.plannedRefs.map((entry, index) => {
|
|
373
|
+
if (!isRecord(entry) || typeof entry.ref !== "string")
|
|
374
|
+
fail(`plannedRefs[${index}].ref must be a string`);
|
|
375
|
+
return entry.ref;
|
|
376
|
+
});
|
|
377
|
+
if (value.plan !== undefined)
|
|
378
|
+
validateImprovePlan(value.plan, value.dryRun, plannedRefNames);
|
|
65
379
|
if (!isRecord(value.scope))
|
|
66
380
|
fail("scope must be an object");
|
|
67
381
|
requireExactFields(value.scope, new Set(["mode", "value"]));
|
|
@@ -127,6 +441,7 @@ function validateCommon(value) {
|
|
|
127
441
|
"cycleMetrics",
|
|
128
442
|
"sync",
|
|
129
443
|
"terminated",
|
|
444
|
+
"plan",
|
|
130
445
|
]) {
|
|
131
446
|
if (value[field] !== undefined && !isRecord(value[field]))
|
|
132
447
|
fail(`${field} must be an object`);
|
package/dist/core/lesson-lint.js
CHANGED
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
* lets it run from any code path (CLI, proposal-accept, asset-spec tests)
|
|
21
21
|
* without dragging in the rest of the runtime.
|
|
22
22
|
*/
|
|
23
|
-
import fs from "node:fs";
|
|
24
23
|
import { parseFrontmatter } from "./asset/frontmatter.js";
|
|
25
24
|
function isNonEmptyString(value) {
|
|
26
25
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -66,8 +65,3 @@ export function lintLessonContent(raw, pathForMessages) {
|
|
|
66
65
|
}
|
|
67
66
|
return { path: pathForMessages, findings };
|
|
68
67
|
}
|
|
69
|
-
/** Lint a lesson file on disk. Throws if the file cannot be read. */
|
|
70
|
-
export function lintLessonFile(filePath) {
|
|
71
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
72
|
-
return lintLessonContent(raw, filePath);
|
|
73
|
-
}
|
|
@@ -10,9 +10,9 @@ import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquire
|
|
|
10
10
|
import { getMaintenanceBarrierPath } from "./paths.js";
|
|
11
11
|
const heldBarrierContext = new AsyncLocalStorage();
|
|
12
12
|
/**
|
|
13
|
-
* Serialize
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Serialize the short critical section that creates each long-lived AKM lock,
|
|
14
|
+
* lease, or state activity. The operation keeps its own ownership record; this
|
|
15
|
+
* barrier is released immediately after acquisition.
|
|
16
16
|
*/
|
|
17
17
|
export function tryAcquireMaintenanceBarrier() {
|
|
18
18
|
const lockPath = getMaintenanceBarrierPath();
|
|
@@ -107,7 +107,7 @@ function withMaintenanceStartBarrierSyncWait(run) {
|
|
|
107
107
|
release();
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
/** Register a long-lived operation atomically with
|
|
110
|
+
/** Register a long-lived state operation atomically with other start acquisitions. */
|
|
111
111
|
export async function acquireMaintenanceActivity(name) {
|
|
112
112
|
return withMaintenanceStartBarrierAsync(async () => {
|
|
113
113
|
const directory = path.join(path.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
|