akm docs

AKM Manual Testing Runbook

This is the authoritative manual QA document for AKM 0.9.x. Other testing documents describe automation and internals; this document owns manual test scope, setup, expected behavior, evidence, and release-facing gates.

Last audited against the implementation: 2026-08-05 (0.9.0-rc.15).

Table of Contents


1. Purpose and Use

1.1 Scope

This runbook covers:

This is a catalog, not an instruction to run every gate for every change. Use Change-Based Test Selection to choose scope.

1.2 Test tiers

Every check carries one or more labels.

Label Meaning Typical time Token/network cost
[CORE] Fast deterministic offline gate 25-35 min None
[LOCAL] Extended local filesystem/process gate +45-75 min None
[SERVICE] Controlled loopback fixture services +15-25 min None
[LIVE] Controlled public git/npm/HTTPS service +15-30 min Network only
[AI] Fake or bounded real agent/LLM checks +15-30 min Fake: none; real: bounded
[PLATFORM] Native OS/runtime/install checks Per platform Runner/VM time
[DESTRUCTIVE] Crash, migration, and recovery rehearsal 1-3 hr Disposable VM only
[RELEASE] Exact package and release-artifact acceptance 1-3 hr CI/VM/network

1.3 Result states

Use these states exactly:

Status Meaning
PASS Behavior, exit code, output, and side effects match the stated expectation.
FAIL Any observable result differs.
BLOCKED A named prerequisite is unavailable. Record it.
N/A The target platform genuinely does not support the surface.

Do not turn an implementation defect into an expected result. Some adversarial gates below intentionally expose unresolved behavior. A release waiver must name the issue, impact, owner, and expiry.

1.4 Evidence header

Record this once per run:

commit:
package version:
artifact digest (when applicable):
OS / architecture:
Bun / Node versions:
CLI invocation path:
sandbox path:
tiers run:
started / finished UTC:
tester:

For every failure retain the command, exit code, stdout, stderr, relevant file or database diff, and whether a second identical run reproduced it. Never attach real credentials or unredacted env/secret payloads.


2. Safety and Prerequisites

2.1 Non-negotiable safety rules

The sandbox isolates AKM state. bun install and bun run build still operate inside the checkout, so release QA should use a disposable worktree and start from a clean git status.

2.2 Prerequisites

[CORE] and [LOCAL] require Bash, Bun, Node.js 22+, Git, jq, standard POSIX filesystem tools, and the repository checkout.

Gate Additional prerequisite
YAML parsing Repository yaml dependency
Git source/sync Disposable remote or local no-push repository
npm source Disposable package/version or controlled registry
Production website Controlled public HTTPS hostname; loopback is blocked
Local semantic model Cold-download network, disk, isolated HF_HOME
Docker Docker/BuildKit and base-image network access
Native scheduler Disposable OS account or VM
Release artifact Exact candidate tarball/binaries/checksums and native runners

2.3 Automated baseline before manual release QA

git status --short
bun install --frozen-lockfile
bun run check
bun run build
test -x dist/akm
test ! -e dist/tests

3. Sandbox Setup

3.1 Create the sandbox

Run from the repository root. The shell function is deliberate; it is more reliable than an alias in copied scripts.

set -u
umask 077

export REPO="$(git rev-parse --show-toplevel)"
export AKM_BIN="$REPO/dist/akm"
export AKM_SANDBOX="$(mktemp -d /tmp/akm-sandbox.XXXXXX)"

export HOME="$AKM_SANDBOX/home"
export XDG_CONFIG_HOME="$AKM_SANDBOX/xdg-config"
export XDG_CACHE_HOME="$AKM_SANDBOX/xdg-cache"
export XDG_DATA_HOME="$AKM_SANDBOX/xdg-data"
export XDG_STATE_HOME="$AKM_SANDBOX/xdg-state"
export AKM_CONFIG_DIR="$AKM_SANDBOX/config"
export AKM_CACHE_DIR="$AKM_SANDBOX/cache"
export AKM_DATA_DIR="$AKM_SANDBOX/data"
export AKM_STATE_DIR="$AKM_SANDBOX/state"
export AKM_BUNDLE_DIR="$AKM_SANDBOX/bundle"
export TMPDIR="$AKM_SANDBOX/tmp"
export TMP="$TMPDIR"
export TEMP="$TMPDIR"
export HF_HOME="$AKM_SANDBOX/huggingface"
export GH_CONFIG_DIR="$AKM_SANDBOX/gh"
export GIT_CONFIG_GLOBAL="$AKM_SANDBOX/gitconfig"
export GIT_CONFIG_NOSYSTEM=1
export NPM_CONFIG_USERCONFIG="$AKM_SANDBOX/npmrc"
export npm_config_cache="$AKM_SANDBOX/npm-cache"
export AKM_FORCE_SETUP_TMP_STASH=1
export AKM_FORCE_INIT_TMP_STASH=1

unset AKM_VERBOSE AKM_DEBUG AKM_REGISTRY_URL AKM_NPM_REGISTRY
unset AKM_LLM_API_KEY AKM_EMBED_API_KEY AKM_NO_AUTO_MIGRATE
unset AKM_EMBED_DETERMINISTIC AKM_UPGRADE_SKIP_CHECKSUM
unset GITHUB_TOKEN GH_TOKEN OPENAI_API_KEY ANTHROPIC_API_KEY
unset OPENCODE_API_KEY X_BEARER_TOKEN X_RSS_TEMPLATE

mkdir -p \
  "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" \
  "$XDG_STATE_HOME" "$AKM_CONFIG_DIR" "$AKM_CACHE_DIR" \
  "$AKM_DATA_DIR" "$AKM_STATE_DIR" "$AKM_BUNDLE_DIR" "$TMPDIR" \
  "$HF_HOME" "$GH_CONFIG_DIR" "$npm_config_cache"

akm() { "$AKM_BIN" "$@"; }

For source-entrypoint testing only:

akm() { bun "$REPO/src/cli.ts" "$@"; }

3.2 Verify isolation before mutation

config path --all is a read-only recovery surface and works before config exists.

akm config path --all --format json >"$AKM_SANDBOX/paths.json"
jq -e --arg root "$AKM_SANDBOX/" \
  'all(to_entries[]; (.value | tostring | startswith($root)))' \
  "$AKM_SANDBOX/paths.json"

Stop immediately if a path escapes the sandbox.

3.3 Deterministic non-interactive setup

Do not use setup --yes to seed ranking checks. It can perform host-dependent environment/agent detection. Avoid scaffolding so fixture counts remain exact.

akm setup \
  --config '{"semanticSearchMode":"off","registries":[]}' \
  --dir "$AKM_BUNDLE_DIR" \
  --no-init \
  --format json >"$AKM_SANDBOX/setup.json"

jq -e --arg dir "$AKM_BUNDLE_DIR" \
  '.written == true and .bundleDir == $dir' \
  "$AKM_SANDBOX/setup.json"
test ! -e "$AKM_BUNDLE_DIR/skills"

3.4 Classified-error helper

Use this only for thrown/classified failures. --quiet keeps stderr to one JSON envelope.

expect_error() {
  expected_status="$1"
  expected_code="$2"
  shift 2

  set +e
  akm --quiet "$@" \
    >"$AKM_SANDBOX/error.stdout" \
    2>"$AKM_SANDBOX/error.stderr"
  actual_status=$?
  set -e

  test "$actual_status" -eq "$expected_status"
  test ! -s "$AKM_SANDBOX/error.stdout"
  jq -e --arg code "$expected_code" \
    '.ok == false and .code == $code and (.error | type == "string")' \
    "$AKM_SANDBOX/error.stderr"
}

Do not use this helper for health warnings/failures, lint findings, workflow/task/agent domain failures, or blocked migration plans. Those emit a normal result on stdout with a nonzero status.

3.5 Byte snapshot helper

snapshot_tree() {
  root="$1"
  output="$2"
  (
    cd "$root"
    find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum
  ) >"$output"
}

On platforms without sha256sum, use the native SHA-256 utility and record it. For failed stateful commands, snapshot config, lockfile, bundle, cache, data, state, and event cursor, not only asset files.


4. Fixtures and Local Services

4.1 Exact ranking baseline

Copy fixture contents, including dotfiles, without setup scaffolding:

cp -R "$REPO/tests/fixtures/stashes/ranking-baseline/." "$AKM_BUNDLE_DIR/"

for entry in agents commands knowledge skills MANIFEST.json; do
  test -e "$AKM_BUNDLE_DIR/$entry"
