Skip to content

feat: lockfile graduation — wire writer in up's feature pipeline (PR-4b)#42

Merged
pofallon merged 2 commits into
mainfrom
pr4b-lockfile-writer-wiring
May 25, 2026
Merged

feat: lockfile graduation — wire writer in up's feature pipeline (PR-4b)#42
pofallon merged 2 commits into
mainfrom
pr4b-lockfile-writer-wiring

Conversation

@pofallon
Copy link
Copy Markdown
Contributor

Re-opened replacement for #35 — the original PR was auto-closed when its base branch pr4-lockfile-graduation (#33) was deleted on merge. Branch content unchanged; base retargeted to main so the squashed PR-4a commit on main reconciles cleanly.

See #35 for the original PR description and reviews.

🤖 Generated with Claude Code

pofallon and others added 2 commits May 25, 2026 12:39
Phase 1 of lockfile graduation per docs/ROADMAP_TO_1.0.md. Ships the
schema parity fixes, the user-visible CLI flag surface, the writer
helper, and upstream-aligned error messages. The actual writer
integration (building a Lockfile from resolved features and writing
it after `up`/`build`) is tracked as PR-4b at issue #32.

## Schema parity (deacon-core/lockfile.rs)

- LockfileFeature.depends_on now serializes as `dependsOn` (camelCase)
  via #[serde(rename)], matching upstream `devcontainers/cli`'s
  generateLockfile in src/spec-configuration/lockfile.ts. Deserializer
  accepts the camelCase form on read.
- write_lockfile() emits a trailing newline to match upstream
  `JSON.stringify(..., 2) + '\n'`. Byte-identical output keeps the
  --frozen-lockfile content comparison stable.
- LockfileValidationResult::format_error() now emits upstream-aligned
  strings: "Lockfile does not exist." / "Lockfile does not match." as
  the leading summary line; trailing actionable guidance references
  the graduated --frozen-lockfile flag rather than the deprecated
  --experimental-frozen-lockfile.
- New LockfileFeature::from_resolved() helper constructs entries in
  the upstream canonical form (resolved = "{registry}/{repo}@{digest}",
  integrity = "{digest}"). This is the writer entry point PR-4b uses.

## CLI surface graduation

up + build both gain:
- --no-lockfile (visible): skip lockfile generation and verification.
- --frozen-lockfile (visible): require an up-to-date lockfile; fail
  if resolution would change it.

Mutual exclusivity (--no-lockfile xor --frozen-lockfile) enforced in
the CLI layer, mirroring upstream's pre-parse validation.

Deprecation:
- --experimental-lockfile and --experimental-frozen-lockfile remain
  accepted as hidden aliases through the 1.x line. The CLI emits a
  WARN on use directing users to the graduated flags.
- effective_frozen = frozen_lockfile || experimental_frozen_lockfile
  matches upstream's effectiveFrozenLockfile coalescing.

## Downstream consumers

- up/mod.rs frozen-validation path now reads args.frozen_lockfile
  (the effective value) instead of args.experimental_frozen_lockfile.
  No behavior change — only the variable name moves.
- build/mod.rs args struct gains no_lockfile + frozen_lockfile fields
  (carrier-only for PR-4b; #[allow(dead_code)] until then).

## Tests

Added in deacon-core/lockfile.rs:
- test_depends_on_serializes_as_camel_case
- test_write_lockfile_emits_trailing_newline
- test_from_resolved_constructs_upstream_form

Updated in deacon/tests/up_lockfile_frozen.rs (5 assertions):
align with the new upstream-format error messages
("Lockfile does not exist." / "Lockfile does not match." / capital "F"
on "Features ..." substrings).

## Verified

- cargo fmt --all -- --check
- cargo clippy --all-targets -- -D warnings
- cargo nextest run --profile dev-fast --no-default-features: 1930/1930 pass
- cargo test --doc --workspace: 130/130 pass

## Follow-up tracked

- #32 — PR-4b: wire writer in up + build feature pipeline.
- Build-command lockfile wiring is gated on the pre-existing TODO at
  build/mod.rs:1285 (build doesn't install features today). Will land
  with #32 or as a sibling PR.

## Refs

- docs/ROADMAP_TO_1.0.md Tier 1 item "Lockfile graduation"
- Upstream: devcontainers/cli#1212 (graduated lockfile in v0.87.0)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to PR-4a (#33): the user-visible flag surface, schema parity
fix, and upstream-aligned error strings landed there, but the actual
writer was never invoked. This wires it in.

After feature resolution + download, build a Lockfile from the resolved
feature set (`build_lockfile_from_features` mirrors upstream
`generateLockfile`), thread it back via FeatureBuildOutput, then:

- default: write `{config_dir}/devcontainer-lock.json` (sorted by key,
  trailing newline — byte-identical to upstream `writeLockfile`)
- `--frozen-lockfile`: byte-compare against on-disk; fail with the
  upstream summary strings (`"Lockfile does not exist."` /
  `"Lockfile does not match."`) so existing CI scripts keep working
- `--no-lockfile`: skip entirely
- deprecated `--experimental-lockfile <PATH>`: still honored for the
  custom-path form (hidden alias path through the 1.x line)

Wired into both single-container (`container.rs`) and compose
(`compose.rs`) flows via shared `handle_lockfile_post_build` helper.
EROFS/EACCES on the write path downgrades to a WARN so read-only
workspaces (CI mounts, read-only volumes) don't break `up`.

Lockfile keys are the user-provided feature ID (e.g.
`ghcr.io/devcontainers/features/node:1`), not the canonical no-tag form
— matching upstream and keeping the existing pre-build structural
validation aligned.

Build (`crates/deacon/src/commands/build/mod.rs`) is intentionally out
of scope — see issue #32 and the standing TODO at build/mod.rs:1285.
That's PR-4c.

Closes #32 (PR-4 phase 2 of the lockfile graduation track).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pofallon pofallon merged commit c3c5407 into main May 25, 2026
10 checks passed
@pofallon pofallon deleted the pr4b-lockfile-writer-wiring branch May 25, 2026 19:17
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.

1 participant