akm-cli 0.9.17-alpha.1 → 0.9.17-alpha.3
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 +51 -0
- package/dist/commands/tasks/tasks.js +19 -2
- package/dist/core/config/config.js +5 -2
- package/dist/core/config/retired-experimental-keys-shim.js +62 -0
- package/dist/core/write-source.js +10 -2
- package/dist/execution/guarded-source.js +15 -1
- package/dist/scripts/akm-migrate-node.js +77 -32
- package/dist/scripts/akm-migrate.js +77 -32
- package/dist/tasks/scheduler-sync.js +88 -14
- package/docs/reference/cli.md +13 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,57 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.17-alpha.3] - 2026-09-24
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Unscoped `akm task sync` no longer aborts the whole host on the first
|
|
14
|
+
bundle root that happens to contain any symlink.** `captureGuardedDirectoryManifest`
|
|
15
|
+
threw for every symbolic directory entry it listed, even one the scheduler
|
|
16
|
+
never reads (e.g. a third-party skill repo's `CLAUDE.md -> AGENTS.md`) —
|
|
17
|
+
`SchedulerSourceCollector` manifests every scanned bundle's root, so one
|
|
18
|
+
such bundle among many enabled ones failed sync entirely, dry-run included.
|
|
19
|
+
A symlink that stays inside its bundle root is now recorded in the guarded
|
|
20
|
+
directory manifest as its own `"symlink"` kind, identified without
|
|
21
|
+
following it (its `readlink` text plus its no-follow `lstat` identity), so
|
|
22
|
+
change detection still works; it is never read or descended into. A symlink
|
|
23
|
+
sitting exactly where a task or workflow source lives (a `.yml` under
|
|
24
|
+
`tasks/`, any `.yml` under an `akm-task` bundle, or a workflow-named file
|
|
25
|
+
under `workflows/`) is reported as its own per-source failure — "is a
|
|
26
|
+
symbolic source; guarded reads require a regular no-follow owner" — and its
|
|
27
|
+
ref is not scheduled, even when a real sibling file shares that ref, while
|
|
28
|
+
every other task and workflow still reconciles. A
|
|
29
|
+
symlink that resolves outside the bundle root, or one that is broken and
|
|
30
|
+
cannot be identified safely, is still refused, and a bundle root whose
|
|
31
|
+
`tasks` or `workflows` entry is itself a symlink still refuses loudly,
|
|
32
|
+
since that is a schedulable source location.
|
|
33
|
+
|
|
34
|
+
## [0.9.17-alpha.2] - 2026-09-24
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **A config carrying the retired `experimental.workflowEngine` key no longer
|
|
39
|
+
fails to load.** `ExperimentalConfigSchema` moved from `.passthrough()` to
|
|
40
|
+
`.strict()` in 0.9.16 (`cc6152e02`), after `workflowEngine` had already been
|
|
41
|
+
removed from it in `e0655d13c`; a real config a 0.9.15 install wrote (whose
|
|
42
|
+
passthrough still accepted the key) then failed every command with
|
|
43
|
+
`Invalid config: experimental: Unrecognized key(s) in object: 'workflowEngine'`.
|
|
44
|
+
The config loader now strips known-retired `experimental.*` keys in memory
|
|
45
|
+
before validation, warning once and naming `akm migrate apply`; a genuinely
|
|
46
|
+
unknown/misspelled key (e.g. `improveAutonomyy`) still fails closed.
|
|
47
|
+
`akm migrate apply` removes the retired key from `config.json` on disk
|
|
48
|
+
(with the usual backup), and `--dry-run` reports the pending removal.
|
|
49
|
+
- **Unscoped `akm task sync` no longer crashes when an enabled website or npm
|
|
50
|
+
bundle is configured.** The sync plan loop resolved every active source
|
|
51
|
+
through the write-target resolver, which rejects any kind other than
|
|
52
|
+
`filesystem`/`git` outright (writes, and therefore scheduler state, are
|
|
53
|
+
undefined for those kinds — the same rejection `akm task enable` already
|
|
54
|
+
hit). Unscoped sync now skips non-filesystem/git bundles when building
|
|
55
|
+
install operations — they never carried schedulable tasks — while
|
|
56
|
+
inactive-bundle removal/revocation still sees them. A scoped
|
|
57
|
+
`akm task sync --bundle <website-or-npm-bundle>` now fails with a clear
|
|
58
|
+
usage error instead of the write-target `ConfigError`.
|
|
59
|
+
|
|
9
60
|
## [0.9.17-alpha.1] - 2026-09-24
|
|
10
61
|
|
|
11
62
|
### Added
|
|
@@ -23,7 +23,7 @@ import { IMPROVE_AUTONOMY_CONFIG_KEY, isImproveAutonomyEnabled } from "../../cor
|
|
|
23
23
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
24
24
|
import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
|
|
25
25
|
import { warn } from "../../core/warn.js";
|
|
26
|
-
import { commitWriteTargetBoundary, deleteAssetFromSource, prepareWriteTargetForMutation, resolveWorkingStashTarget, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
26
|
+
import { commitWriteTargetBoundary, deleteAssetFromSource, isWriteCapableSourceKind, prepareWriteTargetForMutation, resolveWorkingStashTarget, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
27
27
|
import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
|
|
28
28
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
29
29
|
import { activeSchedulerActivations, isSchedulerRefEnabled, setSchedulerRefEnabled, } from "../../tasks/activation-config.js";
|
|
@@ -411,7 +411,24 @@ async function buildSchedulerSyncPlan(deps, bundleTarget, options) {
|
|
|
411
411
|
const nativeArtifacts = inspection.artifacts;
|
|
412
412
|
const configuredSources = resolveConfiguredSources(config);
|
|
413
413
|
const activeSources = resolveActiveConfiguredSources(config);
|
|
414
|
-
|
|
414
|
+
if (bundleTarget) {
|
|
415
|
+
// adaptConfiguredSource (src/core/write-source.ts) rejects any kind
|
|
416
|
+
// other than filesystem/git outright, so a website/npm bundle can never
|
|
417
|
+
// carry scheduler state (akm task enable already fails the same way).
|
|
418
|
+
// Surface that as a clear usage error here instead of letting the
|
|
419
|
+
// write-target resolution below raise a generic ConfigError.
|
|
420
|
+
const targetSource = activeSources.find((source) => source.name === bundleTarget);
|
|
421
|
+
if (targetSource && !isWriteCapableSourceKind(targetSource.type)) {
|
|
422
|
+
throw new UsageError(`Bundle "${bundleTarget}" has kind "${targetSource.type}"; task scheduling is only supported for filesystem and git bundles.`, "INVALID_FLAG_VALUE");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Unscoped sync only installs/removes bindings for bundles that can carry
|
|
426
|
+
// them (filesystem/git). A website/npm bundle contributes no installs and
|
|
427
|
+
// must not crash the loop; inactiveOperations below still sees it via
|
|
428
|
+
// configuredSources for removal/revocation.
|
|
429
|
+
const sourceNames = bundleTarget
|
|
430
|
+
? [bundleTarget]
|
|
431
|
+
: activeSources.filter((source) => isWriteCapableSourceKind(source.type)).map((source) => source.name);
|
|
415
432
|
const inactiveOperations = bundleTarget
|
|
416
433
|
? []
|
|
417
434
|
: inactiveBundleRemovalOperations(config, configuredSources, allEntries, nativeArtifacts);
|
|
@@ -16,6 +16,7 @@ import { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundlesTo
|
|
|
16
16
|
import { upgradeConfigVersion } from "./config-version-shim.js";
|
|
17
17
|
import { deepMergeConfig, isPlainObject } from "./deep-merge.js";
|
|
18
18
|
import { migrateLegacySourceShape } from "./legacy-source-shape-shim.js";
|
|
19
|
+
import { stripRetiredExperimentalKeys } from "./retired-experimental-keys-shim.js";
|
|
19
20
|
import { isApiKeyReference, SECRET_STORE_REFERENCE_PATTERN } from "./schema/primitives.js";
|
|
20
21
|
export { stripJsonComments } from "./config-io.js";
|
|
21
22
|
import { getConfigPath } from "../paths.js";
|
|
@@ -147,7 +148,8 @@ export function acquireConfigReadFence() {
|
|
|
147
148
|
* before it is either validated (the local/top-level file) or merged in as
|
|
148
149
|
* an `extends` base: JSONC parse already done by the caller, then version
|
|
149
150
|
* shim, then legacy `stashDir`/`sources[]`/`installed[]` shim, then the
|
|
150
|
-
* legacy `extraParams` lift (#852)
|
|
151
|
+
* legacy `extraParams` lift (#852), then the retired `experimental.*` key
|
|
152
|
+
* shim. Shared by {@link parseAndValidateConfigText}
|
|
151
153
|
* (the local file) and {@link resolveExtendsChain} (each base in the chain) so
|
|
152
154
|
* a fleet-shared base config can carry its own old `configVersion` / legacy
|
|
153
155
|
* shape independently of the file that extends it.
|
|
@@ -155,7 +157,8 @@ export function acquireConfigReadFence() {
|
|
|
155
157
|
function runConfigFilePipeline(text, sourcePath) {
|
|
156
158
|
const versioned = upgradeConfigVersion(parseConfigText(text, sourcePath), sourcePath);
|
|
157
159
|
const parsedRaw = migrateLegacySourceShape(versioned, sourcePath);
|
|
158
|
-
|
|
160
|
+
const liftedRaw = liftExtraParamsOrThrow(parsedRaw, sourcePath);
|
|
161
|
+
return stripRetiredExperimentalKeys(liftedRaw, sourcePath);
|
|
159
162
|
}
|
|
160
163
|
/**
|
|
161
164
|
* #852 (following #815): a config still using legacy `extraParams` keys —
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* `experimental.*` retired-key shim.
|
|
6
|
+
*
|
|
7
|
+
* `ExperimentalConfigSchema` (`./schema/experimental.ts`) moved from
|
|
8
|
+
* `.passthrough()` to `.strict()` in 0.9.16 (`cc6152e02`) so a typo in an
|
|
9
|
+
* authority flag (e.g. `improveAutonomyy`) fails loudly instead of silently
|
|
10
|
+
* doing nothing. `workflowEngine` was removed from that block earlier, in
|
|
11
|
+
* `e0655d13c`, but 0.9.15's passthrough still accepted it — so a real config
|
|
12
|
+
* written by 0.9.15 can carry `experimental.workflowEngine` and now fails
|
|
13
|
+
* every command with `Invalid config: experimental: Unrecognized key(s)`.
|
|
14
|
+
*
|
|
15
|
+
* Per AGENTS.md "Reading persisted data": a reader tolerates what older
|
|
16
|
+
* releases wrote, converts in memory, warns once, and leaves the on-disk
|
|
17
|
+
* rewrite to `akm migrate apply`. This mirrors `./legacy-source-shape-shim.ts`
|
|
18
|
+
* exactly — strip the retired key(s) before schema validation, warn once
|
|
19
|
+
* naming the key and the migrate command — rather than reintroducing
|
|
20
|
+
* `.passthrough()`, which would also let a live key typo through silently.
|
|
21
|
+
*/
|
|
22
|
+
import { isRecord } from "../common.js";
|
|
23
|
+
import { warnOnce } from "../warn.js";
|
|
24
|
+
/**
|
|
25
|
+
* Every `experimental.*` key `ExperimentalConfigSchema` has ever retired.
|
|
26
|
+
* `workflowEngine` (removed in `e0655d13c`) is the only one so far — kept as
|
|
27
|
+
* a list because the shim and `akm migrate apply`
|
|
28
|
+
* (`scripts/akm-migrate/migrate/config-retired-experimental-keys.ts`) share
|
|
29
|
+
* it.
|
|
30
|
+
*/
|
|
31
|
+
export const RETIRED_EXPERIMENTAL_KEYS = ["workflowEngine"];
|
|
32
|
+
/**
|
|
33
|
+
* Which `RETIRED_EXPERIMENTAL_KEYS` are present in a raw config's
|
|
34
|
+
* `experimental` section. Returns `[]` when `raw.experimental` is missing or
|
|
35
|
+
* not a record. Shared by `stripRetiredExperimentalKeys` below and by
|
|
36
|
+
* `akm migrate apply`'s on-disk counterpart
|
|
37
|
+
* (`scripts/akm-migrate/migrate/config-retired-experimental-keys.ts`).
|
|
38
|
+
*/
|
|
39
|
+
export function retiredExperimentalKeysIn(raw) {
|
|
40
|
+
const experimental = raw.experimental;
|
|
41
|
+
if (!isRecord(experimental))
|
|
42
|
+
return [];
|
|
43
|
+
return RETIRED_EXPERIMENTAL_KEYS.filter((key) => key in experimental);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Drop retired `experimental.*` keys from a raw parsed config object before
|
|
47
|
+
* schema validation, warning once per source when any were present. Live
|
|
48
|
+
* keys (including an unrecognized one, e.g. a typo) are left untouched for
|
|
49
|
+
* `ExperimentalConfigSchema.strict()` to reject as before.
|
|
50
|
+
*/
|
|
51
|
+
export function stripRetiredExperimentalKeys(raw, sourcePath) {
|
|
52
|
+
const present = retiredExperimentalKeysIn(raw);
|
|
53
|
+
if (present.length === 0)
|
|
54
|
+
return raw;
|
|
55
|
+
const original = raw.experimental;
|
|
56
|
+
const experimental = { ...original };
|
|
57
|
+
for (const key of present)
|
|
58
|
+
delete experimental[key];
|
|
59
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
60
|
+
warnOnce(`config:retired-experimental-key${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses the retired experimental key(s) ${present.join(", ")} — ignored in memory. Run \`akm migrate apply\` to remove ${present.length === 1 ? "it" : "them"} from the config file and silence this warning.`);
|
|
61
|
+
return { ...raw, experimental };
|
|
62
|
+
}
|
|
@@ -1028,6 +1028,14 @@ export function assertAkmAssetWrite(source, allowedAdapters = ["akm"]) {
|
|
|
1028
1028
|
return;
|
|
1029
1029
|
throw new UsageError(`Bundle "${source.name}" uses adapter "${source.adapterId}", which does not support AKM asset writes.`, "INVALID_FLAG_VALUE");
|
|
1030
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* The write-capable source kinds. Writes (and therefore scheduler state,
|
|
1033
|
+
* which only ever binds to a writable source) are defined only for these
|
|
1034
|
+
* two kinds; anything else throws `ConfigError`.
|
|
1035
|
+
*/
|
|
1036
|
+
export function isWriteCapableSourceKind(kind) {
|
|
1037
|
+
return kind === "filesystem" || kind === "git";
|
|
1038
|
+
}
|
|
1031
1039
|
/**
|
|
1032
1040
|
* Reject any kind reaching the write/delete helpers other than the two
|
|
1033
1041
|
* supported writable kinds. The config loader is the first line of defence
|
|
@@ -1035,7 +1043,7 @@ export function assertAkmAssetWrite(source, allowedAdapters = ["akm"]) {
|
|
|
1035
1043
|
* bypass the loader still get a clear error.
|
|
1036
1044
|
*/
|
|
1037
1045
|
function assertSupportedKind(source) {
|
|
1038
|
-
if (source.kind
|
|
1046
|
+
if (isWriteCapableSourceKind(source.kind))
|
|
1039
1047
|
return;
|
|
1040
1048
|
throw new ConfigError(`write-source: unsupported kind "${source.kind}" for source "${source.name}". ` +
|
|
1041
1049
|
"Writes are only defined for `filesystem` and `git` sources.", "INVALID_CONFIG_FILE", 'Set `kind: "filesystem"` (or `kind: "git"`) on the source, or add a parallel filesystem entry.');
|
|
@@ -1073,7 +1081,7 @@ function adaptConfiguredSource(runtime) {
|
|
|
1073
1081
|
// reaching this point is a config-loader bug (assertWritableAllowedForKind
|
|
1074
1082
|
// should have rejected it). Throw a ConfigError rather than silently
|
|
1075
1083
|
// forwarding an unsupported kind.
|
|
1076
|
-
if (runtime.type
|
|
1084
|
+
if (!isWriteCapableSourceKind(runtime.type)) {
|
|
1077
1085
|
throw new ConfigError(`write-source: source "${runtime.name}" has unsupported kind "${runtime.type}" for writes. ` +
|
|
1078
1086
|
"Writes are only defined for `filesystem` and `git` sources.", "INVALID_CONFIG_FILE", 'Use `kind: "filesystem"` or `kind: "git"` for writable sources.');
|
|
1079
1087
|
}
|
|
@@ -179,6 +179,18 @@ export function captureGuardedDirectoryManifest(directoryPathInput, containmentR
|
|
|
179
179
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
180
180
|
throw new UsageError(`${entryPath} resolves outside the bundle root through a symbolic source.`, "PATH_ESCAPE_VIOLATION");
|
|
181
181
|
}
|
|
182
|
+
// A symlink that stays inside the containment root is recorded as
|
|
183
|
+
// its own kind, identified without following it (readlink text
|
|
184
|
+
// plus its own no-follow lstat identity), so change detection
|
|
185
|
+
// still works. It is never a directory or file candidate to any
|
|
186
|
+
// manifest consumer.
|
|
187
|
+
return Object.freeze({
|
|
188
|
+
name: entry.name,
|
|
189
|
+
kind: "symlink",
|
|
190
|
+
physicalIdentity: physicalIdentity(entryPath, entryStat),
|
|
191
|
+
version: statVersion(entryStat),
|
|
192
|
+
target: fs.readlinkSync(entryPath),
|
|
193
|
+
});
|
|
182
194
|
}
|
|
183
195
|
throw new UsageError(`${entryPath} is a symbolic source with a physical source identity collision; guarded reads require one no-follow owner.`, "RESOURCE_ALREADY_EXISTS");
|
|
184
196
|
}
|
|
@@ -323,8 +335,10 @@ export class GuardedExecutionSourceCollector {
|
|
|
323
335
|
const candidate = path.join(directory, entry.name);
|
|
324
336
|
if (entry.kind === "directory")
|
|
325
337
|
visit(candidate);
|
|
326
|
-
else
|
|
338
|
+
else if (entry.kind === "file")
|
|
327
339
|
files.push(candidate);
|
|
340
|
+
// A "symlink" entry is recorded for change detection but is never
|
|
341
|
+
// read, descended into, or made a candidate source.
|
|
328
342
|
}
|
|
329
343
|
};
|
|
330
344
|
visit(path.resolve(directoryPath));
|
|
@@ -77671,6 +77671,31 @@ function upgradeConfigVersion(raw, sourcePath) {
|
|
|
77671
77671
|
|
|
77672
77672
|
// src/core/config/config.ts
|
|
77673
77673
|
init_legacy_source_shape_shim();
|
|
77674
|
+
|
|
77675
|
+
// src/core/config/retired-experimental-keys-shim.ts
|
|
77676
|
+
init_common();
|
|
77677
|
+
init_warn();
|
|
77678
|
+
var RETIRED_EXPERIMENTAL_KEYS = ["workflowEngine"];
|
|
77679
|
+
function retiredExperimentalKeysIn(raw) {
|
|
77680
|
+
const experimental = raw.experimental;
|
|
77681
|
+
if (!isRecord(experimental))
|
|
77682
|
+
return [];
|
|
77683
|
+
return RETIRED_EXPERIMENTAL_KEYS.filter((key) => (key in experimental));
|
|
77684
|
+
}
|
|
77685
|
+
function stripRetiredExperimentalKeys(raw, sourcePath) {
|
|
77686
|
+
const present = retiredExperimentalKeysIn(raw);
|
|
77687
|
+
if (present.length === 0)
|
|
77688
|
+
return raw;
|
|
77689
|
+
const original = raw.experimental;
|
|
77690
|
+
const experimental = { ...original };
|
|
77691
|
+
for (const key of present)
|
|
77692
|
+
delete experimental[key];
|
|
77693
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
77694
|
+
warnOnce(`config:retired-experimental-key${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses the retired experimental key(s) ${present.join(", ")} — ignored in memory. Run \`akm migrate apply\` to remove ${present.length === 1 ? "it" : "them"} from the config file and silence this warning.`);
|
|
77695
|
+
return { ...raw, experimental };
|
|
77696
|
+
}
|
|
77697
|
+
|
|
77698
|
+
// src/core/config/config.ts
|
|
77674
77699
|
init_paths();
|
|
77675
77700
|
init_warn();
|
|
77676
77701
|
var DEFAULT_CONFIG = {
|
|
@@ -77730,7 +77755,8 @@ function loadUserConfig() {
|
|
|
77730
77755
|
function runConfigFilePipeline(text, sourcePath) {
|
|
77731
77756
|
const versioned = upgradeConfigVersion(parseConfigText(text, sourcePath), sourcePath);
|
|
77732
77757
|
const parsedRaw = migrateLegacySourceShape(versioned, sourcePath);
|
|
77733
|
-
|
|
77758
|
+
const liftedRaw = liftExtraParamsOrThrow(parsedRaw, sourcePath);
|
|
77759
|
+
return stripRetiredExperimentalKeys(liftedRaw, sourcePath);
|
|
77734
77760
|
}
|
|
77735
77761
|
function liftExtraParamsOrThrow(parsedRaw, sourcePath) {
|
|
77736
77762
|
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
@@ -86389,8 +86415,11 @@ function assertAkmAssetWrite(source, allowedAdapters = ["akm"]) {
|
|
|
86389
86415
|
return;
|
|
86390
86416
|
throw new UsageError(`Bundle "${source.name}" uses adapter "${source.adapterId}", which does not support AKM asset writes.`, "INVALID_FLAG_VALUE");
|
|
86391
86417
|
}
|
|
86418
|
+
function isWriteCapableSourceKind(kind) {
|
|
86419
|
+
return kind === "filesystem" || kind === "git";
|
|
86420
|
+
}
|
|
86392
86421
|
function adaptConfiguredSource(runtime) {
|
|
86393
|
-
if (runtime.type
|
|
86422
|
+
if (!isWriteCapableSourceKind(runtime.type)) {
|
|
86394
86423
|
throw new ConfigError(`write-source: source "${runtime.name}" has unsupported kind "${runtime.type}" for writes. ` + "Writes are only defined for `filesystem` and `git` sources.", "INVALID_CONFIG_FILE", 'Use `kind: "filesystem"` or `kind: "git"` for writable sources.');
|
|
86395
86424
|
}
|
|
86396
86425
|
const kind = runtime.type;
|
|
@@ -93226,32 +93255,7 @@ var GLOBAL_OUTPUT_ARGS2 = {
|
|
|
93226
93255
|
init_errors();
|
|
93227
93256
|
|
|
93228
93257
|
// scripts/akm-migrate/help.txt
|
|
93229
|
-
var help_default =
|
|
93230
|
-
|
|
93231
|
-
The one migration tool for an akm installation. Every historical shape akm
|
|
93232
|
-
has ever written lives here; the CLI proper reads only current schemas.
|
|
93233
|
-
\`status\` and \`apply\` run every step, in order, and print one combined JSON
|
|
93234
|
-
plan (exit 1 when any step is blocked):
|
|
93235
|
-
|
|
93236
|
-
1. legacy config \`extraParams\` keys lifted onto first-class engine fields
|
|
93237
|
-
2. scheduler grants bound to the configured source installation that was
|
|
93238
|
-
approved (stale grants whose bundle no longer exists are removed)
|
|
93239
|
-
3. pending state.db migrations, historical-destructive ones included,
|
|
93240
|
-
with a verified sibling safety copy (the only path that admits them)
|
|
93241
|
-
4. task-v2 files to task v3, then task-v3 files to task source v4
|
|
93242
|
-
5. superseded pre-0.9.0 \`.akm\` residue and stale filesystem transactions
|
|
93243
|
-
6. live \`.akm\` writers relocated to \`$STATE\`/\`$CACHE\`, for every local
|
|
93244
|
-
bundle (distill-rejected, eval-cases, measurement verdicts, and stale
|
|
93245
|
-
improve-pipeline locks — a lock a live run still holds is left alone)
|
|
93246
|
-
|
|
93247
|
-
\`akm migrate status|apply\` wraps this executable; \`akm upgrade\` runs
|
|
93248
|
-
\`apply\` after its install step, so an image that ships akm can put either
|
|
93249
|
-
in its entrypoint (a current installation is a no-op).
|
|
93250
|
-
|
|
93251
|
-
Commands:
|
|
93252
|
-
status Inspect every pending migration without changing anything.
|
|
93253
|
-
apply [--dry-run] Back up and apply every pending migration.
|
|
93254
|
-
`;
|
|
93258
|
+
var help_default = "Usage: akm-migrate <command> [options]\n\nThe one migration tool for an akm installation. Every historical shape akm\nhas ever written lives here; the CLI proper reads only current schemas.\n`status` and `apply` run every step, in order, and print one combined JSON\nplan (exit 1 when any step is blocked):\n\n 1. legacy config `extraParams` keys lifted onto first-class engine fields\n 2. retired `experimental.*` config keys removed (today `workflowEngine`)\n 3. scheduler grants bound to the configured source installation that was\n approved (stale grants whose bundle no longer exists are removed)\n 4. pending state.db migrations, historical-destructive ones included,\n with a verified sibling safety copy (the only path that admits them)\n 5. source-owned schedule enablement converted to host-local scheduler\n grants\n 6. task-v2 files to task v3, then task-v3 files to task source v4\n 7. superseded pre-0.9.0 `.akm` residue and stale filesystem transactions\n 8. live `.akm` writers relocated to `$STATE`/`$CACHE`, for every local\n bundle (distill-rejected, eval-cases, measurement verdicts, and stale\n improve-pipeline locks — a lock a live run still holds is left alone)\n\n`akm migrate status|apply` wraps this executable; `akm upgrade` runs\n`apply` after its install step, so an image that ships akm can put either\nin its entrypoint (a current installation is a no-op).\n\nCommands:\n status Inspect every pending migration without changing anything.\n apply [--dry-run] Back up and apply every pending migration.\n";
|
|
93255
93259
|
|
|
93256
93260
|
// scripts/akm-migrate/run-migrate.ts
|
|
93257
93261
|
init_common();
|
|
@@ -94787,10 +94791,44 @@ function applyConfigExtraParamsLift(configPath) {
|
|
|
94787
94791
|
return { applied: true, lifted, conflicts: [] };
|
|
94788
94792
|
}
|
|
94789
94793
|
|
|
94794
|
+
// scripts/akm-migrate/migrate/config-retired-experimental-keys.ts
|
|
94795
|
+
function readRawConfig2(configPath) {
|
|
94796
|
+
const text = readConfigText(configPath);
|
|
94797
|
+
if (text === undefined)
|
|
94798
|
+
return;
|
|
94799
|
+
return parseConfigText(text, configPath);
|
|
94800
|
+
}
|
|
94801
|
+
function findConfigRetiredExperimentalKeys(configPath) {
|
|
94802
|
+
const raw = readRawConfig2(configPath);
|
|
94803
|
+
if (!raw)
|
|
94804
|
+
return { removed: [] };
|
|
94805
|
+
return { removed: retiredExperimentalKeysIn(raw).map((key) => `experimental.${key}`) };
|
|
94806
|
+
}
|
|
94807
|
+
function applyConfigRetiredExperimentalKeys(configPath) {
|
|
94808
|
+
const raw = readRawConfig2(configPath);
|
|
94809
|
+
if (!raw)
|
|
94810
|
+
return { applied: false, removed: [] };
|
|
94811
|
+
const retired = retiredExperimentalKeysIn(raw);
|
|
94812
|
+
if (retired.length === 0)
|
|
94813
|
+
return { applied: false, removed: [] };
|
|
94814
|
+
const experimental = { ...raw.experimental };
|
|
94815
|
+
for (const key of retired)
|
|
94816
|
+
delete experimental[key];
|
|
94817
|
+
const config = { ...raw, experimental };
|
|
94818
|
+
const release = acquireConfigLock();
|
|
94819
|
+
try {
|
|
94820
|
+
backupExistingConfig(configPath);
|
|
94821
|
+
writeConfigAtomic(configPath, config);
|
|
94822
|
+
} finally {
|
|
94823
|
+
release();
|
|
94824
|
+
}
|
|
94825
|
+
return { applied: true, removed: retired.map((key) => `experimental.${key}`) };
|
|
94826
|
+
}
|
|
94827
|
+
|
|
94790
94828
|
// scripts/akm-migrate/migrate/config-scheduler-source-ids.ts
|
|
94791
94829
|
init_asset_ref();
|
|
94792
94830
|
init_bundle_id();
|
|
94793
|
-
function
|
|
94831
|
+
function readRawConfig3(configPath) {
|
|
94794
94832
|
const text = readConfigText(configPath);
|
|
94795
94833
|
return text === undefined ? undefined : parseConfigText(text, configPath);
|
|
94796
94834
|
}
|
|
@@ -94845,13 +94883,13 @@ function implicitBundleSourceId(raw, bundleId) {
|
|
|
94845
94883
|
return implicitId === bundleId ? filesystemBundleSourceId(root) : undefined;
|
|
94846
94884
|
}
|
|
94847
94885
|
function findConfigSchedulerSourceIdMigration(configPath) {
|
|
94848
|
-
const raw =
|
|
94886
|
+
const raw = readRawConfig3(configPath);
|
|
94849
94887
|
return Object.freeze({ changes: Object.freeze(raw ? migrationChanges(raw) : []) });
|
|
94850
94888
|
}
|
|
94851
94889
|
function applyConfigSchedulerSourceIdMigration(configPath) {
|
|
94852
94890
|
const release = acquireConfigLock();
|
|
94853
94891
|
try {
|
|
94854
|
-
const raw =
|
|
94892
|
+
const raw = readRawConfig3(configPath);
|
|
94855
94893
|
if (!raw)
|
|
94856
94894
|
return Object.freeze({ applied: false, changes: Object.freeze([]) });
|
|
94857
94895
|
const changes = migrationChanges(raw);
|
|
@@ -104071,12 +104109,16 @@ async function runMigration(options) {
|
|
|
104071
104109
|
if (apply && configExtraParams.applied)
|
|
104072
104110
|
resetConfigCache();
|
|
104073
104111
|
const pendingLift = apply ? undefined : configExtraParams.pending;
|
|
104112
|
+
const configRetiredExperimentalKeys = apply ? applyConfigRetiredExperimentalKeys(configPath) : { pending: findConfigRetiredExperimentalKeys(configPath) };
|
|
104113
|
+
if (apply && configRetiredExperimentalKeys.applied)
|
|
104114
|
+
resetConfigCache();
|
|
104074
104115
|
if (pendingLift && pendingLift.lifted.length > 0) {
|
|
104075
104116
|
return {
|
|
104076
104117
|
schemaVersion: 1,
|
|
104077
104118
|
status: "blocked",
|
|
104078
104119
|
blockers: pendingLift.lifted,
|
|
104079
104120
|
configExtraParams,
|
|
104121
|
+
configRetiredExperimentalKeys,
|
|
104080
104122
|
stateMigrations: { pending: listPendingStateMigrations() }
|
|
104081
104123
|
};
|
|
104082
104124
|
}
|
|
@@ -104091,6 +104133,7 @@ async function runMigration(options) {
|
|
|
104091
104133
|
blockers: pendingSchedulerBindings.changes.map((change) => `${change.kind === "bind" ? "bind" : "drop"} scheduler activation ${change.ref}` + (change.reason ? `: ${change.reason}` : "")),
|
|
104092
104134
|
configExtraParams,
|
|
104093
104135
|
configSchedulerSourceIds,
|
|
104136
|
+
configRetiredExperimentalKeys,
|
|
104094
104137
|
stateMigrations: { pending: listPendingStateMigrations() }
|
|
104095
104138
|
};
|
|
104096
104139
|
}
|
|
@@ -104110,12 +104153,14 @@ async function runMigration(options) {
|
|
|
104110
104153
|
}
|
|
104111
104154
|
const stateStatus = "pending" in stateMigrations && stateMigrations.pending.length > 0 ? "ready" : "current";
|
|
104112
104155
|
const schedulerStatus = "pending" in schedulerActivation && schedulerActivation.pending.length > 0 ? "ready" : "current";
|
|
104156
|
+
const retiredKeysStatus = "pending" in configRetiredExperimentalKeys && configRetiredExperimentalKeys.pending.removed.length > 0 ? "ready" : "current";
|
|
104113
104157
|
return {
|
|
104114
104158
|
schemaVersion: 1,
|
|
104115
|
-
status: worstStatus(worstStatus(worstStatus(taskV3.status, taskV4.status), stateStatus), schedulerStatus),
|
|
104159
|
+
status: worstStatus(worstStatus(worstStatus(worstStatus(taskV3.status, taskV4.status), stateStatus), schedulerStatus), retiredKeysStatus),
|
|
104116
104160
|
blockers: [...taskV3.blockers, ...taskV4.blockers],
|
|
104117
104161
|
configExtraParams,
|
|
104118
104162
|
configSchedulerSourceIds,
|
|
104163
|
+
configRetiredExperimentalKeys,
|
|
104119
104164
|
stateMigrations,
|
|
104120
104165
|
schedulerActivation,
|
|
104121
104166
|
taskV3Migration: taskV3.taskV3Migration,
|
|
@@ -76999,6 +76999,31 @@ function upgradeConfigVersion(raw, sourcePath) {
|
|
|
76999
76999
|
|
|
77000
77000
|
// src/core/config/config.ts
|
|
77001
77001
|
init_legacy_source_shape_shim();
|
|
77002
|
+
|
|
77003
|
+
// src/core/config/retired-experimental-keys-shim.ts
|
|
77004
|
+
init_common();
|
|
77005
|
+
init_warn();
|
|
77006
|
+
var RETIRED_EXPERIMENTAL_KEYS = ["workflowEngine"];
|
|
77007
|
+
function retiredExperimentalKeysIn(raw) {
|
|
77008
|
+
const experimental = raw.experimental;
|
|
77009
|
+
if (!isRecord(experimental))
|
|
77010
|
+
return [];
|
|
77011
|
+
return RETIRED_EXPERIMENTAL_KEYS.filter((key) => (key in experimental));
|
|
77012
|
+
}
|
|
77013
|
+
function stripRetiredExperimentalKeys(raw, sourcePath) {
|
|
77014
|
+
const present = retiredExperimentalKeysIn(raw);
|
|
77015
|
+
if (present.length === 0)
|
|
77016
|
+
return raw;
|
|
77017
|
+
const original = raw.experimental;
|
|
77018
|
+
const experimental = { ...original };
|
|
77019
|
+
for (const key of present)
|
|
77020
|
+
delete experimental[key];
|
|
77021
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
77022
|
+
warnOnce(`config:retired-experimental-key${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses the retired experimental key(s) ${present.join(", ")} \u2014 ignored in memory. Run \`akm migrate apply\` to remove ${present.length === 1 ? "it" : "them"} from the config file and silence this warning.`);
|
|
77023
|
+
return { ...raw, experimental };
|
|
77024
|
+
}
|
|
77025
|
+
|
|
77026
|
+
// src/core/config/config.ts
|
|
77002
77027
|
init_paths();
|
|
77003
77028
|
init_warn();
|
|
77004
77029
|
var DEFAULT_CONFIG = {
|
|
@@ -77058,7 +77083,8 @@ function loadUserConfig() {
|
|
|
77058
77083
|
function runConfigFilePipeline(text, sourcePath) {
|
|
77059
77084
|
const versioned = upgradeConfigVersion(parseConfigText(text, sourcePath), sourcePath);
|
|
77060
77085
|
const parsedRaw = migrateLegacySourceShape(versioned, sourcePath);
|
|
77061
|
-
|
|
77086
|
+
const liftedRaw = liftExtraParamsOrThrow(parsedRaw, sourcePath);
|
|
77087
|
+
return stripRetiredExperimentalKeys(liftedRaw, sourcePath);
|
|
77062
77088
|
}
|
|
77063
77089
|
function liftExtraParamsOrThrow(parsedRaw, sourcePath) {
|
|
77064
77090
|
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
@@ -85717,8 +85743,11 @@ function assertAkmAssetWrite(source, allowedAdapters = ["akm"]) {
|
|
|
85717
85743
|
return;
|
|
85718
85744
|
throw new UsageError(`Bundle "${source.name}" uses adapter "${source.adapterId}", which does not support AKM asset writes.`, "INVALID_FLAG_VALUE");
|
|
85719
85745
|
}
|
|
85746
|
+
function isWriteCapableSourceKind(kind) {
|
|
85747
|
+
return kind === "filesystem" || kind === "git";
|
|
85748
|
+
}
|
|
85720
85749
|
function adaptConfiguredSource(runtime) {
|
|
85721
|
-
if (runtime.type
|
|
85750
|
+
if (!isWriteCapableSourceKind(runtime.type)) {
|
|
85722
85751
|
throw new ConfigError(`write-source: source "${runtime.name}" has unsupported kind "${runtime.type}" for writes. ` + "Writes are only defined for `filesystem` and `git` sources.", "INVALID_CONFIG_FILE", 'Use `kind: "filesystem"` or `kind: "git"` for writable sources.');
|
|
85723
85752
|
}
|
|
85724
85753
|
const kind = runtime.type;
|
|
@@ -93182,32 +93211,7 @@ var GLOBAL_OUTPUT_ARGS = {
|
|
|
93182
93211
|
init_errors();
|
|
93183
93212
|
|
|
93184
93213
|
// scripts/akm-migrate/help.txt
|
|
93185
|
-
var help_default =
|
|
93186
|
-
|
|
93187
|
-
The one migration tool for an akm installation. Every historical shape akm
|
|
93188
|
-
has ever written lives here; the CLI proper reads only current schemas.
|
|
93189
|
-
\`status\` and \`apply\` run every step, in order, and print one combined JSON
|
|
93190
|
-
plan (exit 1 when any step is blocked):
|
|
93191
|
-
|
|
93192
|
-
1. legacy config \`extraParams\` keys lifted onto first-class engine fields
|
|
93193
|
-
2. scheduler grants bound to the configured source installation that was
|
|
93194
|
-
approved (stale grants whose bundle no longer exists are removed)
|
|
93195
|
-
3. pending state.db migrations, historical-destructive ones included,
|
|
93196
|
-
with a verified sibling safety copy (the only path that admits them)
|
|
93197
|
-
4. task-v2 files to task v3, then task-v3 files to task source v4
|
|
93198
|
-
5. superseded pre-0.9.0 \`.akm\` residue and stale filesystem transactions
|
|
93199
|
-
6. live \`.akm\` writers relocated to \`$STATE\`/\`$CACHE\`, for every local
|
|
93200
|
-
bundle (distill-rejected, eval-cases, measurement verdicts, and stale
|
|
93201
|
-
improve-pipeline locks \u2014 a lock a live run still holds is left alone)
|
|
93202
|
-
|
|
93203
|
-
\`akm migrate status|apply\` wraps this executable; \`akm upgrade\` runs
|
|
93204
|
-
\`apply\` after its install step, so an image that ships akm can put either
|
|
93205
|
-
in its entrypoint (a current installation is a no-op).
|
|
93206
|
-
|
|
93207
|
-
Commands:
|
|
93208
|
-
status Inspect every pending migration without changing anything.
|
|
93209
|
-
apply [--dry-run] Back up and apply every pending migration.
|
|
93210
|
-
`;
|
|
93214
|
+
var help_default = "Usage: akm-migrate <command> [options]\n\nThe one migration tool for an akm installation. Every historical shape akm\nhas ever written lives here; the CLI proper reads only current schemas.\n`status` and `apply` run every step, in order, and print one combined JSON\nplan (exit 1 when any step is blocked):\n\n 1. legacy config `extraParams` keys lifted onto first-class engine fields\n 2. retired `experimental.*` config keys removed (today `workflowEngine`)\n 3. scheduler grants bound to the configured source installation that was\n approved (stale grants whose bundle no longer exists are removed)\n 4. pending state.db migrations, historical-destructive ones included,\n with a verified sibling safety copy (the only path that admits them)\n 5. source-owned schedule enablement converted to host-local scheduler\n grants\n 6. task-v2 files to task v3, then task-v3 files to task source v4\n 7. superseded pre-0.9.0 `.akm` residue and stale filesystem transactions\n 8. live `.akm` writers relocated to `$STATE`/`$CACHE`, for every local\n bundle (distill-rejected, eval-cases, measurement verdicts, and stale\n improve-pipeline locks \u2014 a lock a live run still holds is left alone)\n\n`akm migrate status|apply` wraps this executable; `akm upgrade` runs\n`apply` after its install step, so an image that ships akm can put either\nin its entrypoint (a current installation is a no-op).\n\nCommands:\n status Inspect every pending migration without changing anything.\n apply [--dry-run] Back up and apply every pending migration.\n";
|
|
93211
93215
|
|
|
93212
93216
|
// scripts/akm-migrate/run-migrate.ts
|
|
93213
93217
|
init_common();
|
|
@@ -94743,10 +94747,44 @@ function applyConfigExtraParamsLift(configPath) {
|
|
|
94743
94747
|
return { applied: true, lifted, conflicts: [] };
|
|
94744
94748
|
}
|
|
94745
94749
|
|
|
94750
|
+
// scripts/akm-migrate/migrate/config-retired-experimental-keys.ts
|
|
94751
|
+
function readRawConfig2(configPath) {
|
|
94752
|
+
const text = readConfigText(configPath);
|
|
94753
|
+
if (text === undefined)
|
|
94754
|
+
return;
|
|
94755
|
+
return parseConfigText(text, configPath);
|
|
94756
|
+
}
|
|
94757
|
+
function findConfigRetiredExperimentalKeys(configPath) {
|
|
94758
|
+
const raw = readRawConfig2(configPath);
|
|
94759
|
+
if (!raw)
|
|
94760
|
+
return { removed: [] };
|
|
94761
|
+
return { removed: retiredExperimentalKeysIn(raw).map((key) => `experimental.${key}`) };
|
|
94762
|
+
}
|
|
94763
|
+
function applyConfigRetiredExperimentalKeys(configPath) {
|
|
94764
|
+
const raw = readRawConfig2(configPath);
|
|
94765
|
+
if (!raw)
|
|
94766
|
+
return { applied: false, removed: [] };
|
|
94767
|
+
const retired = retiredExperimentalKeysIn(raw);
|
|
94768
|
+
if (retired.length === 0)
|
|
94769
|
+
return { applied: false, removed: [] };
|
|
94770
|
+
const experimental = { ...raw.experimental };
|
|
94771
|
+
for (const key of retired)
|
|
94772
|
+
delete experimental[key];
|
|
94773
|
+
const config = { ...raw, experimental };
|
|
94774
|
+
const release = acquireConfigLock();
|
|
94775
|
+
try {
|
|
94776
|
+
backupExistingConfig(configPath);
|
|
94777
|
+
writeConfigAtomic(configPath, config);
|
|
94778
|
+
} finally {
|
|
94779
|
+
release();
|
|
94780
|
+
}
|
|
94781
|
+
return { applied: true, removed: retired.map((key) => `experimental.${key}`) };
|
|
94782
|
+
}
|
|
94783
|
+
|
|
94746
94784
|
// scripts/akm-migrate/migrate/config-scheduler-source-ids.ts
|
|
94747
94785
|
init_asset_ref();
|
|
94748
94786
|
init_bundle_id();
|
|
94749
|
-
function
|
|
94787
|
+
function readRawConfig3(configPath) {
|
|
94750
94788
|
const text = readConfigText(configPath);
|
|
94751
94789
|
return text === undefined ? undefined : parseConfigText(text, configPath);
|
|
94752
94790
|
}
|
|
@@ -94801,13 +94839,13 @@ function implicitBundleSourceId(raw, bundleId) {
|
|
|
94801
94839
|
return implicitId === bundleId ? filesystemBundleSourceId(root2) : undefined;
|
|
94802
94840
|
}
|
|
94803
94841
|
function findConfigSchedulerSourceIdMigration(configPath) {
|
|
94804
|
-
const raw =
|
|
94842
|
+
const raw = readRawConfig3(configPath);
|
|
94805
94843
|
return Object.freeze({ changes: Object.freeze(raw ? migrationChanges(raw) : []) });
|
|
94806
94844
|
}
|
|
94807
94845
|
function applyConfigSchedulerSourceIdMigration(configPath) {
|
|
94808
94846
|
const release = acquireConfigLock();
|
|
94809
94847
|
try {
|
|
94810
|
-
const raw =
|
|
94848
|
+
const raw = readRawConfig3(configPath);
|
|
94811
94849
|
if (!raw)
|
|
94812
94850
|
return Object.freeze({ applied: false, changes: Object.freeze([]) });
|
|
94813
94851
|
const changes = migrationChanges(raw);
|
|
@@ -104027,12 +104065,16 @@ async function runMigration(options) {
|
|
|
104027
104065
|
if (apply && configExtraParams.applied)
|
|
104028
104066
|
resetConfigCache();
|
|
104029
104067
|
const pendingLift = apply ? undefined : configExtraParams.pending;
|
|
104068
|
+
const configRetiredExperimentalKeys = apply ? applyConfigRetiredExperimentalKeys(configPath) : { pending: findConfigRetiredExperimentalKeys(configPath) };
|
|
104069
|
+
if (apply && configRetiredExperimentalKeys.applied)
|
|
104070
|
+
resetConfigCache();
|
|
104030
104071
|
if (pendingLift && pendingLift.lifted.length > 0) {
|
|
104031
104072
|
return {
|
|
104032
104073
|
schemaVersion: 1,
|
|
104033
104074
|
status: "blocked",
|
|
104034
104075
|
blockers: pendingLift.lifted,
|
|
104035
104076
|
configExtraParams,
|
|
104077
|
+
configRetiredExperimentalKeys,
|
|
104036
104078
|
stateMigrations: { pending: listPendingStateMigrations() }
|
|
104037
104079
|
};
|
|
104038
104080
|
}
|
|
@@ -104047,6 +104089,7 @@ async function runMigration(options) {
|
|
|
104047
104089
|
blockers: pendingSchedulerBindings.changes.map((change) => `${change.kind === "bind" ? "bind" : "drop"} scheduler activation ${change.ref}` + (change.reason ? `: ${change.reason}` : "")),
|
|
104048
104090
|
configExtraParams,
|
|
104049
104091
|
configSchedulerSourceIds,
|
|
104092
|
+
configRetiredExperimentalKeys,
|
|
104050
104093
|
stateMigrations: { pending: listPendingStateMigrations() }
|
|
104051
104094
|
};
|
|
104052
104095
|
}
|
|
@@ -104066,12 +104109,14 @@ async function runMigration(options) {
|
|
|
104066
104109
|
}
|
|
104067
104110
|
const stateStatus = "pending" in stateMigrations && stateMigrations.pending.length > 0 ? "ready" : "current";
|
|
104068
104111
|
const schedulerStatus = "pending" in schedulerActivation && schedulerActivation.pending.length > 0 ? "ready" : "current";
|
|
104112
|
+
const retiredKeysStatus = "pending" in configRetiredExperimentalKeys && configRetiredExperimentalKeys.pending.removed.length > 0 ? "ready" : "current";
|
|
104069
104113
|
return {
|
|
104070
104114
|
schemaVersion: 1,
|
|
104071
|
-
status: worstStatus(worstStatus(worstStatus(taskV3.status, taskV4.status), stateStatus), schedulerStatus),
|
|
104115
|
+
status: worstStatus(worstStatus(worstStatus(worstStatus(taskV3.status, taskV4.status), stateStatus), schedulerStatus), retiredKeysStatus),
|
|
104072
104116
|
blockers: [...taskV3.blockers, ...taskV4.blockers],
|
|
104073
104117
|
configExtraParams,
|
|
104074
104118
|
configSchedulerSourceIds,
|
|
104119
|
+
configRetiredExperimentalKeys,
|
|
104075
104120
|
stateMigrations,
|
|
104076
104121
|
schedulerActivation,
|
|
104077
104122
|
taskV3Migration: taskV3.taskV3Migration,
|
|
@@ -299,6 +299,15 @@ async function compileDesiredSourceSet(input, collector) {
|
|
|
299
299
|
async function compileTaskSources(input, collector, out, failures) {
|
|
300
300
|
if (input.adapterId !== "akm" && input.adapterId !== "akm-task")
|
|
301
301
|
return;
|
|
302
|
+
for (const symlink of collector.symlinkSources()) {
|
|
303
|
+
if (!isAuthoredTaskRelativePath(input.adapterId, symlink.relativePath))
|
|
304
|
+
continue;
|
|
305
|
+
const qualifiedRef = makeBundleRef(input.bundleName, symlink.relativePath.slice(0, -4));
|
|
306
|
+
if (input.enabledActivations && !input.enabledActivations.has(schedulerActivationKey("task", qualifiedRef))) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
failures.push(taskFailure(symlink.sourcePath, qualifiedRef, symbolicSourceError(symlink.sourcePath)));
|
|
310
|
+
}
|
|
302
311
|
const physicalOwners = new Map();
|
|
303
312
|
for (const guarded of collector.authoredTaskSources(input.adapterId)) {
|
|
304
313
|
const sourcePath = guarded.sourcePath;
|
|
@@ -531,6 +540,26 @@ function enumerateWorkflowLookups(input, collector, failures) {
|
|
|
531
540
|
owners.push(guarded);
|
|
532
541
|
lookups.set(canonicalName, owners);
|
|
533
542
|
}
|
|
543
|
+
for (const symlink of collector.symlinkSources()) {
|
|
544
|
+
if (!isAuthoredWorkflowRelativePath(input.adapterId, symlink.relativePath))
|
|
545
|
+
continue;
|
|
546
|
+
if (path.basename(symlink.sourcePath).toLowerCase() === "readme.md")
|
|
547
|
+
continue;
|
|
548
|
+
const authoredName = workflowNameForSourcePath(input.sourceRoot, input.adapterId, symlink.sourcePath);
|
|
549
|
+
if (authoredName === undefined)
|
|
550
|
+
continue;
|
|
551
|
+
const canonicalName = canonicalizeWorkflowName(authoredName);
|
|
552
|
+
// A real sibling sharing this name must not compile either: runtime
|
|
553
|
+
// resolution follows the symlink and may pick it over the file the
|
|
554
|
+
// binding was compiled from. The symbolic failure below is the ref's
|
|
555
|
+
// only report.
|
|
556
|
+
lookups.delete(canonicalName);
|
|
557
|
+
const failureRef = makeBundleRef(input.bundleName, input.adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName);
|
|
558
|
+
if (input.enabledActivations && !input.enabledActivations.has(schedulerActivationKey("workflow", failureRef))) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
failures.push(workflowFailure(symlink.sourcePath, failureRef, symbolicSourceError(symlink.sourcePath)));
|
|
562
|
+
}
|
|
534
563
|
return new Map([...lookups]
|
|
535
564
|
.sort(([left], [right]) => compareCodePoints(left, right))
|
|
536
565
|
.map(([name, sources]) => [name, Object.freeze(sources.sort(compareGuardedSources))]));
|
|
@@ -596,6 +625,14 @@ function assertUniqueInstalledIds(installed) {
|
|
|
596
625
|
seen.add(binding.id);
|
|
597
626
|
}
|
|
598
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* The reason recorded for a symlinked task/workflow source: it stays a
|
|
630
|
+
* per-source failure (like the read boundary it replaces), never a silent
|
|
631
|
+
* drop and never a follow.
|
|
632
|
+
*/
|
|
633
|
+
function symbolicSourceError(sourcePath) {
|
|
634
|
+
return new UsageError(`${sourcePath} is a symbolic source; guarded reads require a regular no-follow owner.`, "RESOURCE_ALREADY_EXISTS");
|
|
635
|
+
}
|
|
599
636
|
function taskFailure(file, ref, cause) {
|
|
600
637
|
const detail = taskSourceErrorDetail(cause);
|
|
601
638
|
const reason = detail === errorMessage(cause) ? `${file}: ${detail}` : detail;
|
|
@@ -629,6 +666,29 @@ export function assertSchedulerSourceSnapshot(snapshot) {
|
|
|
629
666
|
throw new UsageError(`Scheduler desired source read set changed after projection; refusing native mutation: ${errorMessage(cause)}`, "RESOURCE_ALREADY_EXISTS");
|
|
630
667
|
}
|
|
631
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* Shared with the symlink classification in {@link compileTaskSources}: a
|
|
671
|
+
* `.yml` under `tasks/` (or, for `akm-task`, anywhere) is a task candidate —
|
|
672
|
+
* one classifier for both a captured file and an uncaptured symlink entry.
|
|
673
|
+
*/
|
|
674
|
+
function isAuthoredTaskRelativePath(adapterId, relativePath) {
|
|
675
|
+
if (!relativePath.endsWith(".yml"))
|
|
676
|
+
return false;
|
|
677
|
+
if (adapterId === "akm-task")
|
|
678
|
+
return true;
|
|
679
|
+
return path.posix.dirname(relativePath) === "tasks";
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Shared with the symlink classification in {@link enumerateWorkflowLookups}:
|
|
683
|
+
* anything under `workflows/` (or, for `akm-workflow`, anywhere) is a
|
|
684
|
+
* workflow candidate — one classifier for both a captured file and an
|
|
685
|
+
* uncaptured symlink entry.
|
|
686
|
+
*/
|
|
687
|
+
function isAuthoredWorkflowRelativePath(adapterId, relativePath) {
|
|
688
|
+
if (adapterId === "akm-workflow")
|
|
689
|
+
return true;
|
|
690
|
+
return relativePath.startsWith("workflows/");
|
|
691
|
+
}
|
|
632
692
|
class SchedulerSourceCollector {
|
|
633
693
|
#adapterId;
|
|
634
694
|
#sourceRoot;
|
|
@@ -637,6 +697,14 @@ class SchedulerSourceCollector {
|
|
|
637
697
|
this.#adapterId = input.adapterId;
|
|
638
698
|
this.#sourceRoot = path.resolve(input.sourceRoot);
|
|
639
699
|
const root = this.#collector.trackDirectory(this.#sourceRoot, this.#sourceRoot);
|
|
700
|
+
if (input.adapterId === "akm") {
|
|
701
|
+
for (const scheduledName of ["tasks", "workflows"]) {
|
|
702
|
+
const entry = root.entries.find((candidate) => candidate.name === scheduledName);
|
|
703
|
+
if (entry?.kind === "symlink") {
|
|
704
|
+
throw new UsageError(`${path.join(this.#sourceRoot, scheduledName)} is a symbolic source with a physical source identity collision; guarded reads require one no-follow owner.`, "RESOURCE_ALREADY_EXISTS");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
640
708
|
const rootDirectories = new Set(root.entries.filter((entry) => entry.kind === "directory").map((entry) => entry.name));
|
|
641
709
|
const candidates = [];
|
|
642
710
|
if (input.adapterId === "akm") {
|
|
@@ -669,27 +737,33 @@ class SchedulerSourceCollector {
|
|
|
669
737
|
authoredTaskSources(adapterId) {
|
|
670
738
|
return this.#collector
|
|
671
739
|
.snapshot()
|
|
672
|
-
.sources.filter((file) =>
|
|
673
|
-
if (!file.authored || !file.relativePath.endsWith(".yml"))
|
|
674
|
-
return false;
|
|
675
|
-
if (adapterId === "akm-task")
|
|
676
|
-
return true;
|
|
677
|
-
return path.posix.dirname(file.relativePath) === "tasks";
|
|
678
|
-
})
|
|
740
|
+
.sources.filter((file) => file.authored && isAuthoredTaskRelativePath(adapterId, file.relativePath))
|
|
679
741
|
.sort(compareGuardedSources);
|
|
680
742
|
}
|
|
681
743
|
authoredWorkflowSources(adapterId) {
|
|
682
744
|
return this.#collector
|
|
683
745
|
.snapshot()
|
|
684
|
-
.sources.filter((file) =>
|
|
685
|
-
if (!file.authored)
|
|
686
|
-
return false;
|
|
687
|
-
if (adapterId === "akm-workflow")
|
|
688
|
-
return true;
|
|
689
|
-
return file.relativePath.startsWith("workflows/");
|
|
690
|
-
})
|
|
746
|
+
.sources.filter((file) => file.authored && isAuthoredWorkflowRelativePath(adapterId, file.relativePath))
|
|
691
747
|
.sort(compareGuardedSources);
|
|
692
748
|
}
|
|
749
|
+
/**
|
|
750
|
+
* Every `kind: "symlink"` entry across the directory manifests captured so
|
|
751
|
+
* far (never read, never followed — see `captureGuardedDirectoryManifest`),
|
|
752
|
+
* with its path relative to the source root so callers can classify it
|
|
753
|
+
* with the same predicates as a real, captured file.
|
|
754
|
+
*/
|
|
755
|
+
symlinkSources() {
|
|
756
|
+
const entries = [];
|
|
757
|
+
for (const manifest of this.#collector.snapshot().directoryManifests) {
|
|
758
|
+
for (const entry of manifest.entries) {
|
|
759
|
+
if (entry.kind !== "symlink")
|
|
760
|
+
continue;
|
|
761
|
+
const sourcePath = path.join(manifest.directoryPath, entry.name);
|
|
762
|
+
entries.push({ sourcePath, relativePath: toPosix(path.relative(this.#sourceRoot, sourcePath)) });
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return entries;
|
|
766
|
+
}
|
|
693
767
|
readBytes(file, containmentRoot) {
|
|
694
768
|
this.#trackAncestors(file, containmentRoot);
|
|
695
769
|
return this.#collector.readBytes(file, containmentRoot);
|
package/docs/reference/cli.md
CHANGED
|
@@ -1673,16 +1673,25 @@ in order:
|
|
|
1673
1673
|
|
|
1674
1674
|
1. legacy config `extraParams` keys lifted onto first-class engine fields
|
|
1675
1675
|
(`configExtraParams`);
|
|
1676
|
-
2.
|
|
1676
|
+
2. retired `experimental.*` config keys removed, today `workflowEngine`
|
|
1677
|
+
(`configRetiredExperimentalKeys`) — config loading already ignores them
|
|
1678
|
+
with a one-time warning, so this only cleans the file;
|
|
1679
|
+
3. scheduler grants bound to the configured source installation that was
|
|
1680
|
+
approved, with stale grants for removed bundles dropped
|
|
1681
|
+
(`configSchedulerSourceIds`);
|
|
1682
|
+
4. pending `state.db` migrations, historical-destructive ones included, with
|
|
1677
1683
|
a verified sibling safety copy (`stateMigrations`) — the only path besides
|
|
1678
1684
|
`akm upgrade` that admits released migration 018, which an ordinary
|
|
1679
1685
|
command refuses;
|
|
1680
|
-
|
|
1686
|
+
5. source-owned schedule enablement converted to host-local scheduler grants
|
|
1687
|
+
(`schedulerActivation`);
|
|
1688
|
+
6. task-v2 files to task v3, then task-v3 files to task source v4
|
|
1681
1689
|
(`taskV3Migration`, `taskV4Migration`), each keeping its own lock, backup,
|
|
1682
1690
|
prevalidation, and rollback, so a file blocked in the first generation does
|
|
1683
1691
|
not stop the second from converting files already at `version: 3`;
|
|
1684
|
-
|
|
1685
|
-
(`deadResidue`, `staleTxns`)
|
|
1692
|
+
7. superseded pre-0.9.0 `.akm` residue and stale filesystem transactions
|
|
1693
|
+
(`deadResidue`, `staleTxns`), then live `.akm` writers relocated to
|
|
1694
|
+
`$STATE`/`$CACHE` (`writerRelocation`).
|
|
1686
1695
|
|
|
1687
1696
|
```sh
|
|
1688
1697
|
akm migrate status
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.17-alpha.
|
|
3
|
+
"version": "0.9.17-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|