done
test ! -e "$AKM_BUNDLE_DIR/scripts"

akm index --full --format json >"$AKM_SANDBOX/index-baseline.json"
jq -e '.totalEntries == 18 and .verification.ok == true' \
  "$AKM_SANDBOX/index-baseline.json"

akm info --format json | jq -e '
  .indexStats.entryCount == 18 and
  .indexStats.byType == {
    "skill": 4,
    "knowledge": 10,
    "command": 2,
    "agent": 2
  }
'

Stable refs:

Type Ref
skill skills/k8s-deploy
skill skills/docker-homelab
knowledge knowledge/incident-response-runbook
agent agents/code-reviewer
command commands/release-manager

4.2 Supplemental manual-QA bundle

Copy this only after exact ranking assertions:

cp -R "$REPO/tests/fixtures/manual-qa/bundle/." "$AKM_BUNDLE_DIR/"
akm index
akm show knowledge/qa-guide | jq -e '.name == "qa-guide"'
akm show workflows/typed-route | jq -e '.type == "workflow"'
akm show tasks/manual-success | jq -e '.type == "task"'

It supplies .meta, typed/gated workflows, command tasks, and fake-agent input assets. It contains no credentials.

4.3 Start controlled fixture services

The service binds only to 127.0.0.1, records route/model/count/presence metadata, and exposes chat, embedding, registry, and website routes.

export AKM_QA_SERVICE_META="$AKM_SANDBOX/service.json"
export AKM_QA_SERVICE_LOG="$AKM_SANDBOX/service-requests.jsonl"
export AKM_QA_SITE_VERSION="$AKM_SANDBOX/site-version.txt"
printf 'v1\n' >"$AKM_QA_SITE_VERSION"

bun "$REPO/tests/fixtures/manual-qa/fake-services.ts" \
  "$AKM_QA_SERVICE_META" \
  "$AKM_QA_SERVICE_LOG" \
  "$AKM_QA_SITE_VERSION" \
  >"$AKM_SANDBOX/service.stdout" \
  2>"$AKM_SANDBOX/service.stderr" &
export AKM_QA_SERVICE_PID=$!

for _ in $(seq 1 100); do
  test -s "$AKM_QA_SERVICE_META" && break
  kill -0 "$AKM_QA_SERVICE_PID"
  sleep 0.05
done
test -s "$AKM_QA_SERVICE_META"

export AKM_QA_BASE_URL="$(jq -r .baseUrl "$AKM_QA_SERVICE_META")"
export AKM_QA_CHAT_URL="$(jq -r .chat.ok "$AKM_QA_SERVICE_META")"
export AKM_QA_PROPOSAL_URL="$(jq -r .chat.proposal "$AKM_QA_SERVICE_META")"
export AKM_QA_EMBED_URL="$(jq -r .embeddings "$AKM_QA_SERVICE_META")"
export AKM_QA_REGISTRY_URL="$(jq -r .registry "$AKM_QA_SERVICE_META")"
export AKM_QA_WEBSITE_URL="$(jq -r .website "$AKM_QA_SERVICE_META")"

Loopback website ingestion needs command-scoped NODE_ENV=test because production correctly blocks private hosts. That proves crawler protocol, not production SSRF behavior.

4.4 Configure fake agent engines

export AKM_QA_BUN="$(command -v bun)"
export AKM_QA_FAKE_AGENT="$REPO/tests/fixtures/manual-qa/fake-agent.ts"

agent_engine_json() {
  mode="$1"
  shift
  extra='[]'
  if test "$#" -gt 0; then
    extra="$(printf '%s\n' "$@" | jq -R . | jq -s .)"
  fi
  jq -nc \
    --arg bin "$AKM_QA_BUN" \
    --arg script "$AKM_QA_FAKE_AGENT" \
    --arg mode "$mode" \
    --argjson extra "$extra" \
    '{kind:"agent",platform:"opencode",bin:$bin,args:([$script,$mode] + $extra)}'
}

akm config set engines.qa-agent "$(agent_engine_json success)" >/dev/null
akm config set engines.qa-agent-fail "$(agent_engine_json fail)" >/dev/null
akm config set engines.qa-agent-capture "$(agent_engine_json capture)" >/dev/null
akm config set engines.qa-agent-secret "$(agent_engine_json echo-secret)" >/dev/null
akm config set engines.qa-judge-pass "$(agent_engine_json judge-pass)" >/dev/null
akm config set engines.qa-judge-reject "$(agent_engine_json judge-reject)" >/dev/null
akm config set defaults.engine qa-agent >/dev/null
akm config set workflow.judgeEngine qa-judge-pass >/dev/null

export AKM_QA_SIGNAL_MARKER="$AKM_SANDBOX/fake-agent.signal"
akm config set engines.qa-agent-sleep \
  "$(agent_engine_json sleep 10000 "$AKM_QA_SIGNAL_MARKER")" >/dev/null
akm config set engines.qa-agent-sleep.timeoutMs 100 >/dev/null

4.5 Configure fake LLM and embedding engines

Run after starting fixture services:

export QA_LLM_KEY='manual-qa-not-a-real-secret'

akm config set engines.qa-chat "$(jq -nc \
  --arg endpoint "$AKM_QA_CHAT_URL" \
  '{kind:"llm",endpoint:$endpoint,model:"qa-model",apiKey:"$QA_LLM_KEY",timeoutMs:1000}')" >/dev/null
akm config set defaults.llmEngine qa-chat >/dev/null

akm config set embedding "$(jq -nc \
  --arg endpoint "$AKM_QA_EMBED_URL" \
  '{provider:"openai",endpoint:$endpoint,model:"qa-embedding",dimension:4,batchSize:4}')" >/dev/null

4.6 Seed proposal cases

bun "$REPO/tests/fixtures/manual-qa/seed-proposals.ts" \
  "$AKM_BUNDLE_DIR" >"$AKM_SANDBOX/proposals.json"
akm index
jq -e '.update.id and .newAsset.id and .emptyDiff.id and .defer.id' \
  "$AKM_SANDBOX/proposals.json"

The seeder creates a fixed update, new asset, empty diff, and deferred candidate. It is destructive and must target only the sandbox.


5. Fast Offline Pass

The fast gate uses no network, model download, provider token, agent process, scheduler, or migration. Run:

Order Required coverage
1 Sections 3.1-3.3: sandbox and deterministic setup
2 Section 4.1: exact ranking baseline
3 Section 6.1: version/root/help surface
4 Sections 7.1-7.4: index/search/curate/show
5 Sections 8.1-8.5: representative formats and errors
6 Sections 10.1-10.3: local remember/import writes
7 Sections 11.1-11.3: env/secret redaction
8 Section 12.1: lint detect/fix/idempotence
9 Section 13.1: feedback/log smoke

Success criteria:


6. Startup, Setup, Help, and Config

6.1 Version, root help, and command help

test "$(akm --version)" = "$(jq -r .version "$REPO/package.json")"
akm --help >"$AKM_SANDBOX/root-help.txt"

for command in \
  setup index health info bundle upgrade search curate show workflow \
  remember import sync clone registry migrate config feedback log agent \
  lint improve proposal help hints completions env secret task; do
  test "$(grep -Ec "^  ${command}[[:space:]]{2,}" "$AKM_SANDBOX/root-help.txt")" -eq 1

6.2 Non-interactive setup

6.3 Interactive setup

Use a real TTY in the sandbox.

6.4 Config management

akm config list --format json | jq -e '.bundles and .defaultBundle'
akm config get semanticSearchMode | jq -e '. == "off"'
akm config set semanticSearchMode auto --silent >"$AKM_SANDBOX/silent.out"
test ! -s "$AKM_SANDBOX/silent.out"
akm config unset semanticSearchMode --silent >"$AKM_SANDBOX/silent.out"
test ! -s "$AKM_SANDBOX/silent.out"

6.5 Help migration notes

6.6 Completions

akm completions --format yaml \
  >"$AKM_SANDBOX/akm-completion.bash" \
  2>"$AKM_SANDBOX/completion.stderr"
bash -n "$AKM_SANDBOX/akm-completion.bash"
grep -q "'--format' has no effect" "$AKM_SANDBOX/completion.stderr"

6.7 Upgrade check

upgrade --check is network-backed, not part of first-run/offline coverage.


7. Index, Search, Curate, and Show

7.1 Index

akm index --full --format json >"$AKM_SANDBOX/index-full.json"
akm index --verbose --format json \
  >"$AKM_SANDBOX/index-verbose.json" \
  2>"$AKM_SANDBOX/index-verbose.stderr"
