Skip to content

[BUG] audit --ci: detect local-path MCP declaration removal (symmetric source-vs-lock diff)#2145

Open
danielmeppiel wants to merge 3 commits into
mainfrom
apm-rt-fix-2136
Open

[BUG] audit --ci: detect local-path MCP declaration removal (symmetric source-vs-lock diff)#2145
danielmeppiel wants to merge 3 commits into
mainfrom
apm-rt-fix-2136

Conversation

@danielmeppiel

Copy link
Copy Markdown
Collaborator

fix(audit): detect removed transitive MCP declarations

TL;DR

apm audit --ci now builds the complete current MCP declaration view before comparing it with apm.lock.yaml. The comparison is symmetric, so changed, added, and removed server declarations all fail config-consistency; unchanged local and installed package declarations remain clean.

Note

Fixes #2136. If #2132 merges first, rebase this branch because both changes touch src/apm_cli/policy/ci_checks.py.

Problem (WHY)

The fix keeps the gate based on deterministic source and lock state: "Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action."

Reproduction

  1. Install a root project with local dependency ./packages/agent-config whose manifest declares MCP server shadcn.
  2. Run apm audit --ci --no-policy --no-fail-fast; it exits 0.
  3. Delete the package's entire dependencies.mcp block.
  4. Before this fix, the same audit still exited 0. After this fix, config-consistency reports shadcn: in lockfile but not in current source (declared by agent-config) and exits 1.

Approach (WHAT)

# Fix
1 Resolve root MCP declarations, current local-package source manifests, and lock-bounded installed package manifests into one declaration view.
2 Deduplicate that view with existing MCP precedence rules.
3 Compare config values plus both name-set directions: source-only and lock-only.
4 Use mcp_config_provenance only to identify the declaring package in diagnostics, never as a removal exemption.

Implementation (HOW)

File Change
src/apm_cli/policy/ci_checks.py Extends _check_config_consistency to resolve the complete declaration view and perform a symmetric source-vs-lock diff.
tests/integration/test_transitive_mcp_audit_e2e.py Adds the install, clean-control, source-removal, and nonzero-audit regression trap.
tests/unit/policy/test_ci_checks.py Covers unchanged local and remote package declarations, manifestless skills, and revised lock-only diagnostics.
docs/src/content/docs/reference/baseline-checks.md Documents the complete declaration graph and added/removed failure modes.
docs/src/content/docs/reference/lockfile-spec.md Clarifies that provenance identifies diagnostic ownership rather than exempting drift.

Diagram

Legend: The dashed nodes are the declaration-resolution and symmetric-diff behavior added by this PR.

flowchart LR
    subgraph Source[Current source view]
        R[Root manifest]
        L[Local package sources]
        P[Installed package manifests]
    end
    subgraph Compare[Config consistency]
        V[Deduplicated MCP configs]
        D[Symmetric source vs lock diff]
    end
    K[Lock mcp_configs]
    X[Exit 1 with ownership diagnostic]
    R --> V
    L --> V
    P --> V
    V --> D
    K --> D
    D --> X
    classDef new stroke-dasharray: 5 5;
    class L,P,D new;
Loading

Trade-offs

  • Chose lock-bounded package discovery instead of recursively scanning apm_modules; stale untracked packages cannot enter the audit view.
  • Chose existing MCPIntegrator serialization and deduplication instead of a new MCP schema or target-specific adapter.
  • Kept provenance in lockfile state for ownership labels; rejected the previous blanket exemption because it hid removals.

Benefits

  1. One removed transitive MCP declaration now makes config-consistency fail and returns exit 1.
  2. The unchanged-source control still returns exit 0.
  3. Local source packages, installed remote packages, and manifestless skills have explicit regression coverage.
  4. Diagnostics name the package that previously declared a removed server.

Validation

Regression test on base before implementation:

1 failed in 3.00s
assert 0 == 1

Targeted audit suite after implementation:

129 passed in 5.54s

