Skip to content

Commit 320256b

Browse files
hyperpolymathclaude
andcommitted
feat(schema/adjust): schema_version on 4 reports + adjust-check recipe
- Add `schema_version: String` with serde(default) to CrashReport, AxialReport, AmuckReport, AbductReport — closes the 4-report gap identified in the schema-versioning audit - Update every construction site (executor, engine, ambush, panll, a2ml, adjudicate, amuck, axial, abduct) - Add 6 new seam_contract_tests entries: schema_version present/default for each of the 4 new report types; all 16 seam tests green - Add ADJUST contractile recipe to contractile.just: verifies PA006 production/test thresholds and suppression-rule count match Adjustfile.a2ml at every `just` run - Update Adjustfile.a2ml active-rules 13 → 17 (4 rules added since last sync); fix stale comment in kanren/core.rs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 98716b1 commit 320256b

14 files changed

Lines changed: 191 additions & 3 deletions

File tree

.machine_readable/contractiles/adjust/Adjustfile.a2ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test-panic-threshold = 20
2727
# Target false-positive rate: ≤2-3% (from ~8% without suppression)
2828
# Add new rules to src/kanren/core.rs load_suppression_rules() when a
2929
# class of persistent false positives is confirmed across ≥3 distinct repos.
30-
active-rules = 13
30+
active-rules = 17
3131
target-fp-rate = 0.03
3232

3333
[framework-detection]

contractile.just

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,60 @@ trust-container-images-pinned:
7373
test ! -f Containerfile || grep -q '@sha256:' Containerfile
7474

7575

76+
# === ADJUST (Calibration & Threshold Drift Detection) ===
77+
# Source: .machine_readable/contractiles/adjust/Adjustfile.a2ml
78+
79+
# Verify detector calibration thresholds declared in Adjustfile match source
80+
adjust-check:
81+
#!/usr/bin/env bash
82+
set -euo pipefail
83+
af=.machine_readable/contractiles/adjust/Adjustfile.a2ml
84+
if [ ! -f "$af" ]; then echo "! Adjustfile missing — skipping"; exit 0; fi
85+
echo "→ adjust-check — verifying calibration thresholds match Adjustfile"
86+
drift=0
87+
declared_prod=$(grep 'production-unwrap-threshold' "$af" | grep -oE '[0-9]+$' || true)
88+
if [ -n "$declared_prod" ]; then
89+
code_val=$(grep -oE 'if stats\.unwrap_calls > [0-9]+' src/assail/analyzer.rs | head -1 | grep -oE '[0-9]+$' || true)
90+
if [ "$code_val" = "$declared_prod" ]; then
91+
echo " ✓ production-unwrap-threshold = $declared_prod"
92+
else
93+
echo " ⚠ ADJUST drift: production-unwrap-threshold Adjustfile=$declared_prod code=$code_val"
94+
drift=$((drift+1))
95+
fi
96+
fi
97+
declared_test=$(grep 'test-unwrap-threshold' "$af" | grep -oE '[0-9]+$' || true)
98+
if [ -n "$declared_test" ]; then
99+
code_val=$(grep -oE 'unwrap_threshold = [0-9]+' src/assail/analyzer.rs | head -1 | grep -oE '[0-9]+$' || true)
100+
if [ "$code_val" = "$declared_test" ]; then
101+
echo " ✓ test-unwrap-threshold = $declared_test"
102+
else
103+
echo " ⚠ ADJUST drift: test-unwrap-threshold Adjustfile=$declared_test code=$code_val"
104+
drift=$((drift+1))
105+
fi
106+
fi
107+
declared_panic=$(grep 'test-panic-threshold' "$af" | grep -oE '[0-9]+$' || true)
108+
if [ -n "$declared_panic" ]; then
109+
code_val=$(grep -oE 'panic_threshold = [0-9]+' src/assail/analyzer.rs | head -1 | grep -oE '[0-9]+$' || true)
110+
if [ "$code_val" = "$declared_panic" ]; then
111+
echo " ✓ test-panic-threshold = $declared_panic"
112+
else
113+
echo " ⚠ ADJUST drift: test-panic-threshold Adjustfile=$declared_panic code=$code_val"
114+
drift=$((drift+1))
115+
fi
116+
fi
117+
declared_rules=$(grep 'active-rules' "$af" | grep -oE '[0-9]+$' || true)
118+
if [ -n "$declared_rules" ]; then
119+
code_count=$(grep -c '// Rule [0-9]' src/kanren/core.rs || echo "0")
120+
if [ "$code_count" = "$declared_rules" ]; then
121+
echo " ✓ active suppression rules = $declared_rules"
122+
else
123+
echo " ⚠ ADJUST drift: active-rules Adjustfile=$declared_rules code=$code_count (advisory — update Adjustfile when rules change)"
124+
drift=$((drift+1))
125+
fi
126+
fi
127+
if [ $drift -gt 0 ]; then
128+
echo " → $drift threshold(s) drifted (advisory — reconcile Adjustfile.a2ml with source)"
129+
else
130+
echo " → all calibration thresholds match"
131+
fi
132+