jq -e . "$AKM_SANDBOX/index-verbose.json"
test -s "$AKM_SANDBOX/index-verbose.stderr"
akm search k8s-deploy \
  --detail full --format json |
  jq -e '
    .hits[0].ref == "skills/k8s-deploy" and
    .hits[0].type == "skill" and
    .hits[0].score >= 0 and .hits[0].score <= 1
  '

json_count="$(akm search docker | jq '.hits | length')"
jsonl_count="$(akm search docker --format jsonl | jq -s 'length')"
test "$json_count" -gt 0
test "$json_count" -eq "$jsonl_count"

7.3 Curate

akm curate "docker homelab" --detail full --format json |
  jq -e '.items[0].ref == "skills/docker-homelab" and (.summary | type == "string")'

7.4 Show, fragments, scope, and meta

akm show skills/k8s-deploy --format json |
  jq -e '
    .type == "skill" and
    .ref == "skills/k8s-deploy" and
    (.path | startswith("/")) and
    (.content | contains("Kubernetes Deployment"))
  '

akm show 'knowledge/incident-response-runbook#severity-levels' \
  --format json |
  jq -e '(.content | contains("SEV1")) and ((.content | contains("Escalation Path")) | not)'

8. Output, Errors, and Exit Codes

8.1 Exit and channel oracle

Outcome Exit Channel
Success 0 Normal payload on stdout
Not found/general failure 1 Classified stderr or domain stdout result
Usage/bad input 2 Structured JSON stderr
Health warning 4 Health stdout result
Unexpected throw 70 Structured internal stderr
Config error 78 Structured JSON stderr

Domain-result exceptions:

Command Nonzero behavior
health Warn 4, fail 1; result stays on stdout
lint --fail-on-flagged Exit 1; lint result stays on stdout
workflow run Failure/rejection/timeout emits workflow stdout result
agent Dispatch failure exits 1 with agent-result stdout
task run Blocked/failed 1, command config failure 78; child code in detail
migrate Child status preserved; progress can precede final formatted plan
env run / secret run Raw child streams and exact child status

8.2 Six formats

akm info --format json | jq -e .
akm info --format jsonl | jq -e .
akm info --format yaml | bun -e '
  import { parse } from "yaml";
  const value = parse(await Bun.stdin.text());
  if (!value?.version) process.exit(1);
'
akm info --format text >"$AKM_SANDBOX/info.txt"
akm info --format md >"$AKM_SANDBOX/info.md"
akm info --format html >"$AKM_SANDBOX/info.html"
grep -qi '<html' "$AKM_SANDBOX/info.html"

8.3 Shape, detail, and output destination

8.4 Format-exempt commands

completions, help, help migrate, env path, env run, and secret run do not emit normal result envelopes.

8.5 Error gauntlet

expect_error 2 MISSING_REQUIRED_ARGUMENT curate ""
expect_error 1 ASSET_NOT_FOUND show skills/does-not-exist
expect_error 2 UNKNOWN_FLAG info --totally-bogus
expect_error 2 INVALID_FORMAT_VALUE info --format xml
expect_error 2 INVALID_DETAIL_VALUE info --detail maximum
expect_error 2 INVALID_SHAPE_VALUE search docker --shape summary
expect_error 2 MISSING_REQUIRED_ARGUMENT help migrate
expect_error 2 INVALID_FLAG_VALUE completions --shell zsh

9. Bundles, Adapters, Sources, and Registries

9.1 Bundle create

Use a second sandbox or disposable secondary paths.

9.2 Identity, precedence, and enabled state

mkdir -p "$AKM_SANDBOX/bundle-a" "$AKM_SANDBOX/bundle-b"
akm bundle add "$AKM_SANDBOX/bundle-a" --name bundle-a
akm bundle add "$AKM_SANDBOX/bundle-b" --name bundle-b
akm remember "body A" --name shared --bundle bundle-a
akm remember "body B" --name shared --bundle bundle-b
akm show bundle-a//memories/shared | jq -e '.content | contains("body A")'
akm show bundle-b//memories/shared | jq -e '.content | contains("body B")'

9.3 Built-in adapter matrix

Adapter Fixture
website-snapshot tests/fixtures/bundles/website-snapshot
agent-skills tests/fixtures/bundles/agent-skills
claude tests/fixtures/bundles/claude
opencode tests/fixtures/bundles/opencode
dotenv tests/fixtures/bundles/dotenv
akm-workflow tests/fixtures/bundles/akm-workflow
akm-task tests/fixtures/bundles/akm-task
llm-wiki tests/fixtures/bundles/llm-wiki
akm tests/fixtures/stashes/ranking-baseline
okf tests/fixtures/bundles/okf-sample-v2
generic-files tests/fixtures/bundles/generic-files

For each adapter:

9.4 LLM Wiki adapter

cp -R "$REPO/tests/fixtures/bundles/llm-wiki" "$AKM_SANDBOX/sample-wiki"
akm bundle add "$AKM_SANDBOX/sample-wiki" --name sample-wiki
akm bundle show sample-wiki --format json
akm index --full
akm show sample-wiki//pages/http-caching
akm show sample-wiki//raw/2026-07-http-rfc
akm lint --dir "$AKM_SANDBOX/sample-wiki"

9.5 Filesystem source

mkdir -p "$AKM_SANDBOX/fs-source/knowledge"
printf '%s\n' '# Filesystem Marker' '' 'qa-filesystem-source-marker' \
  >"$AKM_SANDBOX/fs-source/knowledge/marker.md"
akm bundle add "$AKM_SANDBOX/fs-source" --name fs-source
akm bundle list --kind filesystem
akm search qa-filesystem-source-marker --from fs-source

9.6 Git source

Use a small disposable HTTPS/SSH repository. file:// is rejected by design.

9.7 npm source

Use a disposable package/version or controlled registry.

9.8 Website source

Production must reject loopback/private hosts:

expect_error 78 INVALID_CONFIG_FILE \
  bundle add "$AKM_QA_WEBSITE_URL" --name private-site --allow-insecure-transport

Protocol-only fixture:

NODE_ENV=test akm bundle add "$AKM_QA_WEBSITE_URL" \
  --name qa-site --allow-insecure-transport --max-pages 2 --max-depth 1
NODE_ENV=test akm search qa-site --from qa-site

9.9 Registry

expect_error 2 INVALID_FLAG_VALUE registry add "$AKM_QA_REGISTRY_URL" --name qa-registry
akm registry add "$AKM_QA_REGISTRY_URL" --name qa-registry --allow-insecure-transport
akm registry list | jq -e '.registries[] | select(.name == "qa-registry")'
akm search kubernetes --from registry --detail full
akm search kubernetes --from registry --assets --detail full
akm registry remove qa-registry --yes

9.10 Source failure atomicity and dangerous env

before_config="$(sha256sum "$AKM_CONFIG_DIR/config.json")"
set +e
akm bundle add "$REPO/tests/fixtures/manual-qa/dangerous-bundle" \
  --name qa-dangerous </dev/null
status=$?
set -e
test "$status" -eq 1
test "$(sha256sum "$AKM_CONFIG_DIR/config.json")" = "$before_config"
! akm bundle list --format json | jq -e '.sources[] | select(.name == "qa-dangerous")'

10. Writes, Import, Clone, and Sync

10.1 Remember

akm remember "test memory body" --name test-memory
akm show memories/test-memory | jq -e '.content | contains("test memory body")'
expect_error 1 RESOURCE_ALREADY_EXISTS remember "duplicate" --name test-memory
akm remember "replacement" --name test-memory --force
printf 'stdin body\n' | akm remember --name from-stdin

10.2 Target precedence

10.3 Import

cat >"$AKM_SANDBOX/incoming.md" <<'EOF'
---
description: Imported manual QA document
custom:
  nested: preserved
---
# Imported Guide

qa-import-marker
EOF

akm import "$AKM_SANDBOX/incoming.md" --name imported-guide
akm import - --name imported-stdin <"$AKM_SANDBOX/incoming.md"

10.4 Supersession

10.5 Clone

10.6 Sync

git -C "$AKM_BUNDLE_DIR" init
git -C "$AKM_BUNDLE_DIR" config user.name 'AKM Manual QA'
git -C "$AKM_BUNDLE_DIR" config user.email 'manual-qa@example.invalid'
git -C "$AKM_BUNDLE_DIR" add .
git -C "$AKM_BUNDLE_DIR" commit -m 'Manual QA baseline'

