Repository navigation
feat(features): install features reachable only through dependsOn - #151
Conversation
There was a problem hiding this comment.
Code Review — b4a57e2 · ADVISORY
The new recursive dependsOn path can lose declared dependency aliases during content deduplication, and bypasses lockfile artifact validation or transitive expansion when the base image label marks a dependency baked. These leave lockfile pins incomplete and can omit required dependency features on upgrade-era base images.
| Finding | Anchor | |
|---|---|---|
| 🟡 MEDIUM | Dedupe drops the dependsOn alias before it can be locked core/version-compatibility |
up.go:1415 |
| 🟡 MEDIUM | Baked dependencies can bypass lockfile digest validation core/version-compatibility |
up.go:473 |
| 🟡 MEDIUM | A baked dependency prevents discovery of its transitive dependencies core/correctness |
up.go:474 |
What I reviewed
Coverage: Reviewed the exact head b4a57e2a28eed794fc64f8bab6e484af8c5859d5 (one commit; 16 changed files relative to lockfile base 45c0015c262fb870cef8107c4a3e7c31d581b7a8). Worked correctness, interface-contract, version-compatibility, and repository mechanisms R5 (reference-CLI divergence) and R8 (tests), plus compose reattach hash behavior. Checked feature resolution, option merging, DAG ordering, build-context use, lockfile pin/commit flow, and baked-label handling. The baked transitive omission is also explicitly described in the new design text as intentional, but that assumption fails for base images made before this behavior shipped. No .dap/code-review.yml or AGENTS.md exists at directives base; applied the repository's .dap/review/engineering.md at that SHA and the core review dimensions. Did not run tests or execute the code. access-control, untrusted-input, and secret-exposure mechanisms were not implicated by the diff; state-integrity and redelivery are not applicable.
ADVISORY — findings worth reading, none of them blocking. Reply in a finding's own thread to argue it. On the pull request I answer @dap-code-review-by-crunchloop review, pause and resume.
b4a57e2 to
419fdce
Compare
There was a problem hiding this comment.
Code Review — 419fdce · ADVISORY
No newly changed behavior was found: the reviewed head has the same tree as the prior head that produced the three already-open findings, so those findings remain open and were not resubmitted.
| Finding | Anchor | |
|---|---|---|
| 🟡 MEDIUM | Dedupe drops the dependsOn alias before it can be locked core/version-compatibility |
up.go:1415 |
| 🟡 MEDIUM | Baked dependencies can bypass lockfile digest validation core/version-compatibility |
up.go:473 |
| 🟡 MEDIUM | A baked dependency prevents discovery of its transitive dependencies core/correctness |
up.go:474 |
What I reviewed
Coverage: Verified the exact requested SHA (419fdcefb53254a7054c6162d21004c754671b7c), current PR scope (one commit, 16 files), and the governing repository guidance at base SHA 864709a381a47798a949e16138d066cbd9f4916f; .dap/code-review.yml is absent, and .dap/review/engineering.md supplies the repository-specific mechanisms. The relevant feature-resolution, dependency-graph, lockfile, base-image-label, and compose reattach paths and their tests were read. Correctness and version-compatibility were considered; the prior three findings remain open on unchanged code. No visual evidence applies, and no tests or system code were executed.
ADVISORY — findings worth reading, none of them blocking. Reply in a finding's own thread to argue it. On the pull request I answer @dap-code-review-by-crunchloop review, pause and resume.
419fdce to
680f353
Compare
* fix(docker): pre-pull the Dockerfile syntax frontend image BuildImage builds through ImageBuild with no BuildKit session, and BuildKit before v0.31 (Docker Engine before 29.6) refuses registry access without one, even for anonymous public pulls: a bearer challenge with no session fails with "no active sessions". FROM images were already pre-pulled through the classic pull API for this reason; a `# syntax=` frontend was not, so any user Dockerfile naming one failed before parsing (#148). Pre-pull the frontend too: the BUILDKIT_SYNTAX build arg, else the syntax parser directive, read as BuildKit's parser.DetectSyntax reads it (leading directive lines only, # or // comments, after an optional BOM and shebang; the image is the value's first word). Verified against a Docker 29.5.0 daemon (containerd image store): the new integration test fails there with the reported error without this change and passes with it. Docker 29.6+ resolves anonymously without a session, so it passes there either way. Fixes #148. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(docker): pre-pull a frontend named by a JSON syntax document BuildKit's parser.DetectSyntax falls back, after the # and // directive forms, to a JSON object with a string "syntax" member, the input of custom frontends that take JSON, and uses the whole value as the frontend image. syntaxFrontend left that form out, so its frontend was not pre-pulled and hit the session-less resolution failure on engines before 29.6 (DAP review finding 3891 on #150). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: bilby91 <2201079+bilby91@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review — 680f353 · ADVISORY
The implementation fixes the three carried findings: duplicate aliases are retained for lockfile commits, baked artifact digests are checked against pins, and dependencies of baked features are traversed. One correctness issue remains: a baked dependency can be treated as satisfying a request even when the request's options differ from those used to build the base image, so those options are silently not applied.
| Finding | Anchor | |
|---|---|---|
| 🟡 MEDIUM | Baked dependsOn features can be reused with the wrong options core/correctness |
up.go:475 |
What I reviewed
Coverage: Reviewed the exact head 680f353bb8f092e44cce80b29ba63e83739a0201 (2 commits, 21 changed files relative to the PR base); its merge base with main is 25711e1bb5b50e4438cf3e05d3ef993f6a7a4e1a. Checked out the exact SHA and verified HEAD. Read the PR rationale, the governing .dap/review/engineering.md at directives base 25711e1bb5b50e4438cf3e05d3ef993f6a7a4e1a, and its README, CONTRIBUTING, and feature design guidance; .dap/code-review.yml is absent. Reviewed feature dependency resolution, graph ordering, baked-image matching and metadata labels, lockfile acceptance/commit behavior, and consumers that skip baked feature installation. Verified and resolved all three carried findings. Relevant mechanisms: correctness, version-compatibility, interface-contract, test-coverage, and operability. No backend-specific path change warranted a path-parity finding; conditional state-integrity and redelivery do not apply. I did not reach unrelated feature/cache and runtime paths.
ADVISORY — findings worth reading, none of them blocking. Reply in a finding's own thread to argue it. On the pull request I answer @dap-code-review-by-crunchloop review, pause and resume.
The DAP review of #151 (finding 3903) notes a baked dependency is reused whatever options it was requested with. The pre-baked match has never compared options, for declared features either; record the limitation in §5.1 and track the fix to the match itself in #154. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…igs (#153) * fix(features): enforce lockfile pins on baked features and empty configs Two findings from the review of #146: - A feature the base image's label satisfied was skipped and its existing lock entry copied, so frozen mode accepted a base image built from another artifact, or one the lockfile had no entry for. The label already records each feature's resolvedRef, but ParseMetadataLabel dropped it (and so did carrying base entries into a rebuilt label). Keep it on FeatureMetadata, read the lockfile before matching the label, and accept a baked feature only when its artifact is the pinned one or, with no pin, a recorded one, which is then locked. Anything else is fetched at its pin and installed. LockfileDisabled keeps the label-only match. - layerFeatures returned before the lockfile when no features were declared, so removing the last feature left its entry behind and frozen mode accepted it. Reconcile an existing lockfile then too: default and upgrade empty it, frozen reports the stale entries. Without a lockfile nothing is created, as with the reference CLI. The pre-baked hot path is unchanged for images this library built, whose labels have recorded resolvedRef since v0.1.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(features): re-resolve baked features in lockfile upgrade mode acceptsBaked found no pin in LockfileUpgrade (pin ignores the lockfile there) and so accepted any recorded baked artifact, which commit then locked: an upgrade could replace a newer pin with the older artifact the base image carries (DAP review finding 3902 on #153). Upgrade mode now accepts no baked feature, so each reference is resolved afresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: bilby91 <2201079+bilby91@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dependsOn only ordered features that devcontainer.json declared anyway: an undeclared dependency was silently skipped, so a feature relying on one installed without it (#147). The spec makes dependsOn a hard dependency, installed recursively with the options it names. - layerFeatures' fetch loop appends each fetched feature's dependsOn requirements and fetches them in turn: lockfile pinning, option validation and defaults apply as for declared features, and they are locked under their dependsOn key as the reference CLI does. - Spec equality decides duplicates: same content (digest) and same options once defaults apply. A repeat of a key with the same options is skipped before fetching, which also ends cycles; the same content under another reference is dropped after. One feature with different options is installed once per set, and topoSort maps an id to all its entries, pointing dependsOn edges at the one with the requested options. - A dependency the base image's label satisfies is not fetched or installed again: bakedFeature, factored from markAlreadyInstalled, includes the lockfile's check of the artifact the label records. Its own dependencies are still followed, as are a declared baked feature's, since an image built before this change lacks them. Labels now record each feature's dependsOn ({} when none); for a label written before that, the baked feature's metadata is fetched at its recorded artifact to find them. - A dependency dropped as a duplicate under another reference is still locked under its own key. - The compose inputs hash also covers a local dependency's content, which the declaring feature's content does not; projects without one hash as before. - OCI references are lowercased before parsing, as the CLI's getRef does: published dependsOn keys such as ghcr.io/codspace/dependson/A failed to parse. The integration test builds the CLI's own lockfile-dependson fixture in frozen mode against its expected lockfile. Closes #147. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DAP review of #151 (finding 3903) notes a baked dependency is reused whatever options it was requested with. The pre-baked match has never compared options, for declared features either; record the limitation in §5.1 and track the fix to the match itself in #154. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DAP review of #153 (finding 3904) noted a baked feature accepted without a pin was locked from its label entry, which then had no dependsOn. This change fills it in before commit; pin it with a test for a label that records dependsOn and for an older one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a5d2c99 to
9a4e5c2
Compare
|
@dap-code-review-by-crunchloop review |
|
On it. A review of the current head is on its way. This branch's automatic review rounds are back, so I'll review new pushes again. |
There was a problem hiding this comment.
Code Review — 9a4e5c2 · BLOCKING
The change introduces a baked-dependency option mismatch and a compose reattach hash gap for local dependencies of remote features; both can produce or retain behavior inconsistent with the newly resolved dependencies. I also found an untrusted-input path where fetched feature metadata can cause local directories outside the project to be read into the build context.
| Finding | Anchor | |
|---|---|---|
| 🔴 CRITICAL | Remote feature metadata can traverse into local files during feature installation core/untrusted-input |
up.go:472 |
| 🟡 MEDIUM | A baked dependency is reused despite different requested options core/correctness |
up.go:475 |
| 🟡 MEDIUM | Compose reattach misses local dependencies of remote features core/correctness |
up_compose_reattach.go:143 |
| 🟡 MEDIUM | Remote dependency metadata can read and package local feature directories outside the project core/untrusted-input |
up.go:472 |
What I reviewed
Coverage: Reviewed the exact head 9a4e5c2b3176d7a990ddaeba61a7f22cb6cc6577: 3 commits and 21 changed files relative to main at ceaba2070fa6783221a83bdebf3a142470b3e066. .dap/code-review.yml is absent at directives base 67a01cce99a1b727d28a081efbe87caf375d9e52; used the repository mechanism catalogue at .dap/review/engineering.md and read the related design and contribution guidance. Worked correctness, untrusted-input, version-compatibility, and test-coverage while tracing dependency expansion, baked metadata and lock checks, duplicate aliases, the feature build context, and compose reattach hashing. Verified the old baked lock-pin mismatch and transitive-dependency discovery findings are fixed in this head; reviewed the option-mismatch rebuttal and found the new dependsOn path exposes it. Did not run the system or tests; examined tests as source. Did not independently review unrelated low-risk README/changelog and mechanical API-renaming hunks in depth. No visual evidence applies.
BLOCKING — at least one finding is at or above this repository's blocking severity. Reply in a finding's own thread to argue it. On the pull request I answer @dap-code-review-by-crunchloop review, pause and resume.
The DAP review of #151 (findings 3912, 3919, 3917) showed a fetched feature's dependsOn could name a local path, absolute or escaping with .., which layerFeatures resolved against devcontainer.json's directory, copied into the build context and ran: published metadata choosing a host directory to execute. The compose inputs hash also missed such a dependency, since it follows local dependencies from declared local features only. A local dependsOn key is now followed only from a local feature, and only as a relative path that resolves, symlinks included, inside the directory of devcontainer.json, as the reference CLI requires of local features; anything else fails Up and Build. With remote features unable to reach local dependencies, the compose hash's traversal covers every one. Declared local features keep their existing resolution. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DAP review of #151 (finding 3913) showed a dependency the base image's label listed was reused whatever options its dependsOn entry asked for: the pre-baked match compares id and version only, and those options are the feature author's, which no user config could route through that match before. Dependencies are now always fetched and installed; a declared baked feature still is not reinstalled, and its dependencies are installed. Until #154 adds option matching, a pre-baked image whose features have dependencies reinstalls them on every build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review — 4811e0a · CLEAN
No new defect is established in the final commit; the prior path-confinement, baked-dependency-option and compose-hash claims no longer hold. I dismissed or resolved the carried findings after checking the relevant code at 4811e0a; the two open arguments without an explicit reply were adjudicated against the code as well.
What I reviewed
Coverage: Reviewed the 5-commit, 21-file change against exact head 4811e0aecb4e81f0166eb4316efd1c84480500c1 (same SHA as HEAD), focusing on correctness, untrusted-input, version-compatibility, compose reattach hashing and dependency graph/lock behavior. Confirmed local dependency confinement and transitive local-content hashing, baked dependencies now fetched rather than accepted from base-image labels, and dedupe aliases retained for lockfile commit. Read applicable repository guidance at directives base 67a01cce99a1b727d28a081efbe87caf375d9e52; .dap/code-review.yml is absent, and the applicable standard is .dap/review/engineering.md plus CONTRIBUTING.md and design/features.md. The eight supplied GitHub comment IDs returned 404 individually; the available pull-request review comments and their replies were read via the PR comments endpoint and used to check the carried claims. No system code or tests were executed. Other behavior outside these paths was not exhaustively reviewed.
CLEAN — this round found nothing. Reply in a finding's own thread to argue it. On the pull request I answer @dap-code-review-by-crunchloop review, pause and resume.
Closes #147. Stacked on #146 (lockfile): pulled-in features are locked too. The base is
feat/feature-lockfile, so this diff shows only thedependsOnchange. Retarget it tomainonce #146 merges.Problem
dependsOnonly ordered features thatdevcontainer.jsonalready declared. An undeclared dependency was skipped silently, so a feature that relies on one was installed without it. The spec makesdependsOna hard dependency, installed recursively with the options it names. The reference CLI does this incomputeDependsOnInstallationOrder.Design decisions
Recorded in
design/features.md§5.1 and §8.2.layerFeatures' fetch loop appends each fetched feature'sdependsOnentries and fetches them in turn. Lockfile pinning, option validation and defaults apply exactly as for declared features. Pulled-in features are locked under theirdependsOnkey, as the CLI does.:1vs:1.0) is dropped after fetching.dependsOnedge points at the entry with the requested options.markAlreadyInstalled's match is factored intobakedFeaturefor this.getRefdoes. PublisheddependsOnkeys with uppercase letters (e.g.ghcr.io/codspace/dependson/A) failed to parse before.Not in this PR
.tgz, which one of the CLI's fixtures uses. The integration test leaves that feature out.overrideFeatureInstallOrderis ignored for fetched features, becauselayerFeaturesre-sorts them withOrder(…, nil). A throwaway test confirmed it. This is pre-existing and also affects declared features, so the README's ✅ for it is inaccurate. It's separate work.Testing
Unit tests cover:
Each fails with the change disabled.
Integration:
TestDependsOn_MatchesCLILockfilebuilds the CLI's ownlockfile-dependsonfixture in frozen mode against real ghcr.io, checked against the CLI's expected lockfile.go vet, lint andgo test -race ./...pass. The integration suite passes, excluding the Docker-in-Docker tests that were broken onmainat the time (since fixed in test(integration): fix docker-in-docker tests on ubuntu-26.04 runners #149).🤖 Generated with Claude Code