Skip to content

fix(update): preserve deployment targets during partial updates - #2924

Merged
Daniel Meppiel (danielmeppiel) merged 10 commits into
microsoft:mainfrom
Wanming08:fix/update-deployment-target
Sep 22, 2026
Merged

Daniel Meppiel (danielmeppiel) merged 10 commits into
microsoft:mainfrom
Wanming08:fix/update-deployment-target

Conversation

@Wanming08

Copy link
Copy Markdown
Contributor

Description

Updating a selected dependency to a new commit can rewrite copilot deployment records as legacy, including records for untouched skills under .agents/skills/. A subsequent install restores them. The regression reproduces on current main when the selected package has actually changed; a full update or a no-op partial update does not reproduce it.

The partial lockfile merge calls add_dependency, which invalidates the canonical deployment ledger. Rebuilding from flat paths loses target provenance for shared directories. Merge through DeploymentLedgerCodec instead: reconcile out the processed owners, retain untouched canonical records, overlay the current deployment records, and apply the merged ledger once. Skip the redundant early partial metadata merge so the processed owner set stays accurate.

Adds real CLI regressions for updating either a skill or an instruction, plus coverage for added files, unchanged dependency commits, surviving shared owners, canonical locators, and serialization. The existing architecture guard now requires the canonical merge call. Command documentation and the shipped usage guide describe the restored behavior.

Fixes #2902

Type of change

  • Bug fix
  • Documentation
  • New feature
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

Validated on macOS with Python 3.12.14, based on d26ccdaf2fbcb29775abc94b7413790b3e32b600:

  • Virtual package lifecycle matrix: 8 passed. Both new parameterized cases fail against the unmodified implementation with {'copilot', 'legacy'} != {'copilot'}.
  • Deployment state, lockfile union, and lockfile tests: 163 passed.
  • Spec conformance: 202 passed, 2 skipped. Test quality: 63 passed.
  • Ruff lint/format, duplicate-code lint, auth lint, architecture boundaries, assertion-quality and exact-duplicate ratchets, and spec orphan checks passed.

The complete unit/console run, excluding two environment failures already reproduced on clean main, finished with 22,270 passed, 28 failed, 42 skipped, 21 xfailed. All 28 remaining failures also reproduce on clean d26ccda. The 30 baseline failures involve missing gh, DNS-dependent HTTP/SSRF cases, and local HTTP/WebSocket tests affected by this machine's proxy configuration. No tests or production networking safeguards were changed to hide these failures.

Spec conformance (OpenAPM v0.1)

This restores existing declared-target provenance during partial updates; it introduces no schema or normative requirement change. The existing conformance suite passes. Mode B reports 8 substantive added lines in its monitored paths and passes without a waiver; the orphan check confirms all 122 requirements remain aligned.

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.

🟢 Approval recommended

The partial-update merge now uses the canonical deployment-ledger merge path with targeted unit/integration regressions and accompanying documentation/changelog updates.

Pull request overview

Fixes a regression where apm update (when run as a partial update against selected packages) could rewrite deployment target records in apm.lock.yaml to legacy by invalidating and rebuilding the deployment ledger without preserving target provenance; aligns partial-update merging with the canonical DeploymentLedgerCodec path so untouched records retain their original target information.

Changes:

  • Route partial lockfile merges through DeploymentLedgerCodec.merge_dependencies() to preserve canonical deployment ledger records (including target provenance) while updating only the selected dependencies.
  • Add unit + integration regressions ensuring partial updates preserve deployment targets and correctly retain untouched deployment records and locators.
  • Document the restored behavior and add a changelog entry.
