Chapter 7

Lifecycle

Keep APM dependencies current while detecting drift and risk deliberately.

Objective

After this chapter you can use APM's maintenance triad — apm outdated, apm update, and apm audit — to keep dependencies current deliberately, separating freshness, permission to change, and integrity, and reviewing actual manifest, lockfile, and deployment outcomes rather than trusting a successful exit alone.

Edition 1.2 targets exactly APM 0.31.0. Captions marked “observed” refer to recorded command-and-file checks in the operations research; source-inspected rules are distinguished below. The retained Meridian excerpt is an exact historical v0.23.1 snapshot, not a current transcript or a new verification stamp.

Concept/Theory

Reproducible is a floor, not a ceiling

Chapter 6 gave you a recorded dependency graph to replay. But a frozen graph, left alone, quietly ages. Upstream skills gain fixes, prompts get sharper, instructions track new conventions — and a project pinned six months ago can be reproducible and stale. The failure mode from Chapter 1 inverts: instead of silent drift you get silent staleness. Reproducibility is a floor, not a ceiling.

So the obvious next worry is: if everything is pinned, how does anything move forward safely? Use the same separation you want from an application package manager:

  1. Inspect availability — apm outdated reports what is newer upstream.
  2. Authorize a change — apm update previews and applies a permitted refresh.
  3. Check the result — apm audit checks recorded integrity and deployment consistency.

These are three different questions. A newer release can be outside your constraint. An allowed update still needs review. A current version can have a broken local deployment. The loop protects Reproducibility by making change deliberate, not by making change impossible.

This chapter uses lifecycle in that maintenance sense, not for the entire scaffold-to-runtime flow. It sits alongside Chapter 5's four-command consumer habit. The consumer guide likewise groups update, outdated, and audit as lifecycle automation. The cadence is yours to choose; it is not a reason to refresh dependencies on every restore.

In APM

Three verbs, three jobs

The three-question model remains useful in 0.31.0, but it is not an absolute side-effect guarantee. In particular, “update” need not move a version, and “verify” may need to acquire missing locked content.

The maintenance triad at APM 0.31.0: intended job and observed operational boundaries.
Verb Question State and network boundary
apm outdated Report: what is newer upstream? Queries sources without changing project files. Unreachable or unresolved sources can be unknown.
apm update Change: which permitted refresh do you accept? May change manifest, lock, cache, and deployments; may instead be a no-op or repair. Queries upstream. Trusted lifecycle scripts add side effects.
apm audit Verify: does recorded state agree with the installation? Diagnosis, not ordinary repair. Replay uses scratch/cache; CI hydration and policy discovery can need network. Explicit stripping is a separate mutating operation.

Report: freshness is not permission

apm outdated starts with the lockfile's dependency inventory, not a scan of the deployed bytes. A floating Git branch is not automatically outdated: when its locked commit still equals the current tip, there is nothing newer to report. Once the tip moves, the report can change even though your manifest, lock, and deployed files have not.

Do not extend that observation into “pinned dependencies never show outdated.” The target-tag reference describes matching-tag comparisons for Git tags and comparison with eligible annotated semver tags for full-SHA revision pins. The separate Git revision-update contract can propose a reviewed SHA rewrite to the commit behind an eligible stable annotated semver tag, not an arbitrary branch tip or lightweight tag. Those special Git-tag rules are source-inspected here, not a newly executed tag-publication experiment. A pin records current identity; it does not hide later availability.

Experimental registry rows make the distinction especially clear: Current is the locked version, Wanted is the highest version satisfying the manifest constraint, and Latest is the highest published parseable version under the registry's ordering. Latest can be outside the constraint and can be a prerelease. Wanted uses declarations from the root or installed package manifests: the inventory is lock-based, but Wanted is not independent of your declarations.

