Skip to content

fix(install): cover skill bundles with per-file content-integrity + target-scoped union manifest (closes #1716)#1718

Merged
danielmeppiel merged 4 commits into
mainfrom
danielmeppiel/root-cause-skills-drift
Jun 9, 2026
Merged

fix(install): cover skill bundles with per-file content-integrity + target-scoped union manifest (closes #1716)#1718
danielmeppiel merged 4 commits into
mainfrom
danielmeppiel/root-cause-skills-drift

Conversation

@danielmeppiel

@danielmeppiel danielmeppiel commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

fix(install): catch silent drift in committed .agents/skills bundles

TL;DR

Committed deployed content under .agents/skills/** could silently drift from its packages/** / .apm/** source while every CI gate stayed green (#1716). The fix makes the lockfile integrity manifest target-complete (per-file hashes for every committed deploy target), makes the drift differ target-aware (walks each primitive's deploy_root), and runs that gate live in CI by dropping --no-drift. Two regression traps lock the behavior in.

Note

Closes #1716. Discovered while shipping #1714 (fixes #1712), where apm.lock.yaml was deliberately left untouched to preserve evidence for this investigation.

Problem (WHY)

  • .agents/skills/apm-review-panel/SKILL.md shipped drifted (415 deployed lines vs 446 in source — an entire persona missing) yet apm audit --ci --no-drift stayed green.
  • The committed apm.lock.yaml carried zero deployed_file_hashes: a multi-target deploy then a copilot-app install rewrote the manifest to a single target, orphaning the committed .github/ + .agents/ files from every manifest-driven gate.
  • Skills were recorded as a single directory entry (e.g. .agents/skills/auth), and compute_deployed_hashes skips directories (phases/lockfile.py line 42), so SKILL.md had no per-file hash coverage.
  • [!] The drift differ never walked .agents/ at all — _governed_root_dirs returned only .apm + .github, so even with drift enabled the skill tree was invisible.

Why these matter: apm audit is APM's governance gate, and a gate that cannot observe its own deployed artifacts is not a gate. Grounding the audit in a deterministic install-replay is what makes drift verifiable rather than assumed — per PROSE, "Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action."

Approach (WHAT)

# Fix
1 Target-complete manifest — new manifest_reconcile.union_preserving reconciles the lockfile symmetrically with on-disk cleanup: the current install's entries are authoritative, other targets' entries are preserved (not clobbered).
2 Per-file skill entriesservices._skill_bundle_file_entries expands each deployed skill dir into the dir entry plus its files, so content-integrity hashes SKILL.md / assets / scripts.
3 Target-aware differdrift._governed_root_dirs now includes every primitive's deploy_root first segment, so the replay differ walks .agents/skills/**.
4 Live CI gateci.yml apm-self-check drops --no-drift and deletes a dead Gate-B git-status step (a guaranteed no-op).
5 Tree correction — regenerated 10 skill bundles to the deterministic apm install output (stale broken ../../agents/ links → ../../../.apm/agents/) and refreshed apm.lock.yaml hashes.

Implementation (HOW)

  • src/apm_cli/install/manifest_reconcile.py (new) — union_preserving + install_governance; a small shared helper imported by both manifest build sites so the reconciliation logic is identical.
  • src/apm_cli/install/services.py_skill_bundle_file_entries at the single skill-integration chokepoint; the directory entry is retained (cleanup's directory-rejection contract depends on it).
  • src/apm_cli/install/phases/lockfile.py & post_deps_local.py — call union_preserving for the per-dependency and project-root manifests.
  • src/apm_cli/install/drift.py_governed_root_dirs walks each deploy_root; the replay reproduces the deploy-time link rewrite, so byte-identical skills do not surface as false drift.
  • .github/workflows/ci.yml — Gate A is now apm audit --ci; dead Gate B removed.
  • apm.lock.yaml + .agents/skills/<10>/SKILL.md — regenerated tool output (data, not logic).

Diagrams

Legend: the manifest path — how a copilot-app install used to clobber skill coverage, and how union_preserving (dashed) plus per-file skill entries (dashed) restore per-file hashing under content-integrity.

flowchart LR
    subgraph Deploy["Deploy"]
        D1["copilot target writes .agents/skills + .github"]
        D2["copilot-app install writes DB-URI rows"]
    end
    subgraph Manifest["Lockfile manifest"]
        M3["union_preserving reconcile"]
        M1["deployed_files"]
        M4["per-file skill entries SKILL.md + assets"]
        M2["deployed_file_hashes"]
    end
    subgraph Audit["apm audit"]
        A1["content-integrity hashes each file"]
    end
    D1 --> M1
    D2 --> M3
    M3 --> M1
    M1 --> M4
    M4 --> M2
    M2 --> A1
    classDef new stroke-dasharray: 5 5;
    class M3,M4 new;
Loading

Legend: the drift path — apm audit --ci replays the install into a scratch tree, and the differ now walks the .agents deploy root (dashed) so committed skill bundles are compared against the replay.

flowchart LR
    subgraph Source["Source"]
        S1[".apm/skills + packages"]
    end
    subgraph Replay["apm audit --ci replay"]
        R1["install replay scratch tree"]
        R2["_governed_root_dirs adds .agents deploy_root"]
    end
    subgraph Compare["Differ"]
        C1["walk .agents/skills"]
        C2["drift found, exit 1"]
    end
    S1 --> R1
    R1 --> R2
    R2 --> C1
    C1 --> C2
    classDef new stroke-dasharray: 5 5;
    class R2,C1 new;
Loading

Trade-offs

  • Shared helper vs inline reconciliation. Extracted manifest_reconcile rather than duplicating the union logic in both build sites — one source of truth, in the spirit of PROSE's "Favor small, chainable primitives over monolithic frameworks."
  • Regenerated the committed tree to tool output. The committed skill links were stale and broken (../../agents/ resolves to a non-existent .agents/agents/); a fresh apm install deterministically emits ../../../.apm/agents/. Chose to make committed == install output so the differ is clean in steady state.
  • Latent install non-idempotency left in place. apm install won't self-heal an already-deployed skill dir whose bytes match source; the new drift gate now catches this, but a self-healing install is a separate PR.
  • post_deps_local union over overwrite. Could in principle preserve a stale entry for an ungoverned top-level file; verified unreachable today (local_deployed_files holds only .github/** + .agents/**, both governed by the copilot target).

Benefits

  1. Tampering any deployed .agents/skills/<s>/SKILL.md now fails apm audit --ci --no-drift (exit 1) instead of passing silently.
  2. Editing a source skill now fails apm audit --ci with the exact deployed path that drifted.
  3. The committed apm.lock.yaml covers every committed file-based deploy target (42 per-file skill entries), not one.
  4. The CI apm-self-check job runs the drift gate on every PR — no --no-drift escape hatch.

Validation

apm audit --ci (drift gate now live, --no-drift dropped):

[+] Replayed 7 package(s)
[+] No drift detected
[+] content-integrity        No critical hidden Unicode or hash drift detected
[+] drift                    no drift detected against lockfile
[*] All 9 check(s) passed
Live trap reproduction (both gates fail on drift)
# Trap A — tamper a deployed skill
$ printf '\nTAMPER\n' >> .agents/skills/auth/SKILL.md
$ uv run apm audit --ci --no-drift   # exit 1: content-integrity hash drift

# Trap B — edit the source skill
$ printf '\n<!-- drift -->\n' >> .apm/skills/auth/SKILL.md
$ uv run apm audit --ci              # exit 1: drift detected: .agents/skills/auth/SKILL.md

CI on head 420ab863: all required checks green, including APM Self-Check running the new target-aware drift gate. Local unit gate: pytest tests/unit tests/test_console.py -n 2 --dist worksteal16819 passed.

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 A hand-edit to a deployed .agents/skills/<s>/SKILL.md is caught by apm audit --ci --no-drift Governed by policy, Secure by default tests/unit/policy/test_ci_checks.py::test_agents_skill_tamper_fails_content_integrity (regression-trap for #1716) unit
2 Editing a source skill surfaces as drift at the deployed path under apm audit --ci Governed by policy tests/unit/install/test_drift.py::test_diff_engine_walks_skill_deploy_root_detects_drift (regression-trap for #1716) unit
3 A byte-identical deployed skill is NOT a false positive Governed by policy, DevX tests/unit/install/test_drift.py::test_diff_engine_skill_deploy_root_clean_when_identical unit
4 The differ's governed roots include each primitive's deploy_root (.agents) Portability by manifest tests/unit/install/test_drift.py::test_governed_root_dirs_includes_primitive_deploy_root unit

How to test

  • Run uv run apm audit --ci on a clean checkout → all 9 checks pass.
  • printf '\nX\n' >> .agents/skills/auth/SKILL.md then uv run apm audit --ci --no-drift → exit 1 (content-integrity); restore with git checkout.
  • printf '\nX\n' >> .apm/skills/auth/SKILL.md then uv run apm audit --ci → exit 1 (drift: .agents/skills/auth/SKILL.md); restore.
  • uv run pytest tests/unit/install/test_drift.py tests/unit/policy/test_ci_checks.py -q → green.

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

…arget-scoped union manifest (closes #1716)

Committed deployed content under `.agents/skills/**` could silently drift
from its `packages/**` / `.apm/**` source without any CI gate catching it.
Root cause was two independent install-manifest defects:

1. Manifest-clobber asymmetry. `apm install` REPLACED the lockfile
   deployment manifest with only the current target's paths, while on-disk
   stale cleanup correctly PRESERVES other targets' files. A `copilot-app`
   (user-machine DB) install therefore wiped all per-file content-integrity
   coverage of the committed `.github/` + `.agents/` tree. Fixed by making
   manifest reconciliation target-scoped UNION (symmetric with cleanup) in
   both the per-dependency block (phases/lockfile.py) and the project-root
   local block (phases/post_deps_local.py), via a shared helper
   `manifest_reconcile.union_preserving`.

2. Skills recorded as directory entries, never per-file hashed. Skill
   bundles were recorded as a single directory entry (e.g.
   `.agents/skills/auth`); `compute_deployed_hashes` skips directories, so
   skills had zero per-file hash coverage and SKILL.md drift escaped the
   documented `apm audit --ci --no-drift` content-integrity gate. Fixed by
   expanding each deployed skill directory into the directory entry PLUS its
   contained files at the single shared integration chokepoint
   (services._skill_bundle_file_entries). The directory entry is retained
   (cleanup's directory-rejection gate and the manifest dir-exclusion
   contract depend on it); the file entries get hashed.

Net: the existing `--no-drift` content-integrity gate now protects
`.agents/skills/...SKILL.md` with zero CI changes.

Also regenerates the committed `apm.lock.yaml` (now per-file hashes for
`.github/agents`, `.github/instructions`, and every `.agents/skills/<s>`
file), re-syncs the drifted `apm-review-panel/SKILL.md` (415 -> 446 lines),
and deploys the previously-missing `cut-release` skill.

A third, deeper defect (the drift replay does not reproduce
`copy_skill_to_target`'s cross-primitive link rewrite, so walking
`.agents/` in the drift differ surfaces false positives) is documented in
drift.py and deferred to a follow-up; Fix 2 already closes #1716 via
content-integrity, so the drift differ need not walk `.agents/`.

Tests: test_lockfile_union.py (union semantics, both manifest blocks);
test_install_services_orchestration.py (skills recorded per-file).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 9, 2026 16:49

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 hardens APM’s install/audit integrity model for multi-target deployments by ensuring the lockfile manifest is target-unioned (not clobbered by the last target installed) and by expanding skill bundle directory deployments into per-file entries so content-integrity can hash and detect drift under apm audit --ci --no-drift.

Changes:

  • Add target-scoped manifest reconciliation (union_preserving) and apply it to both per-dependency deployed manifests and the project-local deployed manifest.
  • Expand deployed skill bundle directories into per-file lockfile entries so skill contents (e.g., SKILL.md, assets/*) are covered by per-file hashing.
  • Regenerate apm.lock.yaml to include per-file deployed hashes for committed skill bundles and related governed content; add regression tests.
Show a summary per file
File Description
src/apm_cli/install/manifest_reconcile.py New shared helper to union current-target manifest entries with preserved other-target entries.
src/apm_cli/install/phases/lockfile.py Use target-scoped union reconciliation when attaching per-dependency deployed files + hashes.
src/apm_cli/install/phases/post_deps_local.py Apply the same union reconciliation to local_deployed_files / local_deployed_file_hashes.
src/apm_cli/install/services.py Expand skill bundle directory deployments into per-file deployed-file entries for hashing coverage.
tests/unit/install/phases/test_lockfile_union.py Unit regression coverage for union-preserving semantics and governance computation.
tests/integration/test_install_services_orchestration.py Integration regression coverage for skill-dir expansion into per-file deployed entries.
src/apm_cli/install/drift.py Document intentional exclusion of .agents/ from drift replay walking due to known replay/link-rewrite inconsistency.
apm.lock.yaml Regenerated lockfile with per-file deployed hashes covering committed deployed content (notably .agents/skills/**).
.agents/skills/apm-review-panel/SKILL.md Deployed skill content updated to include the performance expert persona and related guidance.
.agents/skills/apm-review-panel/assets/panelist-return-schema.json Schema enum extended to include performance-expert.
.agents/skills/cut-release/SKILL.md New deployed cut-release skill bundle defining a release-cut workflow.
.agents/skills/cut-release/assets/entry-sanitizer.md Changelog entry sanitization rubric for release PR preparation.
.agents/skills/cut-release/assets/pr-body-template.md Template for release PR body composition.
.agents/skills/cut-release/assets/semver-rubric.md Semver bump decision rubric used by the release-cut workflow.
.agents/skills/cut-release/evals/evals.json Trigger-eval configuration for the cut-release skill.
.agents/skills/cut-release/scripts/bump-version.sh Script to bump pyproject.toml version and refresh uv.lock.
.agents/skills/cut-release/scripts/list-changes-since-tag.sh Script to enumerate merged PRs since last tag and emit structured JSON.
.agents/skills/cut-release/scripts/verify-lint-mirror.sh Script to run the repo’s CI-mirror lint chain locally.

Copilot's findings

  • Files reviewed: 18/18 changed files
  • Comments generated: 4

Comment on lines +29 to +30
def install_governance(targets) -> tuple[set[str], set[str]]:
"""Return ``(file_roots, uri_schemes)`` governed by *targets*.
Comment on lines +79 to +83
def union_preserving(
current_files,
current_hashes,
prior_files,
prior_hashes,
Comment on lines 526 to +531
for tp in skill_result.target_paths:
deployed.append(_deployed_path_entry(tp, project_root, targets))
# #1716: also record the bundle's contained files so per-file
# content hashes cover SKILL.md / assets / scripts. The directory
# entry above is retained (cleanup's directory-rejection gate and
# the manifest dir-exclusion contract depend on it); the file
Comment on lines +80 to +82
# Extract PR numbers from commit subjects (squash-merge convention: "(#NNNN)").
# Fallback: use `gh pr list --search` keyed by sha if no number is in the subject.
PR_NUMS=()
danielmeppiel and others added 2 commits June 9, 2026 20:20
… traps (#1716)

Phase 2 of the #1716 fix, extending the Phase 1 target-complete integrity
manifest. Makes the install-replay drift differ walk per-primitive deploy
roots so committed skill bundles under .agents/skills/** are compared against
their source, and runs that gate live in CI.

Changes:
- drift.py: _governed_root_dirs() now includes each target primitive's
  deploy_root (first path segment), not just the target root_dir, so the
  differ walks .agents/ for the copilot skills target. Previously it only
  collected .apm + .github, leaving .agents/skills/** invisible to drift.
- ci.yml (apm-self-check): drop --no-drift from the audit gate so drift runs
  target-aware; delete the dead Gate B git-status step (a guaranteed no-op --
  no apm install runs in the job and its watch list omitted .agents/ anyway).
- apm.lock.yaml + .agents/skills/<10>/SKILL.md: regenerate the committed
  deployed tree to the deterministic `apm install` output form. The committed
  copies carried stale cross-primitive agent links (../../agents/... pointing
  at the non-existent .agents/agents/) that install never self-heals because
  it skips skill dirs already byte-identical to source. Fresh install emits
  ../../../.apm/agents/... (resolves to the real source agent); regenerating
  makes committed == install == replay so the new drift gate is clean.
- Acceptance traps as regression tests (red->green across the fix):
  - Trap A (content-integrity): tamper a deployed .agents/skills/<s>/SKILL.md
    -> _check_content_integrity fails. tests/unit/policy/test_ci_checks.py.
  - Trap B (drift): a skill deploy_root walked by the differ surfaces drift.
    tests/unit/install/test_drift.py (deploy_root-walk tests fail on the
    pre-fix _governed_root_dirs).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ift helpers

Fold Copilot review: annotate the new manifest_reconcile helpers
(install_governance, union_preserving) and the drift _governed_root_dirs
helper with explicit parameter types, satisfying the repo typing guideline
(.github/instructions/python.instructions.md). TargetProfile is imported
under TYPE_CHECKING (no runtime cost, no import cycle).

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

Copy link
Copy Markdown
Collaborator Author

Shepherd-driver advisory (terminal pass)

Drove this PR's #1716 fix to a landing-ready state: classified the Copilot inline review, folded in-scope items, ran a focused defect review of the full diff, and confirmed CI green.

Folded in this run

  • (Copilot) Missing parameter type hints on the new manifest_reconcile helpers (install_governance, union_preserving) -- resolved in 420ab863.
  • (consistency) Same untyped targets param on the Phase-2 drift._governed_root_dirs helper I touched -- annotated alongside, TargetProfile imported under TYPE_CHECKING -- resolved in 420ab863.

Copilot signals reviewed

  • src/apm_cli/install/manifest_reconcile.py:30 -- LEGIT: install_governance(targets) was untyped; per .github/instructions/python.instructions.md (resolved in 420ab863).
  • src/apm_cli/install/manifest_reconcile.py:83 -- LEGIT: union_preserving params untyped (resolved in 420ab863).
  • src/apm_cli/install/services.py:531 -- NOT-LEGIT: the bot suggests normalizing the skill directory entry to a trailing /. The directory-vs-file distinction is made by compute_deployed_hashes via is_file() (phases/lockfile.py:42), not by a trailing slash, so the dir entry is already correctly skipped for hashing. The entry format is pre-existing convention (the _deployed_path_entry line predates this PR) and the full apm audit --ci is green; adding a trailing slash would be an unrelated spec/convention change.
  • .agents/skills/cut-release/scripts/list-changes-since-tag.sh:82 -- LEGIT but DEFERRED: the header comment describes a gh pr list --search fallback the script does not implement. This is a content bug in the cut-release skill's source script, only present in this diff because the skill was deployed to make the committed tree match install output; fixing it means editing an unrelated skill's behavior. See Deferred below.

Deferred (out-of-scope follow-ups)

  • (Copilot) list-changes-since-tag.sh header comment claims a non-existent gh pr list --search fallback -- scope boundary: cut-release skill content, unrelated to the .agents/skills/ deployed content escapes drift check + lockfile integrity (silent drift) #1716 install/audit integrity fix; suggested follow-up: a separate cut-release script-correctness issue.
  • (review) post_deps_local now unions local_deployed_files instead of authoritatively overwriting. A future install that stopped deploying a tracked top-level file outside any governed root could preserve a stale manifest entry. Verified unreachable today (local_deployed_files holds only .github/** + .agents/**, both governed by the copilot target); this is the intended cross-target preservation semantics. Flagged for awareness only.

Regression-trap evidence (mutation-break gate)

  • tests/unit/install/test_drift.py::test_diff_engine_walks_skill_deploy_root_detects_drift (and the two _governed_root_dirs deploy_root tests) -- reverted the _governed_root_dirs deploy_root change; the tests FAILED (assert 0 == 1, differ found no .agents/ drift); restored the guard -> green. Proven red->green across the fix.
  • Live end-to-end: editing source .apm/skills/auth/SKILL.md makes apm audit --ci exit 1 with drift detected: .agents/skills/auth/SKILL.md; tampering deployed .agents/skills/auth/SKILL.md makes apm audit --ci --no-drift exit 1 on content-integrity.

Lint contract

uv run --extra dev ruff check src/ tests/ and uv run --extra dev ruff format --check src/ tests/ both silent; pylint R0801 10.00/10; auth-signals clean; YAML-I/O / file-length / relative_to guards clean.

CI

All required checks green on head 420ab863 (APM Self-Check 11s -- now running the target-aware drift gate with --no-drift dropped; Build & Test shards 1+2, Lint, Spec conformance, gate). The prior Phase-2 commit 273c708a also went fully green, confirming the new CI drift gate passes in actual CI. CI fix iterations: 0.

Focused defect review

Ran a high-signal review over the full origin/main...HEAD diff (install reconciliation, drift differ, ci.yml). No significant issues: union-preserving target symmetry, URI-scheme governance, the deploy_root walk, and the per-file skill bundle expansion all check out; the regenerated SKILL.md links and apm.lock.yaml hashes reconcile with the replay (drift: no drift detected, all 9 checks green).

Mergeability status

PR head SHA stance iters folds defers Copilot rounds CI mergeable mergeStateStatus
#1718 420ab863 ship (advisory clean) 1 2 2 1 green MERGEABLE BLOCKED (awaiting review)

Convergence

1 outer iteration; 1 Copilot round. Focused review verdict: clean / ship. Ready for maintainer review.

The #1716 fix is user-observable (apm audit now catches drift/tampering in
committed .agents/skills bundles), so it needs a changelog entry per
.github/instructions/changelog.instructions.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel merged commit 1818e53 into main Jun 9, 2026
20 checks passed
@danielmeppiel danielmeppiel deleted the danielmeppiel/root-cause-skills-drift branch June 9, 2026 19:52
danielmeppiel added a commit that referenced this pull request Jun 9, 2026
Merge origin/main (PRs #1714, #1717, #1718, #1719) into the release
branch. Only #1718 is user-facing (src/apm_cli/install drift +
target-complete lockfile manifest); its entry was authored on main and
now sits in the [0.19.0] Fixed block with the PR number appended.
#1714, #1717, #1719 are maintainer-toolkit / test-only and dropped per
the entry-sanitizer DROP list. Lint mirror green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel added a commit that referenced this pull request Jun 9, 2026
…ckfile

The APM Self-Check (apm audit --ci) content-integrity gate failed:
apm.lock.yaml recorded a stale deployed_file_hashes entry
(fe6a73...) for .agents/skills/apm-review-panel/SKILL.md while the
deployed file and its packages/** source are both fc63e25... #1714
updated the skill content but deliberately left apm.lock.yaml
untouched; #1718 then turned the drift gate live (dropped --no-drift),
exposing the mismatch. apm install reconciles the recorded hash (and
refreshes apm_version to 0.19.0). All 9 audit checks now pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel added a commit that referenced this pull request Jun 9, 2026
* chore: release v0.19.0

Bump pyproject.toml + uv.lock to 0.19.0 and move the [Unreleased]
CHANGELOG block to [0.19.0] - 2026-06-09 with one 'so what' entry per
merged PR. Lint mirror green locally (ruff check + format, pylint
R0801, auth-signals).

Post-merge: tag v0.19.0 to trigger the release workflow.

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

* chore: fold #1718 into v0.19.0 changelog after main merge

Merge origin/main (PRs #1714, #1717, #1718, #1719) into the release
branch. Only #1718 is user-facing (src/apm_cli/install drift +
target-complete lockfile manifest); its entry was authored on main and
now sits in the [0.19.0] Fixed block with the PR number appended.
#1714, #1717, #1719 are maintainer-toolkit / test-only and dropped per
the entry-sanitizer DROP list. Lint mirror green.

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

* fix(self-check): reconcile stale apm-review-panel SKILL.md hash in lockfile

The APM Self-Check (apm audit --ci) content-integrity gate failed:
apm.lock.yaml recorded a stale deployed_file_hashes entry
(fe6a73...) for .agents/skills/apm-review-panel/SKILL.md while the
deployed file and its packages/** source are both fc63e25... #1714
updated the skill content but deliberately left apm.lock.yaml
untouched; #1718 then turned the drift gate live (dropped --no-drift),
exposing the mismatch. apm install reconciles the recorded hash (and
refreshes apm_version to 0.19.0). All 9 audit checks now pass.

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

---------

Co-authored-by: danielmeppiel <danielmeppiel@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sergio-sisternes-epam pushed a commit that referenced this pull request Jun 9, 2026
…e integrity)

Sync with main through v0.19.0 (#1715, #1714, #1718, #1719, #1717).

Conflicts resolved:
- CHANGELOG.md: main cut the v0.19.0 release, moving prior Unreleased
  entries into a dated section. Took main's restructure; relocated the
  #1681 "Tightened Stage 2 code-complexity thresholds" entry to the new
  ## [Unreleased] section (released sections stay immutable per Keep a
  Changelog). Took main's #1702/#1710 close-ref for the lockfile entry.
- src/apm_cli/install/services.py: #1718 added per-file skill-bundle
  integrity recording inline at a site my refactor had extracted into
  _log_skill_result. Kept the new _skill_bundle_file_entries helper in
  services.py (sibling of _deployed_path_entry); dropped main's duplicate
  inline _log_hook_display_payloads (already relocated to
  services_integrate.py and re-exported in my #1700 port); accepted the
  _log_skill_result delegator at the call site.
- src/apm_cli/install/services_integrate.py: ported #1718's
  deployed.extend(_skill_bundle_file_entries(tp, ...)) into
  _log_skill_result's deployed-path loop (deployed aliases
  result["deployed_files"]), with a lazy import of the helper.

Shadow gate: no markers, all src <=800 lines, ruff check + format clean,
pylint R0801 EXIT=0, auth-signals clean; 11144 tests pass across the
install/integration suites plus the #1718, #1700, and #1709 targeted sets.

Co-authored-by: Copilot <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.

.agents/skills/ deployed content escapes drift check + lockfile integrity (silent drift)

2 participants