akm-cli 0.10.261011-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 +238 -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/assets/prompts/distill-lesson-system.md +18 -5
- 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/improve/stage.js +3 -1
- 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/subprocess.js +19 -7
- 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
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,244 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.10.261013] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A secret is now also redacted when it appears in its JSON-escaped spelling
|
|
14
|
+
(#1238). Text redaction matched only the exact and URL-encoded forms, so a child
|
|
15
|
+
that printed `JSON.stringify({ token })` for a secret with a quote, backslash,
|
|
16
|
+
newline or non-ASCII character left it in the unit journal when the output failed
|
|
17
|
+
its schema, had no schema, or came with a non-zero exit. The shared redactor now
|
|
18
|
+
also matches the `\"`, `\\`, `\n` and `\uXXXX` forms (lowercase or uppercase
|
|
19
|
+
hex, DEL included), for workflow, engine and task-output redaction alike.
|
|
20
|
+
- `akm bundle rename` now rewrites the refs of a bundle whose id contains an
|
|
21
|
+
emoji or other character outside the Basic Multilingual Plane correctly
|
|
22
|
+
(#1239). It used to cut into the `//` separator, storing `team/workflows/x`
|
|
23
|
+
for `💼//workflows/x` and stranding the bundle's workflow runs, task history
|
|
24
|
+
and proposals.
|
|
25
|
+
- A task's `uses:` script or workflow in an npm, website or read-only bundle now
|
|
26
|
+
resolves (#1238). It used to fail with "unsupported kind for writes", because
|
|
27
|
+
the lookup went through the write-target resolver. An unknown bundle in such a
|
|
28
|
+
ref is reported as not configured, without naming a flag.
|
|
29
|
+
- `akm bundle update` no longer re-clones a git or npm bundle whose upstream
|
|
30
|
+
revision has not changed. Re-cloning replaced the bundle's directory under any
|
|
31
|
+
running task or workflow, and left an empty revision directory behind after
|
|
32
|
+
each real change. An unchanged bundle is now reported as unchanged and left
|
|
33
|
+
alone (`--force` still fetches it again). After a real change, the previous
|
|
34
|
+
revision is kept and older revision directories are removed (#1235).
|
|
35
|
+
- Scheduled tasks in the `AKM_BUNDLE_DIR` working stash were silently dropped by
|
|
36
|
+
an unscoped `akm task sync` (and `--rebind`) once any other bundle was
|
|
37
|
+
configured (#1236). Task sync now uses the same set of active bundles as
|
|
38
|
+
search, show and workflows, so the working stash is synced the same way
|
|
39
|
+
whether you sync everything or name it with `--bundle`.
|
|
40
|
+
- A `bundle update` while a workflow is running no longer fails the run (#1238,
|
|
41
|
+
#1235). A running workflow used to remember the directory it was started in, so
|
|
42
|
+
once the update moved the bundle to a new revision directory, the next step
|
|
43
|
+
failed with `PATH_ESCAPE_VIOLATION` and the only remedy was `abandon`. The
|
|
44
|
+
run now finds its bundle again at each step and carries on from the new
|
|
45
|
+
directory. It continues from its frozen plan even if the new revision renamed
|
|
46
|
+
or removed the workflow file, and a step fails only when the run's bundle is
|
|
47
|
+
no longer configured.
|
|
48
|
+
- `exec.cwd` is applied once (#1238). `cwd: sub` used to run in `<bundle>/sub/sub`.
|
|
49
|
+
It now runs in `<bundle>/sub`.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- `akm workflow plan --format text` now shows each step's argv, cwd and env refs,
|
|
54
|
+
and the bundle-qualified ref that every `${ref}` token, `env:` entry and child
|
|
55
|
+
`workflow:` resolves to; a ref no bundle owns, in the workflow or in a child
|
|
56
|
+
workflow it runs, fails the plan and names it.
|
|
57
|
+
`akm workflow status <id> --units` shows the same argv, cwd and resolved refs
|
|
58
|
+
for a failed unit (#1238).
|
|
59
|
+
- A workflow exec step's command now receives `AKM_CONFIG_DIR`, `AKM_DATA_DIR`,
|
|
60
|
+
`AKM_CACHE_DIR` and `AKM_STATE_DIR` set to the directories the akm that started
|
|
61
|
+
it resolved, including ones it found through `XDG_*_HOME`, and `AKM_BUNDLE_DIR`
|
|
62
|
+
when it had one. A step that runs `akm` uses the same bundle and state without
|
|
63
|
+
`pass_env` (#1238).
|
|
64
|
+
- `akm env`, `akm secret`, workflow `env:` bindings and child workflows, task
|
|
65
|
+
`uses:` assets, and `improve`/`distill`/schema-repair file lookups now find an
|
|
66
|
+
asset with the same lookup `akm show` uses, instead of each keeping its own
|
|
67
|
+
copy of it (#1238). The visible differences: a missing child workflow fails
|
|
68
|
+
with the ordinary "Workflow not found" error, an env ref in a workflow does
|
|
69
|
+
not resolve into a disabled bundle, and `improve` finds a concept in a
|
|
70
|
+
bundle whose adapter names the file (such as an OKF table) from disk as well
|
|
71
|
+
as from the index.
|
|
72
|
+
- An asset is addressed by its akm ref and nothing else (#1238). `akm secret
|
|
73
|
+
set|run` and `akm env create|run|path|export|remove` now take a full ref
|
|
74
|
+
(`secrets/gitea_token`, `team//env/prod`); a bare name such as `gitea_token`
|
|
75
|
+
or `prod` is a usage error that names the full form. `akm env create prod` is
|
|
76
|
+
now `akm env create env/prod`.
|
|
77
|
+
- One substitution token, `${ref}`, replaces `${secret:NAME}` in env values and
|
|
78
|
+
`secret://name` in config credentials (#1238). A `${secrets/name}` token is
|
|
79
|
+
replaced with the secret's value and any other asset ref, such as
|
|
80
|
+
`${scripts/lab/fetch.sh}`, with its absolute path; the ref may name any bundle
|
|
81
|
+
(`${team//secrets/discord_bot_token}`) rather than only the env file's own
|
|
82
|
+
bundle. A ref into a bundle of any adapter works when it is qualified and the
|
|
83
|
+
bundle is installed (`${docs//tables/customers}` in an OKF bundle). A `${...}`
|
|
84
|
+
body that is not an akm ref, such as `${HOME}` or shell substitution like
|
|
85
|
+
`${v//-/_}`, is left as text. `engines.*.apiKey`, `embedding.apiKey`,
|
|
86
|
+
`search.curateRerank.apiKey` and a git bundle's `credential` accept `$VAR`,
|
|
87
|
+
`${VAR}` or `${secrets/<name>}` (in any bundle, such as `${team//secrets/x}`); a
|
|
88
|
+
`${...}` naming any other kind of asset, such as `${scripts/x}`, is rejected
|
|
89
|
+
when the config loads. An env file that still holds `${secret:NAME}` keeps
|
|
90
|
+
working: akm reads it as `${<the env file's bundle>//secrets/NAME}` and warns
|
|
91
|
+
once, naming the env ref. The file is not rewritten, so write the full ref.
|
|
92
|
+
- A workflow unit's `env:` bindings are now read at dispatch with the same
|
|
93
|
+
function `akm env run` uses, so a bundle's env asset, its `${ref}` tokens and
|
|
94
|
+
the secrets they name behave the same in a workflow as on the command line
|
|
95
|
+
(#1238). An edit to an env file after a run started is picked up by the next
|
|
96
|
+
dispatch instead of failing with "key set changed".
|
|
97
|
+
- Only secret values are redacted from workflow output (#1238). A value
|
|
98
|
+
substituted from a `secrets/` ref is still `[REDACTED]` in stdout, stderr,
|
|
99
|
+
diagnostics and the journal, but the rest of an env asset, such as a URL or a
|
|
100
|
+
channel id, now reaches the journal and the next step intact. A credential
|
|
101
|
+
typed straight into an env file is no longer redacted; keep credentials in
|
|
102
|
+
`secrets/`.
|
|
103
|
+
- A secret's value is read the same way everywhere (#1238): the file content with
|
|
104
|
+
surrounding whitespace trimmed. `akm secret run` and env tokens used to pass
|
|
105
|
+
the raw bytes, trailing newline included, while config credentials and the
|
|
106
|
+
website fetchers trimmed.
|
|
107
|
+
- Installing a bundle is now the trust decision (#1237). A bundle whose `env/`
|
|
108
|
+
files set a key such as `PATH` installs, updates unattended, and works in
|
|
109
|
+
`akm env run`, `akm secret run` and workflow `env:` bindings with no flag, no
|
|
110
|
+
prompt and no block, whichever bundle it comes from. `akm lint` still reports
|
|
111
|
+
`dangerous-env-key` findings, non-blocking.
|
|
112
|
+
- An agent's own `tools` list is now honoured for an agent in any bundle, not
|
|
113
|
+
only the primary stash. `execution.allowedTools` still caps what a run grants.
|
|
114
|
+
- A task `env:` value that looks like a credential is now a warning, in the same
|
|
115
|
+
style workflow params give, instead of an error that stopped the task. The
|
|
116
|
+
warning points to `akm secret run` or `akm env run` in the task's `run:`.
|
|
117
|
+
- `--allow-dangerous-env-keys` on `bundle add`, `bundle update` and `env run` is
|
|
118
|
+
now a deprecated no-op, accepted through 0.10 so existing scripts keep working.
|
|
119
|
+
It is an unknown flag from 0.11.
|
|
120
|
+
- Reads follow symlinks (#1237). An env file or secret that is a symlink to a
|
|
121
|
+
file outside its bundle directory now resolves through `akm env`, `akm secret`,
|
|
122
|
+
`${ref}` substitution and a workflow `env:` binding, and a ref
|
|
123
|
+
resolved by path does the same, instead of failing with "escapes the secrets
|
|
124
|
+
directory" or "Ref resolves outside the stash root", so a secret can point at
|
|
125
|
+
a container's `/run/secrets/<name>`. A command, agent or script that is such a
|
|
126
|
+
symlink also resolves when a workflow, task or `akm agent` runs it, and
|
|
127
|
+
`akm index` indexes a file symlink like any other file, so `akm show` and
|
|
128
|
+
search find it, and `akm env list` and `akm secret list` list it. A symlinked
|
|
129
|
+
script runs under the interpreter its own name selects, and cloning a skill
|
|
130
|
+
copies a symlinked file by content. A directory symlink is still never
|
|
131
|
+
descended. Names with `..`
|
|
132
|
+
segments or absolute names are still refused, and writes still never follow a
|
|
133
|
+
symlink out of the bundle.
|
|
134
|
+
- `akm workflow run <ref>` always starts a new run, and `akm workflow run <run-id>`
|
|
135
|
+
continues one (#1239). `akm workflow resume <run-id>` only re-activates a blocked
|
|
136
|
+
or failed run so `run <run-id>` can continue it. Before, running a ref
|
|
137
|
+
silently resumed any active run of that ref started from the same directory,
|
|
138
|
+
which is why scripts passed `--new` and why a scheduled task and a shell could
|
|
139
|
+
each start their own run. Now, when other runs of the ref are still active, the
|
|
140
|
+
start prints one warning naming their ids and the `akm workflow run <id>` /
|
|
141
|
+
`akm workflow abandon <id>` commands, and starts anyway. Parameters are accepted
|
|
142
|
+
on every ref start, and `--new` is accepted and does nothing (deprecated,
|
|
143
|
+
through 0.10).
|
|
144
|
+
- Workflow runs are no longer tied to the directory you run from (#1239).
|
|
145
|
+
`akm workflow list` and `akm workflow status <ref>` show every run on the host,
|
|
146
|
+
most recently updated first, from any directory, so the runs of a scheduled
|
|
147
|
+
task are listed like any other. `--all-scopes` is accepted and does nothing
|
|
148
|
+
(deprecated, through 0.10), and the `list` result no longer carries a
|
|
149
|
+
`scopeKey`. Runs started by 0.10 are not visible to a 0.9.15 binary reading the
|
|
150
|
+
same `state.db`.
|
|
151
|
+
- `akm workflow run <ref> --skip-if-locked` also skips (exit 0, nothing started)
|
|
152
|
+
when an active run of that ref is being driven by a live process (#1239). Without
|
|
153
|
+
the flag a start is never refused.
|
|
154
|
+
- `akm bundle rename` rewrites the workflow ref on that bundle's workflow runs, so
|
|
155
|
+
`akm workflow list` shows the old runs under the new name, and lists the bundle's
|
|
156
|
+
active and blocked runs in its output, each with its status (#1239). Their frozen
|
|
157
|
+
plans keep the old refs; continue or abandon them.
|
|
158
|
+
- `akm workflow create` no longer indexes the new workflow, and a new run no longer
|
|
159
|
+
records an index entry id (#1239).
|
|
160
|
+
- A workflow plan records the refs it uses and no locations (#1238). New runs
|
|
161
|
+
freeze plan `irVersion` 7, which holds no bundle directory, no directory
|
|
162
|
+
identity and, for each `env:` ref, only its bundle-qualified ref: the env
|
|
163
|
+
file, its keys and the secrets it names are read at each step. Runs frozen
|
|
164
|
+
by earlier releases (`irVersion` 5 and 6) carry those fields, which are never
|
|
165
|
+
read but are kept as stored, so a run interrupted before the upgrade finds the
|
|
166
|
+
child workflow run it had already started instead of starting a second one, and
|
|
167
|
+
continues the same way. A plan frozen by a newer akm is refused with a message
|
|
168
|
+
that says to upgrade akm, whether or not this akm could decode it. A plan with an
|
|
169
|
+
agent step records an empty directory identity, so a 0.10 pre-release that finds
|
|
170
|
+
the run stops at that step instead of running the agent in the wrong directory.
|
|
171
|
+
`akm workflow plan` lists an env ref without its `keys` and `secretNames`.
|
|
172
|
+
- A workflow's exec units, agents and gate judges work in the root of the bundle
|
|
173
|
+
that holds the workflow, found at each step, and `exec.cwd` is joined onto it (#1238).
|
|
174
|
+
`exec.cwd` may now be absolute, and it is no longer required to stay inside the
|
|
175
|
+
bundle: a command can reach any directory anyway. A step whose workflow no
|
|
176
|
+
longer resolves to a bundle fails naming the workflow (a step with a gate is
|
|
177
|
+
blocked by its judge first, for `akm workflow resume`) instead of running in
|
|
178
|
+
akm's own working directory.
|
|
179
|
+
- An exec step's argv and `exec.cwd` can name an asset in any bundle with the same
|
|
180
|
+
`${ref}` token env assets use (#1238): `["bun", "${homelab//scripts/lab-status/fetch.ts}"]`.
|
|
181
|
+
A `secrets/` ref is replaced by the secret's value, which is redacted from the
|
|
182
|
+
unit's output after it is parsed, like a secret bound through an env asset; any
|
|
183
|
+
other asset ref by its absolute path. A ref no bundle owns
|
|
184
|
+
fails the unit, naming it, before anything is spawned. Prompts are not
|
|
185
|
+
substituted. A workflow in bundle A can now run a script from bundle B with an
|
|
186
|
+
env asset from bundle C that names a secret in bundle D.
|
|
187
|
+
- A task no longer captures its working directory's identity when it is prepared
|
|
188
|
+
and re-checks it before it runs (#1238). A `run:` task runs in
|
|
189
|
+
`<bundle>/<working-directory>`, and a `uses: scripts/<file>` target that is a
|
|
190
|
+
symlink to a file outside the bundle works: the task freezes that file's
|
|
191
|
+
bytes.
|
|
192
|
+
- A task's `working-directory` follows the same rule as a workflow's `exec.cwd`
|
|
193
|
+
(#1238): a non-empty path, relative to the task's bundle root or absolute, and
|
|
194
|
+
no longer required to stay inside the bundle. A symlink to a directory outside
|
|
195
|
+
the bundle, an absolute path and a path with `..` all work, and `akm lint` and
|
|
196
|
+
`akm task sync` no longer need the bundle root to check it.
|
|
197
|
+
|
|
198
|
+
### Deprecated
|
|
199
|
+
|
|
200
|
+
- `secret://name` in config is retired in favour of `${secrets/name}` (#1238). A
|
|
201
|
+
config that still holds it loads as if it said `${secrets/name}`, with one
|
|
202
|
+
warning naming the key, and `akm migrate apply` rewrites `config.json`.
|
|
203
|
+
`akm config set` and `bundle add --credential` no longer accept it. A workflow run
|
|
204
|
+
frozen before the upgrade keeps resolving its engine credential when it is
|
|
205
|
+
continued, and keeps the `secret://` text it stored.
|
|
206
|
+
|
|
207
|
+
### Removed
|
|
208
|
+
|
|
209
|
+
- The dangerous-key audit on `bundle add` and `bundle update`, its prompt, its
|
|
210
|
+
rollback of a freshly installed bundle, and the `blocked` per-bundle status in
|
|
211
|
+
`bundle update --all`. The `DANGEROUS_ENV_KEY` and `DANGEROUS_ENV_AUDIT_FAILED`
|
|
212
|
+
error codes are gone with them (#1237).
|
|
213
|
+
- The first-party and third-party distinction for env and secret injection
|
|
214
|
+
(`src/core/activation-policy.ts`) and the unused `trusted` field on a derived
|
|
215
|
+
bundle installation (#1237).
|
|
216
|
+
- `akm show` no longer reports an `activeRun` field. Run state is under
|
|
217
|
+
`akm workflow` (#1239).
|
|
218
|
+
- The "indexed root no longer matches the bundle source" warnings when a command
|
|
219
|
+
or persona is dispatched (#1238). A bundle at a different location than the
|
|
220
|
+
index recorded is no longer reported as drift.
|
|
221
|
+
- The `exec_cwd_escape` failure reason and the checks behind it: the parser, the
|
|
222
|
+
compiler and the exec runner no longer reject an `exec.cwd` that leaves the
|
|
223
|
+
workflow's bundle (#1238).
|
|
224
|
+
|
|
225
|
+
## [0.10.261012] - 2026-10-22
|
|
226
|
+
|
|
227
|
+
### Changed
|
|
228
|
+
|
|
229
|
+
- Distill writes more useful lessons without writing wrong ones. The lesson
|
|
230
|
+
writer treats a tested or measured result about a change or method, stated
|
|
231
|
+
with the conditions and limits it held under, as a lesson. It answers NONE for
|
|
232
|
+
a dated status, for the report of one run or day whose cause is only suspected
|
|
233
|
+
with no fix verified, and for a recap or reminder list whose rules a related
|
|
234
|
+
asset already states. Lessons are shorter (about half the memory), in the
|
|
235
|
+
writer's own words, and keep the memory's numbers, names and terms. The lesson
|
|
236
|
+
judge's REUSABLE criterion credits such a tested result and scores a lesson
|
|
237
|
+
drawn from an unconfirmed one-run report as not reusable; judging an update
|
|
238
|
+
to an existing lesson keeps the original criterion.
|
|
239
|
+
|
|
240
|
+
### Fixed
|
|
241
|
+
|
|
242
|
+
- A SIGINT or SIGTERM that reached akm just after it started an agent or harness
|
|
243
|
+
in its own process group could end akm with that group still running (#1230).
|
|
244
|
+
The signal guard is now in place before the child is spawned, so the group is
|
|
245
|
+
always terminated with akm.
|
|
246
|
+
|
|
9
247
|
## [0.10.261011] - 2026-10-22
|
|
10
248
|
|
|
11
249
|
### Changed
|
package/SECURITY.md
CHANGED
|
@@ -75,8 +75,8 @@ The built-in YouTube snapshot fetcher (`src/sources/snapshot-fetchers/youtube.ts
|
|
|
75
75
|
and `<stash>/secrets/`. They are protected against other local users by
|
|
76
76
|
filesystem permissions but are not encrypted at rest. Do not commit these
|
|
77
77
|
files to source control. Normal `akm env` and `akm secret` output never echoes
|
|
78
|
-
values; materialize values only at the command boundary with `akm env run
|
|
79
|
-
|
|
78
|
+
values; materialize values only at the command boundary with `akm env run`
|
|
79
|
+
or `akm secret run`.
|
|
80
80
|
|
|
81
81
|
The knowledge graph extracted from your assets (entities/relations) lives
|
|
82
82
|
only in `$DATA/index.db` and is surfaced as aggregate counts via `akm health`
|
package/STABILITY.md
CHANGED
|
@@ -100,9 +100,9 @@ enumeration of the whole `proposal` noun group.
|
|
|
100
100
|
| `akm search` | Stable | |
|
|
101
101
|
| `akm curate` | Stable | |
|
|
102
102
|
| `akm show` | Stable | |
|
|
103
|
-
| `akm workflow status` | Experimental | `--all-scopes` added in 0.9.15 (#942),
|
|
103
|
+
| `akm workflow status` | Experimental | `--all-scopes` added in 0.9.15 (#942); a deprecated no-op since 0.10 (#1239), accepted through 0.10. |
|
|
104
104
|
| `akm workflow plan` | Experimental | New in 0.9.2; secret-free provenance output; envelope shape may change. |
|
|
105
|
-
| `akm workflow list` | Experimental | `--all-scopes` and a top-level `scopeKey` envelope field added in 0.9.15 (#942),
|
|
105
|
+
| `akm workflow list` | Experimental | `--all-scopes` and a top-level `scopeKey` envelope field added in 0.9.15 (#942); since 0.10 (#1239) `--all-scopes` is a deprecated no-op, accepted through 0.10, and the `scopeKey` field is removed from the list result. |
|
|
106
106
|
| `akm workflow create` | Experimental | |
|
|
107
107
|
| `akm workflow resume` | Experimental | |
|
|
108
108
|
| `akm workflow abandon` | Experimental | |
|
|
@@ -92,7 +92,7 @@ akm import https://example.com/docs/auth # Fetch one URL and import it as
|
|
|
92
92
|
akm import ./doc.md --bundle my-other-bundle # Route import to a named writable bundle source
|
|
93
93
|
akm workflow create ship-release # Create a workflow asset in the bundle
|
|
94
94
|
akm lint --type workflows # Parse and compile every workflow source; list every error
|
|
95
|
-
akm workflow run workflows/ship-release # Start
|
|
95
|
+
akm workflow run workflows/ship-release # Start a new run and execute it
|
|
96
96
|
akm feedback skills/code-review --positive # Record that an asset helped (ranks it higher; no rewrite)
|
|
97
97
|
akm feedback agents/reviewer --negative --reason "says to run jest; the suite runs on vitest" # Content wrong or stale: lowers its ranking; improve may repair its frontmatter
|
|
98
98
|
akm feedback knowledge/opencode-server --negative --reason "the default port is 4096, not 8000" --replace "port 8000" --with "port 4096" --source "https://opencode.ai/docs/server/" # Apply an exact fix
|
|
@@ -150,8 +150,8 @@ the index (comments can contain commented-out credentials). akm does not edit
|
|
|
150
150
|
entries — you edit the file with your own editor and akm loads it.
|
|
151
151
|
|
|
152
152
|
```sh
|
|
153
|
-
akm env create prod # Create an empty env file
|
|
154
|
-
akm env create prod --from-file ./.env # Ingest an existing .env
|
|
153
|
+
akm env create env/prod # Create an empty env file
|
|
154
|
+
akm env create env/prod --from-file ./.env # Ingest an existing .env
|
|
155
155
|
akm env list # List all env files across bundles with key names
|
|
156
156
|
akm show env/prod # Inspect key names (never values or comments)
|
|
157
157
|
akm env run env/prod -- ./deploy.sh # Run a command with the whole .env injected (the safe path)
|
|
@@ -186,10 +186,10 @@ workflow. Direct run-id commands still target the exact run.
|
|
|
186
186
|
akm workflow create ship-release --print # Print a starter workflow template, without writing
|
|
187
187
|
akm workflow create ship-release # Scaffold a new workflow asset
|
|
188
188
|
akm workflow run workflows/ship-release --version=1.2.3 # Start and execute with exact-name parameter flags
|
|
189
|
-
akm workflow run <run-id> --max-retries 2 --timeout 10m #
|
|
189
|
+
akm workflow run <run-id> --max-retries 2 --timeout 10m # Continue a run with invocation-wide controls
|
|
190
190
|
akm workflow status <run-id> # Show the exact run by id
|
|
191
|
-
akm workflow resume <run-id> #
|
|
192
|
-
akm workflow list # List workflow runs
|
|
191
|
+
akm workflow resume <run-id> # Re-activate a blocked or failed run, then `run <run-id>`
|
|
192
|
+
akm workflow list # List workflow runs on this host
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
## Clone
|
|
@@ -282,7 +282,6 @@ akm bundle list --kind git # Filter by provider (files
|
|
|
282
282
|
akm bundle remove <target> # Remove by id, ref, path, or name
|
|
283
283
|
akm bundle update --all # Update all managed sources
|
|
284
284
|
akm bundle update <target> --force # Force re-download
|
|
285
|
-
akm bundle update <target> --allow-dangerous-env-keys # Approve reviewed dangerous env keys
|
|
286
285
|
akm bundle update --all --skip-if-locked # Scheduled refresh; exit 0 on DB contention
|
|
287
286
|
```
|
|
288
287
|
|
|
@@ -15,9 +15,8 @@ For workflow tasks:
|
|
|
15
15
|
2. `akm workflow run workflows/<name> --param_name=value` — execute every step and verification gate
|
|
16
16
|
3. `akm workflow status <run-id> --units` — inspect a stopped or failed run
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
and `workflow list` operate within the current scope only.
|
|
18
|
+
A workflow ref (`workflows/<name>`) starts a new run; a run id drives that exact
|
|
19
|
+
run. `workflow list` and `workflow status` see every run on this host.
|
|
21
20
|
|
|
22
21
|
## Quick Reference
|
|
23
22
|
|
|
@@ -29,7 +28,7 @@ akm search "memories/projectA/" # List a subtree (conceptId prefix
|
|
|
29
28
|
akm search "<query>" --from all # Also search registries
|
|
30
29
|
akm show <ref> # View asset details
|
|
31
30
|
akm help <command> # Show a command's options and subcommands
|
|
32
|
-
akm workflow run <ref> # Start
|
|
31
|
+
akm workflow run <ref> # Start a new run and execute it
|
|
33
32
|
akm remember "Deployment needs VPN access" # Record a memory in your bundle
|
|
34
33
|
akm remember "note" --bundle my-bundle # Route write to a named writable bundle source
|
|
35
34
|
akm remember "note" --xref knowledge/auth-flow # Cite provenance in frontmatter xrefs (repeatable)
|
|
@@ -4,7 +4,10 @@ holds a lesson and, if it does, write the lesson.
|
|
|
4
4
|
|
|
5
5
|
A memory holds a lesson when it states a cause and what to do about it: a
|
|
6
6
|
failure or surprise with its cause and the fix that worked, or a rule with the
|
|
7
|
-
reason it holds.
|
|
7
|
+
reason it holds. A measured or tested result also holds a lesson when someone
|
|
8
|
+
could reuse it, even with no fix: what was found, under which conditions, and
|
|
9
|
+
the limits the memory states (what was not checked, not confirmed, or only
|
|
10
|
+
held in one place). It holds a lesson even when it is short and names one project,
|
|
8
11
|
tool or incident, if the cause and the fix would help someone in a similar
|
|
9
12
|
situation.
|
|
10
13
|
|
|
@@ -13,9 +16,15 @@ or decided, what is pending or planned, how a system is set up now, or the steps
|
|
|
13
16
|
of a procedure, with no failure and cause behind it, or when its feedback says
|
|
14
17
|
only that it is out of date or superseded. ANSWER NONE then: the single word and
|
|
15
18
|
nothing else. A reply bound to a JSON schema answers NONE by
|
|
16
|
-
setting `decision` to `none` and leaving the other fields empty.
|
|
19
|
+
setting `decision` to `none` and leaving the other fields empty. A status on
|
|
20
|
+
a date (blocked, waiting on someone, pending approval, ready to ship) is not a
|
|
21
|
+
result, and neither is the report of one run or day (counts passed and failed,
|
|
22
|
+
durations, which items failed) whose cause is only suspected, with no fix
|
|
23
|
+
verified: answer NONE, even if a rule could be drawn from it. A result that holds a lesson is a finding about a change or a method (what
|
|
24
|
+
it did, under which conditions), not an account of how one run went. Answer NONE too
|
|
17
25
|
when a related asset listed below the memory already states the rule the memory
|
|
18
|
-
would give
|
|
26
|
+
would give, including when the memory is a recap or a list of reminders whose
|
|
27
|
+
rules a related asset already states.
|
|
19
28
|
|
|
20
29
|
When the memory holds a lesson, write it from what the memory and its feedback
|
|
21
30
|
say, and nothing more:
|
|
@@ -23,7 +32,11 @@ say, and nothing more:
|
|
|
23
32
|
- Keep the scope the memory has. A fix verified in one place is a fix for that
|
|
24
33
|
place, and what was not checked stays unchecked. One case is not "always" or
|
|
25
34
|
"never".
|
|
26
|
-
- Be shorter than the memory
|
|
35
|
+
- Be clearly shorter than the memory (about half its length; the description,
|
|
36
|
+
when_to_use and body together, not only the body), and write it in
|
|
37
|
+
your own words. Leave out the incident's
|
|
38
|
+
story; keep only the rule, its cause and its limits. Keep the memory's
|
|
39
|
+
numbers, names, versions and terms exactly as written.
|
|
27
40
|
|
|
28
41
|
YOUR RESPONSE MUST START EXACTLY WITH `---` ON THE VERY FIRST LINE.
|
|
29
42
|
DO NOT output any prose, explanation, or code fences before or after.
|
|
@@ -34,7 +47,7 @@ description: <one complete sentence (ending with `.`) summarising what the lesso
|
|
|
34
47
|
when_to_use: <one complete sentence describing the concrete trigger condition>
|
|
35
48
|
---
|
|
36
49
|
|
|
37
|
-
<lesson body —
|
|
50
|
+
<lesson body — at most three sentences that add what the description lacks (the cause, the facts, the limits); never repeat the description>
|
|
38
51
|
|
|
39
52
|
## description field (MANDATORY)
|
|
40
53
|
- A single complete sentence in present tense, 20–400 chars, NO markdown.
|
|
@@ -6,7 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { adapterForId } from "../../core/adapter/registry.js";
|
|
8
8
|
import { parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
9
|
-
import { isWithin } from "../../core/common.js";
|
|
9
|
+
import { isContainedResolvedPath, isWithin } from "../../core/common.js";
|
|
10
10
|
import { loadConfig } from "../../core/config/config.js";
|
|
11
11
|
import { bundleComponentConfig, bundlesToSourceEntries } from "../../core/config/config-sources.js";
|
|
12
12
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
@@ -55,7 +55,7 @@ function isLexicallyWithin(candidate, root) {
|
|
|
55
55
|
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
56
56
|
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
57
57
|
}
|
|
58
|
-
function implicitComponentForEntry(entry, config
|
|
58
|
+
function implicitComponentForEntry(entry, config) {
|
|
59
59
|
const sources = resolveSourceEntries(undefined, config);
|
|
60
60
|
const installations = deriveInstallations(sources);
|
|
61
61
|
for (const [index, installation] of installations.entries()) {
|
|
@@ -66,9 +66,6 @@ function implicitComponentForEntry(entry, config, realRoot) {
|
|
|
66
66
|
if (!source || !component)
|
|
67
67
|
continue;
|
|
68
68
|
const canonicalRoot = realDirectory(component.root, `Canonical implicit source root for ${entry.bundleId}`);
|
|
69
|
-
if (canonicalRoot !== realRoot) {
|
|
70
|
-
warnOnce(`execution-source-root-drift:${entry.itemRef}`, `[command] Indexed root for ${JSON.stringify(entry.itemRef)} no longer matches the working bundle source; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
|
|
71
|
-
}
|
|
72
69
|
if (component.adapter !== entry.adapterId) {
|
|
73
70
|
warnOnce(`execution-source-adapter-drift:${entry.itemRef}`, `[command] Indexed adapter ${JSON.stringify(entry.adapterId)} for ${JSON.stringify(entry.itemRef)} no longer matches the working source's adapter ${JSON.stringify(component.adapter)}; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
|
|
74
71
|
}
|
|
@@ -84,7 +81,7 @@ function implicitComponentForEntry(entry, config, realRoot) {
|
|
|
84
81
|
function componentForEntry(entry, config, realRoot) {
|
|
85
82
|
const configured = config.bundles?.[entry.bundleId];
|
|
86
83
|
if (!configured) {
|
|
87
|
-
const implicit = implicitComponentForEntry(entry, config
|
|
84
|
+
const implicit = implicitComponentForEntry(entry, config);
|
|
88
85
|
if (implicit)
|
|
89
86
|
return implicit;
|
|
90
87
|
throw new ConfigError(`Indexed execution source ${JSON.stringify(entry.itemRef)} belongs to unconfigured bundle ${JSON.stringify(entry.bundleId)}.`, "INVALID_CONFIG_FILE", "Refresh the bundle configuration and rebuild the index before dispatching this asset.");
|
|
@@ -116,9 +113,6 @@ function componentForEntry(entry, config, realRoot) {
|
|
|
116
113
|
if (!isWithin(configuredRoot, configuredSourceRoot)) {
|
|
117
114
|
throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Remove the escaping symlink or component path, then run `akm index --full`.");
|
|
118
115
|
}
|
|
119
|
-
if (configuredRoot !== realRoot) {
|
|
120
|
-
warnOnce(`execution-source-root-drift:${entry.itemRef}`, `[command] Indexed root for ${JSON.stringify(entry.itemRef)} no longer matches the ${JSON.stringify(configuredSource.type)} bundle source; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
|
|
121
|
-
}
|
|
122
116
|
}
|
|
123
117
|
const writable = component?.writable ?? configured.writable ?? configured.path !== undefined;
|
|
124
118
|
return Object.freeze({
|
|
@@ -131,8 +125,8 @@ function componentForEntry(entry, config, realRoot) {
|
|
|
131
125
|
function expectedIndexedType(kind) {
|
|
132
126
|
return kind === "command" ? "command" : "agent";
|
|
133
127
|
}
|
|
134
|
-
function assertRenderedIdentity(source, entry,
|
|
135
|
-
const expectedFile = path.relative(
|
|
128
|
+
function assertRenderedIdentity(source, entry, content) {
|
|
129
|
+
const expectedFile = path.relative(path.resolve(entry.stashDir), path.resolve(entry.filePath)).replace(/\\/g, "/");
|
|
136
130
|
const expectedHash = createHash("sha256").update(content, "utf8").digest("hex");
|
|
137
131
|
if (source.identity.ref !== entry.itemRef ||
|
|
138
132
|
source.identity.bundle !== entry.bundleId ||
|
|
@@ -157,8 +151,10 @@ export async function loadAdapterExecutionSource(refInput, expectedKind, options
|
|
|
157
151
|
}
|
|
158
152
|
const config = await effectiveConfig(options.config);
|
|
159
153
|
const realRoot = realDirectory(entry.stashDir, `Indexed component root for ${entry.itemRef}`);
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
realRegularFile(entry.filePath, `Indexed execution source ${entry.itemRef}`);
|
|
155
|
+
// An index row must name a file inside its own component root. Checked on the
|
|
156
|
+
// authored path, so a file that is a symlink to one elsewhere still loads.
|
|
157
|
+
if (!isContainedResolvedPath(path.resolve(entry.filePath), path.resolve(entry.stashDir))) {
|
|
162
158
|
throw new ConfigError(`Indexed execution source ${JSON.stringify(entry.itemRef)} resolves outside its component root.`, "INVALID_CONFIG_FILE", "Remove the escaping path and rebuild the index.");
|
|
163
159
|
}
|
|
164
160
|
const component = componentForEntry(entry, config, realRoot);
|
|
@@ -174,6 +170,6 @@ export async function loadAdapterExecutionSource(refInput, expectedKind, options
|
|
|
174
170
|
if (!rendered || rendered.kind !== expectedKind) {
|
|
175
171
|
throw new ConfigError(`Bundle adapter ${JSON.stringify(entry.adapterId)} did not render ${JSON.stringify(entry.itemRef)} as a ${expectedKind}.`, "INVALID_CONFIG_FILE", "Rebuild the index; if the mismatch remains, repair the source file or owning adapter.");
|
|
176
172
|
}
|
|
177
|
-
assertRenderedIdentity(rendered, entry,
|
|
173
|
+
assertRenderedIdentity(rendered, entry, fileContext.content());
|
|
178
174
|
return rendered;
|
|
179
175
|
}
|
|
@@ -9,37 +9,43 @@
|
|
|
9
9
|
* REUSES the exact same machinery instead of forking it. Every safety
|
|
10
10
|
* invariant carries over unchanged:
|
|
11
11
|
*
|
|
12
|
-
* - `${
|
|
13
|
-
* secret
|
|
14
|
-
*
|
|
15
|
-
* third-party-sourced stashes and warned about for first-party ones.
|
|
12
|
+
* - `${ref}` tokens resolve through the one ref walk (a `secrets/` ref to the
|
|
13
|
+
* secret's trimmed value, any other asset ref to its absolute path); a
|
|
14
|
+
* missing ref is a hard error and NOTHING is injected (no partial injection).
|
|
16
15
|
* - An `env_access` audit event records key NAMES only, never values.
|
|
17
16
|
* - Resolved values must never be written to stdout or logs by callers.
|
|
18
17
|
*/
|
|
19
|
-
import
|
|
20
|
-
import path from "node:path";
|
|
21
|
-
import { decideDangerousEnvInjection } from "../../core/activation-policy.js";
|
|
22
|
-
import { assetPathForName } from "../../core/asset/asset-placement.js";
|
|
23
|
-
import { isWithin } from "../../core/common.js";
|
|
24
|
-
import { makeEnvRef, makeSecretRef, resolveEnvPath } from "../../core/env-secret-ref.js";
|
|
18
|
+
import { isInstalledBundle, makeEnvRef, resolveEnvPath, resolveRefOwner } from "../../core/env-secret-ref.js";
|
|
25
19
|
import { NotFoundError, UsageError } from "../../core/errors.js";
|
|
26
20
|
import { appendEvent } from "../../core/events.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
21
|
+
import { substituteRefTokens } from "../../core/ref-token.js";
|
|
22
|
+
import { readValue } from "../../core/secret-value.js";
|
|
23
|
+
import { warnOnce } from "../../core/warn.js";
|
|
24
|
+
import { loadEnv } from "./env.js";
|
|
25
|
+
/** The retired `${secret:NAME}` env-value token (see `resolveEnvBinding`). */
|
|
26
|
+
const LEGACY_SECRET_TOKEN_RE = /\$\{secret:([^}\s]+)\}/g;
|
|
27
|
+
/**
|
|
28
|
+
* What a `${ref}` token stands for: a `secrets/` ref yields the secret's
|
|
29
|
+
* trimmed value, any other asset ref its absolute path, a ref no bundle owns
|
|
30
|
+
* `undefined`. The one resolver behind env values and exec argv.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveRefToken(ref) {
|
|
33
|
+
const owner = resolveRefOwner(ref);
|
|
34
|
+
if (owner === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
return owner.type === "secret"
|
|
37
|
+
? { kind: "secret", value: readValue(owner.absPath) }
|
|
38
|
+
: { kind: "path", path: owner.absPath };
|
|
39
|
+
}
|
|
30
40
|
/**
|
|
31
41
|
* Resolve an env ref to its injectable values: load the file, apply key
|
|
32
|
-
* filtering, substitute `${
|
|
33
|
-
* directory, enforce the dangerous-key policy, and append the keys-only
|
|
42
|
+
* filtering, substitute `${ref}` tokens, and append the keys-only
|
|
34
43
|
* `env_access` audit event.
|
|
35
44
|
*/
|
|
36
45
|
export function resolveEnvBinding(target, options = {}) {
|
|
37
46
|
const warn = options.warn ?? ((message) => process.stderr.write(`warning: ${message}\n`));
|
|
38
|
-
const { name, absPath, source } = resolveEnvPath(target);
|
|
47
|
+
const { name, absPath, source, bundleId } = resolveEnvPath(target);
|
|
39
48
|
const envRef = makeEnvRef(name, source);
|
|
40
|
-
if (!fs.existsSync(absPath)) {
|
|
41
|
-
throw new NotFoundError(`Env not found: ${envRef}`);
|
|
42
|
-
}
|
|
43
49
|
const allValues = loadEnv(absPath);
|
|
44
50
|
// Value-safe key filtering (--only / --except operate on key NAMES only).
|
|
45
51
|
let envValues = allValues;
|
|
@@ -58,49 +64,32 @@ export function resolveEnvBinding(target, options = {}) {
|
|
|
58
64
|
const excluded = new Set(options.except);
|
|
59
65
|
envValues = Object.fromEntries(Object.entries(allValues).filter(([k]) => !excluded.has(k)));
|
|
60
66
|
}
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
// Read-compat: env files written for 0.9 / early 0.10 spell a secret
|
|
68
|
+
// `${secret:NAME}`, meaning `secrets/NAME` in this env asset's own bundle.
|
|
69
|
+
// Read it as the `${ref}` it stands for; the file is bundle content and is
|
|
70
|
+
// never rewritten.
|
|
71
|
+
let firstLegacy;
|
|
72
|
+
envValues = Object.fromEntries(Object.entries(envValues).map(([key, value]) => [
|
|
73
|
+
key,
|
|
74
|
+
value.replace(LEGACY_SECRET_TOKEN_RE, (_token, secretName) => {
|
|
75
|
+
const spelling = `\${${bundleId}//secrets/${secretName}}`;
|
|
76
|
+
firstLegacy ??= spelling;
|
|
77
|
+
return spelling;
|
|
78
|
+
}),
|
|
79
|
+
]));
|
|
80
|
+
if (firstLegacy !== undefined) {
|
|
81
|
+
warnOnce(`legacy-secret-token:${envRef}`, `Env "${envRef}" uses the retired \${secret:NAME} spelling; reading it as ${firstLegacy}. Write the full ref in the env file.`);
|
|
82
|
+
}
|
|
83
|
+
// Substitute `${ref}` tokens. A missing ref is a hard error — inject NOTHING.
|
|
84
|
+
const { values: substituted, missing, secretValues, } = substituteRefTokens(envValues, resolveRefToken, isInstalledBundle);
|
|
76
85
|
if (missing.length > 0) {
|
|
77
|
-
throw new NotFoundError(`Env "${envRef}" references
|
|
86
|
+
throw new NotFoundError(`Env "${envRef}" references asset(s) that no bundle owns: ${missing.join(", ")}. Nothing was injected.`, "FILE_NOT_FOUND",
|
|
87
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: literal `${ref}` token syntax documented for users, not interpolation
|
|
88
|
+
"Write each `${ref}` as a full akm ref that exists, e.g. `${secrets/name}` or `${team//scripts/run.sh}`.");
|
|
78
89
|
}
|
|
79
90
|
envValues = substituted;
|
|
80
91
|
const keys = Object.keys(envValues);
|
|
81
|
-
// Scan injected keys for known process-hijacking variables. The workspace
|
|
82
|
-
// activation policy decides block (third-party, registryId set) vs. warn
|
|
83
|
-
// (first-party stash, where the operator owns the file).
|
|
84
|
-
const dangerous = keys.filter(isDangerousEnvKey);
|
|
85
|
-
if (dangerous.length > 0) {
|
|
86
|
-
const detail = `Env "${envRef}" injects process-hijacking variable(s): ${dangerous.join(", ")}.`;
|
|
87
|
-
const decision = decideDangerousEnvInjection({
|
|
88
|
-
dangerousKeys: dangerous,
|
|
89
|
-
thirdParty: Boolean(source.registryId),
|
|
90
|
-
allowDangerousEnvKeys: options.allowDangerousEnvKeys,
|
|
91
|
-
});
|
|
92
|
-
if (decision === "block") {
|
|
93
|
-
throw new UsageError(`Refusing to inject env from a third-party stash. ${detail}\n` +
|
|
94
|
-
` Review the file, then copy the values into a first-party env if you trust them, ` +
|
|
95
|
-
`or pass --allow-dangerous-env-keys once you have.`, "INVALID_FLAG_VALUE");
|
|
96
|
-
}
|
|
97
|
-
if (decision === "warn") {
|
|
98
|
-
warn(options.allowDangerousEnvKeys && source.registryId
|
|
99
|
-
? `${detail} Injecting anyway (--allow-dangerous-env-keys).`
|
|
100
|
-
: `${detail} Injecting anyway (first-party stash).`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
92
|
// Audit trail: keys only, never values.
|
|
104
93
|
appendEvent({ eventType: "env_access", ref: envRef, metadata: { keys } });
|
|
105
|
-
return { ref: envRef, values: envValues, keys };
|
|
94
|
+
return { ref: envRef, values: envValues, keys, secretValues };
|
|
106
95
|
}
|