Skip to content

Baseline CI checks

apm audit --ci runs a fixed set of baseline checks against the project on disk. Baseline checks are always on – they need no apm-policy.yml. With policy discovery active, the org policy contributes additional checks on top (see apm policy and the policy schema).

This page documents the baseline set defined in src/apm_cli/policy/ci_checks.py. For the command surface, see apm audit. For the CI wiring, see Enforce in CI.

Outcome Exit code
All baseline checks pass (and any policy + drift checks pass) 0
Any baseline, policy, or drift check fails 1

--no-fail-fast runs every check before exiting; the default stops at the first failure to skip expensive I/O.

ID Severity Source Always on?
manifest-parse block ci_checks.py only when apm.yml cannot be parsed
lockfile-exists block ci_checks.py yes
ref-consistency block ci_checks.py yes
deployment-ledger-owners block ci_checks.py yes
deployed-files-present block ci_checks.py yes
no-orphaned-packages block ci_checks.py yes
skill-subset-consistency block ci_checks.py yes
config-consistency block ci_checks.py yes
content-integrity block ci_checks.py yes
includes-consent advisory ci_checks.py yes (advisory; promotable by policy)
drift block _check_drift in ci_checks.py, invoked from commands/audit.py yes, unless --no-drift

Policy checks (dependency-allowlist, dependency-denylist, required-packages, required-packages-deployed, required-package-version, transitive-depth, and others in policy/policy_checks.py) only run when an apm-policy.yml is resolved through discovery or --policy. They are out of scope for this page; see the policy schema.

  • What it verifies. That apm.yml, if present, is valid YAML and a valid APM manifest.
  • Fails when. The file exists but cannot be parsed (yaml.YAMLError, ValueError, or OSError).
  • Effect. Skips every other check and exits 1. This is the only check that runs before the lockfile gate.
  • Remediation. Fix the YAML syntax error reported in the message and re-run.
  • What it verifies. That apm.lock.yaml is present whenever the project has APM or MCP dependencies (including devDependencies), or whenever an existing lockfile records local content under the synthesized self-entry.
  • Fails when. apm.yml declares dependencies (production or dev) but no lockfile is on disk.
  • Effect. Subsequent checks are skipped (the lockfile is required input).
  • Remediation. Run apm install to generate apm.lock.yaml and commit it.
  • What it verifies. That every dependency’s reference in apm.yml (both dependencies.apm and devDependencies.apm) matches the resolved_ref recorded in the lockfile.
  • Fails when. A manifest ref differs from the lockfile entry, or the manifest declares a dependency that is missing from the lockfile.
  • Remediation. Run the command the check message names: apm install --update when the lockfile already holds a resolution that must be refreshed or re-keyed (the source-identity rewrite and commit-pin mismatch cases), or plain apm install for a first resolution – so the lockfile re-resolves to the manifest, then commit apm.lock.yaml.
  • Runs before deployment-ledger-owners. This external manifest-versus-lockfile identity check is evaluated ahead of the internal ledger-owner check on purpose. A source-identity tamper (a rewritten dependency host/repo_url in the lockfile) trips both, but only ref-consistency points at the safe fix – re-resolving from the trusted manifest via apm install --update. The ledger-owner remedy (apm prune) reconciles ownership toward the current lockfile, so it must never be the surfaced cause when the manifest and lockfile disagree about a dependency’s identity.
  • What it verifies. That every canonical deployments row’s owners and active_owner resolve to a member of the valid owner universe: a current key in apm.lock.yaml’s dependencies, the workspace self-owner ., or local-bundle.
  • Fails when. A row references a dependency key that has since been removed from the lockfile (a stale, “ghost” owner), or active_owner itself is invalid. This is a hard integrity failure, distinct from ordinary drift – unlike hand-edits or missing files, it fails in both bare apm audit and apm audit --ci.
  • Effect. Findings render in text, JSON, SARIF, and markdown output alike, naming the locator and its invalid owner(s). apm audit --strip refuses to modify content while any reference is invalid.
  • Remediation. Run apm prune, then rerun apm audit. Prune repairs the stale ownership metadata; it does not delete files based on a ghost row alone. See apm prune and Lockfile spec.
  • What it verifies. That every path in each lockfile entry’s deployed_files exists on disk under the project root. Gitignored paths are considered expected-absent and do not cause a failure.
  • Fails when. One or more non-gitignored deployed files are missing (e.g. a developer ran apm install then deleted integrated files, or skipped install entirely).
  • Remediation. Run apm install to restore integrated files. When apm.yml declares targets, install also removes stale entries outside the declared, gated, and dynamic target set. Then commit the updated lockfile: git add apm.lock.yaml && git commit.
  • What it verifies. That every dependency in the lockfile is still declared in apm.yml (in either dependencies.apm or devDependencies.apm). The synthesized self-entry is excluded.
  • Fails when. The lockfile holds a package that the manifest no longer lists.
  • Remediation. Run apm install to prune the orphan, then commit apm.lock.yaml.
  • What it verifies. That each skills: selection in apm.yml matches the skill_subset recorded in the lockfile, and that every recorded skill path exists in the resolved package tree. When CI audit has prepared a lock-pinned scratch replay, this check uses its dependency tree instead of checkout-local apm_modules/; no checkout install is required.
  • Fails when. The sorted manifest skill list differs from the sorted lockfile skill_subset, or a recorded subset path does not map to a deployable skill in the dependency tree being checked.
  • Remediation. Run apm install to regenerate the lockfile against the current selection.
  • What it verifies. That MCP server configs derived from the root dependencies.mcp and devDependencies.mcp, plus dependencies.mcp from every current local or installed-remote package manifest bounded by the lockfile, match the mcp_configs baseline. Dependency-package devDependencies.mcp is author-only and is excluded from the consumer baseline.
  • Fails when. A server’s resolved config differs from the lockfile, a server exists on only one side, or a locked package manifest is unreadable. In apm audit --ci, when apm_modules/ is absent but the lockfile is present, APM first self-hydrates a lock-pinned scratch install and derives the current MCP truth from that isolated modules tree; if the scratch replay itself fails, config-consistency fails closed with the replay error. A missing manifest also fails unless the lockfile records a skill bundle, or records a remote Claude skill at a repository root or subdirectory. Before remote materialization the locked type is authoritative; once materialized, the on-disk shape and lock metadata must both identify a Claude skill. Present local paths use the same shape-and-lock check, while a missing local path still fails. mcp_config_provenance identifies the package in lock-only diagnostics but never exempts a removed declaration.
  • Remediation. Run apm install to reconcile the MCP configuration or restore an unreadable package source.
  • What it verifies. Two independent signals, including local .apm/ deployments recorded through the synthesized self-entry:
    1. Critical hidden Unicode in recognized prompt documents and decoded native prompt fields. Discovery uses each resolved target’s filenames and primitive patterns, including untracked files and shared hook settings. Recorded paths use the same applicability rules: tracking a command or script does not turn it into prompt text. Hook definitions remain visible even when their command-only content is not applicable to Unicode prompt checks. Session transcripts, history, caches, command strings and referenced executables are not scanned. See Discovery and prompt coverage for the format boundaries.
    2. SHA-256 drift between the on-disk content and the hash recorded in deployed_file_hashes at install time. Necessarily lockfile-scoped: an unrecorded file has no baseline to compare against.
  • Fails when. An applicable prompt contains critical Unicode, recognized content is unreadable or unsupported (incomplete coverage), a recorded hash differs, or a legacy hash lacks canonical ownership metadata. Missing files are reported by deployed-files-present; unrecorded files have no hash baseline. Existing path and symlink protections still apply.
  • Remediation. Review the reported format or access error for incomplete coverage. Use apm audit --strip only for supported regular prompt documents; structured/shared settings and external-root findings require manual review. Run apm install to restore hash-drifted files. Discovery does not grant ownership or permission to rewrite user configuration.
  • What it verifies. That projects deploying local content (the lockfile’s local_deployed_files is non-empty) declare an includes: field in apm.yml for explicit governance.
  • Fails when. Never – this check is advisory and always passes. The message advises adding includes: auto (or an explicit list) when local content is deployed without a declaration.
  • Promote to a block. Set manifest.require_explicit_includes in apm-policy.yml to make missing includes: fail the policy layer.
  • Remediation. Add includes: auto to apm.yml, or list the paths explicitly.
  • What it verifies. That the working tree matches what an install from the current lockfile would produce. The check replays the install pipeline into a scratch tree and diffs the result against the project.
  • Fails when. Any deployed file differs from the replay output (hand-edits, missing integrations, orphaned files). For shared hook configuration files, the comparison covers only APM-owned hook entries; user-owned hooks are preserved and do not create drift. The complete APM-owned sidecar is compared as JSON, ignoring object-key order and formatting but preserving all fields and list order; changed content, malformed JSON, duplicate keys, and non-JSON constants (NaN, Infinity, -Infinity) still fail. In apm audit --ci, a cold cache no longer yields a green skip: if apm_modules/ is absent, APM self-hydrates a lock-pinned scratch install first and then diffs against the checkout. If that scratch replay cannot be materialized, the drift check fails closed with the replay error.
  • Skips when. Only in bare apm audit, where the replay remains cache-only and a missing cache yields an informational skip advising the user to run apm install first.
  • Skip with. apm audit --ci --no-drift (reduces coverage; reserve for performance-constrained CI loops).
  • Remediation. Run apm install to restore the deployed state, or revert the hand-edit. For a cache-miss skip, the same apm install warms the cache and enables the check on the next run.

The aggregate runner in run_baseline_checks evaluates checks in this order: manifest-parse (only when apm.yml is unparseable), lockfile-exists, ref-consistency, deployment-ledger-owners, deployed-files-present, no-orphaned-packages, skill-subset-consistency, config-consistency, content-integrity, includes-consent. Drift is invoked separately by the audit command after the baseline batch, but in --ci mode it shares the same cold-cache scratch materialization with skill-subset-consistency and config-consistency.

With fail-fast on (the default), the runner stops at the first failing check. apm audit --ci --no-fail-fast evaluates every check so the report lists every problem at once.

  • apm audit – the command surface and modes.
  • apm prune – reconciles deployment-ledger-owners findings.
  • apm policy – inspect, validate, and resolve org policy.
  • Policy schema – the policy-gated checks layered on top of this baseline.
  • Enforce in CI – wiring the gate into branch protection.