Observed 0.31.0 values from the operations/registry loopback fixture before updating; all three rows report outdated. The manifest supplies one exact pin and two ranges.
Package Selector Current Wanted Latest
operations/exact1.0.01.0.01.0.02.0.0-beta.1
operations/range^1.0.01.0.01.1.02.0.0-beta.1
operations/other^1.0.01.0.01.1.02.0.0-beta.1

registry: fixture (outside constraint) explains why a published Latest is not a requested upgrade. Even the exact pin is reported outdated, while its Wanted remains 1.0.0. Registry comparison adds the Wanted column; Git-only output keeps Current / Latest / Status / Source, and mixed Git rows use - for Wanted. This is a shipped experimental client, not evidence of a Microsoft-hosted public registry; the exercise uses only a controlled loopback source.

Finding outdated dependencies exits 0: this is an informational report, not a build gate. Do not treat unknown as proof of freshness, either. Local dependencies are skipped. The inspected command has neither a package filter nor a JSON-output flag — the JSON option used below belongs to audit.

Change: review the permitted resolution

apm update is the recommended consented refresh of existing declarations, not the only operation capable of changing resolution. Explicit manifest edits followed by install, and install or lock refresh modes, can also change it. The distinction from Chapter 5 is replaying unchanged intent versus reconciling changed intent, not a magical guarantee attached to one verb.

Preview with apm update --dry-run; a package argument can narrow the refresh. The observed selected plan correctly reported 1 updated, 2 unchanged. Separate exact-registry and Git-semver controls reported All dependencies already at their latest matching refs. A plan need not overstate movement or print an unknown destination. A correct plan is useful evidence, but it is not a substitute for checking the applied state.

--yes accepts a plan; it does not widen a constraint. In the registry control, an all-package update advanced allowed ranges to 1.1.0 and kept the exact 1.0.0 pin. Noninteractive selected change without --yes exited 1 with Cannot prompt for confirmation in non-interactive shell. The interactive default of No is the documented contract; an unattended job must handle the observed noninteractive refusal as a failure, not a successful decline.

Nor does every successful update move a version or rewrite the lock. An unchanged-ref update restored an empty materialized cache without consent or lock mutation. An MCP-only accepted update repaired missing native configuration; its preview did not. These are repairs, not dependency bumps. Dependency maintenance is also distinct from upgrading the APM executable itself.

Verify: know what the gate actually checked

Audit combines content scanning with consistency checks and an install replay. CI mode checks hashes where the lock supplies a baseline; replay compares expected deployment with the working tree. Canonical ownership adds a separate question: does each recorded owner still name a valid dependency or supported workspace/bundle owner? This implements Chapter 6's recorded-state model, not upstream freshness.

Two modes on the same deliberately edited Copilot rule in the operations repro fixture: advisory audit exits 0; CI audit exits 1. Neither repairs it. Offline local-source control. apm v0.31.0 — observed
apm audit -f json
apm audit --ci --no-fail-fast -f json

Here bare audit means the mode without --ci, even when you request JSON. Its JSON reported "passed": true while stderr reported ordinary drift. That result is not contradictory: ordinary replay drift is advisory in this mode. --ci is the integrity gate.

Isolated APM 0.31.0 observations from the operations repro and security controls, not a universal three-exit-code formula.
ConditionBare auditCI audit
Clean recorded local project00
Benign modified deployed rule0, drift advisory1, hash/replay drift
Invalid canonical owner / active owner11
Replayed file has no recorded ownership, even though bytes agreeNot measured in this control1, unrecorded
Critical character in an unrecorded governed file11
Warning-only U+200B content, installed and correctly hashed20

Warning-only content does not, by itself, fail CI; adding a hash mismatch would be a different condition. Invalid ownership is also not merely advisory drift: it fails both modes. In a repair control, apm prune removed invalid owner rows, but the next audit still found an unrecorded file. Install then restored complete ownership and the audit passed. Removing bad metadata is not proof that every deployment discrepancy has been repaired.