akm remember "sync marker" --name sync-marker
akm sync --no-push -m "Manual QA sync"
git -C "$AKM_BUNDLE_DIR" show --stat --oneline -1

10.7 Write concurrency, atomicity, and paths


11. Env and Secret

Use conspicuous dummy values only. AKM keeps env and secret values out of its structured results, but a child launched by env run or secret run owns its raw stdout/stderr and can print injected values.

11.1 Create, list, path, and indexing

export QA_SECRET_VALUE='manual-qa-secret-not-a-credential'

printf '%s\n' \
  'QA_PUBLIC=manual-qa-public' \
  'QA_SECOND=manual-qa-second' \
  'QA_TOKEN=Bearer ${secret:qa-token}' \
  >"$AKM_SANDBOX/qa-runtime.env"

akm env create qa-runtime \
  --from-file "$AKM_SANDBOX/qa-runtime.env" --format json \
  >"$AKM_SANDBOX/env-create.json"
printf '%s\n' "$QA_SECRET_VALUE" |
  akm secret set secrets/qa-token --format json \
    >"$AKM_SANDBOX/secret-set.json"

akm env list --format json >"$AKM_SANDBOX/env-list.json"
akm secret list --format json >"$AKM_SANDBOX/secret-list.json"
akm index
akm show env/qa-runtime --format json \
  >"$AKM_SANDBOX/env-show.json"
akm show secrets/qa-token --format json \
  >"$AKM_SANDBOX/secret-show.json"

jq -e 'any(.envs[]; .ref == "env/qa-runtime" and (.keys | index("QA_PUBLIC")))' \
  "$AKM_SANDBOX/env-list.json"
jq -e 'any(.secrets[]; .ref == "secrets/qa-token")' \
  "$AKM_SANDBOX/secret-list.json"

for capture in \
  "$AKM_SANDBOX/env-create.json" \
  "$AKM_SANDBOX/secret-set.json" \
  "$AKM_SANDBOX/env-list.json" \
  "$AKM_SANDBOX/secret-list.json" \
  "$AKM_SANDBOX/env-show.json" \
  "$AKM_SANDBOX/secret-show.json"; do
  ! grep -F "$QA_SECRET_VALUE" "$capture"
  ! grep -F 'manual-qa-public' "$capture"
done

11.2 Safe value use and raw child channels

export QA_INHERITED='manual-qa-inherited'

akm env run env/qa-runtime \
  --only QA_PUBLIC,QA_TOKEN --clean --inherit QA_INHERITED \
  -- bun -e '
    if (process.env.QA_PUBLIC !== "manual-qa-public") process.exit(11);
    if (process.env.QA_TOKEN !== "Bearer manual-qa-secret-not-a-credential") process.exit(12);
    if (process.env.QA_INHERITED !== "manual-qa-inherited") process.exit(13);
    if (process.env.QA_SECOND !== undefined) process.exit(14);
  '

akm secret run secrets/qa-token QA_INJECTED --clean \
  -- bun -e '
    if (process.env.QA_INJECTED !== "manual-qa-secret-not-a-credential") process.exit(15);
  '
test -z "${QA_INJECTED+x}"

set +e
akm env run env/qa-runtime -- bun -e 'process.exit(7)'
env_child_status=$?
akm secret run secrets/qa-token QA_INJECTED -- bun -e 'process.exit(9)'
secret_child_status=$?
set -e
test "$env_child_status" -eq 7
test "$secret_child_status" -eq 9

11.3 Export, overwrite, targets, and removal

printf 'QA_LITERAL=$(touch %s)\n' "$AKM_SANDBOX/export-payload-ran" \
  >"$AKM_SANDBOX/qa-export.env"
akm env create qa-export --from-file "$AKM_SANDBOX/qa-export.env"
akm env export env/qa-export --out "$AKM_SANDBOX/qa-export.sh" \
  --format json >"$AKM_SANDBOX/env-export.json"

test ! -e "$AKM_SANDBOX/export-payload-ran"
sh -c '. "$1"' sh "$AKM_SANDBOX/qa-export.sh"
test ! -e "$AKM_SANDBOX/export-payload-ran"

printf 'replacement-without-trailing-newline' |
  akm secret set secrets/qa-token
akm env remove env/qa-export --yes

11.4 Dangerous keys and redaction boundary


12. Lint

12.1 Detect, gate, fix, and repeat

Create a disposable native-AKM lint root:

export QA_LINT_DIR="$AKM_SANDBOX/lint-bundle"
mkdir -p "$QA_LINT_DIR/knowledge"
cat >"$QA_LINT_DIR/knowledge/fixable.md" <<'EOF'
---
description: Manual QA: fixable colon
---
# Fixable
EOF

akm lint --dir "$QA_LINT_DIR" --format json \
  >"$AKM_SANDBOX/lint-detect.json"
jq -e '.ok == true and .summary.flagged >= 1' \
  "$AKM_SANDBOX/lint-detect.json"

set +e
akm lint --dir "$QA_LINT_DIR" --fail-on-flagged --format json \
  >"$AKM_SANDBOX/lint-gate.json" \
  2>"$AKM_SANDBOX/lint-gate.stderr"
lint_gate_status=$?
set -e
test "$lint_gate_status" -eq 1
test ! -s "$AKM_SANDBOX/lint-gate.stderr"
jq -e '.ok == true and .summary.flagged >= 1' \
  "$AKM_SANDBOX/lint-gate.json"

akm lint --dir "$QA_LINT_DIR" --fix --format json \
  >"$AKM_SANDBOX/lint-fix.json"
jq -e '.summary.fixed >= 1' "$AKM_SANDBOX/lint-fix.json"

snapshot_tree "$QA_LINT_DIR" "$AKM_SANDBOX/lint-fixed.sha256"
akm lint --dir "$QA_LINT_DIR" --auto-fix --format json \
  >"$AKM_SANDBOX/lint-fix-second.json"
jq -e '.summary.fixed == 0' "$AKM_SANDBOX/lint-fix-second.json"
snapshot_tree "$QA_LINT_DIR" "$AKM_SANDBOX/lint-fixed-again.sha256"
cmp "$AKM_SANDBOX/lint-fixed.sha256" \
  "$AKM_SANDBOX/lint-fixed-again.sha256"

12.2 Native and adapter scopes

akm lint --dir "$AKM_BUNDLE_DIR" --type workflows --format json \
  >"$AKM_SANDBOX/lint-workflows.json"
akm lint --dir "$REPO/tests/fixtures/stashes/all-types" --format json \
  >"$AKM_SANDBOX/lint-all-types.json"
akm lint --dir "$REPO/tests/fixtures/bundles/llm-wiki" --format json \
  >"$AKM_SANDBOX/lint-wiki.json"

12.3 Rules, suppressions, and mutation boundaries

Core issue codes include unquoted-colon, missing-updated, orphaned-stub, placeholder-stub, missing-name-or-type, missing-type, stale-path, missing-skill-md, invalid-task-yaml, missing-ref, dangerous-env-key, invalid-workflow-structure, and missing-category. Adapters may add named diagnostics; unknown adapter codes map to adapter-diagnostic while preserving the original code in detail.

12.4 Advisory channel

Non-fatal workflow compile advisories (a step with no output: schema, a reference to an undeclared param) travel in their own channel so a hint cannot fail a build.

akm lint --type workflows --format json > "$AKM_SANDBOX/lint-advisory.json"
jq -e '.warnings | length > 0' "$AKM_SANDBOX/lint-advisory.json"
jq -e '[.flagged[].issue] | index("workflow-warning") == null' "$AKM_SANDBOX/lint-advisory.json"
akm lint --type workflows --fail-on-flagged; echo "exit=$?"

12.5 Output parity and automated coverage


13. Feedback, Log, and Health

13.1 Feedback taxonomy and ranking

akm feedback skills/k8s-deploy --positive --tag slice:manual --tag team:qa
expect_error 2 MISSING_REQUIRED_ARGUMENT \
  feedback skills/k8s-deploy --negative
akm feedback skills/k8s-deploy --negative \
  --reason "manual QA relevance check"

13.2 Durable log and cursors

akm log --type feedback --ref skills/k8s-deploy --detail full --format json \
  > "$AKM_SANDBOX/feedback-log.json"
event_id="$(jq -r '.events[-1].id' "$AKM_SANDBOX/feedback-log.json")"
akm log --since "@offset:$event_id" --format json

13.3 Health pass, warn, and fail

set +e
akm health --format json > "$AKM_SANDBOX/health.json"
health_status=$?
set -e

