Skip to content

fix(native): keep the VM responsive during image builds and stop a silent guest - #629

Closed
roodboi wants to merge 7 commits into
claude/native-image-builds-upfrom
claude/hack-1924-build-vm-hang
Closed

roodboi wants to merge 7 commits into
claude/native-image-builds-upfrom
claude/hack-1924-build-vm-hang

Conversation

@roodboi

@roodboi roodboi commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Refs HACK-1924

Cause

The guest stops responding because of a busy loop in the pinned provider's host-side socket forwarding (SmolVM 1.14.3, its bundled libkrun vsock muxer on macOS). It is not a guest CPU or memory limit, and not a BuildKit or bake problem.

How it happens:

  1. The build client (docker-buildx, docker driver) closes many engine connections before it has read the whole response, and closes the rest when it exits.
  2. The guest is still sending on such a connection. The provider's send to the closed host socket fails, and the stream is reset.
  3. A later credit update from the guest re-arms the dead stream with write interest only.
  4. Deleting that interest fails. The macOS epoll shim deletes the read and write filters in a single kevent() change list, and that call stops at the first change that fails. Here the read filter is not registered, so the call fails with ENOENT and the write filter is never deleted.
  5. The dead socket now reports EV_EOF on every wait. Each report queues another RST to the guest, signals an interrupt, and restarts the stream's 5-second reaper timer. The stream is never removed.
  6. The guest vCPUs (about 300% on 4 vCPUs) and the shared vsock RX queue stay saturated. Every later agent and engine request times out (no active session … context deadline exceeded, engine_protocol, agent_protocol_uncertain).

Upstream libkrun fixed the delete in "utils/macos: delete each kqueue filter in its own kevent call (#198)". SmolVM v1.32.3 is the first release that ships that fix. This PR does not change the provider pin.

Fix

  • provider/image_build/client_relay.rs (new). The build client now reaches the engine through a private relay: a fresh mode-0700 socket directory under /private/tmp, removed when the build ends. The relay follows three rules:

    • It always reads provider bytes promptly. Once the client has gone, it discards them, so the provider never sees a failed send.
    • It forwards the client's end of stream only as a half-close, and only after every client byte was delivered.
    • It closes a provider-side connection only after the provider reports end of stream (the guest finished). The wait is bounded by FINISH_GRACE (10 s) after the client exits.

    The relay works on any Docker client and does not inspect bytes. Build behaviour, receipts and the client environment are unchanged except for the DOCKER_HOST path.

  • Separate commit: runtime down and runtime recover can stop a guest that stopped responding. This applies when a guest step of down fails with agent_protocol_uncertain, or when recover finds the recorded provider alive. Both then ping the agent 3 times over about 13 s. If no ping gets a reply, they stop this home's recorded provider after verifying its PID, start time, user and executable at the signal. Nothing in the guest is quiesced, and the pool is recorded recovered-unclean. A guest that answers keeps the clean stop, and recover still refuses it.

  • Third commit: relay directories carry their creating PID. A new relay removes a directory whose process no longer exists, but only when that directory is mode-0700, owned by this user, and holds nothing except the relay socket. Before this, a build process that was killed (for example by a_killed_coordinator_leaves_its_client_fenced) left its relay directory behind.

  • Fourth commit (review fix): a relay queue now drops bytes it has already delivered once they reach half the buffer, so its storage follows the pending bytes rather than the whole transfer. A regression test sends 128 MiB through a slow peer and checks that capacity stays at most 4×(limit + one read); the previous code grew to 128 MiB.

  • Fifth commit (review fix): a build drops the operation lease while its client runs and keeps only the shared build fence. So both silent-guest stop paths now check that fence while holding the lease: recover checks before probing, and both paths check again just before signalling. If a build holds it, they refuse with provider_busy and say to finish or cancel the build first; the next attempt then stops the silent guest. The fence is a flock, so it is released when the build's processes exit, including on SIGKILL. The existing a_killed_coordinator_leaves_its_client_fenced test already proves that.

