Skip to main content

bvisor/contract/
qualification.rs

1//! The QUALIFICATION axis (proof-spine §0/§1) — ORTHOGONAL to the RuntimeGuarantee
2//! axis ([`crate::contract::capability::Enforcement`]). Never collapse them.
3//!
4//! - **RuntimeGuarantee** ([`Enforcement`]): what a backend CLAIMS it can guarantee
5//!   for a particular plan + machine profile.
6//! - **QualificationStatus** (this module): what the REPOSITORY has INDEPENDENTLY
7//!   qualified through the complete path `spec → admission → lowering → execution →
8//!   independent observation`, INCLUDING the fail-closed / teardown branches.
9//!
10//! THE COUPLING LAW: a production profile may advertise [`Enforcement::Enforced`]
11//! for a requirement key ONLY when the committed qualification ledger holds
12//! [`QualificationStatus::Proven`] for that key — with a profile floor the machine
13//! satisfies and a matching mechanism digest. The coupling is enforced as a CI
14//! GATE: the backend's ceiling claim is explicit; the ledger is DERIVED from proof
15//! receipts; the gate asserts they agree. A backend can never self-stamp `Proven`.
16//!
17//! This is the structural fix for the over-claim class: the family `support_matrix`
18//! may CLAIM `Enforced` as aspiration, but production advertises it only when the
19//! claim is `Proven` — so an unproven advertised cell is an `Incomplete`
20//! qualification, not a lie.
21
22use crate::contract::capability::Enforcement;
23use crate::contract::support::RequirementKind;
24use serde::{Deserialize, Serialize};
25
26/// What the repository has INDEPENDENTLY qualified about a capability claim.
27/// Distinct from [`Enforcement`] (the runtime claim a backend makes).
28///
29/// Serialized to/from the committed qualification ledger (kebab-case) so the
30/// integrity coupling gate — which cannot depend on this crate — reads it as data.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
32#[serde(rename_all = "kebab-case")]
33#[non_exhaustive]
34pub enum QualificationStatus {
35    /// The complete contract path PLUS its fail-closed / teardown branches were
36    /// observed by an INDEPENDENT oracle (host-side kernel-state preferred over
37    /// workload self-report; dual-channel). The ONLY status that permits a
38    /// production `Enforced` claim, for the matching key + profile-floor + digest.
39    Proven,
40    /// Explicitly unsupported on this platform/profile; planning REFUSES before
41    /// execution. The fail-closed default for any unproven capability. NOT a
42    /// waiver — a platform limitation is `FailClosed`, never `Waived`.
43    FailClosed,
44    /// Some layers exist (a mechanism, a partial path) but the claimed guarantee
45    /// is NOT proven end-to-end. May not be advertised `Enforced` in production.
46    Incomplete,
47    /// Mechanically impossible to witness, OR deliberately excluded — carried as an
48    /// owner-signed, EXPIRING waiver (owner + reason + expiry). NEVER describes a
49    /// platform capability limitation.
50    Waived,
51    /// Test-only support that exists to PROVE detection (a red fixture's planted
52    /// lie). Never a production claim.
53    FaultInjected,
54}
55
56impl QualificationStatus {
57    /// The coupling-law predicate: whether this qualification permits a production
58    /// [`Enforcement::Enforced`] runtime claim. ONLY [`Self::Proven`] does.
59    #[must_use]
60    pub fn permits_enforced(self) -> bool {
61        matches!(self, Self::Proven)
62    }
63}
64
65/// The coupling-law check at a single (key, profile, claim) point: a runtime
66/// [`Enforcement`] claim is admissible against a qualification only when either the
67/// claim is not `Enforced`, or the qualification is `Proven`. (The full gate also
68/// matches the profile floor and mechanism digest — those live in the ledger + the
69/// integrity gate; this is the pointwise core, kept here so it is unit-testable and
70/// shared by the runtime if it ever consults a qualification.)
71#[must_use]
72pub fn enforced_claim_is_qualified(claim: Enforcement, qualification: QualificationStatus) -> bool {
73    match claim {
74        Enforcement::Enforced => qualification.permits_enforced(),
75        // A backend may always claim a weaker guarantee than it has proven.
76        Enforcement::Mediated | Enforcement::Unsupported => true,
77    }
78}
79
80/// A `blake3` digest of a backend's MECHANISM string for a requirement key (§1's
81/// `H_M`). The coupling law binds a production `Enforced` claim to a `Proven`
82/// ledger row ONLY when the row's digest matches the digest of the mechanism the
83/// backend would actually use — so a backend cannot satisfy the gate by swapping
84/// in a different (unproven) mechanism under the same key.
85///
86/// Derived with [`batpak::event::hash::compute_hash`] (the same blake3 the
87/// launcher attestation uses), over the UTF-8 bytes of the mechanism string a
88/// backend authors in its `mechanism(req, enforcement)` (e.g.
89/// `"linux:landlock:Enforced"`).
90#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
91pub struct MechanismDigest(pub [u8; 32]);
92
93impl MechanismDigest {
94    /// Derive the digest from a backend's mechanism string (the exact bytes a
95    /// backend's `mechanism(req, enforcement)` returns, e.g.
96    /// `"linux:landlock:Enforced"`).
97    #[must_use]
98    pub fn of_mechanism(mechanism: &str) -> Self {
99        Self(batpak::event::hash::compute_hash(mechanism.as_bytes()))
100    }
101
102    /// The lowercase hex spelling of the 32-byte digest (for the committed ledger
103    /// + diagnostics). Deterministic, so a ledger row's digest is one stable line.
104    #[must_use]
105    pub fn to_hex(self) -> String {
106        self.0.iter().map(|b| format!("{b:02x}")).collect()
107    }
108}
109
110/// The MINIMUM machine facts a qualification covers (§3 PROFILE-CLASS): the floor
111/// predicate a production profile must DOMINATE (`p_prod ⊒ floor`) for a `Proven`
112/// receipt earned at the floor to transfer to it.
113///
114/// A qualification is earned on ONE probed CI runner, but production runs
115/// elsewhere; an exact-profile digest would never transfer. So the floor states
116/// the LEAST the mechanism needs, and the load-bearing argument is that proving AT
117/// the floor generalizes UPWARD — a stronger machine still satisfies every minimum.
118/// `satisfied_by` is exactly that domination check.
119#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
120pub struct ProfileFloor {
121    /// The minimum landlock ABI the mechanism requires (`None` = landlock is not
122    /// part of this floor — e.g. a cgroup-only Kill cell does not need landlock).
123    pub landlock_abi_min: Option<u8>,
124    /// The mechanism requires atomic `cgroup.kill` run-tree teardown.
125    pub requires_cgroup_kill: bool,
126    /// The mechanism requires the `pids.peak` usage witness (cgroup v2 ≥ 6.1).
127    pub requires_pids_peak: bool,
128    /// The mechanism requires UNPRIVILEGED user + network namespace creation (the
129    /// `NetworkDenyAll` empty-netns floor, S9 / D3): an unprivileged process may create a
130    /// new netns ONLY when it is also root in a new userns (the S8 rendezvous). A kernel
131    /// that forbids unprivileged userns cannot realize the empty netns, so the cell is
132    /// FAIL_CLOSED there — never a silent pass.
133    pub requires_unprivileged_userns: bool,
134    /// The mechanism requires installing a seccomp BPF FILTER (the
135    /// `ChildSpawn::DenyNewTasks` floor, S10): a kernel without `CONFIG_SECCOMP_FILTER`
136    /// cannot install the task-creation denylist, so the cell is FAIL_CLOSED there — never
137    /// a silent unfiltered run.
138    pub requires_seccomp_filter: bool,
139}
140
141impl ProfileFloor {
142    /// The empty floor: no minimum machine facts (structural mechanisms that hold
143    /// on every machine of the platform — e.g. process spawn / pipe capture).
144    #[must_use]
145    pub const fn structural() -> Self {
146        Self {
147            landlock_abi_min: None,
148            requires_cgroup_kill: false,
149            requires_pids_peak: false,
150            requires_unprivileged_userns: false,
151            requires_seccomp_filter: false,
152        }
153    }
154
155    /// The empty-netns `NetworkDenyAll` floor (S9 / D3): requires unprivileged
156    /// user + network namespace creation, no landlock / cgroup minimum. Structural
157    /// otherwise (the empty netns holds on any kernel that permits unprivileged userns).
158    #[must_use]
159    pub const fn unprivileged_userns_netns() -> Self {
160        Self {
161            landlock_abi_min: None,
162            requires_cgroup_kill: false,
163            requires_pids_peak: false,
164            requires_unprivileged_userns: true,
165            requires_seccomp_filter: false,
166        }
167    }
168
169    /// The `ChildSpawn::DenyNewTasks` floor (S10): requires seccomp FILTER support. No
170    /// landlock / cgroup / userns minimum — the task-creation denylist is structural above
171    /// that floor (it holds on any kernel with `CONFIG_SECCOMP_FILTER`), so the proof
172    /// transfers upward. Below the floor the cell drops from the ceiling (fail-closed).
173    #[must_use]
174    pub const fn seccomp_filter() -> Self {
175        Self {
176            landlock_abi_min: None,
177            requires_cgroup_kill: false,
178            requires_pids_peak: false,
179            requires_unprivileged_userns: false,
180            requires_seccomp_filter: true,
181        }
182    }
183
184    /// The `ChildSpawn::AllowDescendantsWithinBoundary` floor (S10): requires the cgroup
185    /// `cgroup.kill` boundary (the S1 Kill mechanism the descendant inherits), no seccomp /
186    /// landlock / userns minimum — the descendant is killable + counted + namespace-trapped
187    /// by the cgroup. Below the floor (no cgroup) the cell drops from the ceiling.
188    #[must_use]
189    pub const fn cgroup_descendant_boundary() -> Self {
190        Self {
191            landlock_abi_min: None,
192            requires_cgroup_kill: true,
193            requires_pids_peak: false,
194            requires_unprivileged_userns: false,
195            requires_seccomp_filter: false,
196        }
197    }
198
199    /// THE §3 DOMINATION CHECK: whether the concrete machine `facts` satisfy every
200    /// minimum this floor requires (`facts ⊒ self`). A production profile may
201    /// advertise the qualification's key ONLY when this holds — so a receipt earned
202    /// at the floor transfers UPWARD to any stronger machine but NEVER downward to a
203    /// weaker one.
204    #[must_use]
205    pub fn satisfied_by(&self, facts: &ProfileFacts) -> bool {
206        if let Some(min) = self.landlock_abi_min {
207            if facts.landlock_abi < i64::from(min) {
208                return false;
209            }
210        }
211        if self.requires_cgroup_kill && !facts.has_cgroup_kill {
212            return false;
213        }
214        if self.requires_pids_peak && !facts.has_pids_peak {
215            return false;
216        }
217        if self.requires_unprivileged_userns && !facts.has_unprivileged_userns {
218            return false;
219        }
220        if self.requires_seccomp_filter && !facts.has_seccomp_filter {
221            return false;
222        }
223        true
224    }
225}
226
227/// The concrete, TYPED machine facts a [`ProfileFloor`] is checked against — the
228/// probe truths a backend derives once at construction (the live landlock ABI, and
229/// whether the kernel exposes atomic `cgroup.kill` / the `pids.peak` witness).
230///
231/// Distinct from [`crate::contract::support::BackendProfile`] (the per-kind
232/// enforcement CEILING): the ceiling is the OUTPUT of these facts, while the floor
233/// predicate is stated over the facts THEMSELVES (the ABI integer, the cgroup
234/// presence) — a ceiling has no ABI integer to compare a floor against.
235#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
236pub struct ProfileFacts {
237    /// The live landlock ABI integer probed from the kernel (`0` = unavailable).
238    pub landlock_abi: i64,
239    /// Whether the probed cgroup base exposes atomic `cgroup.kill`.
240    pub has_cgroup_kill: bool,
241    /// Whether the probed cgroup base exposes the `pids.peak` usage witness.
242    pub has_pids_peak: bool,
243    /// Whether the host permits UNPRIVILEGED user + network namespace creation (the
244    /// `NetworkDenyAll` empty-netns floor, S9 / D3). Probed once at construction
245    /// (`unprivileged_userns_available`); `false` ⇒ the empty-netns cell fails closed.
246    pub has_unprivileged_userns: bool,
247    /// Whether the host supports installing a seccomp BPF FILTER (the
248    /// `ChildSpawn::DenyNewTasks` floor, S10). Probed once at construction
249    /// (`seccomp_filter_available`); `false` ⇒ the task-creation-deny cell fails closed.
250    pub has_seccomp_filter: bool,
251}
252
253/// One committed row of the qualification LEDGER (§1 `Qualification(p,k)` joined to
254/// its profile-class + mechanism digest + proof receipt). The ledger is the
255/// REPOSITORY's independent record; a backend never authors it.
256///
257/// A row may carry [`QualificationStatus::Proven`] ONLY if every entry in
258/// `proof_receipts` cites a REAL, currently-passing oracle test (`path::fn`) — and
259/// together they prove the COMPLETE §4 contract path INCLUDING the fail-closed /
260/// teardown branch (a cell's guarantee-holds and its setup-failure ⇒ no-effect are
261/// usually distinct oracles). The coupling test enforces the §1 law: every
262/// production-`Enforced` ceiling cell must have a `Proven` row here whose
263/// [`ProfileFloor`] the running profile satisfies AND whose [`MechanismDigest`]
264/// matches the backend's mechanism for that key; the proof-receipt RESOLVER
265/// (`coupling_proof.rs`) additionally fails CI if any cited receipt is a ghost.
266#[derive(Clone, Debug, PartialEq, Eq)]
267pub struct QualificationRow {
268    /// The backend family id this qualification was earned for (e.g. `"linux"`).
269    pub backend: &'static str,
270    /// The requirement key this row qualifies.
271    pub key: RequirementKind,
272    /// The minimum machine facts the proof generalizes from (§3).
273    pub profile_floor: ProfileFloor,
274    /// The exact backend MECHANISM STRING this row proves (e.g.
275    /// `"linux:landlock:Enforced"`). Stored as the source string — not a
276    /// pre-baked digest — because blake3 is not `const`; the coupling test
277    /// re-derives [`MechanismDigest::of_mechanism`] from this AND from the
278    /// backend's live `mechanism(req, enforcement)` and asserts they MATCH, so a
279    /// backend cannot satisfy the gate under a different (unproven) mechanism.
280    pub mechanism: &'static str,
281    /// What the repository independently qualified for this cell.
282    pub status: QualificationStatus,
283    /// The proof receipts: the `path::fn`s of the oracle tests that TOGETHER prove
284    /// the cell's complete §4 path (guarantee-holds + fail-closed branch). Empty for
285    /// non-`Proven` rows. Every entry is machine-resolved to a real `#[test]` by the
286    /// resolver gate — a `Proven` row may NOT cite a ghost.
287    pub proof_receipts: &'static [&'static str],
288}
289
290impl QualificationRow {
291    /// The blake3 [`MechanismDigest`] (§1 `H_M`) of this row's committed mechanism
292    /// string — the digest the coupling gate matches the backend's live mechanism
293    /// against.
294    #[must_use]
295    pub fn mechanism_digest(&self) -> MechanismDigest {
296        MechanismDigest::of_mechanism(self.mechanism)
297    }
298}
299
300/// Derive the [`MechanismDigest`] for a Linux mechanism string built the way
301/// `backend_impl::mechanism` builds it: `"{id}:{primitive}:{enforcement:?}"`. The
302/// ledger is committed `const`, but a digest is a runtime blake3, so the ledger
303/// stores the SOURCE mechanism string and the coupling test re-derives the digest;
304/// this helper keeps the two spellings (ledger vs. backend) in one place.
305#[must_use]
306pub fn linux_mechanism(primitive: &str, enforcement: Enforcement) -> String {
307    format!("linux:{primitive}:{enforcement:?}")
308}
309
310/// Derive the [`MechanismDigest`] source string for a Wasm mechanism.
311#[must_use]
312pub fn wasm_mechanism(primitive: &str, enforcement: Enforcement) -> String {
313    format!("wasm:{primitive}:{enforcement:?}")
314}
315
316/// The COMMITTED Linux qualification ledger (S1): the CURRENTLY-PROVEN cells, each
317/// citing its real passing oracle(s), plus the explicitly-stated non-proven cells.
318///
319/// PROVEN rows cite a real, currently-passing oracle that proves the complete
320/// contract path (verified to exist in `crates/bvisor/tests/`):
321/// - `Filesystem` — landlock denial proven on-disk by the independent G-grid oracle
322///   (above the ABI floor; below it the cell is fail-closed, never advertised).
323/// - `Kill` — atomic `cgroup.kill` teardown DRAINS the tree to empty (observed via
324///   the bounded `wait_until_empty` poll), paired with the pids cap proof.
325/// - `LaunchWorkload` / `CaptureStreams` — the launcher spawns + the host captures
326///   the workload's own stdout/stderr cleanly (no launcher contamination).
327/// - `process_count` budget — the cgroup `pids.max` cap GENUINELY denies forks past
328///   the cap (kernel `pids.events max` counter), witnessed from `pids.peak`.
329///   (process_count is a budget dimension, not a `RequirementKind`; it rides the
330///   `Kill` cell's cgroup floor, so it is documented here, not a separate row.)
331///
332/// `Environment` (explicit envp) — the admitted `Environment::Exact` policy is lowered
333/// to the launcher's explicit env (literals plus parent-resolved secret leases); the
334/// child env EQUALS the admitted table EXACTLY, witnessed by the host reading the
335/// child's `proc` environ AND the workload's own self-report (dual channel, no ambient
336/// leak), with the fail-closed branches (an unresolvable lease means the target never
337/// runs; an invalid policy means admission refuses) proven by the same oracle.
338///
339/// `InheritedFdsNone` (fd-scrub) — the admitted `FdPolicy::None` drives the launcher's
340/// child-side fd-scrub; the child's open fds (read HOST-SIDE from `/proc/<pid>/fd`)
341/// contain ONLY the declared allowlist, a parent-opened non-CLOEXEC sentinel fd is ABSENT
342/// (scrubbed), with the fail-closed branches (an undeclared fd is scrubbed before the
343/// workload; an unrealized fd policy ⇒ the target never runs) proven by the oracle.
344///
345/// `NetworkDenyAll` (empty netns, S9 / D3) — the admitted `NetPolicy::DenyAll` engages a
346/// NEW, EMPTY network namespace (`CLONE_NEWNET`, alongside the S8 userns rendezvous it
347/// requires); the netns has NO external interface, witnessed HOST-SIDE (the host reads the
348/// CHILD's `/proc/<pid>/net/dev` and sees ONLY `lo`) AND by the workload's own self-report
349/// (it cannot reach the network), with the launcher's own control channel still working
350/// through the netns (HostControl carve-out — fd-passed sockets are unaffected). Fail-closed
351/// branches: a kernel without unprivileged userns+netns ⇒ the cell SKIPs LOUD (floor not
352/// met), and an unrealized `AllowList` ⇒ the target never runs. `NetworkAllowList` STAYS
353/// `FailClosed` (no broker in v1).
354///
355/// NON-PROVEN cells are stated explicitly (the coupling test asserts they are NOT
356/// advertised `Enforced` in production): `InheritedFdsOnly` is `Incomplete` (the scrub
357/// realizes only `None`; the selective-keep allowlist has no lowering + no oracle); every
358/// other capability — including `NetworkAllowList` (no broker in v1) and all THREE
359/// `ChildSpawn` child-task keys (proof-spine §2 split + the S6 3-variant freeze) — is
360/// `FailClosed`.
361pub const LINUX_QUALIFICATION_LEDGER: &[QualificationRow] = &[
362    QualificationRow {
363        backend: "linux",
364        key: RequirementKind::Filesystem,
365        // Landlock ABI v1 already enforces path-beneath read/write/execute — the
366        // backend's floor. The proof transfers to any higher ABI.
367        profile_floor: ProfileFloor {
368            landlock_abi_min: Some(1),
369            requires_cgroup_kill: false,
370            requires_pids_peak: false,
371            requires_unprivileged_userns: false,
372            requires_seccomp_filter: false,
373        },
374        mechanism: "linux:landlock:Enforced",
375        status: QualificationStatus::Proven,
376        // Guarantee-holds: independent on-disk G-grid denial oracle. Fail-closed:
377        // below the ABI floor the cell drops from the ceiling (the coupling test
378        // `below_floor_profile_drops_filesystem_from_the_ceiling` proves it).
379        proof_receipts: &[
380            "crates/bvisor/tests/grid_linux_fs.rs::g1_landlock_denies_secret_read_outside_declared_root",
381        ],
382    },
383    QualificationRow {
384        backend: "linux",
385        key: RequirementKind::LaunchWorkload,
386        profile_floor: ProfileFloor::structural(),
387        mechanism: "linux:process_spawn:Enforced",
388        status: QualificationStatus::Proven,
389        // §4 BOTH branches: guarantee-holds (a workload launches + runs to a verdict)
390        // AND fail-closed (a setup problem REFUSES before any child — the target
391        // never runs).
392        proof_receipts: &[
393            "crates/bvisor/tests/launcher_capture_linux.rs::launcher_captures_workload_streams_cleanly_and_deterministically",
394            "crates/bvisor/tests/launcher_skeleton_linux.rs::missing_primitive_refuses_before_any_child",
395        ],
396    },
397    QualificationRow {
398        backend: "linux",
399        key: RequirementKind::CaptureStreams,
400        profile_floor: ProfileFloor::structural(),
401        mechanism: "linux:pipe_capture:Enforced",
402        status: QualificationStatus::Proven,
403        proof_receipts: &[
404            "crates/bvisor/tests/launcher_capture_linux.rs::launcher_captures_workload_streams_cleanly_and_deterministically",
405        ],
406    },
407    QualificationRow {
408        backend: "linux",
409        key: RequirementKind::Kill,
410        // Kill rides cgroup v2 atomic `cgroup.kill`; no landlock minimum.
411        profile_floor: ProfileFloor {
412            landlock_abi_min: None,
413            requires_cgroup_kill: true,
414            requires_pids_peak: false,
415            requires_unprivileged_userns: false,
416            requires_seccomp_filter: false,
417        },
418        mechanism: "linux:cgroup_kill:Enforced",
419        status: QualificationStatus::Proven,
420        // §4: host-state oracle (kernel pids.events) + workload exit (dual channel)
421        // AND teardown observed (cgroup.kill drains the tree to empty).
422        proof_receipts: &[
423            "crates/bvisor/tests/cgroup_enforcement_linux.rs::pids_max_genuinely_denies_forks_past_the_cap_or_explicit_skip",
424        ],
425    },
426    // Non-proven cells, status stated explicitly. These keys are absent from the
427    // production ceiling, so the coupling test never demands a Proven row for them;
428    // they are listed so the ledger states the qualification status explicitly. The
429    // mechanism string for an unimplemented cell mirrors `backend_impl::mechanism`'s
430    // `"none/unimplemented-this-chunk"` primitive (Unsupported in the ceiling).
431    QualificationRow {
432        backend: "linux",
433        key: RequirementKind::Environment,
434        // Explicit-envp lowering is structural (no kernel-version floor): the launcher
435        // serves the admitted env to fexecve on any Linux. The proof transfers to every
436        // machine of the platform.
437        profile_floor: ProfileFloor::structural(),
438        mechanism: "linux:explicit_env:Enforced",
439        status: QualificationStatus::Proven,
440        // §4 BOTH branches, dual-channel:
441        //  - guarantee-holds: the child's env EQUALS the admitted table EXACTLY,
442        //    witnessed by the HOST reading /proc/<child_pid>/environ (kernel state,
443        //    independent) AND the workload's own reported env; a parent sentinel var is
444        //    ABSENT in the child (no ambient leak); a SecretLease resolves IN THE CHILD
445        //    while the serialized plan+report carry only the REF, never the value;
446        //  - fail-closed: an unresolvable lease ⇒ the target NEVER runs (no child
447        //    output), and a contract-invalid policy ⇒ admission refuses before execution.
448        proof_receipts: &[
449            "crates/bvisor/tests/env_exact_linux.rs::child_env_equals_the_admitted_table_with_no_ambient_leak",
450            "crates/bvisor/tests/env_exact_linux.rs::an_unresolvable_lease_fails_closed_and_the_target_never_runs",
451            "crates/bvisor/tests/env_exact_linux.rs::a_contract_invalid_policy_is_refused_before_execution",
452            // The full execute()/BoundaryRunner contract-path witness (vs the launcher-
453            // direct /proc oracle above): the durable plan+report carry only the lease ref.
454            "crates/bvisor/tests/env_exact_linux.rs::a_secret_lease_resolves_but_the_durable_plan_and_report_carry_only_the_ref",
455        ],
456    },
457    QualificationRow {
458        backend: "linux",
459        key: RequirementKind::InheritedFdsNone,
460        // The fd-scrub is structural (no kernel-version floor): the launcher reads
461        // /proc/self/fd + raw SYS_close on any Linux. The proof transfers to every
462        // machine of the platform.
463        profile_floor: ProfileFloor::structural(),
464        mechanism: "linux:fd_scrub:Enforced",
465        status: QualificationStatus::Proven,
466        // §4 BOTH branches, dual-channel:
467        //  - guarantee-holds: the child's open fds (read HOST-SIDE from
468        //    /proc/<child_pid>/fd — kernel state, independent) contain ONLY the declared
469        //    allowlist (stdio); a parent-opened non-CLOEXEC SENTINEL fd is ABSENT from the
470        //    child (scrubbed), witnessed both host-side AND by the workload's own attempt
471        //    to write to it failing (no leak across the boundary);
472        //  - fail-closed: an undeclared inherited fd is scrubbed BEFORE the workload (the
473        //    launcher mechanism proof), AND a contract-level setup failure ⇒ the target
474        //    NEVER runs (the full execute()/BoundaryRunner path refuses an unrealized fd
475        //    policy). The execute()-path witness proves the lowering rides the production
476        //    contract, not only a run_launcher-direct plan.
477        proof_receipts: &[
478            "crates/bvisor/tests/inherited_fds_none_linux.rs::child_inherits_only_the_declared_fds_no_sentinel_leak",
479            "crates/bvisor/tests/launcher_inherited_fds_linux.rs::undeclared_inherited_fd_is_scrubbed_before_the_workload",
480            "crates/bvisor/tests/inherited_fds_none_linux.rs::an_unrealized_fd_policy_fails_closed_and_the_target_never_runs",
481            // The full execute()/BoundaryRunner contract-path witness (vs the launcher-
482            // direct /proc oracle above): a None-policy spec runs to a clean verdict.
483            "crates/bvisor/tests/inherited_fds_none_linux.rs::a_none_policy_spec_runs_through_the_execute_path",
484        ],
485    },
486    QualificationRow {
487        backend: "linux",
488        key: RequirementKind::InheritedFdsOnly,
489        profile_floor: ProfileFloor::structural(),
490        mechanism: "linux:none/unimplemented-this-chunk:Unsupported",
491        status: QualificationStatus::Incomplete,
492        proof_receipts: &[],
493    },
494    QualificationRow {
495        backend: "linux",
496        key: RequirementKind::NetworkDenyAll,
497        // The empty-netns floor (S9 / D3): requires UNPRIVILEGED user + network namespace
498        // creation (the S8 rendezvous makes the child root-in-userns; CLONE_NEWNET then
499        // births it into an empty netns). No landlock/cgroup minimum — the empty netns is
500        // structural above that floor, so the proof transfers to any kernel that permits
501        // unprivileged userns. Below the floor the cell drops from the ceiling (fail-closed).
502        profile_floor: ProfileFloor::unprivileged_userns_netns(),
503        mechanism: "linux:empty_netns:Enforced",
504        status: QualificationStatus::Proven,
505        // §4 BOTH branches, DUAL channel (host-side kernel-state strongest, per §4):
506        //  - guarantee-holds (A) HOST-SIDE: the host reads the CHILD's netns interface list
507        //    from /proc/<child_pid>/net/dev and asserts it contains ONLY `lo` — NO external
508        //    interface (the independent "zero external interfaces" witness, kernel state the
509        //    launcher cannot forge); (B) WORKLOAD self-report: the workload enumerates its
510        //    own interfaces + routing table and OBSERVES it has ZERO routes (only `lo`, which
511        //    has no address ⇒ no reachable destination). NO-LEAK: no inherited routable socket
512        //    survives (the S5 scrub).
513        //    HOSTCONTROL: the launcher's own control channel still works through the netns
514        //    (the workload runs to a verdict);
515        //  - fail-closed: a kernel without unprivileged userns+netns ⇒ the cell SKIPs LOUD
516        //    (never a silent pass), and a contract-level setup failure ⇒ the target never runs
517        //    (the empty netns engages on the full execute()/BoundaryRunner path).
518        proof_receipts: &[
519            "crates/bvisor/tests/network_deny_all_linux.rs::host_sees_only_loopback_in_the_child_netns_no_external_interface_or_skip",
520            "crates/bvisor/tests/network_deny_all_linux.rs::workload_cannot_reach_the_network_from_the_empty_netns_or_skip",
521            "crates/bvisor/tests/network_deny_all_linux.rs::a_deny_all_spec_runs_through_the_execute_path_or_skip",
522            "crates/bvisor/tests/network_deny_all_linux.rs::network_allow_list_fails_closed_at_admission_the_target_never_runs",
523        ],
524    },
525    QualificationRow {
526        backend: "linux",
527        key: RequirementKind::NetworkAllowList,
528        profile_floor: ProfileFloor::structural(),
529        mechanism: "linux:none/unimplemented-this-chunk:Unsupported",
530        status: QualificationStatus::FailClosed,
531        proof_receipts: &[],
532    },
533    // The three FROZEN child-task semantics (proof-spine S6). S6 froze the taxonomy +
534    // the clone3-pointer/classic-BPF enforcement CONSTRAINT (see `SpawnPolicy`); S10
535    // realizes two of the three and keeps the unenforceable one FailClosed:
536    //
537    // - DenyNewTasks → PROVEN via a seccomp DENYLIST (deny clone/clone3/fork/vfork at the
538    //   syscall-NUMBER level — no clone3 arg-deref). Floor = seccomp filter support.
539    // - AllowDescendants → PROVEN via the CGROUP boundary (the descendant inherits the run
540    //   cgroup ⇒ killable via cgroup.kill, counted by pids.max, namespace-trapped — NOT
541    //   seccomp). Floor = cgroup.kill.
542    // - AllowThreads → STAYS FailClosed: the clone3-pointer/classic-BPF problem (S6) means
543    //   seccomp cannot permit-threads-but-deny-processes precisely, and denying clone3
544    //   outright breaks glibc threads. The OPEN enforcement problem — NOT faked, absent
545    //   from the ceiling (Unsupported), so the coupling gate never demands a Proven row.
546    QualificationRow {
547        backend: "linux",
548        key: RequirementKind::ChildSpawnDenyNewTasks,
549        // The seccomp-filter floor (S10): requires CONFIG_SECCOMP_FILTER. No landlock /
550        // cgroup / userns minimum — the task-creation denylist is structural above that
551        // floor (it holds on any kernel with seccomp filter mode), so the proof transfers
552        // upward. Below the floor the cell drops from the ceiling (fail-closed).
553        profile_floor: ProfileFloor::seccomp_filter(),
554        mechanism: "linux:seccomp_deny_tasks:Enforced",
555        status: QualificationStatus::Proven,
556        // §4 BOTH branches, DUAL channel (host-side kernel-state strongest, per §4):
557        //  - guarantee-holds (A) HOST-SIDE: the host reads the CHILD's /proc/<pid>/status
558        //    and observes `Seccomp: 2` (filter mode installed — the kernel-state witness the
559        //    launcher cannot forge); (B) WORKLOAD self-report: the workload attempts to spawn
560        //    a child and OBSERVES the fork/spawn is REFUSED (EPERM). NO-LEAK: the filter is
561        //    installed LAST, after landlock, before fexecve;
562        //  - fail-closed: a kernel without seccomp filter support ⇒ the cell SKIPs LOUD
563        //    (never a silent pass), and a contract-level setup failure ⇒ the target never
564        //    runs (the seccomp denylist engages on the full execute()/BoundaryRunner path).
565        proof_receipts: &[
566            "crates/bvisor/tests/child_spawn_linux.rs::deny_new_tasks_fork_is_refused_and_host_sees_seccomp_filter_or_skip",
567            "crates/bvisor/tests/child_spawn_linux.rs::a_deny_new_tasks_spec_runs_through_the_execute_path_or_skip",
568            "crates/bvisor/tests/child_spawn_linux.rs::allow_threads_fails_closed_at_admission_the_target_never_runs",
569        ],
570    },
571    QualificationRow {
572        backend: "linux",
573        key: RequirementKind::ChildSpawnAllowThreads,
574        // STAYS FailClosed: the open clone3-pointer/classic-BPF problem (S6). NOT faked.
575        profile_floor: ProfileFloor::structural(),
576        mechanism: "linux:none/unimplemented-this-chunk:Unsupported",
577        status: QualificationStatus::FailClosed,
578        proof_receipts: &[],
579    },
580    QualificationRow {
581        backend: "linux",
582        key: RequirementKind::ChildSpawnAllowDescendants,
583        // The cgroup-boundary floor (S10): requires cgroup.kill (the descendant inherits the
584        // run cgroup ⇒ killable + counted + namespace-trapped — NOT seccomp). Below the
585        // floor (no cgroup) the cell drops from the ceiling (fail-closed).
586        profile_floor: ProfileFloor::cgroup_descendant_boundary(),
587        mechanism: "linux:cgroup_descendant_boundary:Enforced",
588        status: QualificationStatus::Proven,
589        // §4 BOTH branches:
590        //  - guarantee-holds: the workload (placed in the run cgroup via CLONE_INTO_CGROUP — the
591        //    SAME placement the S1 Kill cell proves) spawns a DESCENDANT that OUTLIVES it; the
592        //    descendant is in the leaf by the kernel fork-membership guarantee (it cannot leave
593        //    its cgroup without a privileged cgroup.procs write it lacks), and cgroup.kill reaps
594        //    the WHOLE leaf to empty (the S1 drain-to-empty witness — the OBSERVED branch). A
595        //    direct per-descendant cgroup.procs membership read is a tracked §4-quality
596        //    strengthening; today the descendant claim rests on the fork-inheritance guarantee +
597        //    the whole-tree drain, not a direct membership observation.
598        //  - fail-closed: no cgroup base ⇒ the cell SKIPs LOUD (absent from the ceiling), and
599        //    a contract-level setup failure ⇒ the target never runs.
600        proof_receipts: &[
601            "crates/bvisor/tests/child_spawn_linux.rs::allow_descendants_is_cgroup_confined_and_cgroup_kill_drains_the_tree_or_skip",
602            "crates/bvisor/tests/child_spawn_linux.rs::an_allow_descendants_spec_runs_through_the_execute_path_or_skip",
603        ],
604    },
605];
606
607/// Look up the committed Linux ledger row for a requirement key (the lookup the
608/// coupling gate uses). `None` ⇒ the key is not in the ledger at all (which, for an
609/// `Enforced` production cell, is itself a coupling violation).
610#[must_use]
611pub fn linux_ledger_row(key: RequirementKind) -> Option<&'static QualificationRow> {
612    LINUX_QUALIFICATION_LEDGER.iter().find(|r| r.key == key)
613}
614
615/// The committed Wasm qualification ledger. Every production `Enforced` Wasm
616/// ceiling cell has a `Proven` row backed by the live grid oracle; structurally
617/// unsupported native cells are stated explicitly and stay absent from the ceiling.
618pub const WASM_QUALIFICATION_LEDGER: &[QualificationRow] = &[
619    QualificationRow {
620        backend: "wasm",
621        key: RequirementKind::Filesystem,
622        profile_floor: ProfileFloor::structural(),
623        mechanism: "wasm:wasi_preopen:Enforced",
624        status: QualificationStatus::Proven,
625        proof_receipts: &[
626            "crates/bvisor/tests/grid_wasm.rs::g1_wasi_preopen_denies_secret_read_outside_declared_root",
627            "crates/bvisor/tests/grid_wasm.rs::g3_wasi_preopen_denies_write_outside_quarantine",
628        ],
629    },
630    QualificationRow {
631        backend: "wasm",
632        key: RequirementKind::LaunchWorkload,
633        profile_floor: ProfileFloor::structural(),
634        mechanism: "wasm:wasi_instantiate:Enforced",
635        status: QualificationStatus::Proven,
636        proof_receipts: &[
637            "crates/bvisor/tests/grid_wasm.rs::wasm_profile_admits_declared_roots_spec",
638            "crates/bvisor/tests/grid_wasm.rs::g7_wasi_preopen_commit_allows_in_root_output",
639        ],
640    },
641    QualificationRow {
642        backend: "wasm",
643        key: RequirementKind::CaptureStreams,
644        profile_floor: ProfileFloor::structural(),
645        mechanism: "wasm:wasi_stdio:Enforced",
646        status: QualificationStatus::Proven,
647        proof_receipts: &[
648            "crates/bvisor/tests/grid_wasm.rs::wasi_capture_streams_records_guest_output",
649        ],
650    },
651    QualificationRow {
652        backend: "wasm",
653        key: RequirementKind::Environment,
654        profile_floor: ProfileFloor::structural(),
655        mechanism: "wasm:wasi_env:Enforced",
656        status: QualificationStatus::Proven,
657        proof_receipts: &[
658            "crates/bvisor/tests/grid_wasm.rs::wasi_env_exact_is_delivered_to_guest",
659        ],
660    },
661    QualificationRow {
662        backend: "wasm",
663        key: RequirementKind::TempRoot,
664        profile_floor: ProfileFloor::structural(),
665        mechanism: "wasm:wasi_preopen_tmp:Enforced",
666        status: QualificationStatus::Proven,
667        proof_receipts: &[
668            "crates/bvisor/tests/grid_wasm.rs::wasi_temp_root_is_private_and_writable",
669        ],
670    },
671    QualificationRow {
672        backend: "wasm",
673        key: RequirementKind::CommitArtifact,
674        profile_floor: ProfileFloor::structural(),
675        mechanism: "wasm:preopen_commit:Enforced",
676        status: QualificationStatus::Proven,
677        proof_receipts: &[
678            "crates/bvisor/tests/grid_wasm.rs::g7_wasi_preopen_commit_allows_in_root_output",
679        ],
680    },
681    QualificationRow {
682        backend: "wasm",
683        key: RequirementKind::DiscardArtifact,
684        profile_floor: ProfileFloor::structural(),
685        mechanism: "wasm:preopen_commit:Enforced",
686        status: QualificationStatus::Proven,
687        proof_receipts: &[
688            "crates/bvisor/tests/grid_wasm.rs::wasi_temp_root_is_private_and_writable",
689        ],
690    },
691    QualificationRow {
692        backend: "wasm",
693        key: RequirementKind::ListOutputs,
694        profile_floor: ProfileFloor::structural(),
695        mechanism: "wasm:preopen_readdir:Enforced",
696        status: QualificationStatus::Proven,
697        proof_receipts: &[
698            "crates/bvisor/tests/grid_wasm.rs::g7_wasi_preopen_commit_allows_in_root_output",
699        ],
700    },
701    QualificationRow {
702        backend: "wasm",
703        key: RequirementKind::NetworkDenyAll,
704        profile_floor: ProfileFloor::structural(),
705        mechanism: "wasm:no_socket_cap:Enforced",
706        status: QualificationStatus::Proven,
707        proof_receipts: &[
708            "crates/bvisor/tests/grid_wasm.rs::wasi_socket_capability_is_absent",
709        ],
710    },
711    QualificationRow {
712        backend: "wasm",
713        key: RequirementKind::InheritedFdsNone,
714        profile_floor: ProfileFloor::structural(),
715        mechanism: "wasm:wasi_stdio:Enforced",
716        status: QualificationStatus::Proven,
717        proof_receipts: &[
718            "crates/bvisor/tests/grid_wasm.rs::wasi_no_inherited_fds_contract_runs_without_raw_fd_authority",
719        ],
720    },
721    QualificationRow {
722        backend: "wasm",
723        key: RequirementKind::InheritedFdsOnly,
724        profile_floor: ProfileFloor::structural(),
725        mechanism: "wasm:none/structurally-unsupported:Unsupported",
726        status: QualificationStatus::FailClosed,
727        proof_receipts: &[],
728    },
729    QualificationRow {
730        backend: "wasm",
731        key: RequirementKind::ChildSpawnDenyNewTasks,
732        profile_floor: ProfileFloor::structural(),
733        mechanism: "wasm:none/structurally-unsupported:Unsupported",
734        status: QualificationStatus::FailClosed,
735        proof_receipts: &[],
736    },
737    QualificationRow {
738        backend: "wasm",
739        key: RequirementKind::ChildSpawnAllowThreads,
740        profile_floor: ProfileFloor::structural(),
741        mechanism: "wasm:none/structurally-unsupported:Unsupported",
742        status: QualificationStatus::FailClosed,
743        proof_receipts: &[],
744    },
745    QualificationRow {
746        backend: "wasm",
747        key: RequirementKind::ChildSpawnAllowDescendants,
748        profile_floor: ProfileFloor::structural(),
749        mechanism: "wasm:none/structurally-unsupported:Unsupported",
750        status: QualificationStatus::FailClosed,
751        proof_receipts: &[],
752    },
753    QualificationRow {
754        backend: "wasm",
755        key: RequirementKind::Kill,
756        profile_floor: ProfileFloor::structural(),
757        mechanism: "wasm:none/structurally-unsupported:Unsupported",
758        status: QualificationStatus::FailClosed,
759        proof_receipts: &[],
760    },
761    QualificationRow {
762        backend: "wasm",
763        key: RequirementKind::ExposePath,
764        profile_floor: ProfileFloor::structural(),
765        mechanism: "wasm:none/structurally-unsupported:Unsupported",
766        status: QualificationStatus::FailClosed,
767        proof_receipts: &[],
768    },
769    QualificationRow {
770        backend: "wasm",
771        key: RequirementKind::NetworkAllowList,
772        profile_floor: ProfileFloor::structural(),
773        mechanism: "wasm:none/structurally-unsupported:Unsupported",
774        status: QualificationStatus::FailClosed,
775        proof_receipts: &[],
776    },
777];
778
779/// Look up the committed Wasm ledger row for a requirement key.
780#[must_use]
781pub fn wasm_ledger_row(key: RequirementKind) -> Option<&'static QualificationRow> {
782    WASM_QUALIFICATION_LEDGER.iter().find(|r| r.key == key)
783}
784
785#[cfg(test)]
786#[path = "qualification_tests.rs"]
787mod tests;