jq -e --argjson status "$health_status" '
  (.status == "pass" and $status == 0) or
  (.status == "warn" and $status == 4) or
  (.status == "fail" and $status == 1)
' "$AKM_SANDBOX/health.json"

13.4 Health reports

akm health --report --format json > "$AKM_SANDBOX/health-report.json"
akm health --report --format html --output "$AKM_SANDBOX/health-report.html"
akm health --group-by run --format md > "$AKM_SANDBOX/health-runs.md"

14. Workflow

14.1 Authoring and validation

akm workflow list --format json | jq -e '.runs == []'
akm workflow create qa-template --print > "$AKM_SANDBOX/qa-template.md"
test ! -e "$AKM_BUNDLE_DIR/workflows/qa-template.md"
akm workflow create qa-created --from "$AKM_SANDBOX/qa-template.md"
akm lint --type workflows --fail-on-flagged

14.2 Typed params and partial run without an engine call

The typed-route fixture executes a route-only first step. --max-steps 1 tests parameter parsing and durable run creation without agent/LLM dispatch.

akm workflow run workflows/typed-route \
  --include_processes=true --count 2 \
  --labels api --labels worker --max-steps 1 \
  > "$AKM_SANDBOX/typed-run.json"

export QA_RUN_ID="$(jq -r .run.id "$AKM_SANDBOX/typed-run.json")"
jq -e '
  .run.status == "active" and
  .run.currentStepId == "finish" and
  .run.params == {
    "include_processes": true,
    "count": 2,
    "labels": ["api", "worker"]
  }
' "$AKM_SANDBOX/typed-run.json"

14.3 Status, list, abandon, resume, and scope

akm workflow status "$QA_RUN_ID"
akm workflow status workflows/typed-route
akm workflow list --active --ref workflows/typed-route
akm workflow status "$QA_RUN_ID" --units
akm workflow abandon "$QA_RUN_ID"
akm workflow resume "$QA_RUN_ID"

14.4 Deterministic execution and gates

Configure fake engines from section 4.4, then continue the active run:

akm workflow run "$QA_RUN_ID" --max-steps 1 \
  > "$AKM_SANDBOX/typed-finished.json"
jq -e '.run.status == "completed"' "$AKM_SANDBOX/typed-finished.json"

Create separate copies of gated-agent for pass/reject/malformed judges so each run freezes its own judge selection.

14.5 Exec (shell) units

An exec unit spawns a command instead of dispatching to an engine, so run this whole subsection with no engine configured — that is the first assertion, not a setup shortcut. Author the fixtures first:

cat > "$AKM_BUNDLE_DIR/workflows/exec-basic.md" <<'EOF'
---
type: workflow
description: Exec unit smoke
steps:
  - id: emit
    unit:
      exec:
        command: ["printf", "hello\n\n"]
---

# Exec Basic

## emit

Print a greeting. Stdout is the promoted artifact; this prose never reaches the
command.
EOF

cat > "$AKM_BUNDLE_DIR/workflows/exec-json.md" <<'EOF'
---
type: workflow
description: Exec unit with a typed artifact
steps:
  - id: emit
    unit:
      exec:
        command: ["echo", '{"verdict":"pass"}']
      output: { type: object, required: [verdict], properties: { verdict: { type: string } } }
---

# Exec JSON

## emit

Emit exactly one JSON value.
EOF

akm index
akm lint --type workflows --fail-on-flagged
akm workflow run workflows/exec-basic --format json > "$AKM_SANDBOX/exec-basic.json"
jq -e '.run.status == "completed"' "$AKM_SANDBOX/exec-basic.json"
akm workflow status "$(jq -r .run.id "$AKM_SANDBOX/exec-basic.json")" --units

Authoring and dispatch:

Failure taxonomy — each reason, and whether retry.on may name it:

Capture and retention:

Environment scope and context:

cwd and isolation:

Gates, retry, and reuse:

14.6 Failure, interruption, concurrency, and events

14.7 Step artifacts, bounds, and evidence

A step's promoted artifact is bounded for persistence only. The distinction between what the running invocation sees and what the row keeps is the point.

14.8 Retired surfaces and fallback


15. Task

15.1 Surface and schema

The current group has only add, run, history, sync, and doctor. Inspection uses generic search/show. Enable/disable is YAML edit plus sync; removal is file deletion plus sync.

15.2 Doctor and manual execution without scheduler mutation

akm task doctor --format json > "$AKM_SANDBOX/task-doctor.json"
akm task run manual-success --format json > "$AKM_SANDBOX/task-success.json"

set +e
akm task run manual-failure --format json > "$AKM_SANDBOX/task-failure.json"
task_failure_status=$?
set -e

test "$task_failure_status" -eq 1
jq -e '.result.detail.exitCode == 7' "$AKM_SANDBOX/task-failure.json"
akm task history --id manual-failure --limit 1

15.3 Scheduler binding and native lifecycle

Platform Evidence
Linux cron Before/after crontab, generated line, execution log, history, removal
macOS launchd Plist, launchctl state, execution log, history, unload/removal
Windows schtasks XML, scheduler query, execution result, history, deletion
AKM_NATIVE_SCHEDULER_TESTS=1 bun test tests/integration/native-scheduler.test.ts

16. Proposal Queue

16.1 Seed, list, show, diff, and resolution

Run the deterministic seeder from section 4.6.

update_id="$(jq -r .update.id "$AKM_SANDBOX/proposals.json")"
new_id="$(jq -r .newAsset.id "$AKM_SANDBOX/proposals.json")"

akm proposal list --status pending --format json
akm proposal show "$update_id" --format json
akm proposal show 11111111 --format json
akm proposal show memories/qa-proposal-update --format json
akm proposal diff "$update_id" --format text

16.2 Accept and revert exact bytes

cp "$AKM_BUNDLE_DIR/memories/qa-proposal-update.md" \
  "$AKM_SANDBOX/qa-proposal-update.before"

akm proposal accept "$update_id" --format json
grep -q 'qa-proposal-after' "$AKM_BUNDLE_DIR/memories/qa-proposal-update.md"

akm proposal revert "$update_id" --format json
cmp "$AKM_SANDBOX/qa-proposal-update.before" \
  "$AKM_BUNDLE_DIR/memories/qa-proposal-update.md"

16.3 Reject and bulk dry-runs

akm proposal reject "$new_id" --reason "manual QA" --yes
akm proposal accept --generator reflect --dry-run --format json
akm proposal reject --generator distill \
  --reason "manual QA dry-run" --dry-run --format json

16.4 Drain and observability

before_event_id="$(akm log --format json | jq -r '.events[-1].id // 0')"
akm proposal drain --dry-run --format json \
  > "$AKM_SANDBOX/drain-dry-run.json"
after_event_id="$(akm log --format json | jq -r '.events[-1].id // 0')"

16.5 Proposal generation and crash recovery


17. Agent, LLM, and Improve

17.1 Fake agent success, failure, capture, timeout, and redaction

Configure fake engines from section 4.4.

akm agent agents/qa-reviewer \
  --engine qa-agent --prompt "Return the fixture marker" --format json \
  > "$AKM_SANDBOX/agent-success.json"
jq -e '.ok == true and .shape == "agent-result" and (.stdout | contains("qa-agent-success"))' \
  "$AKM_SANDBOX/agent-success.json"

akm agent --engine qa-agent-capture \
  --prompt "capture prompt" --cwd "$AKM_SANDBOX" --format json \
  > "$AKM_SANDBOX/agent-capture.json"

set +e
akm agent --engine qa-agent-fail --prompt "fail" --format json \
  > "$AKM_SANDBOX/agent-failure.json"
agent_failure_status=$?
set -e
test "$agent_failure_status" -eq 1
jq -e '.ok == false and .exitCode == 7' "$AKM_SANDBOX/agent-failure.json"

17.2 Fake OpenAI-compatible service

Start/configure sections 4.3 and 4.5.

: >"$AKM_QA_SERVICE_LOG"

akm remember "Observed manual QA on 2026-08-05" \
  --name qa-enriched --tag manual-qa-seed --enrich --format json \
  > "$AKM_SANDBOX/enriched.json"

grep -q 'manual-qa' "$AKM_BUNDLE_DIR/memories/qa-enriched.md"

akm config set engines.qa-chat.endpoint "$AKM_QA_PROPOSAL_URL" --silent
akm proposal new lesson qa-llm-proposal \
  --task "Create the controlled manual QA lesson" \
  --engine qa-chat --format json >"$AKM_SANDBOX/llm-proposal-default.json"