Evidence (Apple Silicon host, load average 4–13, one VM at a time, isolated HOME/TMPDIR, HACK_DAEMON_AUTOSTART_DISABLE=1)

  • Reproduced without Hack. The pinned buildx 0.34.1 ran against a plain smolvm machine create --docker-socket VM with the guest Docker 29.5.2 engine. A 0-second FROM scratch build succeeded, then VM CPU went to 240–310% and /_ping timed out. With provider debug logging on, one forwarded stream logged about 3.57 million process_event: HANG_UP / push_reset / deferring proxy removal events in 94 s, starting right after flush_tx: host send failed … Broken pipe. A host sample shows the vsock muxer thread busy in update_polling → Epoll::ctl and signal_used_queue.
  • The kqueue behaviour, shown alone. A C model of the shim's delete runs both deletes in one kevent() call on an fd that has only the write filter registered and whose peer closed. The call returns -1 ENOENT, and the write filter keeps reporting EV_EOF on every wait. Deleting the write filter in its own call clears it.
  • Prototype relay vs. no relay, same VM. Without a relay, the first build stormed (post-build CPU 283%, engine unreachable). With a relay that follows these rules, 10 of 10 no-cache builds were clean (post-build CPU 0%, engine answering). That run relayed 100 connections; 54 of them had the client leave before the response ended.
  • Candidate bundle built from this branch, full path. Each run used a fresh native home, a fresh copy of a small build: fixture, HACK_NATIVE_BUILDS=1 hack up to ready (cold build), hack down, then runtime down. Result: 11 of 11 consecutive runs reached ready. Up took 23–28 s, and post-ready VM CPU was 0.5–14%. runtime down exited 0 in 2–3 s with phase stopped and no provider process left, every time. No relay directories were left behind.
  • Stopping a guest that stopped responding (candidate bundle). On a ready pool, I ran buildx directly against the raw forwarded socket (no relay) to reproduce the storm on purpose: CPU 310–322%, engine ping timing out.
    • runtime down alone stopped it: rc 0, phase recovered-unclean, no provider left. It took 59 s, most of it waiting on the guest step that timed out.
    • On a second pool, runtime recover alone stopped it: rc 0 in 13 s, recovered-unclean.
    • When the raw build did not trigger the storm, recover refused the responsive guest with recovery_required.
  • Rust tests. 5 relay tests against a fake provider socket: both directions, half-close forwarding, draining 4.25 MiB after the client left without closing first, the forced close at the bound, a refused provider connection, and socket path length, mode and removal. 1 test for the silence probe: refused, accepted-but-unanswered and closed connections count as silent; a single reply byte counts as an answer. The existing image-build and failed-boot stop tests still pass.

Validation

  • The host proof above was run on ffe2b3fd. The third commit, 5643b8c9, changes only the cleanup of abandoned relay directories. A unit test covers it: an abandoned directory is removed, while a live process's directory and a directory with other files are kept. At 5643b8c9, check:local passes and the library tests pass (1188 passed, 105 ignored), with no relay directories left behind.
  • RUSTUP_TOOLCHAIN=1.97.1 bun run check:local: pass (rustfmt check, plus Clippy on all targets with -D warnings).
  • RUSTUP_TOOLCHAIN=1.97.1 bun run test:local -- --no-fail-fast, run from a short-path checkout of the head: exit 0. The library has 1187 passed and 105 ignored, and every integration binary passed, including node_contract (20 passed, 1 ignored). From the long scratch checkout, only node_contract fails, because its socket paths exceed sockaddr_un; that is a known path-length limit, not related to this change.
  • gitleaks protect --staged before each commit and gitleaks detect --log-opts=<base>..HEAD: no leaks.
  • No TypeScript changed.

