Skip to content

fix(gate): repair three latent failures in the Linux-native deb proof - #283

Closed
TheShiftedBit wants to merge 1 commit into
google:mainfrom
TheShiftedBit:fix/linux-native-deb-proof
Closed

TheShiftedBit wants to merge 1 commit into
google:mainfrom
TheShiftedBit:fix/linux-native-deb-proof

Conversation

@TheShiftedBit

Copy link
Copy Markdown

Summary

The exact Debian package proof (DebProof) only runs on a native Linux host with /dev/kvm + /dev/vhost-vsock. CI runners lack vhost-vsock (the proof selector skips the proof) and macOS release machines cross-compile host≠target (also skipped) — so this rail had never executed end-to-end until I ran capsem-gate cross-compile x86_64 on a gLinux workstation (host uid 399710). That run surfaced three independent bugs, each a hard gate failure. With all three fixed, the full cross-compile gate passes on a native Linux+KVM host: deb install inside the sealed systemd container, release verification, and the guest shell boot proof.

Bug 1 — staging ownership in the proof container (debproof.py)

Symptom: mkdir: cannot create directory 'cache/target/tests': Permission denied during stage_content_from.

Cause: .dockerignore excludes /cache, so the sealed install-test image has no /src/cache. Docker materializes the mountpoint chain /src/cache/target for the read-only cache/target/packages mount as root. Staging runs as the container user capsem (uid 1000), which only works on hosts whose uid happens to be 1000 (e.g. CI).

Fix: InstallContainer already solved exactly this with _claim_paths() (root mkdir -p + chown -R of layout.owned_paths and owned_parent_paths) called right after systemd is ready. DebProof now does the same.

Bug 2 — authored-graph path ignores the channel (releasegraph.py)

Symptom: record-binary fails with read .../install-proof/assets/local/manifest.json: No such file or directory.

Cause: capsem-admin assets channel build --channel X writes <out_dir>/assets/X/manifest.json, but ReleaseGraph.build_channel() returned the hardcoded config constant graph_manifest = "assets/local/manifest.json". The two coincide only for the install gate's channel local. DebProof authors the package channel (default stable; its own validation refuses local), so the gate read a path the admin never wrote.

Fix: build_channel() derives the authored location from the channel it just built (assets/<channel>/<basename>), and hand_off() takes the channel and validates against the same channel-aware path. Install-gate behavior (channel=local) is unchanged.

Bug 3 — split provenance in installed-release verification (debproof.py)

Symptom: installed release verification failed: manifest-metadata manifest_url is '<baked URL>', expected '<handoff URL>' — after dpkg -i already succeeded.

Cause: for a preverified payload the postinst deliberately keeps the package's baked manifest-metadata (update_status_refresh_skipped reason=preverified_install_payload) while the bytes are hydrated from the proof's authored handoff channel. verify-installed-release models exactly this split via --metadata-manifest-url (the release probe already passes it), but DebProof._verify_release only passed --manifest-url.

Fix: pass --metadata-manifest-url <baked URL> alongside --manifest-url <handoff URL>.

Tests

