akm-cli 0.10.261012-beta → 0.10.261013-beta
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 +216 -0
- package/SECURITY.md +2 -2
- package/STABILITY.md +2 -2
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/hints/cli-hints-short.md +3 -4
- package/dist/commands/command/execution-source-loader.js +10 -14
- package/dist/commands/env/env-binding.js +47 -58
- package/dist/commands/env/env-cli.js +20 -22
- package/dist/commands/env/env.js +10 -58
- package/dist/commands/env/secret-cli.js +9 -27
- package/dist/commands/env/secret.js +3 -10
- package/dist/commands/health/scheduler-binary.js +1 -1
- package/dist/commands/improve/distill.js +0 -2
- package/dist/commands/improve/eligibility.js +0 -2
- package/dist/commands/lint/env-key-rules.js +7 -6
- package/dist/commands/lint/index.js +1 -1
- package/dist/commands/proposal/validators/proposal-validators.js +1 -4
- package/dist/commands/read/show.js +4 -25
- package/dist/commands/sources/add-cli.js +8 -63
- package/dist/commands/sources/bundle-cli.js +8 -9
- package/dist/commands/sources/installed-stashes.js +117 -61
- package/dist/commands/sources/schema-repair.js +0 -2
- package/dist/commands/sources/source-clone.js +31 -7
- package/dist/commands/tasks/explain.js +1 -1
- package/dist/commands/tasks/tasks.js +38 -56
- package/dist/commands/ui/redact.js +4 -4
- package/dist/commands/ui/workspace-actions.js +1 -1
- package/dist/commands/workflow/plan.js +47 -8
- package/dist/commands/workflow-cli.js +37 -41
- package/dist/core/adapter/adapters/akm-adapter.js +3 -3
- package/dist/core/adapter/adapters/akm-lint.js +3 -4
- package/dist/core/adapter/adapters/akm-metadata.js +2 -2
- package/dist/core/adapter/adapters/akm-task-adapter.js +5 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +4 -3
- package/dist/core/asset/resolve-ref.js +5 -0
- package/dist/core/bundle-rename.js +12 -4
- package/dist/core/common.js +4 -52
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +55 -18
- package/dist/core/config/schema/primitives.js +12 -6
- package/dist/core/config/schema/sources-bundles.js +2 -2
- package/dist/core/env-secret-ref.js +84 -73
- package/dist/core/errors.js +1 -1
- package/dist/core/paths.js +1 -6
- package/dist/core/redaction.js +16 -3
- package/dist/core/ref-token.js +82 -0
- package/dist/core/secret-value.js +14 -0
- package/dist/core/write-source.js +20 -4
- package/dist/indexer/indexer.js +27 -36
- package/dist/indexer/installations.js +1 -8
- package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
- package/dist/indexer/materialize-embeddings.js +1 -1
- package/dist/indexer/scan/drain-dir.js +3 -3
- package/dist/indexer/search/search-source.js +4 -2
- package/dist/indexer/walk/path-resolver.js +13 -68
- package/dist/indexer/walk/walker.js +18 -22
- package/dist/integrations/agent/engine-resolution.js +9 -7
- package/dist/integrations/agent/runner-dispatch.js +4 -5
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/llm/client.js +1 -1
- package/dist/llm/embedders/remote.js +4 -3
- package/dist/output/renderers.js +3 -4
- package/dist/output/shapes/helpers.js +0 -2
- package/dist/output/text/command-format.js +12 -2
- package/dist/output/text/show-directives.js +16 -28
- package/dist/output/text/workflow-format.js +37 -7
- package/dist/scripts/akm-migrate-node.js +45865 -44692
- package/dist/scripts/akm-migrate.js +45804 -44630
- package/dist/setup/steps/tasks.js +1 -2
- package/dist/sources/providers/provider-utils.js +12 -0
- package/dist/sources/resolve.js +12 -16
- package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
- package/dist/sources/snapshot-fetchers/x.js +1 -1
- package/dist/storage/repositories/proposals-repository.js +2 -2
- package/dist/storage/repositories/task-history-repository.js +2 -2
- package/dist/storage/repositories/workflow-runs-repository.js +38 -116
- package/dist/tasks/embedded.js +1 -1
- package/dist/tasks/prepare/prepare-support.js +3 -2
- package/dist/tasks/prepare/prepare.js +3 -4
- package/dist/tasks/prepare/script-capture.js +8 -22
- package/dist/tasks/run/load-task.js +46 -14
- package/dist/tasks/run/run-native-task.js +0 -8
- package/dist/tasks/run/run-workflow-task.js +2 -2
- package/dist/tasks/scheduler-sync.js +0 -1
- package/dist/tasks/source/bounded-document.js +0 -41
- package/dist/tasks/source/parse-task-source.js +0 -1
- package/dist/tasks/source/task-source-v4.js +1 -4
- package/dist/workflows/compile.js +1 -15
- package/dist/workflows/exec/child-workflow.js +1 -2
- package/dist/workflows/exec/dispatch-redaction.js +24 -6
- package/dist/workflows/exec/environment.js +11 -74
- package/dist/workflows/exec/exec-unit.js +31 -23
- package/dist/workflows/exec/frozen-judge.js +17 -3
- package/dist/workflows/exec/native-executor.js +70 -26
- package/dist/workflows/exec/run-workflow.js +31 -18
- package/dist/workflows/exec/step-work.js +4 -3
- package/dist/workflows/exec/unit-dispatch.js +1 -8
- package/dist/workflows/freeze/environment.js +14 -119
- package/dist/workflows/freeze/freeze.js +0 -1
- package/dist/workflows/freeze/step-values.js +1 -2
- package/dist/workflows/freeze/targets/child-workflow.js +3 -5
- package/dist/workflows/freeze/targets/command.js +6 -9
- package/dist/workflows/freeze/targets/shell.js +0 -3
- package/dist/workflows/parser.js +6 -25
- package/dist/workflows/plan.js +6 -2
- package/dist/workflows/renderer.js +77 -4
- package/dist/workflows/runtime/run-plan.js +37 -23
- package/dist/workflows/runtime/runs.js +41 -84
- package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
- package/dist/workflows/source-files.js +7 -19
- package/docs/architecture/0.10-process-rules.md +6 -6
- package/docs/architecture/architecture.md +6 -6
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +12 -18
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +6 -8
- package/docs/architecture/internals/storage-locations.md +4 -4
- package/docs/architecture/persisted-data-compat.md +3 -2
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +1 -1
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +3 -3
- package/docs/architecture/testing/manual-testing-checklist.md +46 -78
- package/docs/architecture/testing/task-workflow-manual-tests.md +22 -40
- package/docs/architecture/testing/testing-workflow.md +9 -11
- package/docs/architecture/workflow-engine.md +41 -27
- package/docs/guides/author-bundles.md +14 -17
- package/docs/guides/author-workflows.md +43 -9
- package/docs/guides/bundles.md +16 -11
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -1
- package/docs/guides/run-workflows.md +25 -13
- package/docs/guides/scheduling.md +1 -1
- package/docs/maintainers/README.md +2 -3
- package/docs/maintainers/release-checklist.md +73 -126
- package/docs/migration/v0.9-to-v0.10.md +5 -1
- package/docs/reference/cli.md +124 -112
- package/docs/reference/configuration.md +16 -9
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +66 -15
- package/docs/reference/registry.md +6 -11
- package/docs/reference/tasks.md +11 -3
- package/docs/reference/ui-api.md +3 -3
- package/docs/reference/workflow-schema.md +109 -36
- package/package.json +1 -1
- package/schemas/akm-task.json +2 -4
- package/dist/commands/sources/dangerous-env-audit.js +0 -134
- package/dist/core/activation-policy.js +0 -67
- package/dist/execution/directory-identity.js +0 -78
- package/dist/workflows/authoring/scope-key.js +0 -79
- package/dist/workflows/source-semantics.js +0 -110
|
@@ -89,7 +89,6 @@ export function listSetupTaskDefinitions() {
|
|
|
89
89
|
const parsed = parseTaskSource({
|
|
90
90
|
yaml: fs.readFileSync(filePath, "utf8"),
|
|
91
91
|
filePath,
|
|
92
|
-
workspaceRoot: target.source.path,
|
|
93
92
|
});
|
|
94
93
|
const document = parsed.v4;
|
|
95
94
|
if (document.schedule.length === 0)
|
|
@@ -129,7 +128,7 @@ export async function prepareSetupTaskDefinitions(tasks, deps = {}) {
|
|
|
129
128
|
document.schedule = plan.schedule;
|
|
130
129
|
yaml = yamlStringify(document);
|
|
131
130
|
}
|
|
132
|
-
const parsed = parseTaskSource({ yaml, filePath
|
|
131
|
+
const parsed = parseTaskSource({ yaml, filePath });
|
|
133
132
|
for (const schedule of parsed.v4.schedule) {
|
|
134
133
|
parseSchedule(schedule.cron, backendNameForPlatform());
|
|
135
134
|
}
|
|
@@ -82,6 +82,18 @@ export function buildInstallCacheDir(cacheRootDir, source, id, version) {
|
|
|
82
82
|
const versionSlug = source === "local" ? uniqueSlug() : (version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? uniqueSlug());
|
|
83
83
|
return path.join(cacheRootDir, slug || source, versionSlug);
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* The `<rev>` directory of a registry install that holds `contentDir`: the two
|
|
87
|
+
* path segments under `cacheRootDir` that {@link buildInstallCacheDir} lays out
|
|
88
|
+
* (`<source>-<id>/<version>`). Undefined for a directory outside that layout.
|
|
89
|
+
*/
|
|
90
|
+
export function installRevisionDir(cacheRootDir, contentDir) {
|
|
91
|
+
const relative = path.relative(path.resolve(cacheRootDir), path.resolve(contentDir));
|
|
92
|
+
if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative))
|
|
93
|
+
return undefined;
|
|
94
|
+
const [slug, rev] = relative.split(path.sep);
|
|
95
|
+
return slug && rev ? path.join(path.resolve(cacheRootDir), slug, rev) : undefined;
|
|
96
|
+
}
|
|
85
97
|
/**
|
|
86
98
|
* Apply an `akm.include` config (a `package.json` `akm.include` array — NOT
|
|
87
99
|
* a `.akm-include` dotfile) if any, by copying the selected paths into a
|
package/dist/sources/resolve.js
CHANGED
|
@@ -6,11 +6,14 @@ import path from "node:path";
|
|
|
6
6
|
import { recognizeMatch } from "../core/adapter/recognize-match.js";
|
|
7
7
|
import { assetPathForName, deriveCanonicalAssetNameFromStashRoot, isRelevantAssetFile, stashDirFor, } from "../core/asset/asset-placement.js";
|
|
8
8
|
import { conceptIdFromTypeName } from "../core/asset/resolve-ref.js";
|
|
9
|
-
import { hasErrnoCode,
|
|
9
|
+
import { hasErrnoCode, isContainedResolvedPath } from "../core/common.js";
|
|
10
10
|
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
11
11
|
import { walkStashFlat } from "../indexer/walk/walker.js";
|
|
12
12
|
/**
|
|
13
|
-
* Resolve an asset path from a stash directory, type, and name.
|
|
13
|
+
* Resolve an asset path from a stash directory, type, and name. The path keeps
|
|
14
|
+
* the authored spelling, not the symlink target's: an asset that links to a
|
|
15
|
+
* file with another name (or none) is still the kind its own name says, and a
|
|
16
|
+
* read through it reaches the same bytes.
|
|
14
17
|
*/
|
|
15
18
|
export async function resolveAssetPath(stashDir, type, name) {
|
|
16
19
|
try {
|
|
@@ -31,25 +34,24 @@ export async function resolveAssetPath(stashDir, type, name) {
|
|
|
31
34
|
function resolveInTypeDir(stashDir, typeDir, type, name) {
|
|
32
35
|
const root = path.join(stashDir, typeDir);
|
|
33
36
|
const target = assetPathForName(type, root, name);
|
|
34
|
-
|
|
37
|
+
resolveAndValidateTypeRoot(root, type, name);
|
|
35
38
|
const resolvedTarget = path.resolve(target);
|
|
36
|
-
|
|
39
|
+
// Name grammar: the name must not climb out of the type directory (`..` or
|
|
40
|
+
// an absolute name). Lexical only: an asset that is a symlink to a file
|
|
41
|
+
// elsewhere resolves, because installing a bundle is the trust decision.
|
|
42
|
+
if (!isContainedResolvedPath(resolvedTarget, path.resolve(root))) {
|
|
37
43
|
throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
|
|
38
44
|
}
|
|
39
45
|
if (!fs.existsSync(resolvedTarget) || !fs.statSync(resolvedTarget).isFile()) {
|
|
40
46
|
throw new NotFoundError(`Stash asset not found for ref: ${conceptIdFromTypeName(type, name)}`, "ASSET_NOT_FOUND");
|
|
41
47
|
}
|
|
42
|
-
const realTarget = fs.realpathSync(resolvedTarget);
|
|
43
|
-
if (!isWithin(realTarget, resolvedRoot)) {
|
|
44
|
-
throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
|
|
45
|
-
}
|
|
46
48
|
if (!isRelevantAssetFile(type, path.basename(resolvedTarget))) {
|
|
47
49
|
if (type === "script") {
|
|
48
50
|
throw new NotFoundError("Script ref must resolve to a file with a supported script extension. Refer to the akm documentation for the complete list of supported script extensions.");
|
|
49
51
|
}
|
|
50
52
|
throw new NotFoundError(`Stash asset not found for ref: ${conceptIdFromTypeName(type, name)}`, "ASSET_NOT_FOUND");
|
|
51
53
|
}
|
|
52
|
-
return
|
|
54
|
+
return resolvedTarget;
|
|
53
55
|
}
|
|
54
56
|
function resolveAndValidateTypeRoot(root, type, name) {
|
|
55
57
|
const rootStat = readTypeRootStat(root, type, name);
|
|
@@ -57,7 +59,6 @@ function resolveAndValidateTypeRoot(root, type, name) {
|
|
|
57
59
|
throw new NotFoundError(`Asset directory for ${type} assets is not accessible — got a file where a directory was expected for ref: ${conceptIdFromTypeName(type, name)}. ` +
|
|
58
60
|
"Run `akm index` to rebuild the index, or check your source configuration.", "ASSET_NOT_FOUND", "Run `akm bundle list` to see your configured sources and verify the source path exists.");
|
|
59
61
|
}
|
|
60
|
-
return fs.realpathSync(root);
|
|
61
62
|
}
|
|
62
63
|
function readTypeRootStat(root, type, name) {
|
|
63
64
|
try {
|
|
@@ -79,12 +80,7 @@ async function resolveByCanonicalName(stashDir, type, name) {
|
|
|
79
80
|
const canonicalName = deriveCanonicalAssetNameFromStashRoot(type, stashDir, ctx.absPath);
|
|
80
81
|
if (canonicalName !== normalizedName)
|
|
81
82
|
continue;
|
|
82
|
-
|
|
83
|
-
const resolvedRoot = fs.realpathSync(stashDir);
|
|
84
|
-
if (!isWithin(realTarget, resolvedRoot)) {
|
|
85
|
-
throw new UsageError("Ref resolves outside the stash root.", "PATH_ESCAPE_VIOLATION");
|
|
86
|
-
}
|
|
87
|
-
return realTarget;
|
|
83
|
+
return ctx.absPath;
|
|
88
84
|
}
|
|
89
85
|
return undefined;
|
|
90
86
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import fs from "node:fs";
|
|
5
4
|
import { resolveSecretPath } from "../../core/env-secret-ref.js";
|
|
5
|
+
import { readValue } from "../../core/secret-value.js";
|
|
6
6
|
/**
|
|
7
7
|
* Reads a secret out of akm's secret store for the fetcher `resolveSecret`
|
|
8
8
|
* seam (see `FetcherContext.resolveSecret`).
|
|
@@ -21,9 +21,7 @@ import { resolveSecretPath } from "../../core/env-secret-ref.js";
|
|
|
21
21
|
export function resolveSecretFromStore(ref) {
|
|
22
22
|
try {
|
|
23
23
|
const { absPath } = resolveSecretPath(ref);
|
|
24
|
-
|
|
25
|
-
return null;
|
|
26
|
-
return fs.readFileSync(absPath, "utf8").trim() || null;
|
|
24
|
+
return readValue(absPath) || null;
|
|
27
25
|
}
|
|
28
26
|
catch {
|
|
29
27
|
return null;
|
|
@@ -89,7 +89,7 @@ export function resolveXBearerToken(context) {
|
|
|
89
89
|
if (fromEnv)
|
|
90
90
|
return fromEnv;
|
|
91
91
|
try {
|
|
92
|
-
return context?.resolveSecret?.(X_BEARER_TOKEN_SECRET_REF)
|
|
92
|
+
return context?.resolveSecret?.(X_BEARER_TOKEN_SECRET_REF) || null;
|
|
93
93
|
}
|
|
94
94
|
catch {
|
|
95
95
|
return null;
|
|
@@ -525,8 +525,8 @@ export function renameProposalsBundleRef(db, oldBundleId, newBundleId) {
|
|
|
525
525
|
const prefix = `${escapeLikePattern(oldBundleId)}//`;
|
|
526
526
|
return db.transaction(() => {
|
|
527
527
|
const refs = Number(db
|
|
528
|
-
.prepare(`UPDATE proposals SET ref = ? || substr(ref, ?) WHERE ref LIKE ? ESCAPE '\\'`)
|
|
529
|
-
.run(newBundleId, oldBundleId
|
|
528
|
+
.prepare(`UPDATE proposals SET ref = ? || substr(ref, length(?) + 1) WHERE ref LIKE ? ESCAPE '\\'`)
|
|
529
|
+
.run(newBundleId, oldBundleId, `${prefix}%`).changes);
|
|
530
530
|
const targets = Number(db
|
|
531
531
|
.prepare(`UPDATE proposals SET metadata_json = json_set(metadata_json, '$.proposedTarget.source', ?)
|
|
532
532
|
WHERE json_extract(metadata_json, '$.proposedTarget.source') = ?`)
|
|
@@ -221,6 +221,6 @@ export function countTaskHistoryTargetRefs(db, oldBundleId) {
|
|
|
221
221
|
export function renameTaskHistoryTargetRefs(db, oldBundleId, newBundleId) {
|
|
222
222
|
const prefix = `${escapeLikePattern(oldBundleId)}//`;
|
|
223
223
|
return Number(db
|
|
224
|
-
.prepare(`UPDATE task_history SET target_ref = ? || substr(target_ref, ?) WHERE target_ref LIKE ? ESCAPE '\\'`)
|
|
225
|
-
.run(newBundleId, oldBundleId
|
|
224
|
+
.prepare(`UPDATE task_history SET target_ref = ? || substr(target_ref, length(?) + 1) WHERE target_ref LIKE ? ESCAPE '\\'`)
|
|
225
|
+
.run(newBundleId, oldBundleId, `${prefix}%`).changes);
|
|
226
226
|
}
|
|
@@ -42,75 +42,10 @@ export class WorkflowRunsRepository {
|
|
|
42
42
|
return withImmediateTransaction(this.db, () => fn(this.db));
|
|
43
43
|
}
|
|
44
44
|
// ── reads (fully materialised) ─────────────────────────────────────────────
|
|
45
|
-
/**
|
|
46
|
-
* The top-level start guard `publishWorkflowRunV4` uses to refuse starting
|
|
47
|
-
* a SECOND active run of the same ref in the same scope. `AND
|
|
48
|
-
* parent_run_id IS NULL` (B-N10's FOURTH site, code-review round 4 finding
|
|
49
|
-
* 2 / Review log R2): a child run carries the PARENT's `scope_key` (P3a
|
|
50
|
-
* §5.2), so once a parent publishes a child of some ref, starting a
|
|
51
|
-
* TOP-LEVEL run of that same ref in that scope must never resolve to the
|
|
52
|
-
* child — the child is not "already an active run in this scope" from a
|
|
53
|
-
* fresh top-level invocation's point of view; the PARENT, if anything, is.
|
|
54
|
-
* Before this filter, `publishWorkflowRunV4` refused with
|
|
55
|
-
* `RESOURCE_ALREADY_EXISTS` naming the CHILD's own run id, instructing the
|
|
56
|
-
* operator to `akm workflow abandon` a child a parent is actively driving.
|
|
57
|
-
* For any database with no child rows the result is byte-identical, same
|
|
58
|
-
* as the other three B-N10 sites below.
|
|
59
|
-
*
|
|
60
|
-
* Always scope-local: `scopeKey` is a real scope, never "every scope" — see
|
|
61
|
-
* {@link findActiveRunOutsideScope} for the cross-scope warning's query.
|
|
62
|
-
*/
|
|
63
|
-
findActiveRunForScope(workflowRefs, scopeKey) {
|
|
64
|
-
const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
|
|
65
|
-
if (refs.length === 0)
|
|
66
|
-
return undefined;
|
|
67
|
-
return this.db
|
|
68
|
-
.prepare(`SELECT id, current_step_id FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND scope_key = ? AND status = 'active' AND parent_run_id IS NULL ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
|
|
69
|
-
.get(...refs, scopeKey);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* The cross-scope start warning's query (#942): the most recently active
|
|
73
|
-
* run of these refs OUTSIDE `scopeKey` — i.e. `scope_key IS NULL OR
|
|
74
|
-
* scope_key != scopeKey`, not merely "the most recent active run of these
|
|
75
|
-
* refs anywhere". A same-scope active row must never win the `LIMIT 1` and
|
|
76
|
-
* mask a DIFFERENT scope's run: with `--new`/`--force`, the caller's own
|
|
77
|
-
* active run could otherwise sort first (most recently updated) and hide a
|
|
78
|
-
* third scope's run entirely, so `startWorkflowRun` silently warned about
|
|
79
|
-
* nothing while a genuinely stale run in another scope went unreported.
|
|
80
|
-
* `findActiveRunForScope` deliberately stays scope-local and is never used
|
|
81
|
-
* for this purpose.
|
|
82
|
-
*/
|
|
83
|
-
findActiveRunOutsideScope(workflowRefs, scopeKey) {
|
|
84
|
-
const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
|
|
85
|
-
if (refs.length === 0)
|
|
86
|
-
return undefined;
|
|
87
|
-
return (this.db
|
|
88
|
-
.prepare(`SELECT * FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND (scope_key IS NULL OR scope_key != ?) AND status = 'active' AND parent_run_id IS NULL ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
|
|
89
|
-
.get(...refs, scopeKey) ?? undefined);
|
|
90
|
-
}
|
|
91
45
|
getRunById(runId) {
|
|
92
46
|
return (this.db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(runId) ??
|
|
93
47
|
undefined);
|
|
94
48
|
}
|
|
95
|
-
/**
|
|
96
|
-
* The scope-attach lookup `akm workflow run <ref>` uses to find an
|
|
97
|
-
* in-progress top-level run to resume instead of starting a new one.
|
|
98
|
-
* `AND parent_run_id IS NULL` (B-N10): a child run must never be attached
|
|
99
|
-
* to directly through this path — a parent-driven child would then have
|
|
100
|
-
* TWO drivers. For any database with no child rows the result is
|
|
101
|
-
* byte-identical.
|
|
102
|
-
*
|
|
103
|
-
* Always scope-local: `scopeKey` is a real scope, never "every scope" — see
|
|
104
|
-
* {@link findActiveRunOutsideScope} for the cross-scope warning's query.
|
|
105
|
-
*/
|
|
106
|
-
getActiveRunRowForScope(workflowRefs, scopeKey) {
|
|
107
|
-
const refs = typeof workflowRefs === "string" ? [workflowRefs] : [...workflowRefs];
|
|
108
|
-
if (refs.length === 0)
|
|
109
|
-
return undefined;
|
|
110
|
-
return (this.db
|
|
111
|
-
.prepare(`SELECT * FROM workflow_runs WHERE workflow_ref IN (${refs.map(() => "?").join(", ")}) AND scope_key = ? AND status = 'active' AND parent_run_id IS NULL ORDER BY updated_at DESC, created_at DESC LIMIT 1`)
|
|
112
|
-
.get(...refs, scopeKey) ?? undefined);
|
|
113
|
-
}
|
|
114
49
|
hasRun(runId) {
|
|
115
50
|
const row = this.db.prepare("SELECT 1 FROM workflow_runs WHERE id = ? LIMIT 1").get(runId);
|
|
116
51
|
return !!row;
|
|
@@ -128,16 +63,9 @@ export class WorkflowRunsRepository {
|
|
|
128
63
|
}
|
|
129
64
|
throw new NotFoundError(`Workflow run "${prefix}" not found.`, "WORKFLOW_NOT_FOUND");
|
|
130
65
|
}
|
|
131
|
-
listRuns(filter) {
|
|
66
|
+
listRuns(filter = {}) {
|
|
132
67
|
const filters = [];
|
|
133
68
|
const params = [];
|
|
134
|
-
// `scopeKey: null` (#942) is "every scope" — the predicate is omitted
|
|
135
|
-
// rather than bound as SQL NULL (which would match nothing, since a real
|
|
136
|
-
// `scope_key` column value is never NULL for a fresh run).
|
|
137
|
-
if (filter.scopeKey !== null) {
|
|
138
|
-
filters.push("scope_key = ?");
|
|
139
|
-
params.push(filter.scopeKey);
|
|
140
|
-
}
|
|
141
69
|
if (filter.workflowRef) {
|
|
142
70
|
filters.push("workflow_ref = ?");
|
|
143
71
|
params.push(filter.workflowRef);
|
|
@@ -152,11 +80,7 @@ export class WorkflowRunsRepository {
|
|
|
152
80
|
// human `resume`), so it must never appear under `--active`, or a script
|
|
153
81
|
// treating `--active` output as executable work would pick up a blocked
|
|
154
82
|
// run (owner manual-validation finding 1). Blocked runs stay visible in
|
|
155
|
-
// plain `list` (all statuses) with their `blocked` status.
|
|
156
|
-
// active-OR-blocked scope semantics some call sites want (the `akm show`
|
|
157
|
-
// scope guard, which surfaces a blocked run as the scope's occupant) live
|
|
158
|
-
// in the SEPARATE {@link findActiveOrBlockedRunForScope} — never folded
|
|
159
|
-
// into this shared list filter.
|
|
83
|
+
// plain `list` (all statuses) with their `blocked` status.
|
|
160
84
|
filters.push("status = 'active'");
|
|
161
85
|
}
|
|
162
86
|
// B-N10: a child run is invisible to this scope query unless the caller
|
|
@@ -178,32 +102,23 @@ export class WorkflowRunsRepository {
|
|
|
178
102
|
getStep(runId, stepId) {
|
|
179
103
|
return this.db.prepare("SELECT * FROM workflow_run_steps WHERE run_id = ? AND step_id = ?").get(runId, stepId);
|
|
180
104
|
}
|
|
181
|
-
/**
|
|
182
|
-
* The `akm show` active-run guard: which run (if any) currently occupies
|
|
183
|
-
* this scope. `AND parent_run_id IS NULL` (B-N10) — a child a parent is
|
|
184
|
-
* driving must never be reported as "the" active run; the PARENT is. For
|
|
185
|
-
* any database with no child rows the result is byte-identical.
|
|
186
|
-
*/
|
|
187
|
-
findActiveOrBlockedRunForScope(scopeKey) {
|
|
188
|
-
return (this.db
|
|
189
|
-
.prepare("SELECT id, current_step_id, workflow_ref FROM workflow_runs WHERE scope_key = ? AND status IN ('active', 'blocked') AND parent_run_id IS NULL ORDER BY updated_at DESC LIMIT 1")
|
|
190
|
-
.get(scopeKey) ?? null);
|
|
191
|
-
}
|
|
192
105
|
// ── writes ─────────────────────────────────────────────────────────────────
|
|
193
106
|
insertRun(input) {
|
|
194
107
|
// R-R3 (P3a Review log; docs/plans/specs/p4-deletions-closeout.md §8):
|
|
195
|
-
// this
|
|
108
|
+
// this column list is hand-duplicated by publishChildWorkflowRun's own
|
|
196
109
|
// INSERT below, which extends it with parent_run_id/parent_unit_id/
|
|
197
110
|
// invocation_key. A signature refactor to share one INSERT builder was
|
|
198
111
|
// considered and deliberately deferred — see
|
|
199
112
|
// docs/architecture/decisions/0009-child-run-publication-column-parity.md.
|
|
200
|
-
// Keep both column lists in sync by hand.
|
|
113
|
+
// Keep both column lists in sync by hand. `scope_key` and
|
|
114
|
+
// `workflow_entry_id` are legacy columns: read tolerantly, never written
|
|
115
|
+
// (#1239), so a fresh row carries NULL in both.
|
|
201
116
|
this.db
|
|
202
117
|
.prepare(`INSERT INTO workflow_runs (
|
|
203
|
-
id, workflow_ref,
|
|
118
|
+
id, workflow_ref, workflow_title, status, params_json, current_step_id, created_at, updated_at,
|
|
204
119
|
agent_harness, agent_session_id
|
|
205
|
-
) VALUES (?, ?, ?,
|
|
206
|
-
.run(input.id, input.workflowRef, input.
|
|
120
|
+
) VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)`)
|
|
121
|
+
.run(input.id, input.workflowRef, input.workflowTitle, input.paramsJson, input.currentStepId, input.createdAt, input.updatedAt, input.agentHarness, input.agentSessionId);
|
|
207
122
|
}
|
|
208
123
|
insertSteps(steps) {
|
|
209
124
|
const insertStep = this.db.prepare(`INSERT INTO workflow_run_steps (
|
|
@@ -252,22 +167,6 @@ export class WorkflowRunsRepository {
|
|
|
252
167
|
*/
|
|
253
168
|
publishWorkflowRunV4(input) {
|
|
254
169
|
this.immediateTransaction((db) => {
|
|
255
|
-
if (!input.force) {
|
|
256
|
-
// The uniqueness guard must never silently skip its scope predicate
|
|
257
|
-
// (#942) — every real caller (`startWorkflowRun`) stamps a concrete
|
|
258
|
-
// scope key, so a null one here means the caller is misusing this
|
|
259
|
-
// top-level guard, not that scoping should be waived.
|
|
260
|
-
if (input.run.scopeKey === null) {
|
|
261
|
-
throw new Error("publishWorkflowRunV4: run.scopeKey must not be null for the scope uniqueness guard.");
|
|
262
|
-
}
|
|
263
|
-
const existing = this.findActiveRunForScope(input.workflowRefs, input.run.scopeKey);
|
|
264
|
-
if (existing) {
|
|
265
|
-
throw new UsageError(`Workflow ${input.run.workflowRef} already has an active run in this scope ` +
|
|
266
|
-
`(id=${existing.id}, scope=${input.run.scopeKey}, step=${existing.current_step_id ?? "—"}). ` +
|
|
267
|
-
`Use 'akm workflow run ${input.run.workflowRef}' to resume it or ` +
|
|
268
|
-
`'akm workflow abandon ${existing.id}' to give up on it.`, "RESOURCE_ALREADY_EXISTS");
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
170
|
this.insertRun(input.run);
|
|
272
171
|
this.insertSteps(input.steps);
|
|
273
172
|
db.prepare("UPDATE workflow_runs SET plan_json = ?, plan_hash = ?, plan_ir_version = ? WHERE id = ?").run(input.planJson, input.planHash, WORKFLOW_PLAN_VERSION, input.run.id);
|
|
@@ -294,9 +193,7 @@ export class WorkflowRunsRepository {
|
|
|
294
193
|
* (`plan_ir_version` = the current plan version), and append its
|
|
295
194
|
* `workflow_started` event — then return the freshly-inserted row.
|
|
296
195
|
*
|
|
297
|
-
*
|
|
298
|
-
* `RESOURCE_ALREADY_EXISTS` (top-level scope-conflict rules do not apply to
|
|
299
|
-
* a child), and reads no source: the child plan was frozen into the parent's.
|
|
196
|
+
* Reads no source: the child plan was frozen into the parent's.
|
|
300
197
|
*
|
|
301
198
|
* This method's serialization guarantee holds only when it is the
|
|
302
199
|
* OUTERMOST transaction on the connection (spec Review log R10,
|
|
@@ -332,14 +229,14 @@ export class WorkflowRunsRepository {
|
|
|
332
229
|
if (existing)
|
|
333
230
|
return existing;
|
|
334
231
|
// R-R3 (P3a Review log; docs/plans/specs/p4-deletions-closeout.md §8):
|
|
335
|
-
// the first
|
|
232
|
+
// the first 10 columns here must stay byte-identical to insertRun's own
|
|
336
233
|
// column list above (hand-duplicated, not shared, by deliberate choice
|
|
337
234
|
// — see docs/architecture/decisions/0009-child-run-publication-column-parity.md).
|
|
338
235
|
// Keep both column lists in sync by hand.
|
|
339
236
|
db.prepare(`INSERT INTO workflow_runs (
|
|
340
|
-
id, workflow_ref,
|
|
237
|
+
id, workflow_ref, workflow_title, status, params_json, current_step_id, created_at, updated_at,
|
|
341
238
|
agent_harness, agent_session_id, parent_run_id, parent_unit_id, invocation_key
|
|
342
|
-
) VALUES (?, ?, ?,
|
|
239
|
+
) VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.run.id, input.run.workflowRef, input.run.workflowTitle, input.run.paramsJson, input.run.currentStepId, input.run.createdAt, input.run.updatedAt, input.run.agentHarness, input.run.agentSessionId, input.parentRunId, input.spawnedByUnitId, input.invocationKey);
|
|
343
240
|
this.insertSteps(input.steps);
|
|
344
241
|
db.prepare("UPDATE workflow_runs SET plan_json = ?, plan_hash = ?, plan_ir_version = ? WHERE id = ?").run(input.planJson, input.planHash, WORKFLOW_PLAN_VERSION, input.run.id);
|
|
345
242
|
insertEventOnce(db, {
|
|
@@ -543,6 +440,31 @@ export class WorkflowRunsRepository {
|
|
|
543
440
|
return this.db.prepare("SELECT * FROM workflow_run_units WHERE run_id = ? AND unit_id = ?").get(runId, unitId);
|
|
544
441
|
}
|
|
545
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Preview counterpart of {@link renameWorkflowRunRefs} for `akm bundle rename`:
|
|
445
|
+
* how many run rows name `oldBundleId`, and which of them are still `active`
|
|
446
|
+
* or `blocked` (their frozen `plan_json` keeps the old qualified refs, so the operator
|
|
447
|
+
* finishes or abandons them — the rename lists them rather than touching them).
|
|
448
|
+
*/
|
|
449
|
+
export function countWorkflowRunRefsForRename(db, oldBundleId) {
|
|
450
|
+
const like = `${escapeLikePattern(oldBundleId)}//%`;
|
|
451
|
+
const refs = db.prepare(`SELECT COUNT(*) AS n FROM workflow_runs WHERE workflow_ref LIKE ? ESCAPE '\\'`).get(like).n;
|
|
452
|
+
const active = db
|
|
453
|
+
.prepare(`SELECT id, workflow_ref AS workflowRef, status FROM workflow_runs WHERE workflow_ref LIKE ? ESCAPE '\\' AND status IN ('active', 'blocked') ORDER BY updated_at DESC, created_at DESC`)
|
|
454
|
+
.all(like);
|
|
455
|
+
return { refs, active };
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Rewrite every `workflow_runs.workflow_ref` naming `oldBundleId` to
|
|
459
|
+
* `newBundleId` (`akm bundle rename`). A legacy unqualified ref is not matched
|
|
460
|
+
* by the `LIKE` prefix and is left alone. Returns the number of rows rewritten.
|
|
461
|
+
*/
|
|
462
|
+
export function renameWorkflowRunRefs(db, oldBundleId, newBundleId) {
|
|
463
|
+
const prefix = `${escapeLikePattern(oldBundleId)}//`;
|
|
464
|
+
return Number(db
|
|
465
|
+
.prepare(`UPDATE workflow_runs SET workflow_ref = ? || substr(workflow_ref, length(?) + 1) WHERE workflow_ref LIKE ? ESCAPE '\\'`)
|
|
466
|
+
.run(newBundleId, oldBundleId, `${prefix}%`).changes);
|
|
467
|
+
}
|
|
546
468
|
/**
|
|
547
469
|
* Run `fn` against a {@link WorkflowRunsRepository} bound to state.db
|
|
548
470
|
* ({@link StorageLocations.stateDb}, the post-cutover home of the
|
package/dist/tasks/embedded.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { makeBundleRef, parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
5
5
|
import { COMPOSITION_INVALID_MULTI_JOB_HINT, ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
6
6
|
import { DURATION_UNITS, parseDuration } from "../../core/time.js";
|
|
7
|
+
import { warn } from "../../core/warn.js";
|
|
7
8
|
import { isPortableExecutionAgentSelector } from "../../execution/source.js";
|
|
8
9
|
import { buildExecution } from "../../integrations/agent/execution.js";
|
|
9
10
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
@@ -22,7 +23,7 @@ export function environmentSnapshot(environment) {
|
|
|
22
23
|
continue;
|
|
23
24
|
const value = String(raw);
|
|
24
25
|
if (isInferredSecretName(key) || detectSecretShapedParams({ [key]: value }).length > 0) {
|
|
25
|
-
|
|
26
|
+
warn(`Task env.${key} looks like a credential. Task source is durable and readable; keep the value in a secret or env file and inject it at run time with \`akm secret run\` or \`akm env run\` in the task's \`run:\`. (Heuristic warning; the value is used as written.)`);
|
|
26
27
|
}
|
|
27
28
|
Object.defineProperty(out, key, { value, enumerable: true, configurable: false, writable: false });
|
|
28
29
|
}
|
|
@@ -120,7 +121,7 @@ export function validatePreparedCommand(invocation, context) {
|
|
|
120
121
|
}
|
|
121
122
|
export function validateWorkflowRuntimeSource(file, workspaceRoot, readFile) {
|
|
122
123
|
const source = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readFile(file, workspaceRoot));
|
|
123
|
-
const compiled = compileWorkflowSource(source, { path: file
|
|
124
|
+
const compiled = compileWorkflowSource(source, { path: file });
|
|
124
125
|
if (!compiled.ok) {
|
|
125
126
|
// P4-N2's mapping (docs/plans/specs/p4-deletions-closeout.md §3.3.4): a
|
|
126
127
|
// task-wrapped workflow target's own multi-job source is a composition
|
|
@@ -19,25 +19,24 @@
|
|
|
19
19
|
* `src/tasks/source/project-v4.ts`'s `projectTaskSourceV4()`.
|
|
20
20
|
*/
|
|
21
21
|
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
22
23
|
import { prepareCommandInvocation } from "../../commands/command/command-execution.js";
|
|
23
24
|
import { UsageError } from "../../core/errors.js";
|
|
24
25
|
import { warn } from "../../core/warn.js";
|
|
25
26
|
import { base, commandEnvironmentSnapshot, currentExecutionValues, defaultTaskShell, environmentSnapshot, qualifyOwnedRef, resolvedOwnedAsset, validatePreparedCommand, validateWorkflowRuntimeSource, } from "./prepare-support.js";
|
|
26
|
-
import {
|
|
27
|
+
import { captureScriptTarget } from "./script-capture.js";
|
|
27
28
|
/** Project one canonical task-v3 source into immutable executable work. */
|
|
28
29
|
export async function prepareTaskV3Execution(document, context) {
|
|
29
30
|
const environment = environmentSnapshot(document.env);
|
|
30
31
|
const commandEnvironment = commandEnvironmentSnapshot(environment, context.schedulerContext);
|
|
31
32
|
const common = base(document, context, environment);
|
|
32
33
|
if (document.target.kind === "run") {
|
|
33
|
-
const cwdIdentity = captureDirectoryIdentity(context.bundleRoot, document.target.workingDirectory);
|
|
34
34
|
return Object.freeze({
|
|
35
35
|
...common,
|
|
36
36
|
kind: "shell",
|
|
37
37
|
command: document.target.run,
|
|
38
38
|
shell: document.target.shell ?? defaultTaskShell(context.platform ?? process.platform),
|
|
39
|
-
cwd:
|
|
40
|
-
cwdIdentity,
|
|
39
|
+
cwd: path.resolve(context.bundleRoot, document.target.workingDirectory ?? "."),
|
|
41
40
|
});
|
|
42
41
|
}
|
|
43
42
|
const target = document.target.uses;
|
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|
* script arm (the moved prepareTaskV3Execution) and
|
|
8
8
|
* prepare-script-target.ts's typed prepareScriptTarget() call
|
|
9
9
|
* captureScriptTarget() below, so the two never drift into two copies of the
|
|
10
|
-
* same capture logic. scriptInterpreter()
|
|
11
|
-
*
|
|
10
|
+
* same capture logic. scriptInterpreter() is moved body-intact out of the
|
|
11
|
+
* pre-P1b src/tasks/runtime-v3.ts.
|
|
12
12
|
*/
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { UsageError } from "../../core/errors.js";
|
|
16
|
-
import { captureFrozenDirectoryIdentity } from "../../execution/directory-identity.js";
|
|
17
16
|
import { isBunStandaloneMain } from "../resolve-akm-bin.js";
|
|
18
17
|
const SCRIPT_INTERPRETERS = Object.freeze({
|
|
19
18
|
".sh": "sh",
|
|
@@ -77,36 +76,23 @@ export function scriptInterpreter(extension, ref, bytes) {
|
|
|
77
76
|
}
|
|
78
77
|
throw new UsageError(`Task v3 script target ${JSON.stringify(ref)} requires Bun for ${extension} execution, but this runtime cannot provide it.`, "TASK_TARGET_UNSUPPORTED");
|
|
79
78
|
}
|
|
80
|
-
export function captureDirectoryIdentity(bundleRoot, workingDirectory) {
|
|
81
|
-
try {
|
|
82
|
-
return captureFrozenDirectoryIdentity(bundleRoot, workingDirectory);
|
|
83
|
-
}
|
|
84
|
-
catch (cause) {
|
|
85
|
-
if (cause instanceof UsageError)
|
|
86
|
-
throw cause;
|
|
87
|
-
throw new UsageError(`Task working directory ${JSON.stringify(workingDirectory ?? ".")} cannot be physically verified: ${cause instanceof Error ? cause.message : String(cause)}`, "TASK_SOURCE_INVALID");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
79
|
/**
|
|
91
|
-
* Read a script's bytes off disk (or the caller's own read seam
|
|
92
|
-
* the interpreter/digest
|
|
93
|
-
* needs. `ref` is used only for interpreter-selection error
|
|
94
|
-
* the script's own qualified ref, never a synthetic task ref.
|
|
95
|
-
*
|
|
96
|
-
* itself, matching prepareTaskV3Execution's historical behavior).
|
|
80
|
+
* Read a script's bytes off disk (or the caller's own read seam, following a
|
|
81
|
+
* symlink to wherever it points) and freeze the interpreter/digest shape every
|
|
82
|
+
* script dispatch needs. `ref` is used only for interpreter-selection error
|
|
83
|
+
* messages — it is the script's own qualified ref, never a synthetic task ref.
|
|
84
|
+
* `bundleRoot` is the script's working directory.
|
|
97
85
|
*/
|
|
98
86
|
export function captureScriptTarget(ref, file, bundleRoot, readFile) {
|
|
99
87
|
const extension = path.extname(file).toLowerCase();
|
|
100
88
|
const raw = readFile(file, bundleRoot);
|
|
101
89
|
const bytes = Uint8Array.from(raw);
|
|
102
|
-
const cwdIdentity = captureDirectoryIdentity(bundleRoot);
|
|
103
90
|
return Object.freeze({
|
|
104
91
|
interpreter: scriptInterpreter(extension, ref, bytes),
|
|
105
92
|
extension,
|
|
106
93
|
bytesBase64: Buffer.from(bytes).toString("base64"),
|
|
107
94
|
byteLength: bytes.byteLength,
|
|
108
95
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
109
|
-
cwd:
|
|
110
|
-
cwdIdentity,
|
|
96
|
+
cwd: path.resolve(bundleRoot),
|
|
111
97
|
});
|
|
112
98
|
}
|
|
@@ -11,11 +11,16 @@
|
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
|
|
13
13
|
import { makeBundleRef } from "../../core/asset/asset-ref.js";
|
|
14
|
+
import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
|
|
15
|
+
import { resolveStashDir } from "../../core/common.js";
|
|
14
16
|
import { loadConfig } from "../../core/config/config.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
+
import { bundleKeyForContentRoot } from "../../core/config/config-sources.js";
|
|
18
|
+
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
19
|
+
import { assertBundleNotDisabled } from "../../core/write-source.js";
|
|
17
20
|
import { applyInputDefaults, materializeInputFlags, validateInputs, } from "../../execution/input-contract.js";
|
|
18
|
-
import { resolveAdapterConceptOwner } from "../../indexer/lookup/adapter-concept-owner.js";
|
|
21
|
+
import { resolveAdapterConceptOwner, resolveConceptOwner } from "../../indexer/lookup/adapter-concept-owner.js";
|
|
22
|
+
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
23
|
+
import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
|
|
19
24
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
20
25
|
import { prepareTaskV3Execution } from "../prepare/prepare.js";
|
|
21
26
|
import { scheduledTaskContextEnv } from "../scheduler-invocation.js";
|
|
@@ -34,6 +39,42 @@ const CONFIG_FREE_TASK_RUNTIME = Object.freeze({
|
|
|
34
39
|
configVersion: "0.9.0",
|
|
35
40
|
semanticSearchMode: "off",
|
|
36
41
|
});
|
|
42
|
+
/**
|
|
43
|
+
* The active source set a task read walks. A working stash that is a disabled
|
|
44
|
+
* configured bundle makes that set throw; it is simply not in the set, so the
|
|
45
|
+
* other bundles still resolve.
|
|
46
|
+
*/
|
|
47
|
+
export function activeTaskSources(config) {
|
|
48
|
+
try {
|
|
49
|
+
return resolveSourceEntries(undefined, config);
|
|
50
|
+
}
|
|
51
|
+
catch (cause) {
|
|
52
|
+
const working = bundleKeyForContentRoot(config, resolveStashDir());
|
|
53
|
+
const entry = working ? config.bundles?.[working] : undefined;
|
|
54
|
+
if (!(cause instanceof ConfigError) || !working || !entry || entry.enabled !== false)
|
|
55
|
+
throw cause;
|
|
56
|
+
const enabled = { ...config, bundles: { ...config.bundles, [working]: { ...entry, enabled: true } } };
|
|
57
|
+
return resolveSourceEntries(undefined, enabled).filter((source) => source.registryId !== working);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve an asset a task names in another bundle (`uses:`) through the one
|
|
62
|
+
* ref-to-file walk. Reading needs no write target, so a bundle of any kind
|
|
63
|
+
* (git, npm, website) resolves. The file is the authored path, not the symlink
|
|
64
|
+
* target, so the script interpreter follows the name the author gave it.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveBundleAsset(config, { bundle, type, name }) {
|
|
67
|
+
const conceptId = conceptIdFromTypeName(type, name);
|
|
68
|
+
const sources = activeTaskSources(config);
|
|
69
|
+
const match = resolveConceptOwner({ bundle, conceptId }, sources);
|
|
70
|
+
if (match)
|
|
71
|
+
return { file: match.owner.path, bundleRoot: match.source.path };
|
|
72
|
+
if (resolveSourcesForOrigin(bundle, sources).length === 0) {
|
|
73
|
+
assertBundleNotDisabled(config, bundle);
|
|
74
|
+
throw new UsageError(`Bundle ${JSON.stringify(bundle)} is not configured. Run \`akm bundle list\` to see available bundles.`, "INVALID_FLAG_VALUE");
|
|
75
|
+
}
|
|
76
|
+
throw new NotFoundError(`Task asset ${makeBundleRef(bundle, conceptId)} was not found.`, "ASSET_NOT_FOUND");
|
|
77
|
+
}
|
|
37
78
|
/** Resolve, parse, and project one task-v3 asset into a frozen, executable projection. */
|
|
38
79
|
export async function loadPreparedTask(id, options) {
|
|
39
80
|
const bundleDir = options.bundleDir;
|
|
@@ -49,7 +90,7 @@ export async function loadPreparedTask(id, options) {
|
|
|
49
90
|
}
|
|
50
91
|
const filePath = owner.path;
|
|
51
92
|
const yaml = fs.readFileSync(filePath, "utf8");
|
|
52
|
-
const parsed = parseTaskSource({ yaml, filePath
|
|
93
|
+
const parsed = parseTaskSource({ yaml, filePath });
|
|
53
94
|
const source = projectTaskSourceV4(parsed.v4);
|
|
54
95
|
const requiresCommandConfig = source.target.kind === "uses" &&
|
|
55
96
|
(source.target.uses.kind === "builtin-command" || source.target.uses.kind === "command");
|
|
@@ -106,16 +147,7 @@ export async function loadPreparedTask(id, options) {
|
|
|
106
147
|
if (bundle === bundleName) {
|
|
107
148
|
return { file: await resolveAssetPath(bundleDir, type, name), bundleRoot: bundleDir };
|
|
108
149
|
}
|
|
109
|
-
|
|
110
|
-
// `bundle` is the qualifier of an asset ref in the task, not a flag.
|
|
111
|
-
const resolvedBundle = resolveWriteTarget(resolutionConfig, bundle, {
|
|
112
|
-
requireWritable: false,
|
|
113
|
-
flag: "The asset ref's bundle",
|
|
114
|
-
});
|
|
115
|
-
return {
|
|
116
|
-
file: await resolveAssetPath(resolvedBundle.source.path, type, name),
|
|
117
|
-
bundleRoot: resolvedBundle.source.path,
|
|
118
|
-
};
|
|
150
|
+
return resolveBundleAsset(requiresCommandConfig ? config : loadConfig(), { bundle, type, name });
|
|
119
151
|
},
|
|
120
152
|
});
|
|
121
153
|
}
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { assertNever } from "../../core/assert.js";
|
|
22
22
|
import { runManagedSubprocess, streamCaptureFailure } from "../../core/subprocess.js";
|
|
23
|
-
import { assertFrozenDirectoryContained } from "../../execution/directory-identity.js";
|
|
24
23
|
import { cleanupFrozenScript, frozenScriptCommand, materializeFrozenScript } from "../frozen-script.js";
|
|
25
24
|
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
26
25
|
import { finishAttempt } from "./attempt-lifecycle.js";
|
|
@@ -163,13 +162,6 @@ export async function runNativeTask(input) {
|
|
|
163
162
|
process.once("SIGTERM", onSigterm);
|
|
164
163
|
process.once("SIGINT", onSigint);
|
|
165
164
|
try {
|
|
166
|
-
// Re-resolve the authored root/cwd immediately before spawn so a
|
|
167
|
-
// symlink, ancestor, bundle-root, or directory/file swap cannot redirect
|
|
168
|
-
// execution outside that physical workspace. This is a live containment
|
|
169
|
-
// check, not an identity comparison — it does not care whether the
|
|
170
|
-
// underlying device/inode changed (e.g. a remount), only whether the
|
|
171
|
-
// resolved cwd still lives inside its root.
|
|
172
|
-
assertFrozenDirectoryContained(task.cwdIdentity);
|
|
173
165
|
if (task.kind === "script") {
|
|
174
166
|
materialized = materializeFrozenScript(task);
|
|
175
167
|
cmd = frozenScriptCommand(task, materialized.file);
|