Ten checks ran in the clean local fixture, including deployment-ledger-owners, content-integrity, and drift. Ten is an observation, not an API constant. Empty projects, configuration, policy, and early failures can change the total. --no-fail-fast requests diagnostic coverage after the first failure; -f json changes the report format (baseline checks). Read check names, findings, and scope, not a fixed “all nine passed” string.

The source-inspected flag contract distinguishes whole-project audit from a package selector or --file: the latter narrow scanning and skip replay. --no-drift explicitly reduces replay coverage; it is not a repair. The earlier _local/package control returned 0 but selected no files, so it proved no successful scan. Chapter 8's corrected ./package control records three scanned files instead. Check the selected input and coverage as well as the exit code, and keep the full-project gate for the maintenance loop.

Cold is not the same as offline

A warm or local-source audit can work offline. Bare replay remains cache-only; the target reference documents an informational skip on a cache miss. CI mode instead can hydrate a lock-pinned scratch replay when modules are absent. A public cold-cache control exited 0 without checkout writes; making its source unreachable exited 1, not a harmless green skip. A local-source cold control succeeded without remote acquisition. Policy discovery can require network too. “Audit is always offline” is obsolete.

Those unchanged-checkout results are scoped observations, not a guarantee for every package form. Chapter 5 records a marker-present plugin replay defect that even mutated materialized cache files. The MCP-only operations control also returned CI 0 for missing or edited native MCP configuration. Inspect actual native artifacts; an audit is not a comprehensive native-config byte comparator or runtime health test.

Likewise, a fixture's “enforcement skipped” warning is not evidence of organization policy compliance. The observed default policy-fetch/hash-failure posture can leave baseline CI green, and APM_POLICY_DISABLE=1 suppressed even an explicit policy source. Chapter 9 explains the authority and bypass boundaries; Chapter 11 protects the required job's inputs. Do not turn a baseline integrity result into a wider security or governance certificate.

When to use / pitfalls

Choose the change you actually mean

With matching declarations, a lock, and unchanged local sources, ordinary install replays recorded state; use the triad to review a refresh. If you deliberately change a selector, install reconciles that new intent: in the registry probe, editing operations/exact from 1.0.0 to 1.1.0, preserving its registry declaration, then running apm install moved it to 1.1.0 with exit 0. That is explicit permission, unlike merely seeing a newer Latest.

apm install --frozen is neither a read-only command nor a universal declaration-change veto. It requires a lock and performs limited structural checks; in 0.31.0 a newly declared local dependency can still deploy and rewrite it. Keep Chapter 5's local-path exception in mind, and use Chapter 6 for the full restore boundary. Audit separately. Do not hand-edit the generated lock to manufacture consistency.

Cleanup: preserve source, then inspect what remains

Removal and target contraction are reconciliation, not permission to erase whole harness folders. Preserve local sources, surviving shared owners, and unrelated human-authored content. The target uninstall reference and release notes describe stronger deletion-failure reporting. The observed Windows behavior below does not satisfy every part of that promise.

Observed APM 0.31.0 cleanup outcomes: operations lifecycle, cleanup-limit, and MCP controls.
Controlled conditionActual outcome
Edited owned target file; apm uninstall _local/package 1; edited file, declaration, and lock retained. Some module cleanup had already happened: no blanket rollback.
Exclusive Windows OS lock on a materialized file; apm uninstall ./package 0 and Uninstall complete, but the locked file remained. The dependency declaration and ownership were removed; the dependency-only fixture lost its lockfile.
Source stops producing an old, locally edited target file; apm install 0 with a warning; the edited stale target file remained.
A directory occupies a required native MCP JSON path; apm install 1; repeating with --force still failed. Force is not general I/O recovery.

The Windows locked-file case is a reproduced limitation, not a network skip and not proof that every uninstall fails. Do not equate a success message with absence of artifacts. Inspect remaining files and the manifest/lock before choosing a retry: after the declaration is gone, the same identifier can fail selection. Preserve edits and resolve the specific filesystem problem rather than deleting source or broad target directories to make a check look clean.