The four gate tests that pinned the buggy constants now pin the fixed behavior (channel-aware authored path; hand_off's channel argument). build_system/tests/gate/test_gate_debproof.py + test_gate_install_ordering.py: 53/53 pass.

Verification

  • capsem-gate cross-compile x86_64 on gLinux (64-core, KVM): ok in 2m45s including the full package proof.
  • The resulting Capsem_0.6.4_amd64.deb installed and verified on the same host (service healthy, capsem doctor 265/265 in-guest diagnostics, VM smoke test).

The exact Debian package proof (DebProof) only runs on a native Linux
host with /dev/kvm and /dev/vhost-vsock. CI runners lack vhost-vsock
(the proof selector skips it) and macOS release machines cross-compile
host!=target (skipped too), so this rail had never executed end-to-end
on a real machine until now. Running `capsem-gate cross-compile x86_64`
on a regular machine surfaced three independent bugs, each of which
failed the gate:

1. Staging ownership: the sealed install-test image excludes /cache via
   .dockerignore, so Docker materializes the mountpoint chain
   /src/cache/target for the read-only packages mount as root. Staging
   then runs as the container user `capsem` (uid 1000) and dies with
   `mkdir: cannot create directory 'cache/target/tests': Permission
   denied` on any host whose uid is not 1000. InstallContainer solved
   exactly this with _claim_paths() after await_systemd; DebProof never
   did. Fix: give DebProof the same _claim_paths() (root mkdir -p +
   chown -R of layout.owned_paths and owned_parent_paths), called right
   after systemd is ready.

2. Authored-graph path: `capsem-admin assets channel build --channel X`
   writes <out_dir>/assets/X/manifest.json, but ReleaseGraph's
   build_channel() returned the hardcoded config constant
   graph_manifest = "assets/local/manifest.json". The paths only
   coincide for the install gate's channel "local"; DebProof authors
   the package channel (default "stable", and its own validation
   refuses "local"), so record-binary immediately failed with
   `read .../assets/local/manifest.json: No such file or directory`.
   Fix: build_channel() now derives the authored path from the channel
   it just built (assets/<channel>/<basename>), and hand_off() takes
   the channel and validates against the same channel-aware location.
   Behavior for the install gate (channel=local) is unchanged.

3. Split provenance in the installed-release verification: for a
   preverified payload the postinst deliberately keeps the package's
   baked manifest-metadata (update_status_refresh_skipped
   reason=preverified_install_payload) while the bytes are hydrated
   from the proof's authored handoff channel. verify-installed-release
   models exactly this split via --metadata-manifest-url (the release
   probe already passes it), but DebProof._verify_release only passed
   --manifest-url, so the proof failed comparing the metadata URL
   against the handoff URL. Fix: pass --metadata-manifest-url with the
   baked URL alongside --manifest-url with the handoff.

Tests: the four gate tests that pinned the buggy constants are updated
to pin the fixed behavior (channel-aware authored path; hand_off's
channel argument). 53/53 pass in
tests/gate/test_gate_debproof.py + test_gate_install_ordering.py.

With these three fixes the full cross-compile gate passes on a native
Linux+KVM host, including dpkg install inside the sealed systemd
container, release verification, and the guest shell boot proof.
@codecov-commenter

codecov-commenter commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 25.00000% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.3%. Comparing base (e6d4581) to head (2147934).

Files with missing lines Patch % Lines
build_system/builder/gate/debproof.py 12.5% 7 Missing ⚠️
build_system/builder/gate/releasegraph.py 37.5% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             main     #283     +/-   ##
=========================================
- Coverage    65.3%    65.3%   -0.1%     
=========================================
  Files        1456     1456             
  Lines      128450   128463     +13     
  Branches    91944    91944             
=========================================
+ Hits        83938    83940      +2     
- Misses      39527    39537     +10     
- Partials     4985     4986      +1     
Flag Coverage Δ
integration 17.3% <ø> (ø)
linux-unit 70.6% <ø> (-0.1%) ⬇️
mcp-server 93.8% <ø> (ø)
python-sdk 98.7% <ø> (ø)
typescript-sdk 97.8% <ø> (ø)
unit 63.4% <25.0%> (-0.1%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
TypeScript SDK 97.8% <ø> (ø)
Python SDK 98.7% <ø> (ø)
Network 84.4% <ø> (ø)
Security 81.6% <ø> (ø)
Tooling 88.6% <ø> (ø)
Monitoring 87.6% <ø> (+<0.1%) ⬆️
Virtualization 64.5% <ø> (ø)
Confined Port Router 76.3% <ø> (-0.6%) ⬇️
Private Network 79.3% <ø> (ø)
Assets 80.8% <ø> (ø)
Gateway API 96.7% <ø> (ø)
Rust SDK 96.3% <ø> (ø)
Configuration 86.8% <ø> (ø)
Credentials 80.7% <ø> (ø)
Host Foundation 76.3% <ø> (ø)
Core Platform 55.8% <ø> (ø)
Runtime 60.7% <ø> (-0.2%) ⬇️
Daemon 41.6% <ø> (ø)
Service 71.8% <ø> (ø)
Process 48.5% <ø> (+<0.1%) ⬆️
Admin 63.7% <ø> (ø)
CLI 47.9% <ø> (ø)
MCP Server 93.8% <ø> (ø)
MCP Aggregator 61.8% <ø> (ø)
MCP Builtin 57.4% <ø> (ø)
Gateway 78.9% <ø> (ø)
TUI 68.5% <ø> (ø)
System Tray 53.2% <ø> (ø)
Guard 92.2% <ø> (ø)
UI 86.6% <ø> (ø)
Release Site 15.0% <ø> (ø)
Builder 43.5% <25.0%> (-0.1%) ⬇️
Mock Server 59.0% <ø> (ø)
Bench 48.0% <ø> (+0.1%) ⬆️
Files with missing lines Coverage Δ
build_system/builder/gate/releasegraph.py 28.7% <37.5%> (+0.9%) ⬆️
build_system/builder/gate/debproof.py 22.4% <12.5%> (-0.7%) ⬇️

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@TheShiftedBit

Copy link
Copy Markdown
Author

Actually, I'm closing this - this PR fixed the issues by simply hardcoding things. The bugs are real, but the fix makes it work for me at the expense of potentially breaking it for other situations.

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.

2 participants