akm-cli 0.9.16-alpha.1 → 0.9.16
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 +56 -132
- package/dist/assets/hints/cli-hints-full.md +13 -6
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
- package/dist/cli/retired-commands.js +0 -4
- package/dist/cli/unknown-flags.js +3 -36
- package/dist/commands/env/env-binding.js +4 -4
- package/dist/commands/env/env-cli.js +3 -3
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +4 -6
- package/dist/commands/improve/improve-cli.js +20 -15
- package/dist/commands/improve/reflect.js +23 -2
- package/dist/commands/lint/base-linter.js +9 -0
- package/dist/commands/lint/env-key-rules.js +2 -2
- package/dist/commands/proposal/propose.js +15 -1
- package/dist/commands/proposal/repository.js +3 -12
- package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
- package/dist/commands/proposal/validators/proposal-validators.js +5 -4
- package/dist/commands/read/curate.js +44 -34
- package/dist/commands/read/search.js +35 -54
- package/dist/commands/read/show.js +21 -2
- package/dist/commands/registry-cli.js +5 -5
- package/dist/commands/sources/add-cli.js +59 -16
- package/dist/commands/sources/bundle-cli.js +35 -11
- package/dist/commands/sources/bundle-config-ops.js +30 -0
- package/dist/commands/sources/dangerous-env-audit.js +4 -4
- package/dist/commands/sources/info.js +8 -8
- package/dist/commands/sources/installed-stashes.js +55 -61
- package/dist/commands/sources/source-add.js +39 -38
- package/dist/commands/sources/source-manage.js +34 -12
- package/dist/commands/sources/stash-cli.js +111 -119
- package/dist/commands/sources/stash-skeleton.js +6 -3
- package/dist/commands/tasks/explain.js +4 -1
- package/dist/commands/tasks/tasks-cli.js +31 -9
- package/dist/commands/tasks/tasks.js +239 -194
- package/dist/commands/tasks/validate.js +20 -32
- package/dist/core/activation-policy.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +8 -35
- package/dist/core/adapter/adapters/akm-metadata.js +1 -11
- package/dist/core/adapter/execution-source.js +10 -29
- package/dist/core/asset/asset-placement.js +0 -35
- package/dist/core/config/config-schema.js +64 -8
- package/dist/core/config/config-sources.js +96 -2
- package/dist/core/config/config.js +190 -24
- package/dist/core/config/legacy-source-shape-shim.js +9 -0
- package/dist/core/config/schema/embedding.js +30 -7
- package/dist/core/config/schema/execution.js +23 -0
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/scheduler.js +20 -0
- package/dist/core/config/schema/search.js +10 -12
- package/dist/core/config/schema/sources-bundles.js +32 -1
- package/dist/core/content-safety.js +52 -0
- package/dist/core/errors.js +2 -5
- package/dist/core/maintenance-barrier.js +11 -13
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +2 -5
- package/dist/core/state/migrations.js +1 -26
- package/dist/core/state-db.js +27 -63
- package/dist/core/type-presentation.js +1 -1
- package/dist/core/write-source.js +13 -8
- package/dist/indexer/bundle-identity-guard.js +45 -8
- package/dist/indexer/ensure-index.js +0 -5
- package/dist/indexer/index-db-contention.js +56 -0
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-written-assets.js +171 -133
- package/dist/indexer/indexer.js +1621 -458
- package/dist/indexer/lookup/adapter-concept-owner.js +5 -19
- package/dist/indexer/materialize-embeddings.js +785 -0
- package/dist/indexer/passes/dir-staleness.js +161 -0
- package/dist/indexer/passes/metadata.js +1 -18
- package/dist/indexer/scan/drain-dir.js +70 -27
- package/dist/indexer/search/db-search.js +89 -373
- package/dist/indexer/search/ranking-contributors.js +16 -21
- package/dist/indexer/search/ranking.js +57 -135
- package/dist/indexer/search/search-source.js +29 -11
- package/dist/integrations/agent/execution-lowering.js +3 -2
- package/dist/integrations/agent/execution-preparation.js +32 -1
- package/dist/integrations/agent/prompts.js +1 -1
- package/dist/integrations/agent/request-lowering.js +3 -2
- package/dist/llm/client.js +3 -11
- package/dist/llm/embedder.js +3 -10
- package/dist/llm/embedders/remote.js +104 -133
- package/dist/llm/feature-gate.js +2 -4
- package/dist/llm/rerank-client.js +3 -3
- package/dist/output/html-render.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/stdout.js +24 -0
- package/dist/output/text/command-format.js +13 -19
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/index.js +2 -5
- package/dist/output/text.js +4 -3
- package/dist/registry/resolve.js +37 -10
- package/dist/scripts/akm-migrate-node.js +15197 -11351
- package/dist/scripts/akm-migrate.js +15514 -11668
- package/dist/setup/semantic-assets.js +2 -2
- package/dist/setup/setup.js +3 -3
- package/dist/setup/steps/connection.js +2 -3
- package/dist/setup/steps/tasks.js +29 -36
- package/dist/sources/providers/git-install.js +17 -11
- package/dist/sources/providers/git-provider.js +12 -5
- package/dist/sources/providers/git-stash.js +38 -16
- package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
- package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
- package/dist/storage/repositories/index-connection.js +3 -1
- package/dist/storage/repositories/index-entries-repository.js +68 -77
- package/dist/storage/repositories/index-entry-schema.js +25 -16
- package/dist/storage/repositories/index-fts-repository.js +263 -29
- package/dist/storage/repositories/index-meta-repository.js +29 -0
- package/dist/storage/repositories/index-schema.js +122 -115
- package/dist/storage/repositories/index-utility-repository.js +1 -1
- package/dist/storage/repositories/index-vec-repository.js +435 -22
- package/dist/tasks/activation-config.js +90 -0
- package/dist/tasks/backends/cron.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -0
- package/dist/tasks/backends/schtasks.js +2 -0
- package/dist/tasks/embedded.js +4 -5
- package/dist/tasks/scheduler-binding.js +2 -2
- package/dist/tasks/scheduler-sync-preview.js +8 -1
- package/dist/tasks/scheduler-sync.js +19 -10
- package/dist/tasks/source/parse-task-source.js +10 -113
- package/dist/tasks/source/project-v4.js +2 -2
- package/dist/tasks/source/task-source-v4.js +4 -12
- package/dist/tasks/source/task-to-v3.js +4 -12
- package/dist/tasks/source/task-to-v4.js +40 -7
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.15.md +36 -34
- package/docs/migration/release-notes/0.9.16.md +60 -98
- package/docs/migration/release-notes/README.md +0 -5
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
- package/docs/reference/cli.md +124 -122
- package/docs/reference/configuration.md +137 -133
- package/docs/reference/data-and-telemetry.md +1 -2
- package/docs/reference/tasks.md +34 -29
- package/package.json +1 -1
- package/schemas/akm-config.json +170 -6
- package/schemas/akm-task.json +1 -2
- package/dist/commands/sources/index-status.js +0 -99
- package/dist/core/hash.js +0 -18
- package/dist/indexer/drain.js +0 -306
- package/dist/indexer/embedding-identity.js +0 -20
- package/dist/indexer/enrich.js +0 -260
- package/dist/indexer/reconcile.js +0 -890
- package/dist/indexer/scan/parse-file.js +0 -66
- package/dist/indexer/units/unit.js +0 -159
- package/dist/llm/embedders/provider-limits.js +0 -288
- package/dist/storage/repositories/files-repository.js +0 -181
- package/dist/storage/repositories/units-repository.js +0 -510
|
@@ -25,7 +25,14 @@ export function renderSchedulerPlanPreview(backend, operations, unchanged = [],
|
|
|
25
25
|
adds.push({ id: operation.binding.id, kind: "install" });
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
|
-
updates.push({
|
|
28
|
+
updates.push({
|
|
29
|
+
id: operation.binding.id,
|
|
30
|
+
kind: "update",
|
|
31
|
+
...(operation.expected.fingerprint !== undefined
|
|
32
|
+
? { installedFingerprint: operation.expected.fingerprint }
|
|
33
|
+
: {}),
|
|
34
|
+
...(operation.resultFingerprint !== undefined ? { expectedFingerprint: operation.resultFingerprint } : {}),
|
|
35
|
+
});
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
38
|
return Object.freeze({
|
|
@@ -51,6 +51,9 @@ export async function prepareSchedulerSyncSourceSet(input) {
|
|
|
51
51
|
failures: compiled.failures,
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
+
function schedulerActivationKey(kind, ref) {
|
|
55
|
+
return `${kind}\0${ref}`;
|
|
56
|
+
}
|
|
54
57
|
export function finalizeSchedulerSyncPlan(input, prepared) {
|
|
55
58
|
const inspection = inspectionForPlan(input);
|
|
56
59
|
const coherentInput = {
|
|
@@ -60,10 +63,8 @@ export function finalizeSchedulerSyncPlan(input, prepared) {
|
|
|
60
63
|
};
|
|
61
64
|
const desired = prepared.desired;
|
|
62
65
|
assertUniqueDesiredIds(desired);
|
|
63
|
-
|
|
64
|
-
assertUniqueInstalledIds(coherentInput.installed);
|
|
66
|
+
assertSchedulerBackendInspection(inspection, desired, input.inspection !== undefined);
|
|
65
67
|
assertNoForeignIds(desired, coherentInput);
|
|
66
|
-
assertSchedulerNativeArtifactOwnership(desired, inspection.artifacts);
|
|
67
68
|
const scopedInstalled = coherentInput.installed.filter((entry) => belongsToBundle(entry, coherentInput));
|
|
68
69
|
const present = new Map(scopedInstalled.map((entry) => [entry.id, entry]));
|
|
69
70
|
const installed = [];
|
|
@@ -123,6 +124,12 @@ export function finalizeSchedulerSyncPlan(input, prepared) {
|
|
|
123
124
|
failures: prepared.failures,
|
|
124
125
|
});
|
|
125
126
|
}
|
|
127
|
+
/** Validate one coherent whole-backend read before deriving any mutation plan. */
|
|
128
|
+
export function assertSchedulerBackendInspection(inspection, desired = [], requireCompleteFingerprint = true) {
|
|
129
|
+
assertCoherentInspection(inspection, requireCompleteFingerprint);
|
|
130
|
+
assertUniqueInstalledIds(inspection.installed);
|
|
131
|
+
assertSchedulerNativeArtifactOwnership(desired, inspection.artifacts);
|
|
132
|
+
}
|
|
126
133
|
/**
|
|
127
134
|
* Build the exact removal operation for one installed binding: same
|
|
128
135
|
* exact-native-fingerprint / ordinal-attribution safety checks
|
|
@@ -299,6 +306,10 @@ async function compileTaskSources(input, collector, out, failures) {
|
|
|
299
306
|
const conceptId = relative.slice(0, -4);
|
|
300
307
|
const id = input.adapterId === "akm-task" ? conceptId : path.basename(sourcePath, ".yml");
|
|
301
308
|
const qualifiedRefForFailure = makeBundleRef(input.bundleName, conceptId);
|
|
309
|
+
if (input.enabledActivations &&
|
|
310
|
+
!input.enabledActivations.has(schedulerActivationKey("task", qualifiedRefForFailure))) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
302
313
|
try {
|
|
303
314
|
const physicalIdentity = guarded.physicalIdentity;
|
|
304
315
|
const priorOwner = physicalOwners.get(physicalIdentity);
|
|
@@ -308,14 +319,11 @@ async function compileTaskSources(input, collector, out, failures) {
|
|
|
308
319
|
physicalOwners.set(physicalIdentity, sourcePath);
|
|
309
320
|
// Project BEFORE prepareTaskV3Execution so projectability is checked —
|
|
310
321
|
// but build the scheduler bindings from the ORIGINAL task source v4
|
|
311
|
-
// document, not the projection, which deliberately drops
|
|
312
|
-
// `
|
|
322
|
+
// document, not the projection, which deliberately drops
|
|
323
|
+
// `schedule[i].inputs` (project-v4.ts) —
|
|
313
324
|
// schedule-supplied inputs are delivered through the scheduler
|
|
314
325
|
// binding's own compiled invocation tail (P2b Lane B, spec §4.4,
|
|
315
326
|
// B-N3), not through the prepare-seam projection. A task source v4
|
|
316
|
-
// document has no document-level `akm.enabled`, so `enabled: true` is
|
|
317
|
-
// passed at the document level and every entry's own `enabled`
|
|
318
|
-
// (always present, defaulted at parse time) decides.
|
|
319
327
|
const parsed = parseTaskSource({
|
|
320
328
|
yaml: guarded.content,
|
|
321
329
|
filePath: sourcePath,
|
|
@@ -349,11 +357,9 @@ async function compileTaskSources(input, collector, out, failures) {
|
|
|
349
357
|
id,
|
|
350
358
|
qualifiedRef,
|
|
351
359
|
...(input.bundleTarget ? { bundleTarget: input.bundleTarget } : {}),
|
|
352
|
-
enabled: true,
|
|
353
360
|
schedules: parsed.v4.schedule.map((schedule) => ({
|
|
354
361
|
cron: schedule.cron,
|
|
355
362
|
ordinal: schedule.ordinal,
|
|
356
|
-
enabled: schedule.enabled,
|
|
357
363
|
source: `${relSource}:${schedule.source}`,
|
|
358
364
|
// P2b Lane B (spec §4.4, B-N3): delivered through the compiled
|
|
359
365
|
// binding's own invocation tail below — the F-B2 flip that closes
|
|
@@ -423,6 +429,9 @@ async function compileWorkflowSources(input, collector, out, evidence, failures)
|
|
|
423
429
|
for (const [canonicalName, sources] of lookups) {
|
|
424
430
|
const failurePath = sources[0]?.sourcePath ?? canonicalName;
|
|
425
431
|
const failureRef = makeBundleRef(input.bundleName, input.adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName);
|
|
432
|
+
if (input.enabledActivations && !input.enabledActivations.has(schedulerActivationKey("workflow", failureRef))) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
426
435
|
try {
|
|
427
436
|
if (sources.length > 1) {
|
|
428
437
|
throw new WorkflowSourceCollisionError(input.adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, sources.map((source) => source.relativePath));
|
|
@@ -2,138 +2,35 @@
|
|
|
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
|
-
*
|
|
6
|
-
* §3.2.2).
|
|
5
|
+
* Current task-source router.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* (the P1b §4.3 invariant this phase carries forward: the shim adds a pure
|
|
13
|
-
* bytes-in/bytes-out detour, never a disk write).
|
|
14
|
-
*
|
|
15
|
-
* The terminal routing table:
|
|
16
|
-
*
|
|
17
|
-
* | root `version` | outcome |
|
|
18
|
-
* |------------------------|-----------------------------------------------------------------|
|
|
19
|
-
* | `4` | `parseTaskSourceV4Document` — the new grammar (row B-13) |
|
|
20
|
-
* | `2` or `3` | in-memory read shim (below): the SAME pure planners `akm migrate apply` uses (`./task-to-v3.ts`, `./task-to-v4.ts`) convert the bytes already in hand to v4 in memory; the result is parsed and returned with a one-line stderr deprecation warning. If the deterministic conversion itself fails (an unmigratable shape — the file needs a human decision, not a re-run), falls back to `TASK_SCHEMA_VERSION_UNSUPPORTED` naming the specific blocked reason (issue #869) — the shim removes friction for the deterministic case, it never hides a real problem |
|
|
21
|
-
* | any other number | `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming the migrator (B-14/B-15) |
|
|
22
|
-
* | absent / not a number | `parseTaskSourceV4Document` — its own `TASK_SOURCE_INVALID` "version is required and must be 4" / "must be exactly 4" wording (row B-16) |
|
|
23
|
-
*
|
|
24
|
-
* A missing or non-numeric `version:` is a MALFORMED v4 document, not a
|
|
25
|
-
* legacy one — it routes into the v4 parser so the field error names the
|
|
26
|
-
* one grammar `src` still accepts, rather than a generic "unsupported"
|
|
27
|
-
* message that would send the user to the migrator for a document that was
|
|
28
|
-
* never task v2 or v3 in the first place.
|
|
29
|
-
*
|
|
30
|
-
* task v2 and task v3 sources are no longer read as their own standing
|
|
31
|
-
* grammar anywhere else in `src` — the only readers of that grammar are the
|
|
32
|
-
* pure, byte-producing planners (`./task-to-v3.ts`, `./task-to-v4.ts`, and
|
|
33
|
-
* the frozen v3 reader `./task-source-v3-frozen.ts`), reached either through
|
|
34
|
-
* this shim (bytes in, bytes out, never touches disk) or through
|
|
35
|
-
* `akm migrate apply` / `akm-migrate` (`scripts/akm-migrate`, which
|
|
36
|
-
* additionally rewrites the file on disk once the user asks for that).
|
|
37
|
-
* Policy: a deterministic byte transform is the tool's job, not the user's —
|
|
38
|
-
* upgrading past a schema bump must not silently break a scheduled task, so
|
|
39
|
-
* v2/v3 files keep reading successfully at the cost of a one-line
|
|
40
|
-
* deprecation warning, and `akm migrate apply` remains available to rewrite
|
|
41
|
-
* the file and silence it. The front end's own pre-version failures (source
|
|
42
|
-
* not a string, source too large, YAML parse/warning/expansion) render with
|
|
43
|
-
* the label `task source` (row B-17, closing the "task v3 source" label
|
|
44
|
-
* wart P2a's §3.4 recorded).
|
|
7
|
+
* Runtime code accepts only the current v4 grammar. Historical v2/v3 files
|
|
8
|
+
* and the former source-owned `schedule[].enabled` field are handled only by
|
|
9
|
+
* the explicit `akm migrate` executable; execution never translates legacy
|
|
10
|
+
* bytes in memory.
|
|
45
11
|
*/
|
|
46
12
|
import { UsageError } from "../../core/errors.js";
|
|
47
|
-
import { warn } from "../../core/warn.js";
|
|
48
13
|
import { readBoundedTaskSourceYaml } from "./bounded-document.js";
|
|
49
|
-
import {
|
|
50
|
-
import { planTaskToV3File } from "./task-to-v3.js";
|
|
51
|
-
import { planTaskToV4File } from "./task-to-v4.js";
|
|
52
|
-
/** Read the root `version` field without over-accepting non-number values (e.g. the string `"4"`). */
|
|
14
|
+
import { parseTaskSourceV4Document, TASK_SOURCE_V4_VERSION } from "./task-source-v4.js";
|
|
53
15
|
export function peekTaskSourceVersion(root) {
|
|
54
16
|
if (root === null || typeof root !== "object" || Array.isArray(root))
|
|
55
17
|
return undefined;
|
|
56
18
|
const value = root.version;
|
|
57
19
|
return typeof value === "number" ? value : undefined;
|
|
58
20
|
}
|
|
59
|
-
const TASK_MIGRATE_HINT = "Run `akm migrate apply --dry-run` to preview the task-v3 to task-source-v4 conversion, then run `akm migrate apply`.";
|
|
60
|
-
/**
|
|
61
|
-
* Thrown only when the deterministic conversion itself could not produce a
|
|
62
|
-
* task source v4 document — a case where a person must decide the intended
|
|
63
|
-
* behavior (e.g. an ambiguous shell command), not one the migrator can just
|
|
64
|
-
* be re-run to fix. `reason`/`detail` are the SAME blocked outcome
|
|
65
|
-
* `akm migrate status`/`apply` reports for this file, so the message names
|
|
66
|
-
* the actual decision instead of pointing at a command that will report the
|
|
67
|
-
* identical block.
|
|
68
|
-
*/
|
|
69
|
-
function unmigratableVersionError(filePath, version, reason, detail) {
|
|
70
|
-
return new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task at ${filePath} uses task schema version ${version} and needs a human decision before it can run — the deterministic migrator cannot convert it automatically (${reason}${detail ? `: ${detail}` : ""}).`, "TASK_SCHEMA_VERSION_UNSUPPORTED", "Review the file and resolve the ambiguity by hand, then it will convert normally; `akm migrate status` reports the same reason.");
|
|
71
|
-
}
|
|
72
21
|
function unsupportedVersionError(filePath, version) {
|
|
73
|
-
return new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task at ${filePath} uses task schema version ${version}, which this release does not accept.`, "TASK_SCHEMA_VERSION_UNSUPPORTED",
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Plan the SAME bytes already in hand through the pure v3->v4 (and, for v2,
|
|
77
|
-
* chained v2->v3->v4) migration planner(s) — never touches disk, never
|
|
78
|
-
* writes the file, never re-reads it from disk. Returns the produced v4
|
|
79
|
-
* YAML text, or the blocked reason/detail when the deterministic conversion
|
|
80
|
-
* cannot proceed (an unmigratable v2/v3 shape) — the caller falls back to
|
|
81
|
-
* the same hard error this gate threw before the shim existed, now naming
|
|
82
|
-
* that reason.
|
|
83
|
-
*/
|
|
84
|
-
function planInMemoryV4Bytes(version, yaml, filePath, workspaceRoot) {
|
|
85
|
-
const bytes = Buffer.from(yaml, "utf8");
|
|
86
|
-
// `writable`/`onDiskWritable` gate the DISK apply path's "don't touch a
|
|
87
|
-
// read-only file" check inside the planners; this shim never writes
|
|
88
|
-
// anything to disk, so that check does not apply here and must not block
|
|
89
|
-
// an otherwise-legal read of a task file that happens to be read-only.
|
|
90
|
-
const baseInput = {
|
|
91
|
-
filePath,
|
|
92
|
-
bytes,
|
|
93
|
-
mode: 0o644,
|
|
94
|
-
writable: true,
|
|
95
|
-
onDiskWritable: true,
|
|
96
|
-
...(workspaceRoot ? { containmentRoot: workspaceRoot } : {}),
|
|
97
|
-
};
|
|
98
|
-
let v3Bytes;
|
|
99
|
-
if (version === 3) {
|
|
100
|
-
v3Bytes = bytes;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
const v3Outcome = planTaskToV3File(baseInput);
|
|
104
|
-
if (v3Outcome.status !== "changed")
|
|
105
|
-
return { reason: v3Outcome.reason, detail: v3Outcome.detail };
|
|
106
|
-
v3Bytes = v3Outcome.after;
|
|
107
|
-
}
|
|
108
|
-
const v4Outcome = planTaskToV4File({ ...baseInput, bytes: v3Bytes });
|
|
109
|
-
if (v4Outcome.status !== "changed")
|
|
110
|
-
return { reason: v4Outcome.reason, detail: v4Outcome.detail };
|
|
111
|
-
return v4Outcome.after.toString("utf8");
|
|
22
|
+
return new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task at ${filePath} uses task schema version ${version}, which this release does not accept.`, "TASK_SCHEMA_VERSION_UNSUPPORTED", "Run `akm migrate apply --dry-run`, review the plan, then run `akm migrate apply`.");
|
|
112
23
|
}
|
|
113
|
-
/** Parse task source YAML, routing per the terminal table above. */
|
|
114
24
|
export function parseTaskSource(input) {
|
|
115
25
|
const { root, lineAt } = readBoundedTaskSourceYaml(input, { sourceLabel: "task source" });
|
|
116
26
|
const version = peekTaskSourceVersion(root);
|
|
117
27
|
if (version !== undefined && version !== TASK_SOURCE_V4_VERSION) {
|
|
118
|
-
if (version === 2 || version === 3) {
|
|
119
|
-
const shimmed = planInMemoryV4Bytes(version, input.yaml, input.filePath, input.workspaceRoot);
|
|
120
|
-
if (typeof shimmed === "string") {
|
|
121
|
-
const v4 = parseTaskSourceV4({
|
|
122
|
-
yaml: shimmed,
|
|
123
|
-
filePath: input.filePath,
|
|
124
|
-
...(input.workspaceRoot ? { workspaceRoot: input.workspaceRoot } : {}),
|
|
125
|
-
});
|
|
126
|
-
warn(`akm: task ${input.filePath} uses schema v${version} — auto-read as v4; run \`akm migrate apply\` to rewrite it and silence this`);
|
|
127
|
-
return Object.freeze({ version: 4, v4 });
|
|
128
|
-
}
|
|
129
|
-
throw unmigratableVersionError(input.filePath, version, shimmed.reason, shimmed.detail);
|
|
130
|
-
}
|
|
131
28
|
throw unsupportedVersionError(input.filePath, version);
|
|
132
29
|
}
|
|
133
|
-
const
|
|
30
|
+
const v4 = parseTaskSourceV4Document(root, {
|
|
134
31
|
filePath: input.filePath,
|
|
135
32
|
...(input.workspaceRoot ? { workspaceRoot: input.workspaceRoot } : {}),
|
|
136
33
|
lineAt,
|
|
137
|
-
};
|
|
138
|
-
return Object.freeze({ version: 4, v4
|
|
34
|
+
});
|
|
35
|
+
return Object.freeze({ version: 4, v4 });
|
|
139
36
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Map every top-level task source v4 execution control and D2-N7 survivor
|
|
6
6
|
* into v3's `akm.*` shape, one field at a time (never a whole-object copy,
|
|
7
|
-
* so a field task source v4 does not represent — `inputs
|
|
8
|
-
*
|
|
7
|
+
* so a field task source v4 does not represent — such as `inputs` — can
|
|
8
|
+
* never leak in by accident). Returns `undefined` when
|
|
9
9
|
* nothing maps, matching v3's own
|
|
10
10
|
* convention of omitting the `akm` key entirely rather than emitting an
|
|
11
11
|
* always-present empty object (`source-v3.ts:789`).
|
|
@@ -75,7 +75,7 @@ export const TASK_SOURCE_V4_TOP_LEVEL_KEYS = [
|
|
|
75
75
|
"maxRetries",
|
|
76
76
|
];
|
|
77
77
|
/** Closes one `schedule:` list entry (D2-N5). */
|
|
78
|
-
export const TASK_SOURCE_V4_SCHEDULE_KEYS = ["cron", "
|
|
78
|
+
export const TASK_SOURCE_V4_SCHEDULE_KEYS = ["cron", "inputs"];
|
|
79
79
|
/**
|
|
80
80
|
* The closed key set for one `inputs.<name>` declaration root (D2-N3). The
|
|
81
81
|
* JSON-Schema-subset portion is DERIVED from
|
|
@@ -479,12 +479,6 @@ function parseScheduleEntry(entryRaw, index, contract, ctx) {
|
|
|
479
479
|
sourceError(ctx, [...entryPath, "cron"], "is required.");
|
|
480
480
|
const cron = stringField(entry.cron, ctx, [...entryPath, "cron"], { nonempty: true });
|
|
481
481
|
noGithubExpression(cron, ctx, [...entryPath, "cron"]);
|
|
482
|
-
let enabled = true;
|
|
483
|
-
if (own(entry, "enabled")) {
|
|
484
|
-
if (typeof entry.enabled !== "boolean")
|
|
485
|
-
sourceError(ctx, [...entryPath, "enabled"], "must be a boolean.");
|
|
486
|
-
enabled = entry.enabled;
|
|
487
|
-
}
|
|
488
482
|
let inputsLiteral = Object.freeze({});
|
|
489
483
|
if (own(entry, "inputs")) {
|
|
490
484
|
const inputsValue = asRecord(presentJsonValue(entry.inputs, ctx, [...entryPath, "inputs"]), ctx, [
|
|
@@ -516,7 +510,7 @@ function parseScheduleEntry(entryRaw, index, contract, ctx) {
|
|
|
516
510
|
inputsLiteral = Object.freeze({ ...inputsValue });
|
|
517
511
|
}
|
|
518
512
|
checkScheduleEntryRunnable(inputsLiteral, contract, ctx, entryPath);
|
|
519
|
-
return Object.freeze({ cron,
|
|
513
|
+
return Object.freeze({ cron, inputs: inputsLiteral, source: `schedule[${index}].cron`, ordinal: index });
|
|
520
514
|
}
|
|
521
515
|
function parseSchedule(input, contract, ctx) {
|
|
522
516
|
if (!own(input, "schedule"))
|
|
@@ -530,12 +524,10 @@ function parseSchedule(input, contract, ctx) {
|
|
|
530
524
|
// runnability contract, at the `schedule` key's own field path (it has
|
|
531
525
|
// neither an ordinal nor an `inputs:` sub-path to point at).
|
|
532
526
|
checkScheduleEntryRunnable(Object.freeze({}), contract, ctx, ["schedule"]);
|
|
533
|
-
return Object.freeze([
|
|
534
|
-
Object.freeze({ cron, enabled: true, inputs: Object.freeze({}), source: "schedule", ordinal: 0 }),
|
|
535
|
-
]);
|
|
527
|
+
return Object.freeze([Object.freeze({ cron, inputs: Object.freeze({}), source: "schedule", ordinal: 0 })]);
|
|
536
528
|
}
|
|
537
529
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
538
|
-
sourceError(ctx, ["schedule"], "must be a non-empty string or a non-empty list of {cron,
|
|
530
|
+
sourceError(ctx, ["schedule"], "must be a non-empty string or a non-empty list of {cron, inputs?} records.");
|
|
539
531
|
}
|
|
540
532
|
if (raw.length > TASK_V3_MAX_SCHEDULES) {
|
|
541
533
|
sourceError(ctx, ["schedule"], `accepts at most ${TASK_V3_MAX_SCHEDULES} entries.`);
|
|
@@ -11,7 +11,6 @@ import { WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_TIMEOUT_MS } from "../../wo
|
|
|
11
11
|
import { validateTaskId } from "../task-id.js";
|
|
12
12
|
import { assertBoundedTaskYamlDocument, TASK_V3_MAX_REDACT_NAMES } from "./bounded-document.js";
|
|
13
13
|
import { classifyTaskV3Uses, parseTaskV3Yaml } from "./task-source-v3-frozen.js";
|
|
14
|
-
import { parseTaskSourceV4 } from "./task-source-v4.js";
|
|
15
14
|
const V2_KEYS = new Set([
|
|
16
15
|
"version",
|
|
17
16
|
"name",
|
|
@@ -442,17 +441,10 @@ export function planTaskToV3File(input) {
|
|
|
442
441
|
}
|
|
443
442
|
}
|
|
444
443
|
if (data.version === 4) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
...(input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}),
|
|
450
|
-
});
|
|
451
|
-
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v4" });
|
|
452
|
-
}
|
|
453
|
-
catch (cause) {
|
|
454
|
-
return blocked(input, "invalid-v4-task", cause instanceof Error ? cause.message : String(cause));
|
|
455
|
-
}
|
|
444
|
+
// Generation 1 owns v2 only. Do not validate v4 here: generation 2 must
|
|
445
|
+
// be allowed to recognize and remove the retired schedule[].enabled field
|
|
446
|
+
// before the current runtime parser sees those bytes.
|
|
447
|
+
return Object.freeze({ status: "skipped", ...base(input), reason: "already-v4" });
|
|
456
448
|
}
|
|
457
449
|
if (data.version !== 2) {
|
|
458
450
|
return blocked(input, "unsupported-task-version", `expected version 2, 3, or 4, got ${String(data.version)}`);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import crypto from "node:crypto";
|
|
19
19
|
import path from "node:path";
|
|
20
|
-
import { LineCounter, parseDocument, stringify as stringifyYaml } from "yaml";
|
|
20
|
+
import { isMap, isSeq, LineCounter, parseDocument, stringify as stringifyYaml } from "yaml";
|
|
21
21
|
import { assertBoundedTaskYamlDocument } from "./bounded-document.js";
|
|
22
22
|
import { classifyTaskV3Uses } from "./task-source-v3-frozen.js";
|
|
23
23
|
import { parseTaskSourceV4 } from "./task-source-v4.js";
|
|
@@ -244,7 +244,6 @@ function planV3DataToV4(input, data) {
|
|
|
244
244
|
if (!input.writable || input.onDiskWritable === false) {
|
|
245
245
|
return blocked(input, "read-only-source", !input.writable ? "the owning source is not writable" : "the source file or publication directory is read-only");
|
|
246
246
|
}
|
|
247
|
-
const enabledFalse = akm !== undefined && akm.enabled === false;
|
|
248
247
|
let scheduleField;
|
|
249
248
|
// Several independent translation facts can need reporting on the SAME
|
|
250
249
|
// file (a manual-only trigger AND a dropped output schema, say), so
|
|
@@ -253,7 +252,7 @@ function planV3DataToV4(input, data) {
|
|
|
253
252
|
const notices = [];
|
|
254
253
|
if (hasAkmSchedule) {
|
|
255
254
|
const cron = akm.schedule;
|
|
256
|
-
scheduleField =
|
|
255
|
+
scheduleField = cron;
|
|
257
256
|
}
|
|
258
257
|
else {
|
|
259
258
|
const rawSchedule = onRecord !== undefined && Object.hasOwn(onRecord, "schedule") ? onRecord.schedule : undefined;
|
|
@@ -276,10 +275,7 @@ function planV3DataToV4(input, data) {
|
|
|
276
275
|
}
|
|
277
276
|
crons.push(record.cron);
|
|
278
277
|
}
|
|
279
|
-
scheduleField = crons.map((cron) => (
|
|
280
|
-
}
|
|
281
|
-
else if (enabledFalse) {
|
|
282
|
-
return blocked(input, "enabled-false-has-no-schedule-entry", "akm.enabled: false has no schedule entry to attach to (the only trigger is on.workflow_dispatch); task source v4 has no top-level enabled flag.");
|
|
278
|
+
scheduleField = crons.map((cron) => ({ cron }));
|
|
283
279
|
}
|
|
284
280
|
else {
|
|
285
281
|
notices.push("schedule: is absent from the migrated document — the source's only trigger was on.workflow_dispatch (manual dispatch); task source v4 tasks are always runnable manually via `akm task run`, so no schedule: entry was emitted.");
|
|
@@ -368,6 +364,43 @@ export function planTaskToV4File(input) {
|
|
|
368
364
|
return blocked(input, "invalid-task-yaml", causeMessage(cause));
|
|
369
365
|
}
|
|
370
366
|
if (data.version === 4) {
|
|
367
|
+
const document = parseDocument(source, { uniqueKeys: true });
|
|
368
|
+
const schedule = document.get("schedule", true);
|
|
369
|
+
let removed = false;
|
|
370
|
+
if (isSeq(schedule)) {
|
|
371
|
+
for (const entry of schedule.items) {
|
|
372
|
+
if (!isMap(entry) || !entry.has("enabled"))
|
|
373
|
+
continue;
|
|
374
|
+
entry.delete("enabled");
|
|
375
|
+
removed = true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (removed) {
|
|
379
|
+
if (!input.writable || input.onDiskWritable === false) {
|
|
380
|
+
return blocked(input, "read-only-source", !input.writable
|
|
381
|
+
? "the owning source is not writable"
|
|
382
|
+
: "the source file or publication directory is read-only");
|
|
383
|
+
}
|
|
384
|
+
const after = Buffer.from(document.toString(), "utf8");
|
|
385
|
+
try {
|
|
386
|
+
parseTaskSourceV4({
|
|
387
|
+
yaml: after.toString("utf8"),
|
|
388
|
+
filePath: input.filePath,
|
|
389
|
+
...(input.containmentRoot ? { workspaceRoot: input.containmentRoot } : {}),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
catch (cause) {
|
|
393
|
+
return blocked(input, "generated-v4-validation-failed", causeMessage(cause));
|
|
394
|
+
}
|
|
395
|
+
return Object.freeze({
|
|
396
|
+
status: "changed",
|
|
397
|
+
...base(input),
|
|
398
|
+
reason: "source-enablement-removed",
|
|
399
|
+
after,
|
|
400
|
+
afterHash: hash(after),
|
|
401
|
+
notice: "Removed source-owned schedule enablement; scheduler activation is now host-local config.",
|
|
402
|
+
});
|
|
403
|
+
}
|
|
371
404
|
try {
|
|
372
405
|
parseTaskSourceV4({
|
|
373
406
|
yaml: source,
|
package/docs/migration/README.md
CHANGED
|
@@ -4,6 +4,7 @@ Upgrade guides and per-release migration notes.
|
|
|
4
4
|
|
|
5
5
|
- [v0.9.1 -> v0.9.2 migration guide](v0.9.1-to-v0.9.2.md) -- Task-v2/task-v3 to task source v4 conversion, the durable-v4-family workflow boundary at executable `irVersion: 5`, and release behavior changes
|
|
6
6
|
- [v0.9.2 release note](release-notes/0.9.2.md) -- Self-contained terminal upgrade summary shipped for `akm help migrate 0.9.2`
|
|
7
|
+
- [v0.9.16 release note](release-notes/0.9.16.md) -- Source-bound scheduler grants, local execution authority, and split unsafe overrides
|
|
7
8
|
- [v0.8 -> current v0.9 migration guide](v0.8-to-v0.9.md) -- Package upgrade with fresh current config/state and explicit task conversion
|
|
8
9
|
- [v0.7 -> v0.8 migration guide](v0.7-to-v0.8.md) -- Task schema and 0.8-era changes
|
|
9
10
|
- [v0.5 -> v0.6 migration guide](https://github.com/itlackey/akm/blob/main/docs/migration/v0.5-to-v0.6.md) -- Terminology cut, registry schema v3, publisher changes
|
|
@@ -85,33 +85,27 @@ scheduled or opportunistic run step aside instead of contending with a rebuild
|
|
|
85
85
|
already in progress; the shipped `index-refresh` scheduled task already passes
|
|
86
86
|
it.
|
|
87
87
|
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
actually probed from the endpoint is treated as authoritative and is never
|
|
99
|
-
second-guessed this way. `embedding.maxTokens` and `embedding.batchSize`
|
|
100
|
-
(the previous per-request token-budget and document-count config keys) are
|
|
101
|
-
retired — see [Retired Configuration](../../reference/configuration.md#retired-configuration)
|
|
102
|
-
— since the provider's own limits are now the source of truth.
|
|
88
|
+
`embedding.maxTokens`'s default (the per-request token budget) is now 6000,
|
|
89
|
+
down from 8000: a field report on an 8192-token llama.cpp embedder showed the
|
|
90
|
+
4-chars-per-token estimator undercounts dense technical text by 7-55%, so the
|
|
91
|
+
old default regularly overshot the endpoint's real context window. If you
|
|
92
|
+
already set `embedding.maxTokens` explicitly, this default change does not
|
|
93
|
+
affect you — your configured value is unchanged. `akm index` also now
|
|
94
|
+
recovers automatically within a run: on the first request rejected for
|
|
95
|
+
exceeding the endpoint's context window, it lowers its effective budget for
|
|
96
|
+
the rest of that run (reported with one line) rather than continuing to hit
|
|
97
|
+
the same wall on every following batch.
|
|
103
98
|
|
|
104
99
|
`embedding.concurrency` (positive integer, 1-16) overrides the number of
|
|
105
100
|
embedding requests kept in flight at once, which otherwise defaults to 1 for
|
|
106
|
-
a loopback endpoint and 2 for a remote one
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
actually use it.
|
|
101
|
+
a loopback endpoint and 2 for a remote one. Set it only for an endpoint that
|
|
102
|
+
genuinely serves parallel requests — a local model server started with a
|
|
103
|
+
multi-slot flag (llama.cpp's `--parallel N`, vLLM) — since the default
|
|
104
|
+
already protects an ordinary single-slot server from reload-thrash.
|
|
105
|
+
Embedding throughput is still tuned first by `embedding.batchSize`
|
|
106
|
+
(documents per request) and `embedding.maxTokens` (token
|
|
107
|
+
budget per request); the concurrency override is a second lever for a
|
|
108
|
+
server that can actually use it.
|
|
115
109
|
|
|
116
110
|
`embedding.timeoutMs` bounds each embedding request (default 120s, up from a
|
|
117
111
|
prior fixed 30s that cut off a slow local model server mid-response). It is
|
|
@@ -148,16 +142,24 @@ this is a drop-in fix for anyone running `akm` under a scheduler,
|
|
|
148
142
|
supervisor, or hook that can time out or kill the launcher process
|
|
149
143
|
directly.
|
|
150
144
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
145
|
+
`embedding.maxInputTokens` (default 512) now caps how much of a single
|
|
146
|
+
document's text is sent to the embedding provider, truncating to the head
|
|
147
|
+
instead of ever failing a whole batch over one oversized document.
|
|
148
|
+
|
|
149
|
+
- An existing install's already-stored vectors are untouched and stay
|
|
150
|
+
valid — this only changes what happens for entries embedded *after*
|
|
151
|
+
upgrading.
|
|
152
|
+
- New embeddings (any entry indexed for the first time, or re-indexed after
|
|
153
|
+
a content change) go through the new 512-token cap by default. If you
|
|
154
|
+
were relying on documents longer than ~2000 characters being embedded in
|
|
155
|
+
full, set `embedding.maxInputTokens` higher in `config.json`.
|
|
156
|
+
- `akm index --reembed` re-embeds every entry under the new cap — run it if
|
|
157
|
+
you want your entire existing index rebuilt against the new default (or a
|
|
158
|
+
custom `embedding.maxInputTokens` you've set).
|
|
159
|
+
- `embedding.contextLength` is Ollama's `num_ctx` only now; it no longer
|
|
160
|
+
also sets the per-request token budget (`embedding.maxTokens`). If you had
|
|
161
|
+
set `contextLength` specifically to control request batching (not your
|
|
162
|
+
Ollama server's context window), set `embedding.maxTokens` instead.
|
|
161
163
|
|
|
162
164
|
**Which token knob fixed the original 8k-context overflow.** A 0.9.15-beta
|
|
163
165
|
field report described documents estimated under the request budget that
|