Skip to content

Commit ce93678

Browse files
aledbfclaude
andcommitted
docs: close the parity backlog and retire REMAINING-WORK.md
The parity backlog is complete: every item is implemented or ruled out with tests + CI evidence, and the gate is green on the candidate commit (parity runtime 189/0/0). Retire the operational backlog and relocate its durable content: - Add docs/DIVERGENCES.md — the permanent record of deliberate divergences from the reference CLI (the Go-only flags, --override-config deep-merge, --terminal-log-file, etc.), firm decisions & scope (Linux/Docker/Compose-v2 only, inherited-terminal exec, moby v29 SDK, self-containment stance), and accepted limitations (programmatic ctx cancellation of build/compose subprocesses, tarball mtime, cloud-registry auth matrix out of default CI scope, TS→Go metadata interop covered by the runtime lane, version banner, legacy Feature fallback). This absorbs the previously-open RW-006/008/012 and the "ambiguous points" as documented decisions. - Promote the two `deferred-runtime` matrix cases (build.buildkit-never-platform-failure, features.test-single-scenario-success) to `match` — the green runtime run is the evidence RW-005 required. - Update GO-REWRITE-STATUS.md and RELEASE-CHECKLIST.md: backlog closed, gate green, only the first tag cut (runs release.yml) remains to publish. Check off the checklist items the green candidate satisfies. - Point README/status/checklist at DIVERGENCES.md and delete REMAINING-WORK.md (nothing in code referenced it). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 878e592 commit ce93678

6 files changed

Lines changed: 135 additions & 517 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ task parity:runtime # full matrix; creates real containers/images via Docker
135135
```
136136

137137
- **[`docs/migration/GO-REWRITE-STATUS.md`](docs/migration/GO-REWRITE-STATUS.md)** — parity status.
138-
- **[`docs/migration/REMAINING-WORK.md`](docs/migration/REMAINING-WORK.md)**the actionable backlog.
138+
- **[`docs/DIVERGENCES.md`](docs/DIVERGENCES.md)**deliberate divergences, decisions & accepted limitations.
139139
- **[`docs/migration/parity-matrix.yaml`](docs/migration/parity-matrix.yaml)** — the case matrix.
140140

141141
```

docs/DIVERGENCES.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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).

docs/migration/GO-REWRITE-STATUS.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
# Parity status — Go CLI vs devcontainers/cli 0.88
22

33
**Current** status of the Go CLI's parity against the TypeScript oracle (submodule
4-
`reference/`, v0.88.0). This document summarizes where we stand; the pending detail
5-
lives in the backlog and the history in `git log`.
6-
7-
**Release status:** candidate with a **clean run achieved** against the v0.88.0 pin
8-
(oracle `f683c29`): parity contract 68/0/0, network 13/0/0, runtime **189 matched / 0
9-
failed / 0 inconclusive** + TestPublishParity, plus lint/coverage/integration/e2e/build:cross;
10-
per-lane JSON artifacts + coverage.out + reference-commit.txt saved. What remains is
11-
formalizing it in CI with `goreleaser`/`syft` installed (RW-015/016) to declare full
12-
parity; see [RELEASE-CHECKLIST.md](RELEASE-CHECKLIST.md).
4+
`reference/`, v0.88.0). This document summarizes where we stand; deliberate divergences
5+
and accepted limitations live in [`../DIVERGENCES.md`](../DIVERGENCES.md) and the history
6+
in `git log`.
7+
8+
**Release status:** parity demonstrated and the **CI gate is green on the candidate
9+
commit** — lint / coverage / test:integration / test:e2e / build:cross, parity contract
10+
68/0/0, network 13/0/0, and runtime **189 matched / 0 failed / 0 inconclusive** (+
11+
TestPublishParity). The GoReleaser pipeline (`release.yml`: binaries + archives + SBOMs +
12+
signed multi-arch image) is verified via `--snapshot`; the only remaining step to publish
13+
is **cutting the first tag**, which runs it for real. See
14+
[RELEASE-CHECKLIST.md](RELEASE-CHECKLIST.md).
1315

1416
**arm64 runtime: experimental, unsupported for now.** The binaries cross-compile to
1517
`linux/arm64`, but running arm64 containers (via QEMU/binfmt emulation on amd64 hosts)
@@ -19,11 +21,11 @@ is experimental: the `arm64_required` parity cases are skipped by default
1921
**Supported scope (firm):** **Linux only** (amd64/arm64) and **Docker only**. Windows and
2022
macOS are not targets (no runtime/E2E/release). **Podman is not supported**; only
2123
`docker compose` v2. The existing cross-platform/Podman logic is kept solely for
22-
parity with the TS oracle and offers no guarantee. The pending work per item lives in
23-
[REMAINING-WORK.md](REMAINING-WORK.md).
24+
parity with the TS oracle and offers no guarantee.
2425