jq -e '.ok == true and (.ref | endswith("//lessons/qa-llm-proposal"))' \
  "$AKM_SANDBOX/llm-proposal-default.json"

akm config set engines.qa-chat.maxTokens 32 --silent
akm proposal new lesson qa-llm-proposal \
  --task "Repeat the controlled manual QA lesson with an engine cap" \
  --engine qa-chat --format json >"$AKM_SANDBOX/llm-proposal-capped.json"
akm config unset engines.qa-chat.maxTokens --silent
akm config set engines.qa-chat.endpoint "$AKM_QA_CHAT_URL" --silent

jq -s -e '
  ([.[] | select(.pathname == "/ok/chat/completions")]) as $enrich |
  ([.[] | select(.pathname == "/proposal/chat/completions")]) as $proposal |
  ($enrich | length) == 1 and
  $enrich[0].authorizationPresent == true and
  $enrich[0].maxTokensPresent == true and
  ($proposal | length) == 2 and
  all($proposal[]; .authorizationPresent == true) and
  $proposal[0].maxTokensPresent == false and
  $proposal[1].maxTokensPresent == true and
  $enrich[0].responseFormatPresent == false and
  all($proposal[]; .responseFormatPresent == true)
' \
  "$AKM_QA_SERVICE_LOG"

Run the standalone enrichment regression separately. It currently fails because the command takes the raw-write path and makes no request:

before_requests="$(wc -l <"$AKM_QA_SERVICE_LOG")"
akm remember "Standalone enrichment dispatch oracle" \
  --name qa-enrich-only --enrich --format json \
  >"$AKM_SANDBOX/enrich-only.json"
after_requests="$(wc -l <"$AKM_QA_SERVICE_LOG")"

test "$after_requests" -eq "$((before_requests + 1))"
grep -q 'manual-qa' "$AKM_BUNDLE_DIR/memories/qa-enrich-only.md"

17.3 Improve dry-run invariants

Dry-run still preflights enabled model-backed processes, so configure the fake LLM first. It must not call the model or mutate any durable surface.

snapshot_tree "$AKM_SANDBOX" "$AKM_SANDBOX/before-improve.sha256"
before_requests="$(wc -l < "$AKM_QA_SERVICE_LOG")"

akm improve skills/k8s-deploy \
  --strategy quick --limit 1 --dry-run --no-sync --no-push \
  --format json > "$AKM_SANDBOX/improve-dry-run.json"

after_requests="$(wc -l < "$AKM_QA_SERVICE_LOG")"
test "$before_requests" -eq "$after_requests"
jq -e '
  .schemaVersion == 2 and
  .shape == "improve" and
  .dryRun == true and
  .strategy == "quick"
' "$AKM_SANDBOX/improve-dry-run.json"

Do not compare the snapshot file against itself. Produce before/after manifests outside the hashed tree or exclude those evidence files, then compare config, bundle, data, state, and cache independently.

17.4 Bounded live improve and lock behavior

Use a controlled test-capable model, one asset, and explicit no-sync/no-push:

akm improve skills/k8s-deploy \
  --task "tighten the description" \
  --strategy quick --limit 1 --no-sync --no-push --json-to-stdout

Semantic intent (off/auto) and runtime readiness are separate. Use a fresh data/cache/state tier for each case because vectors, fingerprints, and blocked status are durable.

18.1 Isolated semantic tiers and state oracle

export AKM_QA_BASE_DATA_DIR="$AKM_DATA_DIR"
export AKM_QA_BASE_CACHE_DIR="$AKM_CACHE_DIR"
export AKM_QA_BASE_STATE_DIR="$AKM_STATE_DIR"

use_semantic_tier() {
  tier="$1"
  export AKM_DATA_DIR="$AKM_SANDBOX/semantic/$tier/data"
  export AKM_CACHE_DIR="$AKM_SANDBOX/semantic/$tier/cache"
  export AKM_STATE_DIR="$AKM_SANDBOX/semantic/$tier/state"
  mkdir -p "$AKM_DATA_DIR" "$AKM_CACHE_DIR" "$AKM_STATE_DIR"
}

restore_semantic_dirs() {
  export AKM_DATA_DIR="$AKM_QA_BASE_DATA_DIR"
  export AKM_CACHE_DIR="$AKM_QA_BASE_CACHE_DIR"
  export AKM_STATE_DIR="$AKM_QA_BASE_STATE_DIR"
}
Effective state Meaning Search behavior
disabled Mode is off FTS only, no embedding request
pending Enabled but not verified/current fingerprint changed FTS fallback with advisory
ready-js Complete vectors (scanned by cosine similarity in JavaScript) Hybrid/vector search
blocked Recent embedding failure FTS fallback until retry/expiry

18.2 Deterministic model-free path

AKM_EMBED_DETERMINISTIC=1 is a test-only stable hash embedder, not a production model or relevance-quality claim.

use_semantic_tier deterministic
akm config unset embedding --silent
akm config set semanticSearchMode auto --silent
export AKM_EMBED_DETERMINISTIC=1

akm index --full --format json \
  >"$AKM_SANDBOX/semantic-deterministic-index.json"
jq -e '
  .verification.ok == true and
  .verification.semanticStatus == "ready-js" and
  .verification.embeddingCount == .verification.entryCount
' "$AKM_SANDBOX/semantic-deterministic-index.json"

akm search "deploy docker compose in a homelab" \
  --detail full --format json \
  >"$AKM_SANDBOX/semantic-deterministic-search.json"
akm log --type search --limit 1 --detail full --format json \
  >"$AKM_SANDBOX/semantic-deterministic-log.json"
jq -e '.events[-1].metadata.mode == "semantic"' \
  "$AKM_SANDBOX/semantic-deterministic-log.json"

unset AKM_EMBED_DETERMINISTIC

18.3 Controlled remote embedding service

Start section 4.3 first. The fixture proves transport, batching, persistence, readiness, and activation, not natural-language relevance.

use_semantic_tier fake-remote
unset AKM_EMBED_DETERMINISTIC AKM_EMBED_API_KEY
: >"$AKM_QA_SERVICE_LOG"

akm config set embedding "$(jq -nc \
  --arg endpoint "$AKM_QA_EMBED_URL" \
  '{provider:"openai",endpoint:$endpoint,model:"qa-embedding",dimension:4,batchSize:4}')" \
  --silent
akm config set semanticSearchMode auto --silent

akm index --full --format json >"$AKM_SANDBOX/semantic-remote-index.json"
jq -e '
  .verification.ok == true and
  .verification.embeddingProvider == "remote" and
  .verification.embeddingCount == .verification.entryCount and
  .verification.semanticStatus == "ready-js"
' "$AKM_SANDBOX/semantic-remote-index.json"

akm search deploy --detail full --format json \
  >"$AKM_SANDBOX/semantic-remote-search.json"
jq -s -e '
  [.[] | select(.pathname == "/v1/embeddings")] as $requests |
  ($requests | length) > 0 and
  all($requests[];
    .model == "qa-embedding" and
    .authorizationPresent == false and
    .inputCount >= 1 and .inputCount <= 4)
' "$AKM_QA_SERVICE_LOG"

18.4 Blocked indexing and query-time fallback

use_semantic_tier blocked
akm config set embedding "$(jq -nc \
  --arg endpoint "$AKM_QA_BASE_URL/error/embeddings" \
  '{provider:"openai",endpoint:$endpoint,model:"qa-failing",dimension:4,batchSize:4}')" \
  --silent
akm config set semanticSearchMode auto --silent

set +e
akm index --full --format json \
  >"$AKM_SANDBOX/semantic-blocked-index.json" \
  2>"$AKM_SANDBOX/semantic-blocked-index.stderr"
blocked_index_status=$?
set -e

test "$blocked_index_status" -eq 0
jq -e '
  .verification.ok == false and
  .verification.semanticStatus == "blocked"
' "$AKM_SANDBOX/semantic-blocked-index.json"

akm search deploy --detail full --format json \
  >"$AKM_SANDBOX/semantic-blocked-search.json"
jq -e '(.hits | length) > 0 and (.warnings | length) > 0' \
  "$AKM_SANDBOX/semantic-blocked-search.json"

18.5 Real local and bounded live gates

use_semantic_tier local-model
unset AKM_EMBED_DETERMINISTIC AKM_EMBED_API_KEY
akm config unset embedding --silent
akm config set semanticSearchMode auto --silent
akm index --full --verbose --format json \
  >"$AKM_SANDBOX/semantic-local-index.json"