Remaining gaps

  • The provider bug is still present. Any other host client that closes a forwarded engine connection while the guest is still sending can hit it. Hack's own engine client reads whole responses and showed no storm in these runs, but it is not relayed. The durable fix is a provider pin at SmolVM ≥ v1.32.3. That is a large pin move with its own qualification, tracked in HACK-1928.
  • After a guest that stopped responding is stopped under a running foreground hack up, the next hack up for that project refuses with graph_retained_startup (hack down reports graph_owner_recovery). The interrupted graph owner needs the existing graph recovery commands. The manual SIGTERM workaround leaves the same state. Tracked in HACK-1929.
  • The silent-guest down path can take about a minute, because it waits for the guest step that is running to time out before it probes.
  • Host proof covers one small fixture on one host. It does not show resource use, qualify larger build contexts, or show release readiness.

🤖 Generated with Claude Code

roodboi and others added 2 commits November 6, 2026 14:26
The pinned provider forwards the guest engine socket through its vsock
muxer. On macOS, when a host client closes a forwarded connection while
the guest is still sending, the muxer re-arms the dead stream with write
interest only and then fails to delete it: one kevent change list stops
at the unregistered read filter. The socket reports a hang-up on every
wait, each one queues a reset to the guest and restarts the stream's
reaper delay, so the stream is never removed. Guest vCPUs and the shared
vsock queue stay saturated and later agent and engine requests time out.

The BuildKit client closes many connections before reading the whole
response and the rest at exit, so most builds reached that state. The
build client now reaches the engine through a private relay that always
drains guest bytes (discarding them once the client is gone), forwards a
client's end of stream only as a half-close, and closes a provider
connection only after the guest ended it, within a bounded wait.

Refs HACK-1924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`runtime down` failed with agent_protocol_uncertain and `runtime
recover` refused while the provider process was alive, so a guest whose
agent stopped answering could only be stopped by signalling the provider
by hand.

When a guest step of `down` fails that way, or `recover` finds the
recorded provider alive, both now ping the agent three times over about
13 seconds. If no ping starts a reply, they stop this home's recorded
provider after verifying its PID, start time, user and executable, with
no guest quiescence, and record the pool `recovered-unclean`, which the
next start accepts after its usual disk checks. A guest that answers
keeps the clean stop, and `recover` still refuses it.

Refs HACK-1924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@linear-code

linear-code Bot commented Nov 6, 2026

Copy link
Copy Markdown

HACK-1924

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T00:37:23.475856Z a0e3e79 Manual request
🔒 Security Review ✅ Completed 2026-11-07T00:39:39.984550Z a0e3e79 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffe2b3fd5f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

A build whose process is killed cannot remove its private relay socket
directory. Relay directories now carry the creating PID, and each new
relay removes those whose process no longer exists, but only a mode-0700
directory owned by this user that holds nothing except the relay socket.

Refs HACK-1924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: ffe2b3fd5f

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Under sustained backpressure, partial writes advanced the start offset
but the delivered prefix stayed in the buffer until the queue drained
completely, so the limits bounded only the unread suffix while storage
grew with the whole transfer (128 MiB for a 1 MiB limit in the new
test). The delivered prefix is now dropped once it reaches half the
buffer, keeping storage within about twice the pending bytes plus one
read at linear cost.

Refs HACK-1924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66d25b8381

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 66d25b8381

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

A build releases the operation lease while its client runs and keeps
only the shared build fence, so the lease alone did not prove that no
build client was using the engine. `runtime recover` could stop the
provider of a silent guest underneath a live build client.

Both silent-guest paths now check the fence under the operation lease
(`recover` before probing, and both again just before signalling) and
refuse with provider_busy, telling the user to finish or cancel the
build first; the next attempt then stops the silent guest. The fence is
a flock released when the build's processes exit, including on SIGKILL,
as a_killed_coordinator_leaves_its_client_fenced already proves.

Also writes the abandoned-relay fixture's extra file before its socket,
so a relay starting in a concurrent test cannot remove it first.

Refs HACK-1924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: a86b3cf718

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: a86b3cf718

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Its only caller is a macOS-only lifecycle test, so Linux test builds
flagged the helper as dead code and failed the candidate-core lint.

Refs HACK-1924

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: f4fd82761b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: f4fd82761b

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…rent relays

