Skip to content

test: minimal aleph.compose/1 V-PROGRAM flow on the pre-release network - #36

Open
odesenfans wants to merge 57 commits into
mainfrom
od/vprogram-compose-validation
Open

test: minimal aleph.compose/1 V-PROGRAM flow on the pre-release network#36
odesenfans wants to merge 57 commits into
mainfrom
od/vprogram-compose-validation

Conversation

@odesenfans

Copy link
Copy Markdown
Contributor

Stacked on od/vprogram-validation (#35, green as of run 32368297653 with the full 2026-08-20 component refresh). Adds the docker-compose leg:

  • fixtures: the aleph.compose/1 runtime bundle (2026.08.20 build, 297 MB, fetched from mainnet IPFS by CID, sha256-pinned) and its manifest template
  • conftest: vprogram_compose_runtime_hash uploads both to the testnet per-run (native storage; the CCN config's 4 GiB max_file_size covers the bundle)
  • test_vprogram_compose.py: vprogram create --compose with a single traefik/whoami service (network_mode host, --port 8080), waits for TEE placement, then asserts an attested / call returns the container's whoami dump

Requires aleph-cli 0.17.0-rc10 (vprogram create --compose, aleph-rs#344) and the compose runtime flavor from aleph-vm#1132.

odesenfans added 30 commits July 3, 2026 13:09
Point the CRN deployment at the aleph-vm dev-accelerate branch (PR #977:
gRPC supervisor rework, integration suite, directory-based VM migration)
so the testnet migration test exercises it end to end before merge.

crn-up.sh fetches aleph-vm.debian-12.deb from the branch's CI build
(build-deb-package-and-integration-tests.yml); the latest dev-accelerate
run is green and produces that artifact.

Revert to version: 1.13.0 once aleph-vm#977 lands.
scheduler 0.1.2 (#188) makes node_watcher poll the v2 executions endpoint,
so a gracefully-stopping VM stays observed (present as STOPPED) instead of
dropping out of the v1 running-only list and being reported 'missing' for the
~34s graceful-shutdown window. That is what broke test_instance_stop_start
on the dev-accelerate aleph-vm (whose stop is a graceful guest shutdown vs
the old near-instant teardown). Pairs with aleph-vm's stop=stop_vm change,
which keeps the stopped VM in the pool so v2 still lists it.
aleph-vm#977 (supervisor rework) merged into dev, so the temporary
dev-accelerate branch pin is obsolete (that branch will be deleted).
Point at dev to keep the migration and graceful-stop tests running
against the merged code until it reaches main and a release is cut.
Scheduler stays at 0.1.2 (graceful-stop tolerance).
Validate Phase 2 (gRPC wire + two-service packaging) on testnet. Phase 1 (#980)
has merged to dev; phase2 carries it plus the Phase 2 changes.
The static AMD SEV-SNP server runs Ubuntu 24.04 (Python 3.12), but the branch
path deployed the debian-12 .deb (cp311), so its vendored pydantic_core native
extension failed to import there and the supervisor crash-looped (confidential
test: :4020 connection refused). Provision CRN droplets as ubuntu-24.04 (CCN
already is) and fetch/deploy the ubuntu-24.04 branch .deb so the Python ABI
matches on every host including the TEE server.
Diagnosing the confidential create/registration on the SEV box: in the
two-service split the agent serves /control/* and /v2/about/executions/list, so
the confidential allocation result and the awaiting_confidential_init state live
in its journal, not the supervisor daemon's. Also switch the TEE journals from
-n 5000 to --since: the supervisor crash-loops during the static-server
reinstall and blew the line budget before the test window.
Validate PR-1 of the agent/supervisor boundary split (contract layer +
import-linter, behavior-neutral) on testnet.
…re origin

The test_confidential init-session 'VM not found' failure reproduced 2x on
#986 (od/agent-supervisor-contract), whose confidential-path code is import-only
vs dev. Run against dev to determine if the breakage is pre-existing (#984/#985)
rather than caused by #986. Will revert the pin afterwards.
…l failure is pre-existing on dev

Isolation run (manifesto pinned to dev) failed test_confidential identically
(VM not found at init-session), proving the breakage is pre-existing on dev
(#984/#985), not caused by #986. Restoring the pin to the PR-1 branch.
…#990)

Validate the complete combined stack (tip branch builds a .deb with all four
PRs) on testnet integration. Combined-stack GitHub CI is green (PR #991).
Known: the confidential SSH test is a pre-existing dev failure (needs the
.deb-selection fix + SEV hw); the other 23 are expected to pass.
The generated supervisor.env omitted ALEPH_VM_SUPERVISOR_GRPC_SOCKET, so the
agent ran its own in-process VmPool and never dialed the supervisor daemon. The
daemon ran idle alongside it. Every prior green #27 validated the in-process
path, not the two-process gRPC connector the boundary work is for.

Set the socket so the agent talks to the daemon over gRPC. Expect split-mode
gaps to surface (endpoints that still reach the in-process pool: backups,
restore, confidential, migration, network recreation, GPU reservation,
persistent programs).
The manifesto pinned od/fix-confidential-reconcile-teardown, deleted after #987
merged, so crn-up.sh could not fetch a build. dev now carries the full boundary
stack + the firecracker Configuration vm_id fix, and crn-up.sh sets the gRPC
socket. Deploy dev to exercise the two-process connector end-to-end for the
first time.
…-accelerate

# Conflicts:
#	.github/workflows/pr-tests.yml
#	scripts/crn-up.sh
Point every component at its V-PROGRAM-capable pre-release so the full
flow (CLI create/show/call with RA-TLS attestation) can be validated on
the testnet:

- pyaleph 0.10.3-rc7: first pinned release with the V-PROGRAM message
  handler, vms DB migration and cost model.
- aleph-cli 0.17.0-rc1: vprogram command set (create/show/call with
  RA-TLS attestation verification). Subsumes the pin from PR #32.
- scheduler-rs / scheduler-api vprogram-rc1: one-off images built from
  od/vprogram-scheduling (scheduler#193), adding V-PROGRAM ingestion,
  SEV-SNP capability matching and the v_programs allocation bucket.
- aleph-vm od/vprogram-integration: dev (gRPC split) plus the full
  vprogram stack (#1050..#1079) including the SNP launch path, measured
  workload volumes and the attestation port host mapping.
Adds the first genuine end-to-end attestation test: deploy the fib-service
measured workload with the Rust CLI, wait for scheduler placement on the
SEV-SNP TEE server, then exercise vprogram call, which only returns a body
after the AMD report chain verifies, the TLS key is bound to the report,
and the launch measurement matches the ones the CLI pinned at create time.
A wrong --expected-measurement must fail without printing any body.

Pieces:
- scripts/vprogram-artifacts.sh: fetch the prebuilt fixtures (runtime
  bundle, manifest template, fib-workload.ext4) from the
  vprogram-fixtures-1 release, sha256-pinned. The fixtures are
  nix-reproducible builds of aleph-vm od/vprogram-integration rev
  de9ed350; rebuild instructions in the release notes.
- conftest: vprogram_dir + vprogram_runtime_hash fixtures. The bundle is
  uploaded per run (fresh CCN each time); the manifest template's zeroed
  bundle.ref is patched with the run's STORE hash before upload.
- local-up.sh: export ALEPH_TESTNET_VPROGRAM_DIR when the fixtures dir
  exists; the test skips otherwise.
- pr-tests.yml: fetch fixtures on the CCN + install cryptsetup-bin there
  (vprogram create runs veritysetup locally to hash the workload).
… sample

First live run (31373843773) taught two things:

- vprogram create --json emits a submission receipt (type: V-PROGRAM,
  item_hash, message_status) rather than a full message envelope; match
  on the type field instead of content.verification.
- The rc1 CLI resolves the attested endpoint exactly once at readiness,
  before the CRN maps the attestation port (the mapping happens only
  after the SNP guest's measured boot: aleph-vm agent/run.py
  reconcile_vprogram_port_forwards runs post-_wait_until_running). Poll
  vprogram show as a fallback so the attestation path still gets
  exercised; drop the fallback once aleph-rs#318 ships in the pinned
  CLI.
Round-2 run (31378982391) launched the V-PROGRAM but the guest never
started: the agent created the VM then hit 'did not reach RUNNING within
120.0s' twice, and no aleph-vm-controller@ unit ever appeared for it.

Root cause: the aleph-vm deb ships both supervisor daemon
implementations behind ALEPH_VM_SUPERVISOR_IMPL (default python), and
the V-PROGRAM SNP auto-launch only exists in the Rust daemon
(lifecycle.rs: session-dir derivation, verity sidecar force-insert,
measured cmdline with workload_roothash; none of it exists in the
python daemon). The python daemon parks the AlephQemuConfidentialInstance
execution waiting for the operator session dance that classic
confidential instances perform and V-PROGRAMs never do.

Set ALEPH_VM_SUPERVISOR_IMPL=rust on the confidential CRN only (the
TEE server, the sole node that can run V-PROGRAMs). The Rust daemon
also implements the classic CVM session flow (confidential.rs), so
test_confidential stays covered; DO CRNs keep the default python
daemon that PR #27 has been validating.

Also stop + reset-failed all aleph-vm-controller@ units in tee-reset.sh:
the state wipe deletes controller.json files but left the units
restart-looping (observed at restart counter 844k), flooding the journal
and drowning the CI log capture.
Round 3 (31382627461) got the full chain working: measured SNP boot
under the Rust daemon, attestation port mapped, endpoint resolved on
the TEE server, and the guest's aleph-attest-agent bound :8443 with an
attested TLS identity - 3 seconds AFTER the test's first call. The CRN
maps the port at RUNNING (qemu up), a few seconds before guest
userspace finishes starting, so the first connection can land in that
gap.

Retry transport-level failures ('error sending request') for up to two
minutes; attestation verification failures still fail immediately.
Round 4 (31385362211): the SNP guest boots, aleph-attest-agent binds
0.0.0.0:8443, the DNAT host port is allocated and surfaced via the
executions list, yet external connections to it fail for minutes
(the CLI only sees 'error sending request'). The confidential
instance's SSH forward works on the same host, so the failure is
specific to the V-PROGRAM mapping.

Capture the host-level view after tests: nft ruleset, interfaces,
routes, listeners, plus two live probes: TEE host -> guest :8443
directly (guest reachability) and runner -> public host port (full
DNAT path). Their combination localizes the break.
Round 5's diagnostics only probed the first dnat-to-:8443 rule, which
turned out to be a STALE mapping from an earlier run (nft rules survive
tee-reset): port 24006 answered 200 while the run's own port 24008 went
unprobed. Loop over all rules so each mapping gets a host->guest and a
runner->DNAT probe, with compact status-code output.
…ails

Rounds 4-6 all show the identical signature: the guest's attest agent is
up and listening, the run's own DNAT rule exists through the failure
window (round 5's diagnostics listed it), a stale sibling rule to the
same guest IP serves curl 200, yet vprogram call fails with 'error
sending request' for the full retry window. Everything points at the
CLI's RA-TLS verifier rejecting the handshake (most plausibly a launch
measurement mismatch), which reqwest hides behind a transport-looking
error until aleph-rs#319 ships.

Capture ground truth at failure time: on the first failed call, curl the
same endpoint with no verification and carry the HTTP status into the
final assertion message. 200 there + CLI failure = verifier rejection,
beyond doubt.
rc2 carries aleph-rs#318 (create --wait polls for the attested endpoint
within the wait budget) and #319 (attestation rejections are named
instead of hiding behind 'error sending request').

- manifesto: aleph-cli 0.17.0-rc1 -> 0.17.0-rc2.
- test: remove the show-polling fallback; the ready payload's
  attested_endpoint is the CLI contract again. The warm-up retry stays
  (the port maps a few seconds before the guest binds), but with #319 a
  verification failure no longer matches the transient predicate, so it
  fails fast with the actual reason.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the aleph.compose/1 V-PROGRAM flow with sha256-pinned fixtures, a clean conftest fixture following the established two-step manifest patching pattern, and a focused end-to-end test. The CI workflow changes (IPv6 pool override, gRPC socket, rust supervisor impl, expanded diagnostics) are all well-justified with thorough comments. No correctness bugs or security issues found. A few minor stale comments and the absence of per-test V-PROGRAM cleanup are non-blocking observations consistent with the existing V-PROGRAM test suite.

.github/workflows/pr-tests.yml (line 301): Comment says "from the vprogram-fixtures release" but scripts/vprogram-artifacts.sh (lines 12-14) documents that the fib workload moved to mainnet storage, not the GitHub prerelease. Consider updating this comment to match the script's current behavior.

tests/test_vprogram_compose.py (line 37): No V-PROGRAM cleanup (e.g. vprogram delete) after the test, unlike test_programs.py which calls program delete in a finally block. This is consistent with test_vprograms.py, and tee-reset.sh wipes state post-run, but worth noting that leftover V-PROGRAMs hold TEE resources until the reset step.

scripts/crn-up.sh (line 75): read_vm_version() uses a direct dict key access m['components']['aleph-vm']['version'] which would KeyError if the manifesto only has branch. This is safe today because the branch check at line 328 short-circuits, but it's a latent fragility — consider using .get('version', '') for symmetry with read_vm_branch's .get('branch', '') pattern.

Run 32372006187: the compose VM booted, attested (nonce freshness
verified) and the call reached the guest, but the whoami container was
not up yet; the attest agent answered with an upstream-unreachable 502
body while the CLI exited 0, so the retry loop took the first sample as
final. Retry on that body too, with a 240s budget for the in-guest
podman load + compose up, and keep transport failures retried as
before.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the compose/1 V-PROGRAM validation leg with thorough documentation, sha256-pinned fixtures, robust retry logic that correctly distinguishes transient failures from attestation failures, and a fail-closed test verifying that wrong measurements produce no response body. The CI workflow improvements (IPv6 pool override, gRPC split, controller unit cleanup, --since log collection, network diagnostics) are well-motivated and cite specific run numbers for design decisions. Minor cosmetic and robustness nits noted but none blocking.

.github/workflows/pr-tests.yml (line 301): Comment says "from the vprogram-fixtures release" but vprogram-artifacts.sh now fetches the fib workload from mainnet storage instead of the GitHub prerelease. Consider updating to match the script's own header comment.

.github/workflows/pr-tests.yml (line 401): The block reads tee-network.txt mid-generation (while the { ... } > tee-network.txt redirect is still open) to extract uplink and tap interface names. This works because the ndppd status and ip addr outputs are written earlier in the block, but it's fragile — a future reordering of the echo blocks would silently break the tcpdump capture. Consider extracting these into shell variables earlier in the block before the redirect, or noting the ordering dependency.

tests/test_vprogram_compose.py (line 99): When the only retryable condition is stack_starting (502 body) and transient_transport never fires, curl_probe stays None and the timeout error reports "unverified curl probe: HTTP None". The 502 body is included via body[:500] so diagnostics are present, but capturing a curl probe on the first stack_starting too (not just transient_transport) would give a more actionable contrast in the timeout message.

.github/workflows/pr-tests.yml (line 275): The IPv6 prefix 2001:bc8:702:32d::/64 is hardcoded in the workflow. Not a secret, but if the Scaleway server's flexible IP allocation changes this will silently break V-PROGRAM IPv6 reachability tests. Consider adding a comment noting this is the Scaleway flexible-IP /64 and must match the server's attached prefix.

Root cause of runs 32372006187/32375910277, reproduced in a local qemu
boot of the compose runtime: rc10's docker fallback saves the archive
from the digest-pinned ref, and docker save name@sha256:... writes
RepoTags: null, so the guest's podman load imports a bare image ID that
podman-compose cannot match against the pinned image: string (short-name
resolution fails without a registries.conf, fail-closed poweroff).

Supply the archive ourselves with a TAGGED docker save and
--image-archive, which the CLI maps verbatim: podman load restores the
tag and compose resolves it from local storage (whoami verified up on
:8080 in the local boot). Archive bytes stay verity-measured either way.
Drop once the CLI saves archives under a podman-matchable reference.

Also raise the job timeout to 75 min: two SNP boots per run pushed the
suite past 60 (run 32375910277 was killed at the limit).

@foxpatch-aleph foxpatch-aleph 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.

This PR adds the docker-compose V-PROGRAM test leg alongside the existing exec V-PROGRAM flow, with well-documented infrastructure changes (gRPC split, IPv6 pool override, controller cleanup, expanded diagnostics). The test logic correctly handles the compose-specific startup race (transport failures + 502 bodies during podman load/compose up) with appropriate retry loops and diagnostic probes. Fixture downloads are sha256-pinned and verified. The temporary TCB floor workaround is clearly documented with a removal condition. No correctness bugs or security issues found. Minor diagnostic gaps (curl_probe not captured on 502-only paths, hardcoded IPv6 pool) are non-blocking.

tests/test_vprogram_compose.py (line 112): The curl_probe is only captured when transient_transport is true (transport failure), not when stack_starting is true (502 body). If the stack only ever returns 502s and never has a transport failure, the error message at line 124 will show unverified curl probe: HTTP None. Consider capturing the curl probe on the 502 path as well, so the diagnostic is always populated regardless of which transient signal fires first.

.github/workflows/pr-tests.yml (line 277): The STATIC_CRN_IPV6_POOL=2001:bc8:702:32d::/64 is hardcoded in the workflow and is specific to the Scaleway Elastic Metal flexible-IP allocation. If the TEE server is ever moved or re-provisioned with a different IP range, this will silently break IPv6 reachability for V-PROGRAMs with no obvious error. Consider extracting this to a repository variable or secret (like vars.TEE_IPV6_POOL) so it's visible in the repo settings and easier to update.

.github/workflows/pr-tests.yml (line 403): The grep on lines 403-404 reads tee-network.txt while the surrounding { ... } > tee-network.txt block is still writing to it. This works because shell > truncates the file at open time and content written earlier in the block (lines 362-386) is already flushed, but it's fragile — a reader could see a partially-written line if buffering changes. Not a bug today, but worth a comment noting the self-read dependency.

…shed aleph-vm aggregation

All three registers-schema adopters (pyaleph 0.10.3-rc9, aleph-cli
0.17.0-rc11, aleph-vm od/vprogram-integration-3 rebuilt on dev 2df8d87e)
move together; scheduler images bumped to the vprogram-rc2 build.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the compose V-PROGRAM flow with thorough comments and solid retry logic. The gRPC socket env var is applied to all CRNs (including Python-daemon DO CRNs) — worth confirming the Python daemon ignores it. The compose test lacks a fail-closed measurement assertion and IPv6 check present in the exec test. The tee-network.txt self-reading diagnostics are fragile but guarded. No blocking issues found.

scripts/crn-up.sh (line 363): ALEPH_VM_SUPERVISOR_GRPC_SOCKET is in the common supervisor.env block, applying to ALL CRNs. But ALEPH_VM_SUPERVISOR_IMPL=rust (line 434) is only set for confidential CRNs — DO CRNs stay on the Python daemon. The comment says "the daemon binds the socket" but the Python daemon may not know how to handle this env var. If the Python daemon ignores unknown env vars this is safe, but it would be cleaner to move this into the confidential-only block (line 431-435) or gate it on the supervisor impl, to match the comment's implication that this is for the two-process Rust split.

tests/test_vprogram_compose.py (line 100): curl_probe is only set when transient_transport failures occur (line 112). If all failures are stack_starting (502 "upstream unreachable"), curl_probe stays None and the deadline error message shows "HTTP None", reducing diagnostic value. Consider also capturing a curl probe on the first stack_starting failure.

tests/test_vprogram_compose.py (line 131): The compose test lacks a fail-closed assertion (wrong expected measurement must abort without printing a body) that test_vprograms.py:162-174 includes. Since the compose flow also pins SEV-SNP measurements, a fail-closed test would confirm the compose measurement pinning works end to end.

.github/workflows/pr-tests.yml (line 403): The uplink/tap interface extraction greps tee-network.txt while the { ... } > tee-network.txt block is still writing to it. The grep patterns may not match the actual output: ^proxy [a-z0-9]+ expects ndppd.conf-style lines, and iface vmtap[0-9]+ doesn't match ip addr output format (which uses N: vmtap0: not iface vmtap0). The if [ -n "$uplink" ] guard prevents breakage but the dual tcpdump capture may never execute.

tests/test_vprograms.py (line 174): Neither test_vprograms.py nor test_vprogram_compose.py include V-PROGRAM cleanup (analogous to program delete in test_programs.py:143). This is acceptable since the testnet is ephemeral and tee-reset.sh wipes VM state, but it diverges from the existing cleanup convention.

Run 32416035775 attempt 2 lost the documented nodestatus-balances race:
the confidential rootfs upload still saw 'Insufficient balance' after
180s while the same upload passed on attempt 1 and every earlier run of
the day. Give the asynchronous funding more room; real cost shortfalls
still fail, just later.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the docker-compose V-PROGRAM leg with sha256-pinned fixtures, proper retry loops for transient startup failures, and thorough CI diagnostics. The test logic in test_vprogram_compose.py correctly handles the compose-specific startup race (502 + transport errors as not-ready signals) and reuses the exec test's attestation primitives. The CRN configuration changes (gRPC socket, rust supervisor, IPv6 pool override) are well-justified with clear comments. The tee-reset controller cleanup fixes a real journal-flooding problem. Non-blocking issues found are in the diagnostics-only CI step and a pre-existing timeout pattern in the upload retry helper.

.github/workflows/pr-tests.yml (line 404): The tap variable will always be empty: grep -oE 'iface vmtap[0-9]+' looks for iface vmtapN in tee-network.txt, but ip addr (line 365) outputs interfaces as 2: vmtap0: — the word iface only appears in ip link output. This silently disables the dual-capture diagnostics block at line 405. Consider grepping for the ip addr format instead, e.g. grep -oE 'vmtap[0-9]+:' | head -1 | tr -d ':'.

tests/conftest.py (line 346): _upload_with_balance_retry does not pass a timeout to the underlying aleph_cli subprocess call. The timeout parameter only gates the balance-retry loop, not the individual upload. With the 297 MB compose bundle (timeout=600), a hung network connection would block indefinitely instead of being caught by the deadline. Consider passing timeout= to the aleph_cli call (e.g. a per-attempt timeout of 300s) so network hangs also surface.

.github/workflows/pr-tests.yml (line 403): The uplink extraction regex ^proxy [a-z0-9]+ matches the ndppd.conf proxy <iface> { line, but only captures interface names matching [a-z0-9]+ — names with other chars (e.g. enp3s0 has no non-matching chars but ens0 also works; however an interface like br-abc123 would fail). Low impact since this is diagnostics-only and guarded by the if [ -n "$uplink" ] check, but worth widening to ^proxy \S+ for robustness.

The validation aggregation branch is fully merged (aleph-vm #1131,
#1147, #1148); dev's tree matches the branch that passed run
32426136992.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the aleph.compose/1 V-PROGRAM flow with thorough documentation of the complex TEE/SEV-SNP infrastructure. The test logic, fixture handling, and CI integration are sound. The main issues are non-blocking: a race condition in the CI network diagnostics where uplink/tap variables are grepped from a file still being written by the same shell redirect (silently skipping the dual-capture diagnostic), and the compose test's curl_probe not being captured for the stack_starting failure mode (producing a confusing 'HTTP None' in the error message).

.github/workflows/pr-tests.yml (line 403): The uplink and tap variables are extracted from tee-network.txt via grep, but the entire { ... } block's output is redirected to tee-network.txt on line 420 (} > tee-network.txt 2>&1). Since the shell hasn't flushed/closed the file yet when these greps run, the file content is either empty or incomplete at this point, so uplink and tap will almost always be empty. The dual-capture tcpdump diagnostic is silently skipped as a result. Consider writing the initial diagnostics to a temp file first, then grepping from that, or restructure to capture the interface names inline (e.g. from the $SSH \"ip addr\" output directly via a variable) before the redirect.

tests/test_vprogram_compose.py (line 112): curl_probe is only set when transient_transport is true, but the compose test also has a stack_starting transient condition (rc=0 with 'upstream unreachable' in body). If only stack_starting failures occur before the deadline, curl_probe remains None and the AssertionError reports 'unverified curl probe: HTTP None', which is unhelpful for debugging. Consider also capturing curl_probe when stack_starting is true, e.g. move the curl probe capture outside the if transient_transport guard to cover both transient cases.

tests/test_vprogram_compose.py (line 131): The exec V-PROGRAM test (test_vprograms.py) includes a fail-closed assertion that a wrong expected measurement aborts the call without printing a response body. The compose test lacks this coverage. While the fail-closed path is the same CLI codepath, adding a similar assertion here would guard against compose-specific regressions in the attestation gate.

scheduler-rs/scheduler-api move to the 2026-08-24 vprogram-rc3 build.
aleph-vm stays pinned to dev, which gained the measured-CPU SNP launch
(#1145), agent-owned VM disks (#1149) and the SNP vCPU probe retry
(#1150) since the last green run.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the compose V-PROGRAM e2e test on top of the existing exec V-PROGRAM validation. The new fixtures, conftest session fixtures, test files, and CI diagnostics are all correctly implemented with thorough documentation of non-obvious decisions (Scaleway IPv6 egress, tagged image-archive workaround, TCB floor relaxation). The retry loops properly distinguish transient startup failures from real attestation failures. The fail-closed measurement test in test_vprograms.py is a valuable security assertion. No correctness bugs or security issues found.

tests/test_vprograms.py (line 122): json.loads(health.stdout) assumes the health endpoint always returns valid JSON when rc==0. If the attest agent ever returns a non-JSON 200 body (e.g. during a partial startup), this raises JSONDecodeError instead of a clean assertion failure. The compose test handles this more defensively with "Hostname:" in body before any parsing. Consider wrapping in a try/except or checking content-type first, though this is non-blocking since rc==0 with non-JSON is itself a bug worth surfacing.

tests/test_vprogram_compose.py (line 111): The string match "upstream unreachable" in body couples the test to the attest agent's 502 response body text. If that string changes in a future compose runtime build, the retry loop would stop recognizing the not-ready-yet signal and the test would fail immediately on the first 502 instead of waiting for the stack to come up. Consider also checking the HTTP status code (if available via the CLI output) for a more stable signal.

.github/workflows/pr-tests.yml (line 403): Inside the { ... } > tee-network.txt block, grep reads tee-network.txt from disk to extract $uplink and $tap. The file is still being written to by the block's stdout redirection at this point. This works in practice because bash flushes prior lines before reaching the grep, but it's fragile — a buffered I/O implementation could miss the ip addr output. Since this is diagnostics-only (runs with if: always() and everything has || true), this is non-blocking.

manifesto.yml (line 63): Switching aleph-vm from a pinned version (1.13.0) to branch: dev means CI runs track the latest dev branch tip. This is appropriate for a testnet, but any breakage on aleph-vm dev will block all PRs until dev is fixed or this pin is reverted. Worth keeping in mind for CI stability.

dev gained agent-side backups with supervisor guest quiescence (#1151),
disk admission before resource download (#1153), the dead proto
reservation cleanup (#1154) and FAILED reporting for dead controller
units (#1152) since the last green run.

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the aleph.compose/1 V-PROGRAM end-to-end test with thorough retry logic, good diagnostic coverage, and sha256-pinned fixtures. The test code is correct for the happy and transient-failure paths. Two issues worth noting: (1) the TEE network diagnostics step in the workflow reads tee-network.txt while it's still being written by the enclosing redirect block, and the uplink interface extraction pattern likely won't match the ip -6 neigh output, so the tcpdump dual-capture diagnostic will silently never run; (2) the compose test lacks a fail-closed assertion (wrong measurement pin) that the exec test includes, leaving a coverage gap for the compose attestation path.

.github/workflows/pr-tests.yml (line 403): The grep -oE '^proxy [a-z0-9]+' tee-network.txt reads tee-network.txt while it is still being written by the enclosing { ... } > tee-network.txt 2>&1 block (line 420). The file is opened with O_WRONLY|O_CREAT|O_TRUNC, so data written so far is visible, but the ^proxy [a-z0-9]+ pattern expects ip -6 neigh show proxy output to start with proxy <iface>, which may not match the actual format (typically proxy <iface> ndp <addr>). If uplink is empty, the entire tcpdump dual-capture block (lines 405-418) is skipped, silently dropping a key diagnostic. Consider extracting the uplink from ip -6 route show default output instead, or restructuring so the diagnostic data is written to a temp file that grep can reliably read.

.github/workflows/pr-tests.yml (line 424): The DNAT rule probing loop appends to tee-network.txt (line 425: >> tee-network.txt) while also grepping from it (line 424: grep -E "dnat.*:8443" tee-network.txt). Since the { } > tee-network.txt block has closed by this point, the grep reads the complete file, but the appended echo "attestation DNAT rule: $rule" >> tee-network.txt on line 425 could theoretically add new lines matching the grep pattern if the rule text itself contains "dnat.*:8443" — causing the while loop to process its own appended output. In practice the appended line starts with "attestation DNAT rule:" not "dnat", so this is safe, but it's fragile.

tests/test_vprogram_compose.py (line 112): curl_probe is only captured when transient_transport is true, not when stack_starting (502 body) is the failure mode. If the deadline expires with only stack-starting failures, the error message reports curl_probe as None. Consider also capturing the unverified curl probe when stack_starting is true, so the diagnostic is populated in the 502-only scenario.

tests/test_vprogram_compose.py (line 131): Missing fail-closed assertion. test_vprograms.py verifies that a wrong --expected-measurement causes the call to fail with no stdout (lines 162-174). The compose test should include the same assertion to verify the compose attestation path also fail-closes on a measurement mismatch.

The Siena/Zen4c TEE host reports x86 microcode 0x0AA0021C (SPL 28) since
its 2026-08-24 reboot, above rc11's Zen4c EntrySign floor of 25, so the
--min-tcb microcode=21 --accept-outdated-tcb override the V-PROGRAM tests
carried since 2026-08-17 is obsolete. Dropping it also makes the runs
exercise the real TCB gate again instead of lowering every silicon line's
floor (which is what the CLI's 'accepting a TCB below the network floor'
warning was about).

@foxpatch-aleph foxpatch-aleph 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.

Well-structured PR adding the docker-compose V-PROGRAM test flow with comprehensive CI diagnostics. The test logic, fixture management, and CI workflow changes are sound and follow existing patterns. The retry loops correctly distinguish transient startup failures from genuine errors, and the attestation verification path is well-tested (including the fail-closed case). A few minor issues in the network diagnostics grep patterns mean the dual-tcpdump capture will likely always be skipped, and the curl_probe diagnostic isn't captured during 502 startup windows. Neither affects test correctness.

.github/workflows/pr-tests.yml (line 403): The grep pattern ^proxy [a-z0-9]+ won't match ip -6 neigh show proxy output, which has lines like 2001:bc8:702:32d:: dev enp1s0 proxy (proxy at end, not start). Consider grep -oE 'dev [a-z0-9]+ .* proxy' or parse the dev field directly. Same issue on line 404: iface vmtap[0-9]+ won't match ip addr output format (3: vmtap0: <BROADCAST...>); consider [0-9]+: vmtap[0-9]+ or vmtap[0-9]+:. Both patterns being empty means the dual tcpdump capture block is always skipped, so the diagnostic data it was designed to collect is never gathered.

tests/test_vprogram_compose.py (line 112): The curl_probe is only captured when transient_transport is True. If the stack is still starting (502 "upstream unreachable" responses with rc=0), curl_probe stays None and the eventual error message reports HTTP None. Consider also capturing the probe when stack_starting is True — a 502 from the unverified curl confirms the proxy is reachable and the stack genuinely isn't ready, while a non-502 would indicate a different issue.

tests/conftest.py (line 346): _upload_with_balance_retry calls aleph_cli(...) without a timeout= argument, so subprocess.run gets timeout=None. The timeout parameter (e.g. 600 for the 297 MB compose bundle) only bounds the retry loop between attempts. If a single upload hangs, the function blocks indefinitely. Consider passing a per-attempt timeout to aleph_cli (e.g. timeout=300) so a hung upload is interrupted and the retry deadline actually applies.

.github/workflows/pr-tests.yml (line 166): cryptsetup-bin is installed on the CCN droplet, but confidential-artifacts.sh checks for cryptsetup on the TEE server (line 126), not the CCN. If there's another reason it's needed on the CCN, a brief comment would help; otherwise this may be unnecessary.

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