Automated real-model gate:

AKM_SEMANTIC_TESTS=1 \
  bun test --timeout=120000 tests/integration/semantic-search-e2e.test.ts

Restore the primary tier after semantic checks:

restore_semantic_dirs
unset AKM_EMBED_DETERMINISTIC
akm config set semanticSearchMode off --silent
akm config unset embedding --silent

19. Migration, Durability, and Concurrency

19.1 Task migration boundary

akm migrate has exactly one responsibility: explicit task migration, run as two generations in one pass — task-v2 to task-v3 source conversion, then task-v3 to task source v4 conversion against the resulting files. It never rewrites config or databases.

Operation Classification
akm migrate status Read-only task inventory, both generations
akm migrate apply --dry-run Read-only validated conversion plan, both generations
akm migrate apply Per-file backup plus atomic task-source replacement, both generations

19.2 Automatic current database upgrades

Managed state.db opens validate the exact migration-ledger prefix and apply known pending additive migrations automatically. Unknown or reordered ledger entries fail closed. There is no external storage migration command.

19.3 Package upgrade boundary

akm upgrade updates executable code only. A 0.8 installation moves its old config/state aside, creates current config/state, and selectively brings authored assets forward. The explicit task migrator can then convert task-v2 sources through to task source v4. No current runtime loads old config/storage layouts.

19.4 Concurrent writers and readers

akm config set semanticSearchMode off --silent

set +e
akm index --full --format json \
  >"$AKM_SANDBOX/index-race-1.json" 2>"$AKM_SANDBOX/index-race-1.stderr" &
index_pid_1=$!
akm index --full --format json \
  >"$AKM_SANDBOX/index-race-2.json" 2>"$AKM_SANDBOX/index-race-2.stderr" &
index_pid_2=$!
wait "$index_pid_1"; index_status_1=$?
wait "$index_pid_2"; index_status_2=$?
set -e

test "$index_status_1" -eq 0
test "$index_status_2" -eq 0
! grep -qi 'database is locked' "$AKM_SANDBOX"/index-race-*.stderr
test ! -e "$AKM_DATA_DIR/index.db.write.lock"

akm config set search.graphBoost.directBoostPerEntity 0.11 --silent &
config_pid_1=$!
akm config set search.graphBoost.directBoostCap 1.25 --silent &
config_pid_2=$!
wait "$config_pid_1"
wait "$config_pid_2"
jq -e '
  .search.graphBoost.directBoostPerEntity == 0.11 and
  .search.graphBoost.directBoostCap == 1.25
' "$AKM_CONFIG_DIR/config.json"

19.5 Database and artifact recovery matrix

Artifact Recovery rule
config.json Corruption exits 78; preserve bytes and restore manually from verified backup
akm.lock Preserve corrupt bytes; mutation fails before config/cache/index changes
index.db Regenerable after evidence capture: remove/quarantine, then full index
state.db Non-regenerable; never delete as repair; restore verified backup
transaction journal Stop writers, retain evidence, use domain recovery
Git source cache Staged swap for read-only sources; writable checkout policy is explicit
npm/website cache Must preserve prior complete generation or report a known failing gate

19.6 Focused automated gates

bun test --timeout=120000 \
  tests/integration/migrate-format.test.ts \
  tests/migrate/task-v2-to-v3-files.test.ts \
  tests/tasks/migrate-v2-to-v3.test.ts \
  tests/integration/config-recovery-concurrency.test.ts \
  tests/integration/file-lock.test.ts \
  tests/integration/index-writer-lock.test.ts \
  tests/integration/index-writer-lock-crossproc.test.ts \
  tests/integration/proposal-durable-recovery.test.ts

20. Security and Adversarial Testing

Security gates are fail-closed. A green regression suite proves only represented cases; it does not waive an uncovered boundary. Never turn a known vulnerability into expected behavior. Hostile archives, DNS/rebinding, kill points, and native process tests run only in a bounded disposable container or VM.

20.1 Failure and evidence oracle

outside="$AKM_SANDBOX/outside-sentinel"
mkdir -p "$outside"
printf 'outside-must-not-change\n' >"$outside/sentinel"
ln -s "$outside" "$AKM_BUNDLE_DIR/memories/qa-link"

set +e
akm remember 'must not escape' --name escaped --path qa-link \
  >"$AKM_SANDBOX/link-write.stdout" \
  2>"$AKM_SANDBOX/link-write.stderr"
link_status=$?
set -e

test "$link_status" -eq 2
test ! -e "$outside/escaped.md"
test "$(cat "$outside/sentinel")" = outside-must-not-change
rm "$AKM_BUNDLE_DIR/memories/qa-link"

20.3 SSRF, redirects, DNS, and network budgets

Section 9.8's NODE_ENV=test crawl is a protocol seam only. Prove an ordinary installed CLI cannot activate that seam merely through ambient environment:

before_requests="$(wc -l <"$AKM_QA_SERVICE_LOG")"
set +e
NODE_ENV=test akm bundle add "$AKM_QA_WEBSITE_URL" \
  --name qa-ambient-bypass --allow-insecure-transport \
  >"$AKM_SANDBOX/ambient-bypass.stdout" \
  2>"$AKM_SANDBOX/ambient-bypass.stderr" </dev/null
ambient_status=$?
set -e

installed="$(akm bundle list --format json |
  jq '[.sources[]? | select(.name == "qa-ambient-bypass")] | length')"
if test "$installed" -ne 0; then
  akm bundle remove qa-ambient-bypass --yes >/dev/null 2>&1 || true
fi

test "$ambient_status" -eq 78
test "$installed" -eq 0
test "$(wc -l <"$AKM_QA_SERVICE_LOG")" -eq "$before_requests"

20.4 Registry, package, Git, and archive trust

20.5 Dangerous env, secrets, output, and permissions

before_event="$(akm log --format json | jq -r '.events[-1].id // 0')"
set +e
akm bundle add \
  "$REPO/tests/fixtures/manual-qa/suppressed-dangerous-bundle" \
  --name qa-suppressed-dangerous \
  >"$AKM_SANDBOX/suppressed.stdout" \
  2>"$AKM_SANDBOX/suppressed.stderr" </dev/null
suppressed_status=$?
set -e

test "$suppressed_status" -eq 1
test "$(akm log --format json | jq -r '.events[-1].id // 0')" = "$before_event"
! akm bundle list --format json |
  jq -e '.sources[]? | select(.name == "qa-suppressed-dangerous")'

20.6 Execution, agents, model output, and listeners

20.7 Malformed input and resource exhaustion

20.8 Focused security suite and disposition

bun test --timeout=120000 \
  tests/redaction.test.ts \
  tests/registry-resolve.test.ts \
  tests/opencode-sdk-runner.test.ts \
  tests/integration/website-ssrf.test.ts \
  tests/integration/tar-utils-scan.test.ts \
  tests/integration/walker.test.ts \
  tests/integration/vault-dangerous-key-install-gate.test.ts \
  tests/integration/vault-dangerous-key-lint.test.ts \
  tests/integration/env-run-dangerous-key-block.test.ts \
  tests/integration/config-sanitize-secrets.test.ts \
  tests/integration/self-update.test.ts

Current known failing gates must be fixed or explicitly waived with expiry:

