Upgrading APM Dependencies

Upgrades in APM mean: pointing one or more dependencies at a newer Git tag in calab-ai/apm-registry, regenerating the lockfile, and re-syncing the installed files. This page covers the three commands that drive that loop and how to recover when things go sideways.


The three commands

CommandWhat it doesWhen to run it
gh calab apm update [<id>]Bumps one (or all) dependencies in apm.yml to the latest GA tag in the catalog, then re-resolves and re-writes the lockfile.You want newer versions.
gh calab apm resolveReads apm.yml, resolves every tag ref to a commit SHA + file hashes, writes apm.lock.yaml. Does not touch installed files.You hand-edited apm.yml.
gh calab apm syncReads apm.lock.yaml and writes (or removes) installed files to match.After update or resolve, or in CI to materialize a checked-in lockfile.

Mental model:

apm.yml           apm.lock.yaml         installed files + .generated-from-apm.json
   |   resolve         |        sync                |
   +-----------------> +--------------------------> +
   ^                                                |
   +-----------------------  update  ---------------+
                       (delegates to resolve + sync)

Upgrade one dependency

gh calab apm update calab-workspace-base

Argument form: apm update accepts the same forms as apm install — short package name (preferred for packages) or reverse-domain id. Primitives must use the reverse-domain id (e.g. gh calab apm update calab.skill.az-cli).

This:

  1. Looks up the latest GA tag for that id in the catalog.
  2. Rewrites the source.ref in apm.yml.
  3. Re-runs the resolver and writes a new apm.lock.yaml.
  4. Re-runs apm sync to materialize the new files.

JSON envelope:

{
  "ok": true,
  "command": "apm update",
  "data": {
    "updated": [
      { "id": "calab.package.workspace-base", "from": "v1.0.0", "to": "v1.1.0" }
    ],
    "already_latest": []
  }
}

If everything is already on the latest GA, the id appears in already_latest and exit code is 0.

Upgrade everything

gh calab apm update

No id → updates every dependency. Output lists every change in data.updated.

Upgrade by hand

When you need to pin to a specific older or pre-release tag:

# 1. Edit apm.yml manually:
#    source.ref: refs/tags/packages/calab-workspace-base/v1.2.0-rc.1
#
# 2. Re-resolve and re-sync:
gh calab apm resolve
gh calab apm sync

The resolver rejects branch refs unless policy.allow_branch_refs: true is set in apm.yml (only allowed in dev — never in production manifests).


Lockfile semantics

apm.lock.yaml is the only input to apm sync. The contract is:

  • Every dependency in apm.yml has exactly one corresponding lockfile entry.
  • Every lockfile entry records a resolved commit SHA (never just the tag) and a SHA-256 hash of every file.
  • Two consumers running apm sync against the same lockfile produce byte-identical installed trees — that is the whole reason the lockfile exists.

Schema: schemas/apm-lock.schema.json.

Lock-only operations

gh calab apm lock           # regenerate apm.lock.yaml in place
gh calab apm lock --check   # fail (exit 5) if the lockfile is out of date

--check is what you wire into CI to keep the manifest and lockfile in sync.


Resolving lockfile drift

The most common upgrade footgun is a manifest edit without a corresponding lockfile regeneration. Symptoms:

  • gh calab apm validate exits 5 (conflict).
  • gh calab apm lock --check exits 5.
  • CI fails with “lockfile drift detected”.

The fix is always the same:

gh calab apm resolve   # regenerate apm.lock.yaml from the current apm.yml
gh calab apm sync      # materialize the new files
git add apm.yml apm.lock.yaml .github/copilot .generated-from-apm.json
git commit -m "chore(apm): refresh lockfile"

If you intentionally want to roll back a manifest change but keep the existing lockfile, revert the manifest edit instead — never hand-edit the lockfile.

Per-file hash mismatch

gh calab apm validate --verify re-downloads every installed file and recomputes hashes. If a hash mismatches, either:

  • a local edit modified an APM-managed file (don’t — APM-managed files are tracked in .generated-from-apm.json and will be safely re-written by the next sync), or
  • the upstream tag was force-pushed (file an issue against calab-ai/apm-registry; tags are immutable by policy).

- name: APM — verify manifest and lockfile
  run: gh calab apm validate
  env:
    GH_TOKEN: ${{ github.token }}
 
- name: APM — verify lockfile is current
  run: gh calab apm lock --check
  env:
    GH_TOKEN: ${{ github.token }}
 
- name: APM — install from lockfile
  run: gh calab apm sync
  env:
    GH_TOKEN: ${{ github.token }}

CI must never call gh calab apm install or gh calab apm update — both mutate the manifest. Reserve those for human commits.


Uninstalling

gh calab apm uninstall <id-or-name>

Removes the dependency from apm.yml, regenerates the lockfile, and removes only the files previously recorded in .generated-from-apm.json for that id. Any file you added by hand stays put.