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.
Files changed (147) hide show
  1. package/CHANGELOG.md +56 -132
  2. package/dist/assets/hints/cli-hints-full.md +13 -6
  3. package/dist/assets/tasks/core/index-refresh.yml +1 -1
  4. package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
  5. package/dist/cli/retired-commands.js +0 -4
  6. package/dist/cli/unknown-flags.js +3 -36
  7. package/dist/commands/env/env-binding.js +4 -4
  8. package/dist/commands/env/env-cli.js +3 -3
  9. package/dist/commands/improve/collapse-detector.js +2 -2
  10. package/dist/commands/improve/consolidate.js +4 -6
  11. package/dist/commands/improve/improve-cli.js +20 -15
  12. package/dist/commands/improve/reflect.js +23 -2
  13. package/dist/commands/lint/base-linter.js +9 -0
  14. package/dist/commands/lint/env-key-rules.js +2 -2
  15. package/dist/commands/proposal/propose.js +15 -1
  16. package/dist/commands/proposal/repository.js +3 -12
  17. package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
  18. package/dist/commands/proposal/validators/proposal-validators.js +5 -4
  19. package/dist/commands/read/curate.js +44 -34
  20. package/dist/commands/read/search.js +35 -54
  21. package/dist/commands/read/show.js +21 -2
  22. package/dist/commands/registry-cli.js +5 -5
  23. package/dist/commands/sources/add-cli.js +59 -16
  24. package/dist/commands/sources/bundle-cli.js +35 -11
  25. package/dist/commands/sources/bundle-config-ops.js +30 -0
  26. package/dist/commands/sources/dangerous-env-audit.js +4 -4
  27. package/dist/commands/sources/info.js +8 -8
  28. package/dist/commands/sources/installed-stashes.js +55 -61
  29. package/dist/commands/sources/source-add.js +39 -38
  30. package/dist/commands/sources/source-manage.js +34 -12
  31. package/dist/commands/sources/stash-cli.js +111 -119
  32. package/dist/commands/sources/stash-skeleton.js +6 -3
  33. package/dist/commands/tasks/explain.js +4 -1
  34. package/dist/commands/tasks/tasks-cli.js +31 -9
  35. package/dist/commands/tasks/tasks.js +239 -194
  36. package/dist/commands/tasks/validate.js +20 -32
  37. package/dist/core/activation-policy.js +4 -4
  38. package/dist/core/adapter/adapters/akm-adapter.js +8 -35
  39. package/dist/core/adapter/adapters/akm-metadata.js +1 -11
  40. package/dist/core/adapter/execution-source.js +10 -29
  41. package/dist/core/asset/asset-placement.js +0 -35
  42. package/dist/core/config/config-schema.js +64 -8
  43. package/dist/core/config/config-sources.js +96 -2
  44. package/dist/core/config/config.js +190 -24
  45. package/dist/core/config/legacy-source-shape-shim.js +9 -0
  46. package/dist/core/config/schema/embedding.js +30 -7
  47. package/dist/core/config/schema/execution.js +23 -0
  48. package/dist/core/config/schema/experimental.js +1 -1
  49. package/dist/core/config/schema/scheduler.js +20 -0
  50. package/dist/core/config/schema/search.js +10 -12
  51. package/dist/core/config/schema/sources-bundles.js +32 -1
  52. package/dist/core/content-safety.js +52 -0
  53. package/dist/core/errors.js +2 -5
  54. package/dist/core/maintenance-barrier.js +11 -13
  55. package/dist/core/paths.js +11 -0
  56. package/dist/core/run-lock.js +2 -5
  57. package/dist/core/state/migrations.js +1 -26
  58. package/dist/core/state-db.js +27 -63
  59. package/dist/core/type-presentation.js +1 -1
  60. package/dist/core/write-source.js +13 -8
  61. package/dist/indexer/bundle-identity-guard.js +45 -8
  62. package/dist/indexer/ensure-index.js +0 -5
  63. package/dist/indexer/index-db-contention.js +56 -0
  64. package/dist/indexer/index-rebuild-lock.js +73 -0
  65. package/dist/indexer/index-written-assets.js +171 -133
  66. package/dist/indexer/indexer.js +1621 -458
  67. package/dist/indexer/lookup/adapter-concept-owner.js +5 -19
  68. package/dist/indexer/materialize-embeddings.js +785 -0
  69. package/dist/indexer/passes/dir-staleness.js +161 -0
  70. package/dist/indexer/passes/metadata.js +1 -18
  71. package/dist/indexer/scan/drain-dir.js +70 -27
  72. package/dist/indexer/search/db-search.js +89 -373
  73. package/dist/indexer/search/ranking-contributors.js +16 -21
  74. package/dist/indexer/search/ranking.js +57 -135
  75. package/dist/indexer/search/search-source.js +29 -11
  76. package/dist/integrations/agent/execution-lowering.js +3 -2
  77. package/dist/integrations/agent/execution-preparation.js +32 -1
  78. package/dist/integrations/agent/prompts.js +1 -1
  79. package/dist/integrations/agent/request-lowering.js +3 -2
  80. package/dist/llm/client.js +3 -11
  81. package/dist/llm/embedder.js +3 -10
  82. package/dist/llm/embedders/remote.js +104 -133
  83. package/dist/llm/feature-gate.js +2 -4
  84. package/dist/llm/rerank-client.js +3 -3
  85. package/dist/output/html-render.js +2 -1
  86. package/dist/output/shapes/passthrough.js +2 -1
  87. package/dist/output/stdout.js +24 -0
  88. package/dist/output/text/command-format.js +13 -19
  89. package/dist/output/text/helpers.js +1 -1
  90. package/dist/output/text/index.js +2 -5
  91. package/dist/output/text.js +4 -3
  92. package/dist/registry/resolve.js +37 -10
  93. package/dist/scripts/akm-migrate-node.js +15197 -11351
  94. package/dist/scripts/akm-migrate.js +15514 -11668
  95. package/dist/setup/semantic-assets.js +2 -2
  96. package/dist/setup/setup.js +3 -3
  97. package/dist/setup/steps/connection.js +2 -3
  98. package/dist/setup/steps/tasks.js +29 -36
  99. package/dist/sources/providers/git-install.js +17 -11
  100. package/dist/sources/providers/git-provider.js +12 -5
  101. package/dist/sources/providers/git-stash.js +38 -16
  102. package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
  103. package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
  104. package/dist/storage/repositories/index-connection.js +3 -1
  105. package/dist/storage/repositories/index-entries-repository.js +68 -77
  106. package/dist/storage/repositories/index-entry-schema.js +25 -16
  107. package/dist/storage/repositories/index-fts-repository.js +263 -29
  108. package/dist/storage/repositories/index-meta-repository.js +29 -0
  109. package/dist/storage/repositories/index-schema.js +122 -115
  110. package/dist/storage/repositories/index-utility-repository.js +1 -1
  111. package/dist/storage/repositories/index-vec-repository.js +435 -22
  112. package/dist/tasks/activation-config.js +90 -0
  113. package/dist/tasks/backends/cron.js +9 -0
  114. package/dist/tasks/backends/launchd.js +1 -0
  115. package/dist/tasks/backends/schtasks.js +2 -0
  116. package/dist/tasks/embedded.js +4 -5
  117. package/dist/tasks/scheduler-binding.js +2 -2
  118. package/dist/tasks/scheduler-sync-preview.js +8 -1
  119. package/dist/tasks/scheduler-sync.js +19 -10
  120. package/dist/tasks/source/parse-task-source.js +10 -113
  121. package/dist/tasks/source/project-v4.js +2 -2
  122. package/dist/tasks/source/task-source-v4.js +4 -12
  123. package/dist/tasks/source/task-to-v3.js +4 -12
  124. package/dist/tasks/source/task-to-v4.js +40 -7
  125. package/docs/migration/README.md +1 -0
  126. package/docs/migration/release-notes/0.9.15.md +36 -34
  127. package/docs/migration/release-notes/0.9.16.md +60 -98
  128. package/docs/migration/release-notes/README.md +0 -5
  129. package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
  130. package/docs/reference/cli.md +124 -122
  131. package/docs/reference/configuration.md +137 -133
  132. package/docs/reference/data-and-telemetry.md +1 -2
  133. package/docs/reference/tasks.md +34 -29
  134. package/package.json +1 -1
  135. package/schemas/akm-config.json +170 -6
  136. package/schemas/akm-task.json +1 -2
  137. package/dist/commands/sources/index-status.js +0 -99
  138. package/dist/core/hash.js +0 -18
  139. package/dist/indexer/drain.js +0 -306
  140. package/dist/indexer/embedding-identity.js +0 -20
  141. package/dist/indexer/enrich.js +0 -260
  142. package/dist/indexer/reconcile.js +0 -890
  143. package/dist/indexer/scan/parse-file.js +0 -66
  144. package/dist/indexer/units/unit.js +0 -159
  145. package/dist/llm/embedders/provider-limits.js +0 -288
  146. package/dist/storage/repositories/files-repository.js +0 -181
  147. 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({ id: operation.binding.id, kind: "update" });
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
- assertCoherentInspection(inspection, input.inspection !== undefined);
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 per-entry
312
- // `enabled` and `schedule[i].inputs` (D2-N5, project-v4.ts) —
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
- * The task source version router (spec docs/plans/specs/p4-deletions-closeout.md
6
- * §3.2.2).
5
+ * Current task-source router.
7
6
  *