File summaries
File Description
tests/unit/core/test_deployment_state.py Adds a focused unit test covering dependency merge behavior for canonical locators, surviving owners, MCP target rows, and round-trip serialization.
tests/integration/test_virtual_package_lifecycle_matrix.py Adds an integration regression verifying partial updates preserve target: copilot for both updated and untouched deployments (skill + instruction cases).
src/apm_cli/install/phases/lockfile.py Skips early “existing lockfile” merge for partial operations and performs the partial merge via the canonical deployment-ledger merge helper.
src/apm_cli/core/deployment_ledger.py Introduces DeploymentLedgerCodec.merge_dependencies() to merge updated deps without discarding canonical deployment locators/targets.
scripts/architecture_linter/checks/install_base_integrator_and_contraction.py Extends the architecture guardrail to require the canonical merge_dependencies call in the partial-merge path.
packages/apm-guide/.apm/skills/apm-usage/commands.md Updates the shipped usage guide to describe preserved deployment targets during partial updates.
docs/src/content/docs/reference/cli/update.md Updates CLI reference docs to reflect that partial updates preserve deployment targets without requiring a follow-up install.
CHANGELOG.md Adds an Unreleased “Fixed” entry describing the regression fix.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@Wanming08

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@sergio-sisternes-epam

Copy link
Copy Markdown
Collaborator

PR triage recommendation

ready-for-review

Recommendation only, not merge or scope approval. A responsible human maintainer must approve. Labels, automated advice, and silence are not approval.

Thank you for this pull request. It is linked to #2902 (Fixes #2902), which currently carries status/accepted. Per the PR triage contract, that makes this advisory classification ready-for-review.

This is not merge approval and does not start a review or merge worker. CODEOWNERS already requested review from Daniel Meppiel (@danielmeppiel) and Sergio Sisternes (@sergio-sisternes-epam). This comment does not add, remove, or change review requests.

Context on #2902: an earlier bot status/accepted was withdrawn by a maintainer on 2026-09-12; status/accepted was reapplied on 2026-09-15. The current label is treated as the live human record. This PR itself is not labelled status/accepted.

Suggested next step: wait for a human maintainer to review the accepted scope. Do not treat this comment or triage/recommended as authorization to merge.

@sergio-sisternes-epam Sergio Sisternes (sergio-sisternes-epam) added triage/recommended Automated advice completed; not human scope approval. type/bug Something does not work as documented. area/lockfile Lockfile schema, per-file provenance, integrity hashes, drift detection. theme/portability One manifest, every target. Multi-target deploy, marketplace, packaging, install. labels Sep 17, 2026
@danielmeppiel

Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_now

Restores deployment-target provenance during partial updates, preserving concrete targets for both refreshed and untouched dependencies.

panel-mode=full; personas=python-architect,devx-ux-expert,supply-chain-security-expert,doc-writer,test-coverage-expert,performance-expert,apm-ceo

cc Wanming08 Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

The panel found no substantive issue in the current fix. The changed-commit, selected-dependency regression is addressed through the canonical ledger codec rather than another target heuristic. The focused tests exercise refreshed and untouched packages, added skill files, surviving owners, typed locators, and serialization. This prevents future target loss; it is not a migration for previously corrupted lockfiles.

On reviewed head 8e86a23dfce2d385fa1cb7b475837def1aba5907, the lifecycle, deployment-state, lockfile-union, cross-package reconciliation, and lockfile suites finished with 174 passed, 1 failed in 15.38 s. The failure, tests/test_lockfile.py::TestLockFile::test_from_installed_packages, is an unspecced Mock.alias fixture error and reproduces unchanged on exact PR base 1c5322979d85c46df09c1d7772953a5923eeef48. This is not a full-suite or CI-green certification.

Dissent. The architect and integrity reviewer initially questioned replacement of historical co-owners when a current deployment writes the same locator. Both withdrew that finding: existing collision tests and a direct canonical-reconciler probe confirm the last-writer contract. Do not union historical owners back into the refreshed record. The DevX wording nit is also optional: explicitly saying a follow-up install is unnecessary answers the workaround reported in #2902, so retaining that sentence is useful.

Aligned with: Portable by manifest -- concrete targets survive partial updates. Secure by default -- the existing current-writer ownership contract remains intact.

Panel summary

