Skip to content

fix(conformance): async-safe docker probes + real imageDigests staleness#363

Merged
pofallon merged 1 commit into
mainfrom
023-runner-async-imagedigests
Jul 24, 2026
Merged

fix(conformance): async-safe docker probes + real imageDigests staleness#363
pofallon merged 1 commit into
mainfrom
023-runner-async-imagedigests

Conversation

@pofallon

Copy link
Copy Markdown
Contributor

Conformance runner: async-safe Docker probes + real imageDigests staleness

Two follow-up fixes from the /code-review of the 022 declarative conformance runner (findings #4 and #5). Both are contained to the dev-only parity-harness crate.

#4 — no blocking std::process::Command on the async executor (Principle V)

The Docker channel observers each spawned their own blocking docker inspect from a sync capture() invoked inside async fns, blocking the tokio executor.

  • The runner now pre-fetches the container's full docker inspect once, off the executor via tokio::task::spawn_blocking, and stores it on RunContext.container_inspect.
  • The four Docker observers (image/process-graph/injected-process/temporal) became pure readers of that pre-fetched value — no subprocess, no blocking.
  • The workspace-container lookup in execute_ops is likewise offloaded via spawn_blocking.

Net effect: a Docker case now pays one inspect instead of 4+, and no observer blocks the async executor. spawn_blocking works on the current-thread runtime #[tokio::test] uses. The one-shot refresh / snapshot check CLIs and the Drop cleanup guard keep direct sync calls (non-concurrent, the sanctioned exception).

#5imageDigests is now a real staleness signal

imageDigests was declared a staleness field but was never populated or recomputed — an image-content change on a future Docker snapshot case would go undetected.

  • New image_digests_for_case: for a Docker case, recomputes the digest of each image its fixtures pin (docker image inspectRepoDigests/.Id); for a non-Docker case it returns empty without touching docker (a read-configuration snapshot must never gate on a base-image digest — the same false-staleness trap avoided for host tool versions in BUG: Features installed in running container instead of during image build #1).
  • Wired into both the record path (capture_provenance) and the live replay gate (snapshot_oracle, offloaded). The hermetic snapshot check (no docker) keeps carrying the recorded digests — it cannot verify a Docker case's images, so it must not falsely flag them (returns None → carry recorded).

The only existing snapshot case (case-readconfig-snapshot) is config-only, so its imageDigests stays empty and the committed snapshot is unchanged.

Verification

  • cargo fmt --all -- --check + cargo clippy --all-targets --all-features -- -D warnings clean.
  • Full hermetic conformance + parity suite green, incl. the real-Docker channel tests (now exercising the pre-fetch path) and new unit tests for image_digests_for_case/fixture_image.
  • Live parity_conformance_runner passes against real Docker + the pinned oracle.

🤖 Generated with Claude Code

https://claude.ai/code/session_019vhr7Tcf8ybvSZSE1owqBT

Two review follow-ups in the dev-only parity-harness runner.

#4 (Principle V): the Docker channel observers spawned blocking `docker
inspect` from a sync capture() invoked inside async fns, blocking the tokio
executor. The runner now pre-fetches the container inspect ONCE off the
executor via tokio::task::spawn_blocking and stores it on
RunContext.container_inspect; the four Docker observers became pure readers of
it (no subprocess). Net: one inspect per case instead of 4+, and no observer
blocks the executor. The one-shot refresh / snapshot-check CLIs and the Drop
cleanup guard keep direct sync calls (the sanctioned non-concurrent exception).

#5: imageDigests was a declared staleness field that was never populated or
recomputed, so image-content drift on a future Docker snapshot case would go
undetected. New image_digests_for_case recomputes the digest of each image a
Docker case's fixtures pin (empty, and docker-free, for non-Docker cases so a
read-configuration snapshot never gates on a base image); wired into
capture_provenance (record) and snapshot_oracle (live gate, offloaded). The
hermetic snapshot check carries recorded digests (no docker → cannot verify).
The only existing snapshot case is config-only, so its imageDigests stays empty
and the committed snapshot is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019vhr7Tcf8ybvSZSE1owqBT
@github-actions github-actions Bot added the fix Bug fix label Jul 24, 2026
@pofallon
pofallon merged commit 98c26a5 into main Jul 24, 2026
15 checks passed
@pofallon
pofallon deleted the 023-runner-async-imagedigests branch July 24, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant