boxdown 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +10 -0
  2. package/assets/devcontainer/README.md +29 -6
  3. package/assets/devcontainer/devcontainer.json +4 -9
  4. package/assets/devcontainer/hooks/initialize.sh +49 -27
  5. package/assets/devcontainer/hooks/post-create.sh +23 -1
  6. package/assets/devcontainer/hooks/post-start.sh +0 -10
  7. package/assets/devcontainer/utils/git-signing-bootstrap.sh +58 -5
  8. package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
  9. package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
  10. package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
  11. package/dist/bin/cli.cjs +1 -1
  12. package/dist/bin/cli.mjs +1 -1
  13. package/dist/{main-Df4E8ARj.cjs → main-CT2n9qcb.cjs} +907 -266
  14. package/dist/{main-XMBsKjIK.mjs → main-O__JaiXe.mjs} +891 -268
  15. package/dist/main-O__JaiXe.mjs.map +1 -0
  16. package/dist/main.cjs +4 -1
  17. package/dist/main.d.cts +98 -26
  18. package/dist/main.d.cts.map +1 -1
  19. package/dist/main.d.mts +98 -26
  20. package/dist/main.d.mts.map +1 -1
  21. package/dist/main.mjs +2 -2
  22. package/docs/features/commit-signing.md +71 -0
  23. package/docs/features/generated-config-and-state.md +19 -4
  24. package/docs/features/github-auth-refresh.md +3 -2
  25. package/docs/features/lifecycle.md +19 -0
  26. package/docs/features/setup.md +5 -0
  27. package/docs/features/start-and-shell.md +10 -0
  28. package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
  29. package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
  30. package/docs/superpowers/plans/2026-07-18-architecture-aware-1password-installer.md +163 -0
  31. package/docs/superpowers/plans/2026-07-18-devcontainer-node-image-digest.md +341 -0
  32. package/docs/superpowers/plans/2026-07-19-container-runtime-readiness.md +1536 -0
  33. package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +20 -0
  34. package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
  35. package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
  36. package/docs/superpowers/specs/2026-07-18-architecture-aware-1password-installer-design.md +63 -0
  37. package/docs/superpowers/specs/2026-07-18-devcontainer-node-image-digest-design.md +108 -0
  38. package/docs/superpowers/specs/2026-07-19-container-runtime-readiness-design.md +353 -0
  39. package/package.json +1 -1
  40. package/src/config.ts +14 -2
  41. package/src/constants.ts +7 -0
  42. package/src/container-runtime.ts +295 -0
  43. package/src/devcontainer.ts +57 -30
  44. package/src/doctor.ts +169 -46
  45. package/src/git-signing.ts +205 -25
  46. package/src/logging.ts +11 -1
  47. package/src/main.ts +97 -12
  48. package/src/paths.ts +21 -1
  49. package/src/process.ts +50 -10
  50. package/src/progress.ts +63 -8
  51. package/src/purge.ts +8 -0
  52. package/src/shell.ts +6 -0
  53. package/dist/main-XMBsKjIK.mjs.map +0 -1
@@ -115,6 +115,12 @@ Boxdown resolves a signing identity in this order:
115
115
  4. With zero or multiple unresolved identities, produce a disabled signing
116
116
  plan and a warning reason. Do not guess.
117
117
 
118
+ `user.signingKey` accepts an inline SSH public key, Git's `key::` public-key
119
+ form, an absolute path, a `~/` path, or a path relative to the workspace.
120
+ Boxdown reads public-key files only. An explicitly configured key is
121
+ authoritative: unreadable, malformed, or unloaded configured keys disable
122
+ signing and never fall through to GitHub or single-identity selection.
123
+
118
124
  Failure to execute `ssh-add`, query GitHub, parse a public key, or resolve a
119
125
  single identity is non-fatal.
120
126
 
@@ -321,7 +327,10 @@ testable. At minimum, reasons distinguish:
321
327
  - host agent unavailable;
322
328
  - no loaded identities;
323
329
  - ambiguous loaded identities;
330
+ - configured host SSH signing key unreadable or malformed;
324
331
  - configured host SSH signing key not loaded;
332
+ - host agent socket unavailable;
333
+ - no local Docker image available for the agent mount probe;
325
334
  - Docker agent bridge unavailable;
326
335
  - public-key snapshot failure;
327
336
  - selected identity missing in the container;
@@ -333,6 +342,17 @@ Human-readable warnings state that commits will remain unsigned but are not
333
342
  blocked. A missing GitHub registration warning instead states that commits are
334
343
  signed locally but will not appear Verified on GitHub.
335
344
 
345
+ User-facing lifecycle commands print the concise warning. The workspace
346
+ command log additionally records the stable reason code and sanitized detail,
347
+ without public-key material or secrets. Internal SSH proxy startup remains
348
+ log-only. Generated configuration passes the stable host reason into container
349
+ bootstrap so later lifecycle warnings preserve the original diagnosis.
350
+
351
+ Docker Desktop may expose the mounted host-agent socket only to root. Boxdown
352
+ therefore relays it through a root-owned Unix-socket proxy to a node-owned
353
+ socket. Explicit user Git-signing preferences remain authoritative; Boxdown
354
+ uses SSH signing only as the default when no preference exists.
355
+
336
356
  ## Testing Strategy
337
357
 
338
358
  ### Unit tests
@@ -0,0 +1,63 @@
1
+ # Node SSH-Agent Proxy for Commit Signing
2
+
3
+ ## Goal
4
+
5
+ Make Boxdown's default SSH commit signing usable by the container's `node`
6
+ user when Docker Desktop mounts the host agent socket as `root:root` with mode
7
+ `0660`. Signing must remain best-effort and never expose a private key.
8
+
9
+ ## Root Cause
10
+
11
+ The host preflight and the raw socket mount both succeed, but lifecycle hooks
12
+ run as `node`. In the reproduced environment, `ssh-add -L` succeeds as root
13
+ and fails as `node` with `Permission denied`. The signing bootstrap therefore
14
+ sets `commit.gpgsign=false` and reports `container-agent-unavailable`.
15
+
16
+ ## Architecture
17
+
18
+ Boxdown continues to bind-mount the host-facing agent socket at
19
+ `/run/boxdown/ssh-agent.sock`. A small Node Unix-socket proxy runs as root and
20
+ forwards each connection from that raw socket to
21
+ `/run/boxdown/ssh-agent-node.sock`. The proxy creates the latter socket with
22
+ ownership and permissions suitable for `node`. Generated configuration sets
23
+ `SSH_AUTH_SOCK` to the node-facing socket and exposes the raw path only through
24
+ `BOXDOWN_GIT_SIGNING_SOURCE_SOCKET` for the proxy.
25
+
26
+ The post-create hook starts the proxy with passwordless `sudo`, waits briefly
27
+ for its socket, verifies `ssh-add -L` as `node`, then invokes the existing
28
+ signing bootstrap. A proxy failure is non-blocking and has its own stable
29
+ reason code.
30
+
31
+ ## Git Configuration Precedence
32
+
33
+ **Decision:** Boxdown does not make its SSH configuration authoritative over
34
+ explicit user Git signing preferences. This is intentional because the
35
+ container begins from a writable copy of the user's Git configuration.
36
+
37
+ - With no explicit global or repository-local signing preference, Boxdown
38
+ configures SSH signing as the default.
39
+ - With an explicit SSH preference, Boxdown preserves the selected identity;
40
+ it only translates an inaccessible host public-key path to the mounted
41
+ public-key snapshot in the container.
42
+ - With `commit.gpgsign=false`, a non-SSH `gpg.format`, or an explicit
43
+ `gpg.program`, Boxdown preserves that preference and does not replace it.
44
+
45
+ Repository-local settings retain Git's normal precedence. In particular, a
46
+ local `commit.gpgsign=false` keeps commits unsigned even when agent forwarding
47
+ is healthy. A future product decision may deliberately reverse this policy and
48
+ make Boxdown authoritative; that would be a behavior change, not a bug fix.
49
+
50
+ ## Failure Handling
51
+
52
+ The proxy never reads, copies, or logs private-key material. It reports
53
+ `container-agent-proxy-unavailable` when `sudo`, Node, proxy readiness, or the
54
+ node-user agent probe fails. The signing bootstrap remains non-blocking and
55
+ preserves explicit user configuration rather than setting a contradictory
56
+ global value.
57
+
58
+ ## Validation
59
+
60
+ Tests cover forwarding a real Unix-socket request through the proxy, a proxy
61
+ startup failure, generated socket environment values, successful default SSH
62
+ signing, and preservation of global and local user preferences. Documentation
63
+ will require `--recreate` after upgrade.
@@ -0,0 +1,194 @@
1
+ # Runtime Secret Environment Design
2
+
3
+ ## Goal
4
+
5
+ Keep Boxdown-provided secrets available as ordinary environment variables in
6
+ container user sessions without placing their values in Docker container
7
+ configuration, the workspace, or Boxdown's persistent state and logs.
8
+
9
+ This design covers `ANTHROPIC_API_KEY`, `SNYK_TOKEN`, and
10
+ `OP_SERVICE_ACCOUNT_TOKEN`. It also prevents the Docker inspection command
11
+ used for image metadata from logging the complete container configuration.
12
+
13
+ ## Non-goals
14
+
15
+ - Migrating, deleting, or rewriting legacy `.env.development` files.
16
+ - Adding a `boxdown doctor --fix-secrets` command.
17
+ - Preventing a user who can enter the container from reading secrets that are
18
+ deliberately available to that user's session.
19
+ - Changing GitHub CLI authentication refresh, which already passes its host
20
+ token over stdin and redacts it from the workspace log.
21
+
22
+ ## Security Model
23
+
24
+ The user has requested compatibility with the current developer experience:
25
+ the three values above remain ordinary environment variables in Bash-based
26
+ container sessions. This intentionally permits session processes to read them.
27
+
28
+ The protection boundary is against accidental exposure outside that session:
29
+
30
+ - Docker inspection must not contain secret values.
31
+ - The repository and its `.env.development` file must not be created,
32
+ modified, or removed by Boxdown secret handling.
33
+ - Boxdown's persistent workspace data and `boxdown.log` must not contain
34
+ secret values.
35
+ - Secret files are short-lived, host-private runtime state and are mounted
36
+ read-only into the container.
37
+
38
+ ## Architecture
39
+
40
+ ### Host runtime secret state
41
+
42
+ `WorkspaceContext` gains a per-workspace runtime-secret directory under a
43
+ user-private runtime root. The root uses `XDG_RUNTIME_DIR` when available and
44
+ a user-private directory under the system temporary directory otherwise. It
45
+ is distinct from `workspaceDataDir`, which is persistent and contains logs and
46
+ metadata.
47
+
48
+ The directory and each secret file are created with owner-only permissions
49
+ (`0700` directory, `0600` files). The three files use fixed names matching
50
+ their environment variables. No secret value appears in generated
51
+ configuration, command arguments, progress output, metadata, or diagnostics.
52
+
53
+ The host-side `initializeCommand` refreshes this directory before each
54
+ Dev Container startup:
55
+
56
+ 1. It creates the runtime directory safely.
57
+ 2. It writes `ANTHROPIC_API_KEY` and `SNYK_TOKEN` only when each corresponding
58
+ host environment value is non-empty.
59
+ 3. It retrieves `OP_SERVICE_ACCOUNT_TOKEN` from the existing 1Password item
60
+ only when `op` is available and can read it.
61
+ 4. It atomically replaces files for available values and removes files for
62
+ unavailable values, preventing a stale token from a previous start.
63
+
64
+ Failures to retrieve 1Password or an absent host variable are informational
65
+ and non-blocking. The affected variable is absent from the next container
66
+ session.
67
+
68
+ ### Generated Dev Container configuration
69
+
70
+ The base `runArgs` no longer includes:
71
+
72
+ ```text
73
+ --env-file ${localWorkspaceFolder}/.env.development
74
+ ```
75
+
76
+ The base `containerEnv` no longer includes secret values via `localEnv`.
77
+ `NODE_ENV` remains ordinary non-secret configuration.
78
+
79
+ Boxdown's generated configuration adds a read-only bind mount from the host
80
+ runtime-secret directory to `/run/boxdown/secrets`. It may contain non-secret
81
+ bootstrap configuration such as `BASH_ENV` and the mounted directory path,
82
+ but never a secret value.
83
+
84
+ Because mounts are create-time settings, `boxdown start --recreate` is needed
85
+ for an already-created container to receive the new mount. New containers get
86
+ it automatically through every Boxdown lifecycle path (`setup`, `start`,
87
+ `codex`, and the other coding-agent commands).
88
+
89
+ ### Container session bootstrap
90
+
91
+ A mounted Boxdown bootstrap script reads only the fixed secret files, then
92
+ exports values that are present. It never echoes values or shell source lines.
93
+ It handles missing or unreadable files by leaving the corresponding variable
94
+ unset.
95
+
96
+ The generated configuration sets the non-secret `BASH_ENV` path so
97
+ non-interactive Bash processes source the bootstrap. The container setup also
98
+ installs the bootstrap in the `node` user's Bash startup path for interactive
99
+ shells and SSH sessions. Boxdown's direct shell and coding-agent launch
100
+ scripts source it explicitly before `exec` as an additional deterministic
101
+ path.
102
+
103
+ Consequently, an ordinary Bash session can use, for example,
104
+ `$ANTHROPIC_API_KEY`, `$SNYK_TOKEN`, and `$OP_SERVICE_ACCOUNT_TOKEN` as it can
105
+ today. Docker's configured environment contains only the bootstrap path and
106
+ other non-secret values, so `docker inspect` cannot reveal secret contents.
107
+
108
+ ### Workspace file handling
109
+
110
+ The initialization hook stops creating or editing `.env.development`. The
111
+ post-start hook stops deleting it. Boxdown therefore treats that file as
112
+ project-owned in all cases.
113
+
114
+ This release deliberately does not modify legacy files. A pre-existing
115
+ `OP_SERVICE_ACCOUNT_TOKEN` value remains the user's responsibility to remove
116
+ or rotate. Documentation will state that Boxdown no longer reads the file and
117
+ that users should remove a legacy accidental entry manually.
118
+
119
+ ### Logging and Docker inspection
120
+
121
+ Boxdown currently runs `docker inspect --format '{{json .}}'` only to record
122
+ the image ID and image name. The full payload includes `.Config.Env`, and the
123
+ command logger writes all child output even when it is not mirrored to the
124
+ terminal.
125
+
126
+ The implementation replaces that command with a narrow template that returns
127
+ only the image ID and configured image name. Its parser accepts that narrow,
128
+ non-sensitive output; it never parses a complete inspect object.
129
+
130
+ The workspace logger also gains structural redaction for assignments of the
131
+ three Boxdown secret variable names. This is a defense-in-depth backstop for
132
+ legacy logs or future accidental command output, not the primary protection.
133
+ Existing value-based redaction remains for dynamically acquired GitHub tokens.
134
+
135
+ ## Lifecycle and Cleanup
136
+
137
+ - `initializeCommand` is the only writer of runtime secret files.
138
+ - `down` and `purge` remove the runtime-secret directory after stopping or
139
+ removing the associated container.
140
+ - A new start refreshes values atomically, so no previous value remains when a
141
+ host value is absent or 1Password lookup fails.
142
+ - Runtime secret state is never written below `workspaceDataDir` and is never
143
+ included in status output.
144
+ - Existing workspaces require `boxdown start --recreate` to replace the old
145
+ Docker environment configuration. Boxdown will not automatically migrate
146
+ existing containers or `.env.development` files.
147
+
148
+ ## Diagnostics and Documentation
149
+
150
+ Startup output may state whether the optional 1Password token was available,
151
+ but must not print values, file contents, or paths that encode secret data.
152
+ `boxdown doctor` verifies that the runtime-secret mount is constructible and
153
+ that the generated configuration contains neither the legacy `--env-file`
154
+ argument nor secret `containerEnv` entries. Missing optional values are
155
+ warnings, not failures.
156
+
157
+ Documentation will explain:
158
+
159
+ - which host values Boxdown forwards;
160
+ - that they are exposed to Bash sessions but not Docker configuration;
161
+ - that `.env.development` is project-owned and ignored by Boxdown;
162
+ - that `--recreate` is required for existing containers; and
163
+ - how to manually remove and rotate a legacy 1Password token if it was
164
+ previously written to a workspace or log.
165
+
166
+ ## Test Strategy
167
+
168
+ Tests will cover:
169
+
170
+ - host secret-state creation, owner-only modes, atomic refresh, and removal of
171
+ stale files without printing values;
172
+ - success, missing host values, missing `op`, and failed 1Password lookup as
173
+ non-blocking cases;
174
+ - generated configuration contains the read-only runtime mount and contains no
175
+ `--env-file`, secret `containerEnv` keys, or secret values;
176
+ - `.env.development` is neither created nor altered by initialization or
177
+ post-start hooks;
178
+ - interactive, non-interactive, SSH, and Boxdown coding-agent Bash launch
179
+ paths export available values and leave unavailable values unset;
180
+ - image inspection uses only a narrow projection and workspace logs never
181
+ receive a synthetic inspect environment value;
182
+ - logger structural redaction for all three known keys;
183
+ - doctor warnings and mount probes, with optional-secret absence non-fatal;
184
+ - focused tests first, then the full test suite, lint, build, Bash syntax
185
+ checks, and `git diff --check`.
186
+
187
+ ## Decision Record
188
+
189
+ The chosen design favors compatibility over strict command-level least
190
+ privilege. The three values are intentionally ordinary environment variables
191
+ inside Bash sessions. If this causes unacceptable in-container exposure or
192
+ developer-experience problems, a future design may replace session bootstrap
193
+ with per-command wrappers. That change would be a deliberate compatibility
194
+ break and is not part of this work.
@@ -0,0 +1,63 @@
1
+ # Architecture-Aware 1Password Installer Design
2
+
3
+ ## Goal
4
+
5
+ Install the pinned 1Password CLI archive that matches the devcontainer's Linux
6
+ architecture, and add fast CI coverage for both supported architecture paths.
7
+
8
+ ## Current Problem
9
+
10
+ `assets/devcontainer/hooks/post-create.sh` always downloads the 1Password
11
+ `op_linux_arm64` archive. On amd64 devcontainers this installs an incompatible
12
+ binary. The existing CI job runs on amd64, but its tests never execute the
13
+ installer's architecture selection, so the mismatch is not detected.
14
+
15
+ ## Design
16
+
17
+ Keep the existing pinned 1Password CLI version and prebuilt zip installation
18
+ flow. Inside `install_1password_cli`, map the value from `uname -m` to the
19
+ 1Password archive architecture:
20
+
21
+ - `x86_64` and `amd64` select `amd64`.
22
+ - `aarch64` and `arm64` select `arm64`.
23
+ - Any other value prints a warning and returns successfully without downloading
24
+ or installing a binary.
25
+
26
+ The selected architecture becomes part of the existing archive URL. No source
27
+ build, Buildx configuration, QEMU emulation, version change, or installer
28
+ refactor is required.
29
+
30
+ ## Test Coverage
31
+
32
+ Add behavioral smoke tests that source the real post-create hook and invoke
33
+ `install_1password_cli` with controlled command shims. The shims replace
34
+ network and privileged filesystem operations while recording the requested
35
+ download URL.
36
+
37
+ The tests cover:
38
+
39
+ - `x86_64` selecting the `op_linux_amd64` archive.
40
+ - `aarch64` selecting the `op_linux_arm64` archive.
41
+ - An unsupported architecture returning successfully, warning, and performing
42
+ no download.
43
+
44
+ These tests run in the existing `pnpm run test` CI step on `ubuntu-latest`.
45
+ This gives the amd64 CI runner explicit coverage of both supported selection
46
+ branches without downloading 1Password or building a multi-architecture
47
+ container.
48
+
49
+ ## Scope
50
+
51
+ - Modify `assets/devcontainer/hooks/post-create.sh` only for 1Password archive
52
+ selection and unsupported-architecture handling.
53
+ - Add focused tests to `__tests__/app.test.ts` using the repository's existing
54
+ shell-script test conventions.
55
+ - Do not alter the Snyk installer, devcontainer image, CI runner matrix, or
56
+ other lifecycle behavior.
57
+
58
+ ## Verification
59
+
60
+ Use test-driven development: add the focused architecture tests and confirm
61
+ they fail against the hard-coded arm64 URL, implement the minimum shell change,
62
+ then rerun the focused tests. Finish with the repository's full test, lint,
63
+ build, shell syntax, and diff checks.
@@ -0,0 +1,108 @@
1
+ # Devcontainer Node Image Digest Pinning
2
+
3
+ ## Goal
4
+
5
+ Make Boxdown's packaged devcontainer base image reproducible across rebuilds
6
+ and host architectures while preserving the existing Node 24, Debian trixie,
7
+ and slim-image track. Keep the immutable image reference current through
8
+ reviewable automated pull requests.
9
+
10
+ ## Scope
11
+
12
+ This change applies to the reusable devcontainer template at
13
+ `assets/devcontainer/devcontainer.json`. It does not change Boxdown's runtime
14
+ generation model, the Dev Container Feature pins, npm dependency automation,
15
+ or GitHub Actions dependency automation.
16
+
17
+ ## Image Reference
18
+
19
+ The template will replace the mutable reference:
20
+
21
+ ```text
22
+ node:24-trixie-slim
23
+ ```
24
+
25
+ with the same tag plus `@sha256:` and the verified 64-hex-character
26
+ multi-platform index digest.
27
+
28
+ The implementation will resolve the current digest from the upstream Node
29
+ image registry and verify that it identifies the multi-platform image index,
30
+ not an architecture-specific child manifest. Keeping the tag documents the
31
+ intended Node 24 and Debian trixie slim update track. The digest makes the
32
+ actual selected index immutable, so AMD64 and ARM64 hosts resolve their
33
+ platform image from the same pinned release set.
34
+
35
+ The tag deliberately remains at Node major-version precision. Node patch
36
+ releases and upstream rebuilds stay explicit through digest-update pull
37
+ requests without moving Boxdown to another Node major or Debian variant.
38
+
39
+ ## Update Automation
40
+
41
+ A root `renovate.json` will configure Renovate's native devcontainer manager.
42
+ The configuration will:
43
+
44
+ - enable only the devcontainer manager, avoiding overlap with the existing
45
+ Dependabot npm and GitHub Actions configuration;
46
+ - extend devcontainer file matching to
47
+ `assets/devcontainer/devcontainer.json`, which is outside Renovate's default
48
+ root `.devcontainer` locations;
49
+ - disable Dev Container Feature updates so the existing digest-pinned Feature
50
+ set remains outside this change;
51
+ - match only the `node` image dependency in the packaged template;
52
+ - preserve the `24-trixie-slim` tag and update its digest; and
53
+ - allow digest-refresh pull requests on a monthly schedule.
54
+
55
+ The Renovate GitHub app performs the registry lookup and opens the pull
56
+ request. Boxdown does not add a repository-owned scheduled workflow or a
57
+ repository token. Enabling the app for the repository is an operational
58
+ prerequisite outside the committed code.
59
+
60
+ ## Documentation
61
+
62
+ The packaged devcontainer README will describe the tag-plus-index-digest
63
+ reference, its cross-platform reproducibility property, and Renovate's role in
64
+ refreshing it through auditable pull requests. Existing documentation about
65
+ the Node 24 trixie slim base and digest-pinned Features remains accurate.
66
+
67
+ ## Test Strategy
68
+
69
+ A focused repository test will read the packaged devcontainer source and
70
+ assert that:
71
+
72
+ - its base image is exactly the intended `node:24-trixie-slim` tag followed by
73
+ a 64-hex-character SHA-256 digest;
74
+ - the Renovate configuration enables only the devcontainer manager;
75
+ - the additional manager file pattern covers the packaged template;
76
+ - Feature dependencies are disabled; and
77
+ - the Node image rule enables digest pinning on the monthly schedule.
78
+
79
+ The test will be added before the production configuration changes and must be
80
+ observed failing for the missing digest and Renovate configuration. After the
81
+ change, verification will run the focused test, full test suite, lint, build,
82
+ JSON and JSON-with-comments parsing, registry inspection of the pinned index,
83
+ and `git diff --check`.
84
+
85
+ ## Alternatives Considered
86
+
87
+ ### Dependabot through a Dockerfile
88
+
89
+ Moving the base reference into a Dockerfile would let Dependabot's Docker
90
+ ecosystem update it, but it would change the devcontainer architecture solely
91
+ to accommodate the updater. GitHub's `devcontainers` ecosystem updates
92
+ Features rather than the `image` field, so it cannot directly maintain the
93
+ current template.
94
+
95
+ ### Repository-owned scheduled workflow
96
+
97
+ A custom workflow could resolve the tag, edit the digest, and open pull
98
+ requests. It would duplicate registry, scheduling, authentication, and pull
99
+ request behavior already provided by Renovate and would add workflow and token
100
+ maintenance.
101
+
102
+ ## Decision Record
103
+
104
+ Use Renovate's native devcontainer support and retain the human-readable
105
+ `node:24-trixie-slim` tag. Pin the verified multi-platform index digest and
106
+ scope automation to only that image in the packaged template. This yields
107
+ immutable rebuild inputs without broadening Boxdown's dependency automation
108
+ or changing its generated-devcontainer architecture.