CI/CD / Promotion and environment contracts / Lesson 7
Dependency caches without hidden correctness
Use caches as disposable acceleration while keeping dependency selection authoritative.
On this page
Lesson goals
- Design a cache key around compatibility boundaries.
- Explain why a cold-cache build is a correctness check.
A cache may disappear
Parcel downloads dependencies before packaging. Retaining downloaded packages can reduce repeated network work, but a cache hit must not be required to obtain the right release. An empty cache should cause a slower correct build or a clear acquisition failure, never an unexplained change in selected versions. Keep dependency resolution in reviewed source and use the package manager’s integrity mechanism. A cache is not a release registry, a secret store, or a substitute for declaring build inputs.
Encode the compatibility boundary
A useful key distinguishes inputs that make cached content incompatible: operating system, architecture, package manager or runtime family, and the reviewed dependency lock. Some caches store portable downloads, while others contain compiled native objects or whole environments tied to a path. Their key requirements differ. Start with the cache’s actual content rather than copying an impressive-looking key from another project. If lock content changes, a precise key should change too; a branch name alone does not establish dependency compatibility.
import hashlib
def key(platform, lock):
return platform + "-" + hashlib.sha256(lock.encode()).hexdigest()[:12]
a = key("linux-x64", "parcel-lib=1")
b = key("linux-x64", "parcel-lib=2")
print(a == key("linux-x64", "parcel-lib=1"))
print(a == b)
True
False
The truncated hash is only a compact teaching key, not a package integrity proof. A production key strategy must account for the package manager’s cache format and relevant toolchain identity.
Fallback changes confidence, not dependency policy
A fallback key can restore older or broader cached downloads and let the package manager fetch missing content. That may be safe when every package is subsequently verified against the current lock. Restoring an entire executable environment from a broad key is a different risk because stale files may remain active. Avoid assuming all cache entries are interchangeable. Describe which restored files are trusted, which are revalidated, and whether untrusted jobs can populate a cache later consumed by a privileged release job.
Control writers and race behavior
When several jobs restore and save the same key, the final saved content may depend on completion order and platform behavior. This can reduce hit rates or mix incompatible state. Prefer a clear producer or separate read-only consumers where the platform supports that distinction. Isolate cache trust domains so proposal code cannot poison release execution. The following conceptual policy names two separate concerns: who may write and whether the consumer verifies the contents. Neither a protected branch label nor a cache key alone establishes both.
proposal cache: unprivileged writers, proposal consumers
release downloads: trusted acquisition, verified integrity
release artifact: retained independently of either cache
This is a policy sketch, not product configuration. Actual protection, sharing, and fallback behavior must be reviewed for the selected runner and CI service. No cache is read, cleared, or uploaded here.
Measure warm and cold paths
Record download time, restore time, archive transfer, and hit rate separately. A huge cache can cost more to transfer than it saves, especially for frequently changing dependencies. Periodic cold-cache checks expose undeclared inputs and ensure the build remains correct when storage is unavailable. Diagnose a missing file by checking the dependency and artifact contracts before clearing every cache. Broad deletion may temporarily mask a race while destroying useful evidence about why the cache became incorrect.
Common errors and fixes
The package job works only after an unrelated earlier run.
- Why it happens
- A cache contains undeclared required output.
- How to fix it
- Produce the required artifact explicitly or declare the dependency acquisition step.
- Prevent it next time
- Validate the intended build with an empty cache.
A native extension is restored on another architecture.
- Why it happens
- The cache key omits a compatibility dimension.
- How to fix it
- Separate entries by relevant runtime, platform, and dependency identity.
- Prevent it next time
- Document what the cache actually contains and how portability is established.
Untrusted proposal content influences a privileged release.
- Why it happens
- Both paths share writable executable cache state.
- How to fix it
- Separate trust domains and verify acquired dependencies under release policy.
- Prevent it next time
- Review cache writers as part of the execution trust boundary.
Check your understanding
1. A cache stores only verified source archives. Must every compiler upgrade invalidate it?
Show worked solution
Not necessarily. If the cache really contains source downloads and current integrity checks remain authoritative, compiler identity may not affect portability. A compiled-output cache is different. Inspect content and validation before deciding which dimensions belong in the key.
2. A five-gigabyte cache saves thirty seconds of downloads but needs two minutes to restore. What change is justified?
Show worked solution
Measure a representative cold path and reduce or remove that cache if transfer dominates. Cache only high-value content with a suitable lifecycle. A higher hit rate is not useful when end-to-end feedback gets slower.
Key takeaways
- A cold cache should preserve correctness.
- Cache keys, writers, and integrity checks form one reviewed contract.