1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// SHIP-TWO-001 — `apr-cli-qa-v1` algorithm-level PARTIAL discharge
// for FALSIFY-QA-002 AND FALSIFY-QA-006.
//
// Contract: `contracts/apr-cli-qa-v1.yaml`.
// Spec: `docs/specifications/aprender-train/ship-two-models-spec.md`
// (apr CLI QA gates).
//
// ## What FALSIFY-QA-002 says
//
// rule: missing model exits non-zero
// prediction: "apr inspect /nonexistent exits 1"
// if_fails: "missing file not detected"
//
// ## What FALSIFY-QA-006 says
//
// rule: exit code honesty
// prediction: "error output implies non-zero exit"
// if_fails: "exit-code lie"
//
// ## What this file proves NOW (`PARTIAL_ALGORITHM_LEVEL`)
//
// Decision rule for BOTH gates: given the `exit_code` of an
// invocation that was expected to fail (missing model OR error
// output present), Pass iff:
//
// exit_code != 0 AND exit_code is in canonical [-128, 255] range
//
// Single shared verdict because both gates have identical
// algorithm-level shape: pure exit-code-is-non-zero predicate.
// Differs from `pub_cli_002_004` which inverts the polarity
// (success-only Pass).
/// Maximum legal POSIX exit-code value.
pub const AC_QA_002_006_MAX_EXIT_CODE: i32 = 255;
/// Minimum legal POSIX exit-code value (signed via 128 + signal_no
/// or some test harnesses passing `-1`). Real POSIX is 0..=255 but
/// some CI tools normalize negative codes; we accept that band but
/// it must still be non-zero.
pub const AC_QA_002_006_MIN_EXIT_CODE: i32 = -128;
/// Binary verdict for `FALSIFY-QA-002` AND `FALSIFY-QA-006`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Qa002006Verdict {
/// `exit_code != 0` AND `exit_code` is in `[-128, 255]`.
Pass,
/// One or more of:
/// - `exit_code == 0` (the regression — "exit-code lie": apr
/// said success but should have failed).
/// - `exit_code < -128` (out of POSIX domain — exit-code
/// corruption).
/// - `exit_code > 255` (out of POSIX domain — panic-truncation
/// bug or signed-exit passthrough).
Fail,
}
/// Pure verdict function for `FALSIFY-QA-002` and `FALSIFY-QA-006`.
///
/// Inputs:
/// - `exit_code`: process exit code of an invocation that SHOULD
/// have failed:
/// * QA-002: `apr inspect /nonexistent/model.gguf`
/// * QA-006: `apr validate /nonexistent` (or any error-output
/// producing invocation)
///
/// Pass iff:
/// 1. `exit_code != 0` (non-zero per contract — error-exit honesty),
/// 2. `-128 <= exit_code <= 255` (POSIX domain).
///
/// Otherwise `Fail`.
///
/// # Examples
///
/// Missing-file error, exit 1 — `Pass`:
/// ```
/// use aprender::format::qa_002_006::{
/// verdict_from_error_exit_nonzero, Qa002006Verdict,
/// };
/// let v = verdict_from_error_exit_nonzero(1);
/// assert_eq!(v, Qa002006Verdict::Pass);
/// ```
///
/// Exit-code lie (apr printed error but exited 0) — `Fail`:
/// ```
/// use aprender::format::qa_002_006::{
/// verdict_from_error_exit_nonzero, Qa002006Verdict,
/// };
/// let v = verdict_from_error_exit_nonzero(0);
/// assert_eq!(v, Qa002006Verdict::Fail);
/// ```
#[must_use]
pub fn verdict_from_error_exit_nonzero(exit_code: i32) -> Qa002006Verdict {
if exit_code == 0 {
return Qa002006Verdict::Fail;
}
if !(AC_QA_002_006_MIN_EXIT_CODE..=AC_QA_002_006_MAX_EXIT_CODE).contains(&exit_code) {
return Qa002006Verdict::Fail;
}
Qa002006Verdict::Pass
}
#[cfg(test)]
mod tests {
use super::*;
// -------------------------------------------------------------------------
// Section 1: Provenance pin — POSIX exit-code range.
// -------------------------------------------------------------------------
#[test]
fn provenance_max_exit_code_is_255() {
assert_eq!(AC_QA_002_006_MAX_EXIT_CODE, 255);
}
#[test]
fn provenance_min_exit_code_is_neg_128() {
assert_eq!(AC_QA_002_006_MIN_EXIT_CODE, -128);
}
// -------------------------------------------------------------------------
// Section 2: Pass band — canonical error exits.
// -------------------------------------------------------------------------
#[test]
fn pass_exit_one_generic_error() {
// The contract example: `apr inspect /nonexistent` exits 1.
let v = verdict_from_error_exit_nonzero(1);
assert_eq!(v, Qa002006Verdict::Pass);
}
#[test]
fn pass_exit_two_clap_user_error() {
let v = verdict_from_error_exit_nonzero(2);
assert_eq!(v, Qa002006Verdict::Pass);
}
#[test]
fn pass_exit_101_panic() {
// Rust panic exit code.
let v = verdict_from_error_exit_nonzero(101);
assert_eq!(v, Qa002006Verdict::Pass);
}
#[test]
fn pass_exit_signal_terminated() {
// 128 + 9 = 137 SIGKILL (e.g., OOM); 128 + 15 = 143 SIGTERM.
let v = verdict_from_error_exit_nonzero(137);
assert_eq!(v, Qa002006Verdict::Pass);
}
#[test]
fn pass_exit_negative_one_signed_passthrough() {
// Some test harnesses pass through signed exit; -1 is in
// [-128, 255] domain.
let v = verdict_from_error_exit_nonzero(-1);
assert_eq!(v, Qa002006Verdict::Pass);
}
// -------------------------------------------------------------------------
// Section 3: Fail band — exit_code == 0 (the regression).
// -------------------------------------------------------------------------
#[test]
fn fail_exit_code_lie_zero() {
// The exact regression: apr printed an error but exited 0.
// Catches QA-006's "exit-code lie" and QA-002's "missing
// file not detected".
let v = verdict_from_error_exit_nonzero(0);
assert_eq!(
v,
Qa002006Verdict::Fail,
"exit==0 must Fail (exit-code lie)"
);
}
// -------------------------------------------------------------------------
// Section 4: Fail band — exit-code domain violations.
// -------------------------------------------------------------------------
#[test]
fn fail_exit_above_255() {
let v = verdict_from_error_exit_nonzero(256);
assert_eq!(v, Qa002006Verdict::Fail);
}
#[test]
fn fail_exit_below_neg_128() {
let v = verdict_from_error_exit_nonzero(-129);
assert_eq!(v, Qa002006Verdict::Fail);
}
#[test]
fn fail_exit_i32_max() {
let v = verdict_from_error_exit_nonzero(i32::MAX);
assert_eq!(v, Qa002006Verdict::Fail);
}
#[test]
fn fail_exit_i32_min() {
let v = verdict_from_error_exit_nonzero(i32::MIN);
assert_eq!(v, Qa002006Verdict::Fail);
}
// -------------------------------------------------------------------------
// Section 5: Boundary sweep — -128 to 256.
// -------------------------------------------------------------------------
#[test]
fn exit_code_sweep_around_zero() {
let probes: Vec<(i32, Qa002006Verdict)> = vec![
(-129, Qa002006Verdict::Fail), // out of domain
(-128, Qa002006Verdict::Pass), // domain min, non-zero
(-1, Qa002006Verdict::Pass),
(0, Qa002006Verdict::Fail), // exit-code lie
(1, Qa002006Verdict::Pass), // canonical error
(2, Qa002006Verdict::Pass),
(101, Qa002006Verdict::Pass),
(137, Qa002006Verdict::Pass),
(255, Qa002006Verdict::Pass), // domain max
(256, Qa002006Verdict::Fail), // out of domain
(1000, Qa002006Verdict::Fail),
];
for (exit, expected) in probes {
let v = verdict_from_error_exit_nonzero(exit);
assert_eq!(v, expected, "exit={exit} expected {expected:?}");
}
}
// -------------------------------------------------------------------------
// Section 6: Domain — Pass iff exit != 0 in canonical range.
// -------------------------------------------------------------------------
#[test]
fn pass_iff_nonzero_and_in_domain() {
for exit in [1_i32, 2, 100, 101, 137, 143, 255, -1, -128] {
let v = verdict_from_error_exit_nonzero(exit);
assert_eq!(v, Qa002006Verdict::Pass, "exit={exit}");
}
for exit in [0_i32, 256, -129, i32::MAX, i32::MIN] {
let v = verdict_from_error_exit_nonzero(exit);
assert_eq!(v, Qa002006Verdict::Fail, "exit={exit}");
}
}
// -------------------------------------------------------------------------
// Section 7: Realistic — both gates' canonical scenarios.
// -------------------------------------------------------------------------
#[test]
fn pass_qa_002_apr_inspect_nonexistent_file() {
// QA-002 contract example.
let v = verdict_from_error_exit_nonzero(1);
assert_eq!(v, Qa002006Verdict::Pass);
}
#[test]
fn pass_qa_006_apr_validate_nonexistent_file() {
// QA-006 contract example.
let v = verdict_from_error_exit_nonzero(1);
assert_eq!(v, Qa002006Verdict::Pass);
}
#[test]
fn fail_qa_002_silent_success_on_missing_file() {
// The regression class: apr inspect printed "error: file
// not found" but exit 0.
let v = verdict_from_error_exit_nonzero(0);
assert_eq!(v, Qa002006Verdict::Fail);
}
#[test]
fn fail_qa_006_exit_code_lie() {
// The regression class: apr validate emitted error message
// but returned 0 — the dishonesty.
let v = verdict_from_error_exit_nonzero(0);
assert_eq!(v, Qa002006Verdict::Fail);
}
#[test]
fn verdict_is_gate_agnostic() {
// Both gates share the verdict for every exit_code.
for exit in [0_i32, 1, 2, 101, 137, -1, 256, i32::MAX] {
let v_qa002 = verdict_from_error_exit_nonzero(exit);
let v_qa006 = verdict_from_error_exit_nonzero(exit);
assert_eq!(
v_qa002, v_qa006,
"verdict must be gate-agnostic for exit={exit}"
);
}
}
}