Manual reproduction after implementation:

fixed_removed=1
- shadcn: in lockfile but not in current source (declared by agent-config)
[x] 1 of 9 check(s) failed

Lint mirror:

All checks passed!
1422 files already formatted
Your code has been rated at 10.00/10
[+] auth-signal lint clean
YAML I/O, file length, and portable path guards passed

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 Removing an installed local package's MCP declaration makes apm audit --ci report drift and exit nonzero Governed by policy, DevX tests/integration/test_transitive_mcp_audit_e2e.py::test_ci_audit_rejects_removed_transitive_mcp_declaration (regression-trap for #2136) integration
2 Leaving the local package declaration unchanged keeps apm audit --ci clean Governed by policy tests/integration/test_transitive_mcp_audit_e2e.py::test_force_install_then_ci_audit_accepts_transitive_mcp integration
3 Unchanged package declarations resolve consistently across local and installed package sources Vendor-neutral, Governed by policy tests/unit/policy/test_ci_checks.py::TestConfigConsistency::test_transitive_mcp_server_resolved_from_local_source and ::test_transitive_mcp_server_resolved_from_installed_remote unit

How to test

  • Run uv run --extra dev python -m pytest tests/integration/test_transitive_mcp_audit_e2e.py -q; expect 2 passed.
  • Install the reproduction workspace with --trust-transitive-mcp; expect the unchanged audit to exit 0.
  • Remove the local package's dependencies.mcp block and rerun apm audit --ci --no-policy --no-fail-fast; expect exit 1 and the lock-only diagnostic.
  • Run the lint contract; expect all checks to exit 0.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 10, 2026 21:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens apm audit --ci's config-consistency check so it detects removed transitive MCP server declarations (lock-only entries) by building a complete current declaration view and performing a symmetric source-vs-lock comparison.

Changes:

  • Expand config-consistency to resolve MCP declarations from the root manifest plus lock-bounded dependency manifests (including local-source deps), then diff name sets in both directions.
  • Add integration + unit regression coverage for transitive MCP resolution (local source, installed remote) and for the removed-declaration failure case.
  • Update docs to describe the broadened declaration graph and provenance semantics.
Show a summary per file
File Description
src/apm_cli/policy/ci_checks.py Builds a complete “current MCP” view from root + locked deps and performs symmetric source-vs-lock comparison.
tests/unit/policy/test_ci_checks.py Adds unit coverage for transitive MCP resolution paths and updates diagnostics expectations.
tests/integration/test_transitive_mcp_audit_e2e.py Adds an end-to-end regression test ensuring removal of a transitive MCP block fails audit --ci.
docs/src/content/docs/reference/lockfile-spec.md Clarifies provenance’s purpose (diagnostic attribution).
docs/src/content/docs/reference/baseline-checks.md Documents the broader declaration graph and updated failure modes (needs a small correction per comments).

Review details

  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +263 to +266
details.append(
f"{locked_dep.repo_url}: cannot parse local package manifest ({exc}) -- "
"fix the manifest or re-run 'apm install'"
)
Comment on lines +96 to 99
- **What it verifies.** That the complete current MCP declaration graph -- the root manifest, current local-package sources, and lock-bounded installed package manifests -- matches the `mcp_configs` baseline stored in the lockfile.
- **Fails when.** A server's resolved config differs from the lockfile, a source declaration was added, or a previously locked declaration was removed.
- **Exception.** A lockfile-only server is expected when `mcp_config_provenance` identifies the sub-package that contributed it, so those transitive servers are not treated as orphans.
- **Remediation.** Run `apm install` to reconcile the MCP configuration.
danielmeppiel and others added 2 commits July 11, 2026 10:23
apm-spec-waiver: Symmetric source-vs-lock audit closes a removal-detection gap in existing lockfile consistency semantics; no new OpenAPM artifact or wire behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] apm audit --ci misses local-path sub-package MCP declaration REMOVAL (exits 0); change-drift exits 1

2 participants