Boundary Required disposition
Ambient test-mode SSRF bypass and secondary URL sinks FAIL until guarded
Git/direct-write symlink escape FAIL until contained
Archive expanded-resource/type/link budgets FAIL until bounded
Registry option/control-data rendering FAIL until redacted
Registry URL credential persistence and rendering PASS — #811 passed independent review after seven hardening cycles
Suppressible/add-only dangerous-key audit and event ordering FAIL until pre-publication
Source lifecycle rollback across config/lock/root/index/events FAIL until atomic/recoverable
OpenCode local-listener authentication/documentation FAIL until authenticated
Exact-value command-target task-log redaction PASS — fixed in 0.9.1 (#755)
Package-manager upgrade exact-version verification PASS — fixed 2026-08-06 (self-update.ts)

21. Package, Runtime, Platform, and Release

Release acceptance runs against the exact candidate commit and bytes. A local source build, packed npm artifact, standalone binary, installer, and published release are different subjects and require separate evidence.

21.1 Supported matrix and build boundaries

Surface Current support/constraint
npm package Node >=22 bootstrap; Bun >=1.0 preferred, Node fallback supported
Node fallback Test Node 22 and 24; requires built dist and usable SQLite dependency
Standalone Linux x64/arm64 glibc, macOS x64/arm64, Windows x64
Unsupported standalone Alpine/musl, 32-bit, native Windows ARM64
POSIX installer Linux/macOS x64/arm64; AKM_INSTALL_DIR override
Windows installer PowerShell 5.1+, x64 binary; ARM64 uses x64 emulation
Scheduler Linux crontab, macOS LaunchAgent, Windows Task Scheduler
bun install --frozen-lockfile
bun run check
bun run build

test -x dist/akm
test -x dist/akm-migrate
test -f dist/cli.js
test -f dist/cli-node.mjs
test -f dist/scripts/akm-migrate.js
test -f dist/scripts/akm-migrate-node.js
test ! -e dist/tests

node dist/akm --version
node dist/cli-node.mjs --version
bun dist/cli.js --version

21.2 npm pack, isolated install, and runtime parity

bun run test:package
bun test --timeout=120000 \
  tests/package-install.test.ts \
  tests/integration/package-install.test.ts \
  tests/integration/package-launcher.test.ts \
  tests/integration/npm-bin-contract.test.ts

bun run build
# Install the exact spec package.json declares — never a range of your own.
# better-sqlite3 compiles from source whenever there is no prebuilt binary for
# the running Node's ABI, and a from-source build against Node 24.19+ headers
# aborts at teardown (#790). CI does the same read-back. Remove it first:
# `npm install pkg@version` is a no-op when that version is already installed
# and will NOT rebuild the binding for the Node you are about to test with.
rm -rf node_modules/better-sqlite3
npm install --no-save \
  "better-sqlite3@$(node -p "require('./package.json').optionalDependencies['better-sqlite3']")"
AKM_SMOKE_NODE=node bun run test:node-smoke
AKM_SMOKE_NODE=node bun run test:node-compat

bun run build:install replaces the configured machine-global installation and is never an ordinary manual QA command.

21.3 Docker and standalone artifacts

./tests/docker/run-docker-tests.sh

# Focused alternatives:
./tests/docker/run-docker-tests.sh ubuntu-binary
./tests/docker/run-docker-tests.sh --bun-only
./tests/docker/run-docker-tests.sh --binary-only

# Test wrapper; without the variable the file skips:
AKM_DOCKER_TESTS=1 bun test tests/integration/docker-install.test.ts

21.4 Installers and self-upgrade

Use release-attached installers, not raw main, and pin a concrete tag:

VERSION="$(jq -r .version package.json)"
TAG="v$VERSION"
INSTALL_ROOT="$(mktemp -d)"

curl -fsSL \
  "https://github.com/itlackey/akm/releases/download/$TAG/install.sh" \
  -o "$INSTALL_ROOT/install.sh"
AKM_INSTALL_DIR="$INSTALL_ROOT/bin" bash "$INSTALL_ROOT/install.sh" "$TAG"
"$INSTALL_ROOT/bin/akm" --version

Real self-upgrade runs only in a disposable VM/container from a compatible older 0.9 release:

unset AKM_UPGRADE_SKIP_CHECKSUM
akm --version
akm migrate status
akm upgrade --check --format json
akm upgrade --format json
akm --version
akm migrate status
akm info

21.5 Native scheduler acceptance

Use an installed npm or standalone candidate and a disposable OS account. Do not hide an ineligible checkout behind --rebind in release acceptance.

A bare AKM_NATIVE_SCHEDULER_TESTS=1 bun test ... is insufficient and must not be recorded as PASS when the test skipped.

21.6 Exact release gate and publication

bun run release:check

# Partial local gate only:
./tests/release-check.sh --skip-docker

22. Evidence and Cleanup

22.1 Required evidence

Retain outside /tmp/akm-* because test cleanup can remove old matching paths:

22.2 Cleanup procedure

if test -n "${AKM_QA_SERVICE_PID:-}"; then
  kill "$AKM_QA_SERVICE_PID" 2>/dev/null || true
  wait "$AKM_QA_SERVICE_PID" 2>/dev/null || true
fi
case "$AKM_SANDBOX" in
  /tmp/akm-sandbox.*) rm -rf -- "$AKM_SANDBOX" ;;
  *) printf 'Refusing unsafe cleanup: %s\n' "$AKM_SANDBOX" >&2; false ;;
esac

23. Change-Based Test Selection

Every production change gets focused tests during iteration and bun run check before merge. check:changed is a fixed fast contract battery, not diff-aware and not a substitute for the full check.

Changed area Required escalation beyond focused tests
CLI dispatcher/output/command CLI check:changed, envelope/output suites, sections 6 and 8, full check
Config/paths/setup/schema setup/install regression, first-run package path, sections 3 and 6, full check
Refs/adapters/index/search/show contract/ranking/index suites, sections 4 and 7; semantic if vectors/status changed
Sources/registry/write-source provider/write/publication suites, controlled service; LIVE git/npm/HTTPS when lifecycle changed
Storage/migration/transactions crash/concurrency/property/published-upgrade gates, section 19, destructive rehearsal for cutover changes
Workflow workflow unit/integration, gate fake agents, slow expansion, crash/contention when scheduler changed
Workflow exec units / subprocess capture section 14.5 end to end on an engine-less install, plus the worktree isolation and stale-sweep gates; any capture, timeout, or process-group change also runs the agent spawn suites, which share that subprocess layer
Workflow child environment / allowlist section 14.5 environment gates plus section 11; a change to the shared floor is native Windows, not Linux emulation
Task/scheduler task suites, Linux standalone; native macOS/Windows for backend/quoting/binding; published upgrade for schema changes
Env/secret/security path/archive/network env/secret plus traversal/SSRF/archive/redaction/dangerous-key suites, section 20
Agent/LLM/improve/proposal family suites and fake-service AI pass; live bounded provider only for changed external dispatch
Package/runtime/build dependencies build/package/bin, Node 22/24, Bun launcher, standalone, Docker, release check
Installers/self-update/standalone installer/update suites, exact checksum/artifact tests, native install/upgrade, full release gate
Workflows/release-check/Docker workflow syntax/contract, actual Docker matrix, complete artifact inventory
Test preload/helpers/runners lint isolation, both sharded targets, multiple shard counts, leaked temp/log review
Documentation only doc-example lint and named contract tests; local evidence required because normal CI may skip docs-only changes

Minimum selection rules:


24. Known Constraints and Blocked Gates

These are current constraints, not historical expected failures. Re-audit this section before each release and remove an item when its implementation/gate is fixed.

24.1 Supported constraints

  1. npm bootstrap requires Node >=22; Bun does not remove that requirement.
  2. No Alpine/musl standalone, native Windows ARM64, 32-bit, or non-listed target.
  3. Standalone binaries cannot load the externalized local transformer model.
  4. macOS/Windows schedules are per-user interactive; schedule grammar is narrower than Linux cron and Windows expansion is bounded.
  5. Docker, semantic E2E, Node compatibility, native scheduler, published upgrade, standalone scheduler, real-agent/model, and slow property gates are opt-in.
  6. Current normal CI is Ubuntu-centric and does not itself run Docker, real embeddings, native macOS/Windows schedulers, or release binaries.
  7. The release workflow runs tests/release-check.sh --skip-docker (build/publication follow it) but not Docker, the slow gate, or the Node matrix — those stay separate, opt-in local/CI gates.
  8. Docker source variants use bun link; binary variants use a locally compiled linux-x64 artifact. They do not prove npm/published bytes.
  9. Self-upgrade discovery follows latest stable/npm @latest, so an unpublished or prerelease candidate cannot complete that happy path.
  10. 0.8-to-0.9 self-upgrade is deliberately blocked; use explicit migration.

24.2 Open release-blocking regression gates

Triaged 2026-08-06 for the 0.9.0 release: every sub-item was assessed against the actual test/lint/code evidence (an item counts as covered only when a test asserts the property, not merely exercises the path). itlackey reviewed the triage the same day and directed per-row outcomes: three rows resolved outright, four targeted fixes landed (registry stale fallback, upgrade version verification, lint fail-closed, plus the full Semantic row), and the remaining gaps carry approved waivers with the expiries recorded below.

For each unchecked row record:

issue:
impact:
owner:
verification test:
temporary mitigation:
waiver approver:
waiver expiry:

The stale-consolidation CLI recovery surface was removed in 0.9.1; its two obsolete skipped improve-memory integration cases were deleted during the 0.9.2 cleanup (#794). Stale transaction journals remain covered through the stale-txn-journals health check. Do not carry historical failures into this list unless they remain reproducible against the exact candidate.