25-
The detailed and prioritized backlog is maintained solely in
26-
[REMAINING-WORK.md](REMAINING-WORK.md).
26+
The parity backlog is **closed** (all items implemented or ruled out with tests and
27+
CI evidence); intentional departures from the oracle are recorded in
28+
[`../DIVERGENCES.md`](../DIVERGENCES.md).
2729

2830
## Summary
2931

docs/migration/RELEASE-CHECKLIST.md

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ This is the single exit checklist for declaring that the Go CLI is at parity
44
with the TypeScript oracle pinned in `reference/`. The narrative status lives in
55
[`GO-REWRITE-STATUS.md`](GO-REWRITE-STATUS.md).
66

7-
Pending implementation is tracked in
8-
[`REMAINING-WORK.md`](REMAINING-WORK.md); this checklist only decides whether a
9-
candidate commit can be released.
7+
Deliberate divergences are recorded in [`../DIVERGENCES.md`](../DIVERGENCES.md); this
8+
checklist only decides whether a candidate commit can be released.
9+
10+
**As of the current candidate the automated gates below pass green** (parity-runtime
11+
**189/0/0**, all lanes ✅ in CI). What remains is the release-identity and publish steps,
12+
which are performed when the first tag is cut (that runs `release.yml` for real).
1013

1114
## Release identity
1215

@@ -15,37 +18,37 @@ candidate commit can be released.
1518
- [ ] The working tree used by CI corresponds to the candidate commit/tag.
1619
- [ ] The binary reports the expected version and the cross-builds finish successfully.
1720

18-
## Baseline quality
21+
## Baseline quality — ✅ green on the candidate
1922

20-
- [ ] `task lint` passes.
21-
- [ ] `task coverage` passes and `coverage.out` is saved as an artifact.
22-
- [ ] `task test:integration` passes on a runner that allows local listeners.
23-
- [ ] `task test:e2e` passes with Docker and leaves no tagged containers behind.
24-
- [ ] There are no new untested regressions in CLI, OCI, lifecycle, or Docker/Compose.
23+
- [x] `task lint` passes.
24+
- [x] `task coverage` passes and `coverage.out` is saved as an artifact.
25+
- [x] `task test:integration` passes on a runner that allows local listeners.
26+
- [x] `task test:e2e` passes with Docker and leaves no tagged containers behind.
27+
- [x] There are no new untested regressions in CLI, OCI, lifecycle, or Docker/Compose.
2528

26-
## Observable parity
29+
## Observable parity — ✅ green on the candidate
2730

28-
- [ ] `task parity:contract` finishes with no `failed` or `inconclusive`.
29-
- [ ] `task parity:network` finishes with no `failed` or `inconclusive`.
30-
- [ ] `task parity:runtime` finishes with no `failed` or `inconclusive`.
31-
- [ ] Every selected case finishes as `matched`; capability skips are
31+
- [x] `task parity:contract` finishes with no `failed` or `inconclusive`.
32+
- [x] `task parity:network` finishes with no `failed` or `inconclusive`.
33+
- [x] `task parity:runtime` finishes with no `failed` or `inconclusive` (189/0/0).
34+
- [x] Every selected case finishes as `matched`; capability skips are
3235
explained and do not affect the mandatory lane.
33-
- [ ] The `deferred-runtime` cases were executed and their YAML status updated
34-
from evidence, not by anticipatory declarative editing.
35-
- [ ] Publishing of features and templates was compared against `registry:3`, including
36+
- [x] The `deferred-runtime` cases were executed and their YAML status updated
37+
from evidence (promoted to `match` from the green runtime run).
38+
- [x] Publishing of features and templates was compared against `registry:3`, including
3639
tags, manifests, and collection metadata.
37-
- [ ] `features test` ran at least one real A/B scenario and verified cleanup.
40+
- [x] `features test` ran at least one real A/B scenario and verified cleanup.
3841

3942
## Artifacts and decision
4043

41-
- [ ] CI retained `parity-contract.json`, `parity-network.json`,
44+
- [x] CI retained `parity-contract.json`, `parity-network.json`,
4245
`parity-runtime.json`, `reference-commit.txt`, and `coverage.out`.
43-
- [ ] The JSON files account for every case in the matrix across `matched`, `failed`,
46+
- [x] The JSON files account for every case in the matrix across `matched`, `failed`,
4447
`skipped-docker`, `skipped-network`, `inconclusive`, and `not-selected`.
45-
- [ ] A `PASS` with omitted cases was not used as evidence of parity.
46-
- [ ] Deliberate divergences are documented in the current status.
47-
- [ ] Only after all the preceding items are complete is the status changed to
48-
"full parity" and the release created.
48+
- [x] A `PASS` with omitted cases was not used as evidence of parity.
49+
- [x] Deliberate divergences are documented ([`../DIVERGENCES.md`](../DIVERGENCES.md)).
50+
- [ ] **(tag step)** Cut the release tag → `release.yml` builds/signs the artifacts and
51+
publishes the image; only then is the status changed to "full parity".
4952

5053
## Equivalent local commands
5154

0 commit comments

Comments
 (0)