11//! Cheap source-only cheat screens (no GPU, no private eval assets).
2- //!
3- //! Run these **before** renting a Lium pod so a bad submission fails fast
4- //! instead of burning hours of GPU. Metrics/receipt consistency checks stay
5- //! post-eval (they need harness output).
62
7- use crate :: types:: CheatCode ;
3+ /// Kind of static source hit (maps to agentic `CheatCode` at the call site).
4+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
5+ pub enum SourceCheatKind {
6+ /// Hardcoded `METRICS_JSON=` short-circuit.
7+ EvalShortCircuit ,
8+ /// Missing Prism telemetry hooks in `training.py`.
9+ MissingTelemetryHooks ,
10+ }
811
912/// One static source finding.
1013#[ derive( Debug , Clone , PartialEq , Eq ) ]
11- pub struct StaticCheatHit {
12- /// Cheat taxonomy code .
13- pub code : CheatCode ,
14+ pub struct SourceCheatHit {
15+ /// Cheat kind .
16+ pub kind : SourceCheatKind ,
1417 /// Human-readable reason (safe to surface in `error_detail`).
1518 pub rationale : String ,
1619}
1720
1821/// Scan miner sources for cheap, deterministic cheat patterns.
19- ///
20- /// Order: hardcoded `METRICS_JSON=` short-circuit first, then missing Prism
21- /// telemetry hooks in `training.py`. Returns the first hit.
2222#[ must_use]
23- pub fn static_source_cheat ( architecture_py : & str , training_py : & str ) -> Option < StaticCheatHit > {
23+ pub fn static_source_cheat ( architecture_py : & str , training_py : & str ) -> Option < SourceCheatHit > {
2424 for ( path, src) in [
2525 ( "architecture.py" , architecture_py) ,
2626 ( "training.py" , training_py) ,
2727 ] {
2828 if src. contains ( "METRICS_JSON=" ) {
29- return Some ( StaticCheatHit {
30- code : CheatCode :: EvalShortCircuit ,
29+ return Some ( SourceCheatHit {
30+ kind : SourceCheatKind :: EvalShortCircuit ,
3131 rationale : format ! ( "static: hardcoded METRICS_JSON in {path}" ) ,
3232 } ) ;
3333 }
3434 }
3535 if !training_has_telemetry_hooks ( training_py) {
36- return Some ( StaticCheatHit {
37- code : CheatCode :: MissingTelemetryHooks ,
36+ return Some ( SourceCheatHit {
37+ kind : SourceCheatKind :: MissingTelemetryHooks ,
3838 rationale : "static: training.py missing prism_telemetry report/finish_evaluation hooks"
3939 . into ( ) ,
4040 } ) ;
@@ -48,9 +48,7 @@ pub fn training_has_telemetry_hooks(training_py: &str) -> bool {
4848 let imports_shim = training_py. contains ( "prism_telemetry" )
4949 || training_py. contains ( "ctx[\" telemetry\" ]" )
5050 || training_py. contains ( "ctx['telemetry']" ) ;
51- let calls_report = training_py. contains ( ".report(" ) ;
52- let calls_finish = training_py. contains ( "finish_evaluation(" ) ;
53- imports_shim && calls_report && calls_finish
51+ training_py. contains ( ".report(" ) && training_py. contains ( "finish_evaluation(" ) && imports_shim
5452}
5553
5654#[ cfg( test) ]
@@ -64,7 +62,7 @@ mod tests {
6462 "def train(m, ctx):\n print('METRICS_JSON={}')\n " ,
6563 )
6664 . expect ( "hit" ) ;
67- assert_eq ! ( hit. code , CheatCode :: EvalShortCircuit ) ;
65+ assert_eq ! ( hit. kind , SourceCheatKind :: EvalShortCircuit ) ;
6866 }
6967
7068 #[ test]
@@ -74,7 +72,7 @@ mod tests {
7472 "def train(m, ctx):\n return {}\n " ,
7573 )
7674 . expect ( "hit" ) ;
77- assert_eq ! ( hit. code , CheatCode :: MissingTelemetryHooks ) ;
75+ assert_eq ! ( hit. kind , SourceCheatKind :: MissingTelemetryHooks ) ;
7876 }
7977
8078 #[ test]
0 commit comments