Persona B R N Takeaway
Python Architect 0 0 1 Canonical merge is sound; one now-unreachable condition can be simplified.
DevX UX Expert 0 0 1 User-facing behavior and docs align; wording preference only.
Supply Chain Security 0 0 0 Ownership concern withdrawn after checking the existing collision contract.
Doc Writer 0 0 0 Docs describe preservation, not historical repair.
Test Coverage 0 0 0 Changed-commit integration regressions and canonical-state unit tests cover the fix proportionately.
Performance Expert 0 0 0 Redundant early merge removed; no introduced performance regression identified.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 1 follow-ups

  1. [Python Architect] Optionally remove self.ctx.only_packages or from src/apm_cli/install/phases/lockfile.py:416 and adjust its nearby partial-install comment. The new early return already handles partial operations, so that disjunct is unreachable. This is housekeeping, not a correctness concern.

Architecture

classDiagram
    direction LR
    class LockfileBuilder {
      +_attach_deployed_files(lockfile)
      +_maybe_merge_partial(lockfile, path, LF)
    }
    class DeploymentReconciler {
      +reconcile_package_claims()
      +reconcile(previous, materializations, intent)
    }
    class DeploymentLedgerCodec {
      <<Codec>>
      +from_lockfile(lockfile)
      +merge_dependencies(lockfile, updated)
      +apply_to_lockfile(ledger, lockfile)
    }
    class DeploymentRecord {
      +owners tuple
      +active_owner str
    }
    class MaterializationResult {
      +owners frozenset
      +status MaterializationStatus
    }
    LockfileBuilder ..> DeploymentReconciler : current claims
    LockfileBuilder ..> DeploymentLedgerCodec : partial merge
    DeploymentReconciler ..> DeploymentRecord : produces
    DeploymentReconciler ..> MaterializationResult : consumes
    note for DeploymentReconciler "Current successful proofs determine refreshed ownership"
Loading
flowchart TD
    A["_attach_deployed_files"] --> B["Reconcile current package claims"]
    B --> C["Build current canonical records"]
    C --> D["_maybe_merge_partial"]
    D --> E["DeploymentLedgerCodec.merge_dependencies"]
    E --> F["Capture current ledger"]
    E --> G["Reconcile prior ledger excluding processed owners"]
    F --> H["Update dependency metadata"]
    G --> H
    H --> I["Overlay current records onto retained records"]
    I --> J["apply_to_lockfile once"]
    J --> K["_write_if_changed: apm.lock.yaml"]
Loading

Recommendation

Ship now from the technical-review perspective. The only suggested code cleanup is cosmetic; the documented no-follow-up-install behavior is supported by the regression scenarios. Daniel and Sergio remain the requested reviewers. Scope and merge decisions remain human decisions; this advisory changes neither.


Full per-persona findings

Python Architect

  • [nit] Unreachable partial-operation condition at src/apm_cli/install/phases/lockfile.py:416.
    _merge_existing now returns early when self.ctx.only_packages is truthy. Its later self.ctx.only_packages or dep_key in self.ctx.intended_dep_keys condition therefore has an unreachable left-hand branch.
    Suggested: Keep only dep_key in self.ctx.intended_dep_keys and update the nearby comment accordingly.

DevX UX Expert

Supply Chain Security

No remaining findings. The initial owner-union suggestion was withdrawn after checking canonical current-proof ownership and the existing last-writer collision tests.

Doc Writer

No findings.

Test Coverage

No findings.

Performance Expert

No findings. No latency measurements or speedup claims are asserted.

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.


Generated by autopilot-pr-review-worker. This comment is AI-generated and may contain errors.

@danielmeppiel
Daniel Meppiel (danielmeppiel) merged commit e3e989c into microsoft:main Sep 22, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/lockfile Lockfile schema, per-file provenance, integrity hashes, drift detection. theme/portability One manifest, every target. Multi-target deploy, marketplace, packaging, install. triage/recommended Automated advice completed; not human scope approval. type/bug Something does not work as documented.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] apm update  writes  target: legacy  instead of  copilot  in v0.29.1 (regression from #2795 fix)

4 participants