src/a2ml/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,7 @@ mod tests {
884884
duration: Duration::from_secs(1),
885885
peak_memory: 1024,
886886
crashes: vec![CrashReport {
887+
schema_version: "2.5".to_string(),
887888
timestamp: "2026-01-01T00:00:00Z".to_string(),
888889
signal: Some("SIGABRT".to_string()),
889890
backtrace: None,
@@ -935,6 +936,7 @@ mod tests {
935936

936937
fn sample_amuck_report() -> amuck::AmuckReport {
937938
amuck::AmuckReport {
939+
schema_version: "2.5".to_string(),
938940
created_at: chrono::Utc::now().to_rfc3339(),
939941
target: PathBuf::from("src/main.rs"),
940942
source_spec: None,
@@ -964,6 +966,7 @@ mod tests {
964966

965967
fn sample_abduct_report() -> abduct::AbductReport {
966968
abduct::AbductReport {
969+
schema_version: "2.5".to_string(),
967970
created_at: chrono::Utc::now().to_rfc3339(),
968971
target: PathBuf::from("src/main.rs"),
969972
source_root: PathBuf::from("src"),
@@ -1038,6 +1041,7 @@ mod tests {
10381041
let mut signal_counts = BTreeMap::new();
10391042
signal_counts.insert("panic_signal".to_string(), 1);
10401043
axial::AxialReport {
1044+
schema_version: "2.5".to_string(),
10411045
created_at: chrono::Utc::now().to_rfc3339(),
10421046
target: PathBuf::from("src/main.rs"),
10431047
executed_program: Some("panic-attack".to_string()),

src/abduct/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,14 @@ pub struct AbductConfig {
4848
pub exec_timeout_secs: u64,
4949
}
5050

51+
fn abduct_schema_version() -> String {
52+
"2.5".to_string()
53+
}
54+
5155
#[derive(Debug, Clone, Serialize, Deserialize)]
5256
pub struct AbductReport {
57+
#[serde(default = "abduct_schema_version")]
58+
pub schema_version: String,
5359
pub created_at: String,
5460
pub target: PathBuf,
5561
pub source_root: PathBuf,
@@ -221,6 +227,7 @@ pub fn run(config: AbductConfig) -> Result<AbductReport> {
221227
}
222228

223229
Ok(AbductReport {
230+
schema_version: "2.5".to_string(),
224231
created_at: chrono::Utc::now().to_rfc3339(),
225232
target,
226233
source_root,

src/adjudicate/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ mod tests {
356356
let dir = TempDir::new().expect("tempdir should create");
357357
let report_path = dir.path().join("amuck.json");
358358
let amuck = AmuckReport {
359+
schema_version: "2.5".to_string(),
359360
created_at: chrono::Utc::now().to_rfc3339(),
360361
target: PathBuf::from("src/main.rs"),
361362
source_spec: None,

src/ambush/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ fn run_program_with_deadline(
443443

444444
fn crash_from_output(output: &Output) -> CrashReport {
445445
CrashReport {
446+
schema_version: "2.5".to_string(),
446447
timestamp: chrono::Utc::now().to_rfc3339(),
447448
signal: extract_signal(&output.stderr),
448449
backtrace: extract_backtrace(&output.stderr),

src/amuck/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,14 @@ pub struct MutationSpecFile {
6868
pub combos: Vec<MutationComboSpec>,
6969
}
7070

71+
fn amuck_schema_version() -> String {
72+
"2.5".to_string()
73+
}
74+
7175
#[derive(Debug, Clone, Serialize, Deserialize)]
7276
pub struct AmuckReport {
77+
#[serde(default = "amuck_schema_version")]
78+
pub schema_version: String,
7379
pub created_at: String,
7480
pub target: PathBuf,
7581
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -219,6 +225,7 @@ pub fn run(config: AmuckConfig) -> Result<AmuckReport> {
219225

220226
let combinations_run = outcomes.iter().filter(|o| o.mutated_file.is_some()).count();
221227
let report = AmuckReport {
228+
schema_version: "2.5".to_string(),
222229
created_at: chrono::Utc::now().to_rfc3339(),
223230
target: config.target,
224231
source_spec: config.spec_path,

src/attack/executor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ impl AttackExecutor {
290290

291291
fn crash_from_output(output: &Output) -> CrashReport {
292292
CrashReport {
293+
schema_version: "2.5".to_string(),
293294
timestamp: chrono::Utc::now().to_rfc3339(),
294295
signal: Self::extract_signal(&output.stderr),
295296
backtrace: Self::extract_backtrace(&output.stderr),

src/axial/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,14 @@ struct RunOnceConfig<'a> {
5252
aspell_lang: &'a str,
5353
}
5454

55+
fn axial_schema_version() -> String {
56+
"2.5".to_string()
57+
}
58+
5559
#[derive(Debug, Clone, Serialize, Deserialize)]
5660
pub struct AxialReport {
61+
#[serde(default = "axial_schema_version")]
62+
pub schema_version: String,
5763
pub created_at: String,
5864
pub target: PathBuf,
5965
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -250,6 +256,7 @@ pub fn run(config: AxialConfig) -> Result<AxialReport> {
250256
};
251257

252258
Ok(AxialReport {
259+
schema_version: "2.5".to_string(),
253260
created_at: chrono::Utc::now().to_rfc3339(),
254261
target: config.target,
255262
executed_program: config.execute.as_ref().map(|e| e.program.clone()),
@@ -943,6 +950,7 @@ mod tests {
943950
fn markdown_writer_outputs_report() {
944951
let dir = TempDir::new().expect("tempdir should create");
945952
let report = AxialReport {
953+
schema_version: "2.5".to_string(),
946954
created_at: chrono::Utc::now().to_rfc3339(),
947955
target: PathBuf::from("src/main.rs"),
948956
executed_program: None,

src/kanren/core.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ impl LogicEngine {
528528
/// a weak point a false positive. When a suppression fact is derived,
529529
/// the corresponding weak point should be downgraded or removed.
530530
///
531-
/// 13 rules targeting ~6-8% FP reduction (from 8% to 2%):
531+
/// 17 rules targeting ~6-8% FP reduction (from 8% to 2%):
532532
///
533533
/// 1. Null-check guarding (malloc → if-null)
534534
/// 2. Error propagation boundary (unwrap in Result-returning fn)
@@ -924,7 +924,7 @@ impl LogicEngine {
924924
/// Scans file statistics and weak point descriptions for defensive patterns
925925
/// (null checks, mutex guards, schema validation, path canonicalization,
926926
/// timeout protection, enum/constant args, etc.) and asserts context facts
927-
/// into the FactDB. The 13 suppression rules loaded by `load_suppression_rules()`
927+
/// into the FactDB. The 17 suppression rules loaded by `load_suppression_rules()`
928928
/// depend on these context facts to fire.
929929
///
930930
/// **Phase 1 — file_statistics-based detection:**

0 commit comments

Comments
 (0)