The abandoned-relay test built its fixtures in the shared temporary directory,
where any concurrently starting relay's sweep could remove a fixture between
its creation and its socket bind. Fixtures now live under a per-test root
that no other relay sweeps, and the relay under test starts there
(ClientRelay::start_in, which start uses with the temporary directory). The
test also checks that the abandoned fixture existed before the sweep.

The refused-provider test treats EINVAL from setting the read timeout as the
closed connection it expects: macOS refuses socket options once the relay has
already closed the connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: a0e3e797b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: a0e3e797b1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

roodboi added a commit that referenced this pull request Nov 7, 2026
, #612, #628, #629, #630) (#639)

## What this is

Merge train batch 6 into `next`: the native image builds stack and its
fixes, as one batch. Builds stay off by default: they run only with
`HACK_NATIVE_BUILDS=1`, which native `up`, restart and doctor all check.
Each component's reviewed head is merged with `git merge --no-ff`, in
stack order. Integration conflicts are resolved in the batch's merge
commits, described below. Hosted CI on this branch is the full-suite
gate for the combined result.

| PR | Title | Linear | Head included | Review |
| --- | --- | --- | --- | --- |
| #583 | feat(native): pin a BuildKit client and scan Dockerfile image
references | HACK-1270 | `f21d5a04` | Codex code + security: no
findings; 0 open threads |
| #606 | feat(native): build Compose images in the guest engine (runtime
build-image) | HACK-1270 | `460ddff2` | Codex code + security: no
findings; 0 open threads |
| #607 | feat(native): admit build storage, budget build cache and
reclaim superseded build images | HACK-1270 | `9f33a1d0` | Codex code +
security: no findings; 0 open threads |
| #612 | feat(native): build Compose build services in native up behind
HACK_NATIVE_BUILDS | HACK-1270 | `7f42e328` | Codex clean at `8ec7460e`;
`7f42e328` adds #628's squash (re-review requested) |
| #628 | fix(native): admit native build fields and name services in
build refusals | HACK-1925 | `f07ea583` | Codex code + security clean;
already squashed into #612's branch |
| #629 | fix(native): keep the VM responsive during image builds and
stop a silent guest | HACK-1924 | `a0e3e797` | Codex code + security: no
findings; 0 open threads |
| #630 | fix(native): restore a sourceless graph after its project files
were touched | HACK-1914 | `a93db842` | Codex clean at `009989c7`;
`a93db842` only merges #629 into it (re-review requested) |

## Security properties in the merged code

- **Build arguments:** they reach the BuildKit client only through a
private 0600 bake definition, never its environment. The client
environment is cleared to an allowlist. Names must be plain identifiers,
and BuildKit-interpreted names such as `BUILDKIT_*`, `BUILDX_*` and
`SOURCE_DATE_EPOCH` are refused. A build argument equal to a managed
secret value is refused without echoing the value. Build arguments are
not secrets, matching Docker.
- **Source policy:** a source policy from `build_policy::document`, set
through `EXPERIMENTAL_BUILDKIT_SOURCE_POLICY` in a private file, applies
to every build. It allows only the scanner's digest-pinned base images
and denies other images, `oci-layout`, `http`, `https`, `git` and blob
sources. Pinned bases whose config has `OnBuild` triggers are refused.
- **Dockerfile scanner:**
- remote `ADD` sources are refused, including the JSON form, multiple
sources and variables;
- JSON-form arguments are read as BuildKit reads them, and a
lenient-decode differential fails closed;
- heredocs are recognized as BuildKit's lexer does, including whitespace
after `<<`;
  - a stage named `scratch` is refused;
  - `ARG` names must be plain identifiers;
  - platform arguments must resolve to `linux/arm64`;
  - expanded values are size-bounded.
- **Build output:** forwarded output is sanitized and then redacted,
including Go-quoted and JSON-escaped forms and values split across a
line cut. It is bounded by size.

## Integration changes