Worked example

Meridian's first monthly refresh, as a pull request

The staff engineer still follows the normal triad: report, review a change, verify immediately. To make “newer upstream” repeatable without pushing a live release or editing a lock, this exercise uses a tiny registry fixture. It is a laboratory for Meridian's maintenance habit, not a migration of the team's Git-based setup to a new service.

Use the registry suite of the operations fixture runner with the prepared absolute APM 0.31.0 executable. It copies the fixture and genuine lock to a new short scratch directory, isolates HOME/cache, and enables registries only there. It serves anonymous reads on 127.0.0.1:18431, with no publishing endpoint or credentials. The fixed port is part of the recorded URLs; if it is busy, do not stop an unrelated process. Do not run these fixture mutations in your book checkout.

backend/examples/updates/1.2/operations/registry/apm.yml — complete fixture manifest. One exact pin, two ranges, one target; experimental registry support is enabled by the isolated runner. loopback network only. apm v0.31.0 — observed fixture
# Anonymous loopback fixture only; run with verify.py's isolated registry suite.
name: operations-registry-constraints
version: 1.0.0
targets: [copilot]
registries:
  fixture:
    url: http://127.0.0.1:18431
  default: fixture
dependencies:
  apm:
    - registry: fixture
      id: operations/exact
      version: 1.0.0
    - registry: fixture
      id: operations/range
      version: ^1.0.0
    - registry: fixture
      id: operations/other
      version: ^1.0.0
includes: auto

The runner first restores and audits the genuine all-1.0.0 lock while the source offers only that version. It then exposes 1.1.0 and 2.0.0-beta.1. The Current / Wanted / Latest table now has a concrete cause. Anonymous HTTP loopback is a supported fixture exception, not permission to send credentials over HTTP to a real registry.

The triad, with preview and an explicit selection, in the operations registry suite. The first three commands exit 0; the immediate audit exits 1 for the cache-state failure explained below. loopback network only. apm v0.31.0 — observed
apm outdated
apm update operations/range --dry-run --verbose
# Review the plan before applying.
apm update operations/range --yes
apm audit --ci --no-fail-fast -f json
Selected preview from the operations registry fixture, condensed from registry-selected-plan: accurate movement, two unchanged dependencies, no project writes in this script-free control. loopback network only. apm v0.31.0 — observed excerpt
[~] operations/range
    ref: 1.0.0 -> 1.1.0

[=] operations/exact
    ref: 1.0.0

[=] operations/other
    ref: 1.0.0

1 updated, 2 unchanged

The lock diff agrees with that plan. But the immediate audit finds something that the plan and lock alone cannot prove: modified: .github/instructions/other.instructions.md. Inspecting the three layers explains it:

Observed state immediately after the selected registry update at APM 0.31.0, before repair.
DependencyLocked versionDeployed textMaterialized cache
operations/range1.1.01.1.01.1.0
operations/other (unselected)1.0.01.0.01.1.0
operations/exact1.0.01.0.01.0.0

This is a reproduced selected-registry-update cache contamination failure in 0.31.0. The unselected dependency kept its locked pin and deployed bytes, but its cache advanced. content-integrity passed because the deployed bytes still matched the lock; cache-based drift replay failed. This does not establish that all updates are broken, nor is it an unavailable-network case. It explains why a successful update and a plausible diff must not be silently presented as a clean result.

In this controlled state, restoring from the unchanged contract repaired the cache:

Operations registry repair: frozen install restores the two cached other files to 1.0.0 without moving the lock; the following full audit exits 0. Both commands exit 0. loopback source available for repair. apm v0.31.0 — observed
apm install --frozen
apm audit --ci --no-fail-fast -f json

Keep the failed audit as well as the repair evidence. Do not replace it with --no-drift, hand-edit the lock, or upgrade the unselected package just to make the discrepancy disappear. The repair establishes recovery for this fixture, not a universal repair guarantee for every audit failure.

