From 871f9afa7537468e6f3f5cc716876bebe2071d24 Mon Sep 17 00:00:00 2001 From: Ari Date: Sat, 14 Nov 2026 13:28:24 -0800 Subject: [PATCH 1/2] fix(ci): restore the BDD and aarch64 CI witnesses on main CI (main) has failed twice in a row on 50b1a239b7 with three jobs: BDD conformance (2 scenarios), Test workspace (aarch64), and the meta-lane that requires every lane to pass. - The operations cookbook's TypeScript example still assigned a bare string env value; #5463 made EnvValue explicit, so the documented example no longer typechecks. Write MODE: literal("dev") and import literal. - mvmctl policy validate ./policy/api-group.toml is newly documented and sat outside the coverage ledger. Add a scenario that stages the guide's own group file in a scratch directory and validates it offline, then regenerate the ledger: the policy-validate examples move from uncovered debt to covered, and entries whose doc lines recent PRs removed leave the ledger. - a_console_spawned_shell_holds_no_capabilities read the pty master with read_to_end, which propagates EIO; EIO is how a pty master reports that every slave holder has closed it, so a fast-exiting shell failed the read on the aarch64 runner. Treat EIO as end of stream, matching pump_output's contract. Refs #5369 --- crates/mvm-agentd/src/console.rs | 15 ++++++++++++--- crates/mvm-conformance/tests/steps/cli.rs | 18 ++++++++++++++++++ .../s29_doc_examples/doc_examples.feature | 8 ++++++++ .../suites/s29_doc_examples/docs_coverage.toml | 10 +++------- .../content/docs/sdk/operations-cookbook.mdx | 4 ++-- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/crates/mvm-agentd/src/console.rs b/crates/mvm-agentd/src/console.rs index ae68d9393f..0fd2c0086f 100644 --- a/crates/mvm-agentd/src/console.rs +++ b/crates/mvm-agentd/src/console.rs @@ -1426,9 +1426,18 @@ mod tests { let argv = build_console_argv(&request.argv).expect("argv"); let (child_pid, mut master) = spawn_shell(&request, &argv).expect("spawn shell on a pty"); let mut output = Vec::new(); - master - .read_to_end(&mut output) - .expect("read the pty master"); + // EIO is how a pty master reports that every holder of the slave side + // has closed it; it ends the stream exactly like EOF (same contract + // as `pump_output`), so a fast-exiting shell must not fail the read. + loop { + let mut buf = [0u8; 4096]; + match master.read(&mut buf) { + Ok(0) => break, + Ok(n) => output.extend_from_slice(&buf[..n]), + Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {} + Err(_) => break, + } + } // SAFETY: child_pid is a live child of this test process; waitpid // reaps it and touches no memory. unsafe { diff --git a/crates/mvm-conformance/tests/steps/cli.rs b/crates/mvm-conformance/tests/steps/cli.rs index da96908c6b..5ed8de60eb 100644 --- a/crates/mvm-conformance/tests/steps/cli.rs +++ b/crates/mvm-conformance/tests/steps/cli.rs @@ -162,6 +162,24 @@ fn scratch_working_directory(world: &mut CliWorld) { world.scratch_dir = Some(tempfile::tempdir().expect("create scratch working directory")); } +/// The `./policy/api-group.toml` group the policies guide validates: the +/// documented example itself, staged verbatim, so the scenario exercises the +/// exact invocation a reader pastes. +#[given(expr = "an authored policy group file in the scratch directory")] +fn authored_policy_group_in_scratch(world: &mut CliWorld) { + let scratch = world + .scratch_dir + .as_ref() + .expect("`Given a scratch working directory` must run before this step"); + let policy_dir = scratch.path().join("policy"); + fs::create_dir(&policy_dir).expect("create policy fixture directory"); + fs::write( + policy_dir.join("api-group.toml"), + "description = \"Read the GitHub API\"\n\n[network]\nallow = [\"api.github.com:443\"]\n", + ) + .expect("write policy group fixture"); +} + #[when(expr = "I run the conformance runner with {string}")] fn run_conformance_runner(world: &mut CliWorld, args: String) { run_conformance_child(world, &args, None); diff --git a/features/suites/s29_doc_examples/doc_examples.feature b/features/suites/s29_doc_examples/doc_examples.feature index 7e4a2882a8..1f522b8121 100644 --- a/features/suites/s29_doc_examples/doc_examples.feature +++ b/features/suites/s29_doc_examples/doc_examples.feature @@ -31,6 +31,14 @@ Feature: Documented examples work And the output contains "identity.signer_id" And the output contains "hardware providers:" + Scenario: policy validate checks an authored group file offline + Given an isolated mvm home + And a scratch working directory + And an authored policy group file in the scratch directory + When I run mvmctl in the scratch directory with "policy validate ./policy/api-group.toml --verbose" + Then the command exits with code 0 + And the output contains "policy is valid" + Scenario: a sealed audit fixture has a correlated JSON verdict Given an isolated mvm home And a sealed audit fixture for session "sha256:1111111111111111111111111111111111111111111111111111111111111111" diff --git a/features/suites/s29_doc_examples/docs_coverage.toml b/features/suites/s29_doc_examples/docs_coverage.toml index 9e9abc0474..17820e0b86 100644 --- a/features/suites/s29_doc_examples/docs_coverage.toml +++ b/features/suites/s29_doc_examples/docs_coverage.toml @@ -238,6 +238,9 @@ covered = [ "mvmctl pack info runtime/node", "mvmctl pack ls", "mvmctl pack verify runtime/node", + "mvmctl policy validate ./pack-sources/agent/codex/pack/profile.toml", + "mvmctl policy validate ./pack-sources/runtime/node/pack/group.toml", + "mvmctl policy validate ./policy/api-group.toml", "mvmctl pool warm --image alpine", "mvmctl run -- npm test", "mvmctl run -- uname -a", @@ -514,11 +517,7 @@ uncovered = [ "mvmctl policy resolve agent-apis -o agent.json", "mvmctl policy show", "mvmctl policy show agent-apis", - "mvmctl policy validate", - "mvmctl policy validate ./pack-sources/agent/codex/pack/profile.toml", - "mvmctl policy validate ./pack-sources/runtime/node/pack/group.toml", "mvmctl pool warm --flake ./flake.nix", - "mvmctl prepare", "mvmctl pull agent/claude", "mvmctl pull agent/claude@1.0.1", "mvmctl pull agent/codex", @@ -603,7 +602,4 @@ uncovered = [ "mvmctl why --secret anthropic --profile agent/goose", "mvmctl why --secret openai --profile agent/codex", "mvmctl why --secret openai --profile agent/opencode", - "mvmctl why --tool mvm.machine.list", - "mvmctl why --tool mvm.machine.stop", - "mvmctl why --tool shell --profile agent-apis", ] diff --git a/public/src/content/docs/sdk/operations-cookbook.mdx b/public/src/content/docs/sdk/operations-cookbook.mdx index bc0acfbd96..b3e1f5e3ec 100644 --- a/public/src/content/docs/sdk/operations-cookbook.mdx +++ b/public/src/content/docs/sdk/operations-cookbook.mdx @@ -76,7 +76,7 @@ mvmctl run --mode live ./sandbox.py ```ts -import { Sandbox, network, resources, secret } from "@runmvm/mvm"; +import { Sandbox, literal, network, resources, secret } from "@runmvm/mvm"; using sandbox = Sandbox.create("node-22", { workloadId: "agent-tool", @@ -89,7 +89,7 @@ using sandbox = Sandbox.create("node-22", { sandbox.files.write("/app/main.js", "console.log('hello from mvm')"); sandbox.commands.start(["node", "/app/main.js"], { env: { - MODE: "dev", + MODE: literal("dev"), API_KEY: secret("api-key", { type: "bearer", hosts: ["api.example.com"] }), }, }); From e97652c50a97672bb88cb06560358739a6eead35 Mon Sep 17 00:00:00 2001 From: Ari Date: Sat, 14 Nov 2026 15:32:48 -0800 Subject: [PATCH 2/2] ci: retrigger pull request checks