Found while writing the falsifiability proofs for #254. Not fixed — this is a design question, and per the Milestone 2 brief a discovery is reported rather than self-authorised.
What the code does
crates/flowproof-replay/src/runrecord.rs:
pub fn is_regression(&self) -> bool {
!self.removed.is_empty() || self.changed.iter().any(|c| c.new == ControlVerdict::Fail)
}
So audit --since exits non-zero for a removed control, or one that turned fail. A control going pass → capability-error exits zero.
Why it might matter
capability-error means "this control could not be certified here". The agent-testing docs are emphatic that this must never read as a pass — assert_no_egress on a non-Linux host fails as a capability error specifically so it does not "pass vacuously".
But at the diff layer that distinction disappears. A control that was being enforced yesterday and cannot be certified today — a runner without seccomp, a driver that lost a capability, a suite moved to a host where containment is unavailable — passes audit --since clean. Under the evidence positioning, "we can no longer certify this" is arguably exactly what a regression gate exists to surface.
Why it might be intended
capability-error is not a failure of the system under test, and treating it as a regression would make the gate red on legitimate platform moves — running the same suite on macOS, for instance. That is a real cost and may be why it reads the way it does.
The decision
Three options, roughly:
- Leave it. Capability loss is an environment fact, not a control regression.
- Treat
pass → capability-error as a regression. Strictest, and consistent with "never pass vacuously".
- Report it as a distinct third category — neither silent nor build-failing — so a reviewer sees "controls no longer certifiable" without the gate going red on a platform move.
Whichever is chosen, the falsifiability suite should gain a proof pinning it, so the behaviour stops being implicit.
Context: #254, PR #262.
Found while writing the falsifiability proofs for #254. Not fixed — this is a design question, and per the Milestone 2 brief a discovery is reported rather than self-authorised.
What the code does
crates/flowproof-replay/src/runrecord.rs:So
audit --sinceexits non-zero for a removed control, or one that turnedfail. A control goingpass→capability-errorexits zero.Why it might matter
capability-errormeans "this control could not be certified here". The agent-testing docs are emphatic that this must never read as a pass —assert_no_egresson a non-Linux host fails as a capability error specifically so it does not "pass vacuously".But at the diff layer that distinction disappears. A control that was being enforced yesterday and cannot be certified today — a runner without seccomp, a driver that lost a capability, a suite moved to a host where containment is unavailable — passes
audit --sinceclean. Under the evidence positioning, "we can no longer certify this" is arguably exactly what a regression gate exists to surface.Why it might be intended
capability-erroris not a failure of the system under test, and treating it as a regression would make the gate red on legitimate platform moves — running the same suite on macOS, for instance. That is a real cost and may be why it reads the way it does.The decision
Three options, roughly:
pass→capability-erroras a regression. Strictest, and consistent with "never pass vacuously".Whichever is chosen, the falsifiability suite should gain a proof pinning it, so the behaviour stops being implicit.
Context: #254, PR #262.