Read a change, not a clock

New 0.31.0 locks omit generated_at; unchanged legacy locks can retain it, and Git-semver resolved_at has a different purpose. Neither a timestamp nor an “updated N” counter proves movement. Compare complete lock bytes or hashes and the relevant file inventories (recorded identity and content).

Timestamp-free evidence at APM 0.31.0. Each row compares its own before/after state; the public Git no-op is a separate observed snapshot, not a promise about future releases.
ControlComplete lock bytesOther observed evidence
Selected registry previewUnchangedRecorded project file-hash inventory unchanged.
Selected registry applyChangedOnly the selected locked version moved; the unselected cache discrepancy still failed audit.
Frozen registry repairUnchangedTwo cache files restored; deployed bytes unchanged; subsequent CI audit 0.
Public Git ^1.0.0 preview and apm update --yesUnchanged in bothBoth exited 0 with matching-ref no-op messages; recorded project file-hash inventories unchanged. needs public network

The pull request's review surface is broader than a lock diff alone: apm.yml shows intent, apm.lock.yaml records resolution and ownership, and the audit plus actual artifacts test the result. An unchanged manifest does not by itself prove pins held; an unchanged lock does not by itself prove a clean cache. That is the practical payoff of keeping all three questions separate.

Historical Meridian lock diff — APM 0.23.1 only

The original 2026-07-02 experiment reconstructed an older checkout by manually setting the transitive's starting commit to its genuine earlier value. The subsequent update was real, but that simulated setup is not an authoring recipe. This exact retained excerpt records one commit change and the old timestamp-bearing format; the timestamp is not the current proof of change. Do not recreate the experiment by editing a generated lock.

Exact historical apm.lock.yaml diff excerpt, including its original annotations. Public-source update after a simulated older starting lock, recorded 2026-07-02. needs public network apm v0.23.1 — historical snapshot, not rerun at 0.31.0
--- apm.lock.yaml (before update)
+++ apm.lock.yaml (after update)
-generated_at: '2026-07-02T01:17:41.266376+00:00'
+generated_at: '2026-07-02T01:23:19.474392+00:00'   # advanced: update rewrote the resolved content

   - repo_url: github/awesome-copilot                 # the transitive #main dep
-  resolved_commit: a4aebcd4bd354b59a6a6c12ab9c5c98a9d9e0276   # old (behind)
+  resolved_commit: 3169734bc2fb25d5e092130fc93d24b0dee3ac3a   # new (branch tip)

Recap & next

Recap

  • Reproducible is a floor, not a ceiling. Keep the report → reviewed change → verify loop, with an owner and a cadence. Meridian chose monthly.
  • Availability is not permission. Registry Current, Wanted, and Latest answer different questions. An exact pin can be outdated without moving under update; an explicit selector edit followed by install is new intent.
  • Update is not always a version change. Accurate unchanged plans, no-ops, and repair-only outcomes exist. Preview can still execute trusted lifecycle scripts; inspect that boundary rather than assuming no side effects.
  • Audit is scoped integrity evidence. Ordinary bare-audit drift is advisory; invalid owners fail both modes; correctly hashed Warning-only content can pass CI. Cold CI replay can need network, and check totals vary. It is not a vulnerability feed.
  • Review actual state, not clocks or success messages. The selected registry update's cache failure and Windows uninstall leftovers are real 0.31.0 limits. Preserve failure evidence, protect source, and verify a deliberate repair; do not suppress checks to manufacture a clean refresh.

Next

Audit was your first look at the checks around agent-context integrity. Chapter 8 — Security by Default opens the install-time pipeline: scanning authorized deployable content before agent-readable deployment, content-hash checks, and scoped executable/MCP trust. Cache and staging writes can happen earlier, and none of these checks is runtime sandboxing. That is the next layer of Provenance / security, with its guarantees and observed limits kept separate.