- **#606 with #605 / #609:** `resolve_build_base` runs on #605's guarded
registry client, so the resolver and address policy, origin-bound
authentication and the realm rules apply. It uses #609's native platform
selection, so builds stay Docker Hub and linux/arm64 only. The older
Docker Hub-only fetch helpers are dropped.
- **#606 / #607 with #605 / #609 / #615:** the `build-image` and
`reclaim-build-images` commands sit beside the image-policy and
emulation options. The build error helpers take `impl
Into<ErrorMessage>`.
- **#607:** the native-build-retention TLA+ contract is registered
beside the restart-policy, pool-emulation and multi-root contracts. Its
model-coverage row now says an orphaned build client keeps the fence and
per-key lock until it exits (`9f33a1d0`).
- **#612 with batch 5:**
- The builds-enabled classification flows through `classifyAdapted`, the
Compose-backend refusal and doctor.
- With the gate off, native `up` refuses a build with a message that
names `HACK_NATIVE_BUILDS`, so doctor's basic-build advice names it too;
one test expectation changed to match.
- Startup and restart keep the dependency-file selection, host aliases,
emulation policy and the inactive-profile skip alongside the build
services.
- **New (#609 x #612):** a build service whose adaptation selects amd64
emulation is refused before any effect, because native builds are
linux/arm64 only. `prefer-native` passes, and a test covers it.
- **#629:** the socket cleanup after a clean stop is kept inside #629's
new guest stop. #629's two flaky relay tests are fixed on its branch
(`a0e3e797`): each test gets its own fixture root, and EINVAL from
`set_read_timeout` after the peer closes is accepted.
- **`next` merge:** the last merge brings in Kit's CI shard and SIGKILL
harness changes (#624, #627). The commit hook formatted
`tests/native-compose-generation.test.ts` with Biome, a whitespace-only
change to that Kit file.

## Validation on this branch (`6337e0ed`)

- Clean CLI typecheck: pass. Biome on the 20 changed files: no errors.
- `RUSTUP_TOOLCHAIN=1.97.1 bun run check:local`: pass. All-features
clippy `-D warnings` (Zig 0.15.2): pass.
- Cargo `--lib`: 1346 pass. `--features native-config-plan`
`provider::graph`: 673 pass. Contracts: `project_contract` 66,
`compose_fields_contract` 10, `interpolation_budget_contract` 1,
`local_contract` 14.
- `bun run test:models` (TLA+ 1.7.4): exit 0, 172 controls verified.
- Focused bun tests, bounded runner, the changed test files: 242 pass, 0
fail.
- `docs:prepare` + `docs:check`, `privacy:check`, gitleaks over
`origin/next..HEAD`: pass.

Host evidence: 11 of 11 builds passed on an M5 host with the relay
(#629). Still open: the source-policy host check (a pinned build
succeeds, and a base trigger that pulls an unpinned image or fetches
https fails with "denied by policy") and the bake `--allow fs.read` /
metadata shape on the pinned buildx 0.34.1. Both are listed in #606.

After this merges, each component PR is closed with a link to the merge
commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1796625833&installation_model_id=17053&pr_number=639&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F639&variant=see_it_in_action&signature=c3ba624f6f34813ba26773c5b4c3caf941bd59515e8c3016c92df8f0e2a6048f"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-see-it-in-action-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-see-it-in-action-light.svg"><img
alt="See it in action"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-see-it-in-action-dark-3x.png"
width="132" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/639?autoLogin=true&ref=codesmith_pr_footer">Open
in [code]smith</a> · Need help on this PR? Tag
<code>@codesmith-bot</code> with what you need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

---------

Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Merged into next through the train batch PR #639 (squash commit e59fcc2). This PR's reviewed head a0e3e797b182edc67e1f66046943e0e84fede96a was included. #639 describes the integration resolutions and the batch security fixes: Go-style image config key reading, non-ASCII key refusal, the build base size bound (unpacked size tracked in HACK-1945), build-base descriptor validation and case-insensitive stage names. Closing as merged by batch; the branch is kept.

🤖 Addressed by Claude Code

@roodboi roodboi closed this Nov 7, 2026
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