8
- * Runs the bounded YAML front end ONCE (`readBoundedTaskSourceYaml`), reads
9
- * `root.version`, and either dispatches into `parseTaskSourceV4Document` or
10
- * routes through the in-memory read shim below — no second parse of the v4
11
- * grammar itself, no re-serialization back to disk, no synthetic document
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 { parseTaskSourceV4, parseTaskSourceV4Document, TASK_SOURCE_V4_VERSION, } from "./task-source-v4.js";
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", TASK_MIGRATE_HINT);
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 documentOptions = {
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: parseTaskSourceV4Document(root, documentOptions) });
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`, per-binding
8
- * `enabled` — can never leak in by accident). Returns `undefined` when
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", "enabled", "inputs"];
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, enabled, inputs: inputsLiteral, source: `schedule[${index}].cron`, ordinal: index });
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, enabled?, inputs?} records.");
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
- try {
446
- parseTaskSourceV4({
447
- yaml: source,
448
- filePath: input.filePath,
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 = enabledFalse ? [{ cron, enabled: false }] : cron;
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) => (enabledFalse ? { cron, enabled: false } : { 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,
@@ -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
- `akm index` now packs embedding requests against the provider's own probed
89
- context window and slot count (llama.cpp's `GET /props`, Ollama's
90
- `POST /api/show`) instead of a flat configured token budget: an 8192-token
91
- llama.cpp embedder, for example, is packed against its real 8192 rather
92
- than a generic guess. An endpoint that answers neither probe (an
93
- OpenAI-compatible server, a gateway) gets a conservative built-in default,
94
- with the same run-scoped recovery as before — on the first request rejected
95
- for exceeding the endpoint's context window, `akm index` lowers its
96
- effective budget for the rest of that run (reported with one line) rather
97
- than continuing to hit the same wall on every following batch; a window akm
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, or the provider's own probed
107
- slot count (llama.cpp's `total_slots`) when the probe reports one. Set an
108
- explicit override only for an endpoint that genuinely serves parallel
109
- requests — a local model server started with a multi-slot flag (llama.cpp's
110
- `--parallel N`, vLLM) — since the default already protects an ordinary
111
- single-slot server from reload-thrash. Embedding throughput is still tuned
112
- first by request SIZE — the probed token budget and document-count cap
113
- above; the concurrency override is a second lever for a server that can
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
- A large document is no longer truncated for embedding — `akm index` splits
152
- it into multiple content-addressed units instead (one vector per section,
153
- sized against the embedding provider's own probed context window), so a
154
- long document's later sections are searchable too, not silently dropped
155
- past a head cap. `embedding.maxInputTokens`, the earlier per-document
156
- truncation cap this superseded within the same 0.9.15 cycle, is retired —
157
- see [Retired Configuration](../../reference/configuration.md#retired-configuration).
158
- `embedding.contextLength` is retired too: Ollama's `num_ctx` is now sent
159
- automatically from the same probe, or set explicitly via
160
- `embedding.ollamaOptions.num_ctx`.
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