|
| 1 | +# Divergences, decisions & accepted limitations |
| 2 | + |
| 3 | +This CLI is validated for behavioral parity with the reference TypeScript |
| 4 | +`@devcontainers/cli` (pinned at **v0.88.0**, see [`migration/`](migration/)). Where it |
| 5 | +deliberately differs, the difference is recorded here — this is the durable record of |
| 6 | +*intentional* departures from the oracle, not a backlog. User-facing additions are |
| 7 | +documented in [`go-only-features.md`](go-only-features.md). |
| 8 | + |
| 9 | +## Deliberate divergences from the reference CLI |
| 10 | + |
| 11 | +These are intentional behavior differences; each is covered by tests and, where it |
| 12 | +touches a compared surface, reflected in the parity matrix. |
| 13 | + |
| 14 | +- **Go-only commands / flags** (full reference in [`go-only-features.md`](go-only-features.md)): |
| 15 | + `check` and `setup` (host preflight/remediation), `up --cache-image` (boot from a |
| 16 | + prebuilt image, skip build + feature install), `read-configuration --cache-key` |
| 17 | + (deterministic content hash; additive — default output is byte-identical to TS), |
| 18 | + `build --secrets-file` (BuildKit build secrets; TS `build` has no such flag), and the |
| 19 | + automatic credential bridge that hands the CLI's resolved auth to `docker build`. |
| 20 | +- **`--override-config` deep-merges** the override onto the base config, whereas TS |
| 21 | + replaces the config wholesale (`readDocument(overrideConfigFile ?? configFile)`). With |
| 22 | + no readable base, the override stands alone — identical to TS. This lets an orchestrator |
| 23 | + pass a partial override. Only matrix case for the flag is the error path, so contract + |
| 24 | + semantic stay green. |
| 25 | +- **`--terminal-log-file` tees the same combined stream** as `--log-file`. TS produces two |
| 26 | + files (a terminal stream with ANSI and a plain one); this CLI keeps a single log stream |
| 27 | + (no self-managed PTY — see decisions), so both flags capture the same output (without |
| 28 | + ANSI). Never a black hole. |
| 29 | +- **`config.build.cacheFrom`** is honored (wired to `--cache-from` after the flag's |
| 30 | + values) — matching `singleContainer.ts`. Upstream defines the field; this is a parity |
| 31 | + fix, noted here because it was previously a dead field. |
| 32 | +- **`BUILDKIT_INLINE_CACHE=1`** is omitted when `--cache-to` is an inline exporter |
| 33 | + (`/type\s*=\s*inline/i`), matching TS `isBuildxCacheToInline` — a parity fix over the |
| 34 | + earlier unconditional build-arg. |
| 35 | + |
| 36 | +## Firm decisions & scope |
| 37 | + |
| 38 | +- **Platform: Linux only** (amd64 / arm64). Windows and macOS are not targets — no |
| 39 | + runtime/E2E/release/`windows-latest`/ConPTY lane. The `platform="win32"` logic is kept |
| 40 | + solely for parity with the oracle. (arm64 runtime is validated via a non-gating, |
| 41 | + QEMU-emulated experimental job.) |
| 42 | +- **Runtime: Docker only.** Podman is not supported (no parity guarantee or test). |
| 43 | +- **Compose: v2 only** (`docker compose`). Compose v1 (`docker-compose`) is not supported. |
| 44 | +- **`exec`: inherited terminal** (`docker exec -it` inherits the controlling terminal), |
| 45 | + no self-managed PTY. The 128+N contract comes from the child process. Interactive |
| 46 | + `docker exec` is deliberately kept as a shell-out. |
| 47 | +- **Docker Go SDK: `github.com/moby/moby/{client,api}`** (the v29 "options-in, |
| 48 | + result-out" surface), replacing the deprecated `github.com/docker/docker`. The |
| 49 | + top-level `github.com/moby/moby` (v2) module is an internal implementation detail and is |
| 50 | + deliberately **not** a dependency. Requires Docker Engine API ≥ v1.44 (Docker v25+). |
| 51 | +- **OCI image: `ghcr.io/spin-stack/devcontainer-cli`** (source repo |
| 52 | + `github.com/spin-stack/devcontainer-cli`), distroless/static, non-root. |
| 53 | +- **Self-containment stance.** Container/engine operations, Docker-context resolution and |
| 54 | + git-root detection run in-process (Go libraries / stdlib), and lifecycle hooks run via |
| 55 | + the Docker exec API rather than `docker exec`. The following are kept as shell-outs on |
| 56 | + purpose: `docker buildx build` (buildx feature breadth + the user's builder/context; a |
| 57 | + library would regress buildx or pull the heavy buildkit client), `docker compose` (its |
| 58 | + output is not in the compared stream, so a library adds large deps for zero parity |
| 59 | + gain), interactive `docker exec -it`, and the credential-helper protocol (external |
| 60 | + executables by design). |
| 61 | + |
| 62 | +## Accepted limitations |
| 63 | + |
| 64 | +Known gaps that are deliberately not closed; each is a conscious trade-off, not an |
| 65 | +oversight. |
| 66 | + |
| 67 | +- **Programmatic context cancellation of build/compose subprocesses is not wired.** The |
| 68 | + `docker build`/`docker compose` shell-outs run under `context.Background()`, so a |
| 69 | + ctx deadline/cancel does not abort an in-flight subprocess. Interactive `Ctrl-C` still |
| 70 | + aborts it (SIGINT reaches the child via the shared process group). Wiring the command |
| 71 | + `ctx` through the runner is a possible future refinement. |
| 72 | +- **Byte-for-byte tarball parity is unattainable** because of `mtime` differences; tarball |
| 73 | + contents are compared by parsed structure, not raw bytes. |
| 74 | +- **Cloud registry auth matrix is out of default CI scope.** The hermetic auth paths |
| 75 | + (401→bearer, credential-helper protocol, `DEVCONTAINERS_OCI_AUTH`, `GITHUB_TOKEN`) are |
| 76 | + unit-tested; a real ACR (identity/refresh) / ECR / authenticated-GHCR matrix is |
| 77 | + secrets-gated and non-blocking. Credential helpers are Linux-only |
| 78 | + (`secretservice`/`pass`). |
| 79 | +- **TS→Go metadata interop** (build with the TS oracle, read with Go) is skip-guarded in |
| 80 | + the hermetic unit tests (which do not compile the oracle); it is exercised end-to-end by |
| 81 | + the runtime lane's metadata cases (`container-metadata-success`, |
| 82 | + `read-configuration.features-configuration`). The Go→Go round-trip and whitespace |
| 83 | + invariance are unit-tested. |
| 84 | +- **Version banner** differs cosmetically: this CLI reports a git hash / CalVer, TS a |
| 85 | + semver, and the banner box width depends on the version length. Verbose commands |
| 86 | + (features-test / features-info) are compared via `exit_code` / stderr rather than the |
| 87 | + banner. |
| 88 | +- **Legacy Feature fallback via GitHub Releases** is not implemented; feature resolution |
| 89 | + is OCI-first (the supported path for v2 features). |
0 commit comments