1use aidens_contracts::{
8 CanonicalToolSideEffectClass, DifferentialConformanceFindingV1, GoldenFixtureManifestV1,
9 MemoryModeV1, ReferenceCaseV1, ReferenceDomainV1, ReferenceInterpreterReportV1, ReportLevelV1,
10 ToolLifecycleStateV1,
11};
12use chrono::{DateTime, Utc};
13use serde_json::{json, Value};
14use std::collections::BTreeSet;
15use thiserror::Error;
16
17pub const REFERENCE_PROVIDER_KINDS: &[&str] = &[
18 "anthropic",
19 "compatible",
20 "disabled",
21 "mock",
22 "ollama",
23 "openai",
24 "openai-compatible",
25 "openrouter",
26];
27
28#[derive(Debug, Error)]
29pub enum ReferenceError {
30 #[error("reference case {case_id} is missing required field {field}")]
31 MissingField { case_id: String, field: String },
32 #[error("reference case {case_id} has invalid field {field}: {reason}")]
33 InvalidField {
34 case_id: String,
35 field: String,
36 reason: String,
37 },
38 #[error("reference case {case_id} uses unsupported domain {domain}")]
39 UnsupportedDomain { case_id: String, domain: String },
40}
41
42pub fn all_provider_kinds() -> Vec<String> {
43 REFERENCE_PROVIDER_KINDS
44 .iter()
45 .map(|kind| (*kind).to_string())
46 .collect()
47}
48
49pub fn all_risk_classes() -> Vec<CanonicalToolSideEffectClass> {
50 vec![
51 CanonicalToolSideEffectClass::ReadOnly,
52 CanonicalToolSideEffectClass::Analysis,
53 CanonicalToolSideEffectClass::PreviewWrite,
54 CanonicalToolSideEffectClass::Write,
55 CanonicalToolSideEffectClass::Admin,
56 ]
57}
58
59pub fn all_memory_modes() -> Vec<MemoryModeV1> {
60 vec![
61 MemoryModeV1::Disabled,
62 MemoryModeV1::Optional,
63 MemoryModeV1::Required,
64 ]
65}
66
67pub fn all_receipt_levels() -> Vec<ReportLevelV1> {
68 vec![
69 ReportLevelV1::Minimal,
70 ReportLevelV1::Standard,
71 ReportLevelV1::Full,
72 ]
73}
74
75pub fn all_tool_lifecycle_states() -> Vec<ToolLifecycleStateV1> {
76 vec![
77 ToolLifecycleStateV1::Declared,
78 ToolLifecycleStateV1::Registered,
79 ToolLifecycleStateV1::Executable,
80 ToolLifecycleStateV1::Exposed,
81 ToolLifecycleStateV1::ExposedThisTurn,
82 ToolLifecycleStateV1::Invoked,
83 ToolLifecycleStateV1::Succeeded,
84 ToolLifecycleStateV1::Failed,
85 ToolLifecycleStateV1::Hidden,
86 ToolLifecycleStateV1::Blocked,
87 ]
88}
89
90pub fn interpret_case(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
91 match case.domain {
92 ReferenceDomainV1::PlanConfig => reference_plan_config_value(case),
93 ReferenceDomainV1::ProviderRoute => reference_provider_route_value(case),
94 ReferenceDomainV1::ToolExposure => reference_tool_exposure_value(case),
95 ReferenceDomainV1::Permit => reference_permit_value(case),
96 ReferenceDomainV1::BoundaryRepair => reference_boundary_repair_value(case),
97 ReferenceDomainV1::ReceiptLineage => reference_receipt_lineage_value(case),
98 ReferenceDomainV1::TemporalQuery => reference_temporal_query_value(case),
99 ReferenceDomainV1::ProofDebt => reference_proof_debt_value(case),
100 ReferenceDomainV1::SemanticState => reference_semantic_state_value(case),
101 ReferenceDomainV1::ViewDisclosure => reference_view_disclosure_value(case),
102 }
103}
104
105pub fn evaluate_reference_cases(cases: &[ReferenceCaseV1]) -> ReferenceInterpreterReportV1 {
106 let mut findings = Vec::new();
107 for case in cases {
108 match interpret_case(case) {
109 Ok(actual) if actual == case.expected => {}
110 Ok(actual) => findings.push(DifferentialConformanceFindingV1::mismatch(
111 case,
112 "aidens-testkit:reference-self-check",
113 "$",
114 case.expected.clone(),
115 actual,
116 )),
117 Err(error) => findings.push(DifferentialConformanceFindingV1::mismatch(
118 case,
119 "aidens-testkit:reference-self-check",
120 "$",
121 case.expected.clone(),
122 json!({"error": error.to_string()}),
123 )),
124 }
125 }
126 ReferenceInterpreterReportV1::new("aidens-testkit:p09", cases.len(), findings, cases)
127}
128
129pub fn compare_case_to_actual(
130 case: &ReferenceCaseV1,
131 production_subject: &str,
132 actual: Value,
133) -> ReferenceInterpreterReportV1 {
134 let expected = interpret_case(case).unwrap_or_else(|error| json!({"error": error.to_string()}));
135 let findings = if expected == actual {
136 Vec::new()
137 } else {
138 vec![DifferentialConformanceFindingV1::mismatch(
139 case,
140 production_subject,
141 "$",
142 expected,
143 actual,
144 )]
145 };
146 ReferenceInterpreterReportV1::new(production_subject, 1, findings, std::slice::from_ref(case))
147}
148
149pub fn reference_cases() -> Vec<ReferenceCaseV1> {
150 let mut cases = Vec::new();
151 for provider_kind in REFERENCE_PROVIDER_KINDS {
152 cases.push(reference_provider_case(provider_kind));
153 }
154 for risk in all_risk_classes() {
155 cases.push(reference_permit_case(risk));
156 }
157 for memory_mode in all_memory_modes() {
158 for receipt_level in all_receipt_levels() {
159 cases.push(reference_plan_config_case(
160 memory_mode.clone(),
161 receipt_level.clone(),
162 ));
163 }
164 }
165 cases.push(reference_safe_coding_exposure_case());
166 cases.push(reference_tool_lifecycle_catalog_case());
167 cases.push(reference_boundary_strict_case());
168 cases.push(reference_boundary_markdown_case());
169 cases.push(reference_boundary_substring_case());
170 cases.push(reference_receipt_lineage_case());
171 cases.push(reference_temporal_query_case());
172 cases.extend(reference_bitemporal_fixture_cases());
173 cases.push(reference_proof_waiver_debt_case());
174 cases.push(reference_semantic_state_contamination_case());
175 cases.push(reference_view_widening_disclosure_case());
176 cases
177}
178
179pub fn golden_fixture_manifest() -> GoldenFixtureManifestV1 {
180 let cases = reference_cases();
181 GoldenFixtureManifestV1::new(
182 vec![
183 "tests/fixtures/reference/reference_case_v1.json".into(),
184 "tests/fixtures/reference/reference_interpreter_report_v1.json".into(),
185 "tests/fixtures/reference/differential_conformance_finding_v1.json".into(),
186 "tests/fixtures/reference/golden_fixture_manifest_v1.json".into(),
187 ],
188 &cases,
189 )
190}
191
192pub fn reference_provider_case(provider_kind: &str) -> ReferenceCaseV1 {
193 let input = match provider_kind {
194 "mock" => json!({"kind": provider_kind, "mock_response_configured": true}),
195 "ollama" => json!({"kind": provider_kind, "model_configured": true}),
196 "openai" | "openrouter" | "anthropic" | "openai-compatible" | "compatible" => {
197 json!({"kind": provider_kind, "api_key_configured": true, "model_configured": true})
198 }
199 _ => json!({"kind": provider_kind}),
200 };
201 let mut case = ReferenceCaseV1::new(
202 ReferenceDomainV1::ProviderRoute,
203 format!("provider route semantics for {provider_kind}"),
204 input,
205 json!({}),
206 )
207 .with_provider_kind(provider_kind)
208 .with_source_fixture("tests/fixtures/reference/reference_case_v1.json");
209 case.expected = reference_provider_route_value(&case).expect("valid provider reference case");
210 case
211}
212
213pub fn reference_permit_case(risk_class: CanonicalToolSideEffectClass) -> ReferenceCaseV1 {
214 let mut case = ReferenceCaseV1::new(
215 ReferenceDomainV1::Permit,
216 format!("default permit semantics for {risk_class}"),
217 json!({
218 "risk_class": json_string(&risk_class),
219 "matching_permit": false
220 }),
221 json!({}),
222 )
223 .with_risk_class(risk_class)
224 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
225 case.expected = reference_permit_value(&case).expect("valid permit reference case");
226 case
227}
228
229pub fn reference_plan_config_case(
230 memory_mode: MemoryModeV1,
231 receipt_level: ReportLevelV1,
232) -> ReferenceCaseV1 {
233 let memory_store_configured = false;
234 let mut case = ReferenceCaseV1::new(
235 ReferenceDomainV1::PlanConfig,
236 format!("plan config semantics for {memory_mode}/{receipt_level}"),
237 json!({
238 "memory_mode": json_string(&memory_mode),
239 "receipt_level": json_string(&receipt_level),
240 "memory_store_configured": memory_store_configured
241 }),
242 json!({}),
243 )
244 .with_memory_mode(memory_mode)
245 .with_receipt_level(receipt_level)
246 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
247 case.expected = reference_plan_config_value(&case).expect("valid plan reference case");
248 case
249}
250
251pub fn reference_safe_coding_exposure_case() -> ReferenceCaseV1 {
252 let mut case = ReferenceCaseV1::new(
253 ReferenceDomainV1::ToolExposure,
254 "safe coding exposure semantics",
255 json!({
256 "max_tools": 16,
257 "native_tool_loop_available": false,
258 "tools": [
259 reference_tool(ReferenceToolSpec::new("aidens:file-write:1", "write")),
260 reference_tool(ReferenceToolSpec::new("aidens:file-stat:1", "read_only")
261 .registered()
262 .executable()
263 .allowed_risk()),
264 reference_tool(ReferenceToolSpec::new("aidens:memory-write:1", "write")),
265 reference_tool(ReferenceToolSpec::new("aidens:network:1", "analysis")),
266 reference_tool(ReferenceToolSpec::new("aidens:patch-apply:1", "write")
267 .registered()
268 .executable()),
269 reference_tool(ReferenceToolSpec::new("aidens:patch-propose:1", "read_only")
270 .registered()
271 .executable()
272 .allowed_risk()),
273 reference_tool(ReferenceToolSpec::new("aidens:repo-list:1", "read_only")
274 .registered()
275 .executable()
276 .allowed_risk()),
277 reference_tool(ReferenceToolSpec::new("aidens:repo-read:1", "read_only")
278 .registered()
279 .executable()
280 .allowed_risk()),
281 reference_tool(ReferenceToolSpec::new("aidens:repo-search:1", "read_only")
282 .registered()
283 .executable()
284 .allowed_risk()),
285 reference_tool(ReferenceToolSpec::new("aidens:run-checks:1", "admin")
286 .registered()
287 .executable()),
288 reference_tool(ReferenceToolSpec::new("aidens:schedule:1", "admin")),
289 reference_tool(ReferenceToolSpec::new("aidens:shell:1", "admin"))
290 ]
291 }),
292 json!({}),
293 )
294 .with_risk_class(CanonicalToolSideEffectClass::ReadOnly)
295 .with_risk_class(CanonicalToolSideEffectClass::Write)
296 .with_tool_lifecycle_state(ToolLifecycleStateV1::Declared)
297 .with_tool_lifecycle_state(ToolLifecycleStateV1::Registered)
298 .with_tool_lifecycle_state(ToolLifecycleStateV1::Executable)
299 .with_tool_lifecycle_state(ToolLifecycleStateV1::ExposedThisTurn)
300 .with_tool_lifecycle_state(ToolLifecycleStateV1::Hidden)
301 .with_tool_lifecycle_state(ToolLifecycleStateV1::Blocked)
302 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
303 case.expected = reference_tool_exposure_value(&case).expect("valid exposure reference case");
304 case
305}
306
307pub fn reference_boundary_markdown_case() -> ReferenceCaseV1 {
308 let mut case = ReferenceCaseV1::new(
309 ReferenceDomainV1::BoundaryRepair,
310 "markdown fence repair semantics",
311 json!({
312 "raw": "```json\n{\"ok\":true}\n```",
313 "allow_markdown_fence_repair": true,
314 "allow_json_substring_extract": true
315 }),
316 json!({}),
317 )
318 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
319 case.expected = reference_boundary_repair_value(&case).expect("valid boundary reference case");
320 case
321}
322
323pub fn reference_temporal_query_case() -> ReferenceCaseV1 {
324 let mut case = ReferenceCaseV1::new(
325 ReferenceDomainV1::TemporalQuery,
326 "temporal as-of reference query semantics",
327 json!({
328 "valid_at": "2026-04-01T00:00:00Z",
329 "recorded_at_or_before": "9999-01-01T00:00:00Z",
330 "records": [
331 {
332 "claim_id": "claim-phase09-temporal",
333 "claim_version_id": "claim-version-phase09-temporal-old",
334 "valid_from": "2026-01-01T00:00:00Z",
335 "valid_to": "2026-03-01T00:00:00Z",
336 "recorded_at": "2026-04-28T12:00:00Z",
337 "content": "phase nine temporal old value"
338 },
339 {
340 "claim_id": "claim-phase09-temporal",
341 "claim_version_id": "claim-version-phase09-temporal-new",
342 "valid_from": "2026-03-01T00:00:00Z",
343 "valid_to": null,
344 "recorded_at": "2026-04-28T12:00:00Z",
345 "content": "phase nine temporal new value"
346 }
347 ]
348 }),
349 json!({}),
350 )
351 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
352 case.expected = reference_temporal_query_value(&case).expect("valid temporal reference case");
353 case
354}
355
356pub fn reference_bitemporal_fixture_cases() -> Vec<ReferenceCaseV1> {
357 vec![
358 reference_temporal_valid_time_only_case(),
359 reference_temporal_recorded_time_only_case(),
360 reference_temporal_retroactive_correction_case(),
361 reference_temporal_stale_projection_case(),
362 reference_temporal_degraded_disclosure_case(),
363 ]
364}
365
366pub fn reference_temporal_valid_time_only_case() -> ReferenceCaseV1 {
367 reference_temporal_fixture_case(
368 "valid-time-only temporal reference query semantics",
369 json!({
370 "valid_at": "2026-02-01T00:00:00Z",
371 "records": [
372 {
373 "claim_id": "claim-phase09-valid-only",
374 "claim_version_id": "claim-version-phase09-valid-old",
375 "valid_from": "2026-01-01T00:00:00Z",
376 "valid_to": "2026-03-01T00:00:00Z",
377 "recorded_at": "2026-01-15T00:00:00Z",
378 "content": "valid-only old value"
379 },
380 {
381 "claim_id": "claim-phase09-valid-only",
382 "claim_version_id": "claim-version-phase09-valid-new",
383 "valid_from": "2026-03-01T00:00:00Z",
384 "valid_to": null,
385 "recorded_at": "2026-01-15T00:00:00Z",
386 "content": "valid-only new value"
387 }
388 ]
389 }),
390 )
391}
392
393pub fn reference_temporal_recorded_time_only_case() -> ReferenceCaseV1 {
394 reference_temporal_fixture_case(
395 "recorded-time-only temporal reference query semantics",
396 json!({
397 "recorded_at_or_before": "2026-02-15T00:00:00Z",
398 "records": [
399 {
400 "claim_id": "claim-phase09-recorded-only",
401 "claim_version_id": "claim-version-phase09-recorded-early",
402 "valid_from": "2026-01-01T00:00:00Z",
403 "valid_to": null,
404 "recorded_at": "2026-02-01T00:00:00Z",
405 "content": "recorded-only early value"
406 },
407 {
408 "claim_id": "claim-phase09-recorded-only",
409 "claim_version_id": "claim-version-phase09-recorded-late",
410 "valid_from": "2026-01-01T00:00:00Z",
411 "valid_to": null,
412 "recorded_at": "2026-03-01T00:00:00Z",
413 "content": "recorded-only late value"
414 }
415 ]
416 }),
417 )
418}
419
420pub fn reference_temporal_retroactive_correction_case() -> ReferenceCaseV1 {
421 reference_temporal_fixture_case(
422 "retroactive correction and supersession temporal reference semantics",
423 json!({
424 "valid_at": "2026-04-01T00:00:00Z",
425 "recorded_at_or_before": "2026-05-01T00:00:00Z",
426 "records": [
427 {
428 "claim_id": "claim-phase09-retro",
429 "claim_version_id": "claim-version-phase09-retro-original",
430 "valid_from": "2026-01-01T00:00:00Z",
431 "valid_to": null,
432 "recorded_at": "2026-02-01T00:00:00Z",
433 "content": "retroactive original value"
434 },
435 {
436 "claim_id": "claim-phase09-retro",
437 "claim_version_id": "claim-version-phase09-retro-correction",
438 "valid_from": "2026-01-01T00:00:00Z",
439 "valid_to": null,
440 "recorded_at": "2026-04-15T00:00:00Z",
441 "supersedes_claim_version_ids": ["claim-version-phase09-retro-original"],
442 "content": "retroactive corrected value"
443 }
444 ]
445 }),
446 )
447}
448
449pub fn reference_temporal_stale_projection_case() -> ReferenceCaseV1 {
450 reference_temporal_fixture_case(
451 "stale projection temporal disclosure semantics",
452 json!({
453 "valid_at": "2026-04-01T00:00:00Z",
454 "recorded_at_or_before": "2026-05-01T00:00:00Z",
455 "projection_recorded_at": "2026-03-01T00:00:00Z",
456 "records": [
457 {
458 "claim_id": "claim-phase09-stale",
459 "claim_version_id": "claim-version-phase09-stale-current",
460 "valid_from": "2026-01-01T00:00:00Z",
461 "valid_to": null,
462 "recorded_at": "2026-04-15T00:00:00Z",
463 "content": "stale projection current value"
464 }
465 ]
466 }),
467 )
468}
469
470pub fn reference_temporal_degraded_disclosure_case() -> ReferenceCaseV1 {
471 reference_temporal_fixture_case(
472 "degraded temporal disclosure semantics",
473 json!({
474 "valid_at": "2026-04-01T00:00:00Z",
475 "recorded_at_or_before": "2026-05-01T00:00:00Z",
476 "temporal_index_exact": false,
477 "records": [
478 {
479 "claim_id": "claim-phase09-degraded",
480 "claim_version_id": "claim-version-phase09-degraded-candidate",
481 "valid_from": "2026-01-01T00:00:00Z",
482 "valid_to": null,
483 "recorded_at": "2026-04-15T00:00:00Z",
484 "content": "degraded temporal candidate"
485 }
486 ]
487 }),
488 )
489}
490
491fn reference_temporal_fixture_case(title: &str, input: Value) -> ReferenceCaseV1 {
492 let mut case = ReferenceCaseV1::new(ReferenceDomainV1::TemporalQuery, title, input, json!({}))
493 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
494 case.expected = reference_temporal_query_value(&case).expect("valid temporal reference case");
495 case
496}
497
498fn reference_boundary_strict_case() -> ReferenceCaseV1 {
499 let mut case = ReferenceCaseV1::new(
500 ReferenceDomainV1::BoundaryRepair,
501 "strict json boundary semantics",
502 json!({"raw": "{\"ok\":true}", "allow_markdown_fence_repair": true, "allow_json_substring_extract": true}),
503 json!({}),
504 )
505 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
506 case.expected = reference_boundary_repair_value(&case).expect("valid boundary reference case");
507 case
508}
509
510fn reference_boundary_substring_case() -> ReferenceCaseV1 {
511 let mut case = ReferenceCaseV1::new(
512 ReferenceDomainV1::BoundaryRepair,
513 "substring repair semantics",
514 json!({"raw": "prefix {\"ok\":true} suffix", "allow_markdown_fence_repair": true, "allow_json_substring_extract": true}),
515 json!({}),
516 )
517 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
518 case.expected = reference_boundary_repair_value(&case).expect("valid boundary reference case");
519 case
520}
521
522fn reference_receipt_lineage_case() -> ReferenceCaseV1 {
523 let mut case = ReferenceCaseV1::new(
524 ReferenceDomainV1::ReceiptLineage,
525 "receipt parent-child lineage semantics",
526 json!({
527 "receipts": [
528 {"receipt_id":"run:fixture","kind":"run","parent_receipt_ids":[]},
529 {"receipt_id":"tool:fixture","kind":"tool-invocation","parent_receipt_ids":["run:fixture"]},
530 {"receipt_id":"schema:fixture","kind":"schema-validation","parent_receipt_ids":["tool:fixture"]}
531 ]
532 }),
533 json!({}),
534 )
535 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
536 case.expected = reference_receipt_lineage_value(&case).expect("valid lineage reference case");
537 case
538}
539
540pub fn reference_proof_waiver_debt_case() -> ReferenceCaseV1 {
541 let mut case = ReferenceCaseV1::new(
542 ReferenceDomainV1::ProofDebt,
543 "proof waiver remains debt with expiry escalation semantics",
544 json!({
545 "now": "2026-05-07T00:00:00Z",
546 "requested_use": "local-advisory-display",
547 "obligations": [
548 {
549 "obligation_id": "proof-obligation:phase09-waiver",
550 "satisfied_by": [],
551 "waived_by": ["proof-waiver:phase09"],
552 "expires_at": "2026-05-01T00:00:00Z"
553 },
554 {
555 "obligation_id": "proof-obligation:phase09-missing",
556 "satisfied_by": [],
557 "waived_by": []
558 }
559 ]
560 }),
561 json!({}),
562 )
563 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
564 case.expected = reference_proof_debt_value(&case).expect("valid proof debt reference case");
565 case
566}
567
568pub fn reference_semantic_state_contamination_case() -> ReferenceCaseV1 {
569 let mut case = ReferenceCaseV1::new(
570 ReferenceDomainV1::SemanticState,
571 "semantic state makes degradation contradiction and execution contamination visible",
572 json!({
573 "exactness": "exact",
574 "degradation_record_ids": ["degradation:phase09"],
575 "view_disclosure_ids": ["view-disclosure:phase09"],
576 "contradiction_record_ids": ["semantic-contradiction:phase09"],
577 "execution_contamination_ids": ["execution-contamination:phase09"]
578 }),
579 json!({}),
580 )
581 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
582 case.expected =
583 reference_semantic_state_value(&case).expect("valid semantic state reference case");
584 case
585}
586
587pub fn reference_view_widening_disclosure_case() -> ReferenceCaseV1 {
588 let mut case = ReferenceCaseV1::new(
589 ReferenceDomainV1::ViewDisclosure,
590 "view widening and degradation are user visible before results",
591 json!({
592 "query_widening_receipt_ids": ["query-widening:phase09"],
593 "degradation_event_ids": ["degradation-event:phase09"],
594 "separates_execution_from_domain_truth": true
595 }),
596 json!({}),
597 )
598 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
599 case.expected =
600 reference_view_disclosure_value(&case).expect("valid view disclosure reference case");
601 case
602}
603
604fn reference_tool_lifecycle_catalog_case() -> ReferenceCaseV1 {
605 let states = all_tool_lifecycle_states();
606 let mut case = ReferenceCaseV1::new(
607 ReferenceDomainV1::ToolExposure,
608 "tool lifecycle state catalog semantics",
609 json!({"catalog_lifecycle_states": states.iter().map(json_string).collect::<Vec<_>>()}),
610 json!({"known_tool_lifecycle_states": states.iter().map(json_string).collect::<Vec<_>>()}),
611 )
612 .with_source_fixture("tests/fixtures/reference/golden_fixture_manifest_v1.json");
613 for state in states {
614 case = case.with_tool_lifecycle_state(state);
615 }
616 case
617}
618
619fn reference_provider_route_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
620 let kind = string_field(case, "kind")?.trim().to_ascii_lowercase();
621 let api_key_configured = bool_field(case, "api_key_configured", false);
622 let model_configured = bool_field(case, "model_configured", false);
623 let mock_response_configured = bool_field(case, "mock_response_configured", false);
624 let (configured, executable, route_label, reason_codes) = match kind.as_str() {
625 "" | "disabled" | "none" => (false, false, "disabled", vec!["provider-disabled"]),
626 "mock" => {
627 if mock_response_configured {
628 (true, true, "mock", vec!["mock-response-configured"])
629 } else {
630 (true, false, "unavailable", vec!["mock-response-missing"])
631 }
632 }
633 "ollama" => {
634 if model_configured {
635 (
636 true,
637 true,
638 "ollama-chat",
639 vec![
640 "ollama-chat-boundary-configured",
641 "ollama-local-service-required",
642 "ollama-native-tool-loop-via-function-calling",
643 ],
644 )
645 } else {
646 (false, false, "unavailable", vec!["ollama-model-missing"])
647 }
648 }
649 "openai" | "openrouter" | "anthropic" | "openai-compatible" | "compatible" => {
650 if api_key_configured {
651 (
652 true,
653 false,
654 "unavailable",
655 vec!["provider-boundary-unavailable"],
656 )
657 } else {
658 (true, false, "unavailable", vec!["api-key-missing"])
659 }
660 }
661 _ => (
662 true,
663 false,
664 "unavailable",
665 vec!["unsupported-provider-kind"],
666 ),
667 };
668 let native_tool_loop = kind == "ollama" && model_configured;
669 Ok(json!({
670 "configured": configured,
671 "executable": executable,
672 "route_label": route_label,
673 "native_tool_loop": native_tool_loop,
674 "reason_codes": reason_codes
675 }))
676}
677
678fn reference_plan_config_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
679 let memory_mode = string_field(case, "memory_mode")?;
680 let receipt_level = string_field(case, "receipt_level")?;
681 let memory_store_configured = bool_field(case, "memory_store_configured", false);
682 let memory_blocked = memory_mode == "required" && !memory_store_configured;
683 let durable_receipts_required = receipt_level != "minimal";
684 let mut reason_codes = vec![
685 format!("memory-mode-{memory_mode}"),
686 format!("receipt-level-{receipt_level}"),
687 ];
688 if memory_blocked {
689 reason_codes.push("memory-required-without-durable-store".into());
690 }
691 Ok(json!({
692 "memory_mode": memory_mode,
693 "receipt_level": receipt_level,
694 "memory_store_configured": memory_store_configured,
695 "durable_receipts_required": durable_receipts_required,
696 "memory_blocked": memory_blocked,
697 "reason_codes": sorted_strings(reason_codes)
698 }))
699}
700
701fn reference_permit_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
702 let risk_class = string_field(case, "risk_class")?;
703 let matching_permit = bool_field(case, "matching_permit", false);
704 let permit_required = risk_class != "read_only";
705 let (decision, reason_codes) = if !permit_required {
706 ("allow", vec!["read-only-risk"])
707 } else if matching_permit {
708 ("allow", vec!["permit-scope-matched"])
709 } else {
710 ("requires-approval", vec!["approval-required"])
711 };
712 Ok(json!({
713 "risk_class": risk_class,
714 "permit_required": permit_required,
715 "decision": decision,
716 "reason_codes": reason_codes
717 }))
718}
719
720fn reference_tool_exposure_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
721 if let Some(catalog) = case.input.get("catalog_lifecycle_states") {
722 return Ok(json!({"known_tool_lifecycle_states": catalog.clone()}));
723 }
724
725 let max_tools = case
726 .input
727 .get("max_tools")
728 .and_then(Value::as_u64)
729 .unwrap_or(u64::MAX) as usize;
730 let native_tool_loop_available = bool_field(case, "native_tool_loop_available", false);
731 let tools = case
732 .input
733 .get("tools")
734 .and_then(Value::as_array)
735 .ok_or_else(|| ReferenceError::MissingField {
736 case_id: case.case_id.0.clone(),
737 field: "tools".into(),
738 })?;
739
740 let mut declared = Vec::new();
741 let mut registered = Vec::new();
742 let mut executable = Vec::new();
743 let mut exposed = Vec::new();
744 let mut hidden = Vec::new();
745 let mut blocked = Vec::new();
746 let mut lifecycles = serde_json::Map::new();
747 let mut reason_codes = Vec::new();
748
749 for tool in tools {
750 let tool_id = tool_field(tool, "tool_id")?;
751 let risk_class = tool_field(tool, "risk_class")?;
752 let is_declared = tool_bool(tool, "declared", true);
753 let is_registered = tool_bool(tool, "registered", false);
754 let is_executable = tool_bool(tool, "executable", false);
755 let is_hidden = tool_bool(tool, "hidden", false);
756 let allowed_risk = tool_bool(tool, "allowed_risk", false);
757 let matching_permit = tool_bool(tool, "matching_permit", false);
758 let requires_native_tool_loop = tool_bool(tool, "requires_native_tool_loop", false);
759 let mut lifecycle = Vec::new();
760 if is_declared {
761 declared.push(tool_id.clone());
762 lifecycle.push("declared");
763 }
764 if is_registered {
765 registered.push(tool_id.clone());
766 lifecycle.push("registered");
767 }
768 if is_executable {
769 executable.push(tool_id.clone());
770 lifecycle.push("executable");
771 }
772
773 let outcome = if !is_registered {
774 reason_codes.push(format!("tool-declared-not-registered:{tool_id}"));
775 "hidden"
776 } else if is_hidden {
777 reason_codes.push(format!("tool-hidden:{tool_id}"));
778 "hidden"
779 } else if !allowed_risk {
780 reason_codes.push(format!("risk-blocked:{risk_class}"));
781 "blocked"
782 } else if risk_class != "read_only" && !matching_permit {
783 reason_codes.push(format!("permit-required:{risk_class}"));
784 "blocked"
785 } else if requires_native_tool_loop && !native_tool_loop_available {
786 reason_codes.push(format!("route-requires-native-tool-loop:{tool_id}"));
787 "blocked"
788 } else if !is_executable {
789 reason_codes.push(format!("tool-executor-missing:{tool_id}"));
790 "hidden"
791 } else if exposed.len() >= max_tools {
792 reason_codes.push("max-tools-reached".into());
793 "hidden"
794 } else {
795 "exposed"
796 };
797
798 match outcome {
799 "exposed" => {
800 exposed.push(tool_id.clone());
801 lifecycle.push("exposed-this-turn");
802 }
803 "blocked" => {
804 blocked.push(tool_id.clone());
805 lifecycle.push("blocked");
806 }
807 _ => {
808 hidden.push(tool_id.clone());
809 lifecycle.push("hidden");
810 }
811 }
812 lifecycles.insert(
813 tool_id,
814 Value::Array(
815 lifecycle
816 .into_iter()
817 .map(|state| Value::String(state.into()))
818 .collect(),
819 ),
820 );
821 }
822
823 Ok(json!({
824 "declared_tool_ids": sorted_strings(declared),
825 "registered_tool_ids": sorted_strings(registered),
826 "executable_tool_ids": sorted_strings(executable),
827 "exposed_tool_ids": sorted_strings(exposed),
828 "hidden_tool_ids": sorted_strings(hidden),
829 "blocked_tool_ids": sorted_strings(blocked),
830 "lifecycles": Value::Object(lifecycles),
831 "reason_codes": sorted_unique_strings(reason_codes)
832 }))
833}
834
835fn reference_boundary_repair_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
836 let raw = string_field(case, "raw")?;
837 let allow_markdown = bool_field(case, "allow_markdown_fence_repair", false);
838 let allow_substring = bool_field(case, "allow_json_substring_extract", false);
839 if serde_json::from_str::<Value>(&raw).is_ok() {
840 return Ok(json!({
841 "accepted": true,
842 "degraded": false,
843 "repair_kind": null,
844 "reason_codes": ["boundary-compile-accepted"]
845 }));
846 }
847 if allow_markdown {
848 if let Some(candidate) = strip_markdown_json_fence(&raw) {
849 if serde_json::from_str::<Value>(&candidate).is_ok() {
850 return Ok(repaired_boundary_value("markdown-json-fence-stripped"));
851 }
852 }
853 }
854 if allow_substring {
855 if let Some(candidate) = extract_first_json_candidate(&raw) {
856 if serde_json::from_str::<Value>(&candidate).is_ok() {
857 return Ok(repaired_boundary_value("json-substring-extracted"));
858 }
859 }
860 }
861 Ok(json!({
862 "accepted": false,
863 "degraded": true,
864 "repair_kind": null,
865 "reason_codes": ["invalid-json"]
866 }))
867}
868
869fn reference_receipt_lineage_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
870 let receipts = case
871 .input
872 .get("receipts")
873 .and_then(Value::as_array)
874 .ok_or_else(|| ReferenceError::MissingField {
875 case_id: case.case_id.0.clone(),
876 field: "receipts".into(),
877 })?;
878 let mut nodes = Vec::new();
879 let mut edges = Vec::new();
880 let mut orphans = Vec::new();
881 for receipt in receipts {
882 let receipt_id = tool_field(receipt, "receipt_id")?;
883 nodes.push(receipt_id.clone());
884 let parents = receipt
885 .get("parent_receipt_ids")
886 .and_then(Value::as_array)
887 .cloned()
888 .unwrap_or_default();
889 if parents.is_empty() {
890 orphans.push(receipt_id.clone());
891 }
892 for parent in parents {
893 if let Some(parent_id) = parent.as_str() {
894 edges.push(format!("{parent_id}->{receipt_id}"));
895 }
896 }
897 }
898 Ok(json!({
899 "node_count": nodes.len(),
900 "edge_count": edges.len(),
901 "receipt_ids": sorted_strings(nodes),
902 "edges": sorted_strings(edges),
903 "orphan_receipt_ids": sorted_strings(orphans)
904 }))
905}
906
907fn reference_temporal_query_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
908 let valid_at = optional_datetime_field(case, "valid_at")?;
909 let recorded_at_or_before = optional_datetime_field(case, "recorded_at_or_before")?;
910 let records = case
911 .input
912 .get("records")
913 .and_then(Value::as_array)
914 .ok_or_else(|| ReferenceError::MissingField {
915 case_id: case.case_id.0.clone(),
916 field: "records".into(),
917 })?;
918 let temporal_index_exact = case
919 .input
920 .get("temporal_index_exact")
921 .and_then(Value::as_bool)
922 .unwrap_or(true);
923 let projection_recorded_at = optional_datetime_field(case, "projection_recorded_at")?;
924 let mut candidate_claim_version_ids = BTreeSet::new();
925 let mut superseded_claim_version_ids = BTreeSet::new();
926 let mut max_candidate_recorded_at: Option<DateTime<Utc>> = None;
927 for record in records {
928 let claim_version_id = value_string_field(record, "claim_version_id", case)?;
929 let valid_from = value_datetime_field(record, "valid_from", case)?;
930 let valid_to = optional_value_datetime_field(record, "valid_to", case)?;
931 let recorded_at = value_datetime_field(record, "recorded_at", case)?;
932 let valid_at_visible = valid_at
933 .map(|valid_at| {
934 valid_from <= valid_at
935 && valid_to.map(|valid_to| valid_at < valid_to).unwrap_or(true)
936 })
937 .unwrap_or(true);
938 let recorded_visible = recorded_at_or_before
939 .map(|recorded_at_or_before| recorded_at <= recorded_at_or_before)
940 .unwrap_or(true);
941 if valid_at_visible && recorded_visible {
942 if max_candidate_recorded_at
943 .map(|current| recorded_at > current)
944 .unwrap_or(true)
945 {
946 max_candidate_recorded_at = Some(recorded_at);
947 }
948 for superseded_id in record
949 .get("supersedes_claim_version_ids")
950 .and_then(Value::as_array)
951 .into_iter()
952 .flatten()
953 .filter_map(Value::as_str)
954 {
955 superseded_claim_version_ids.insert(superseded_id.to_string());
956 }
957 candidate_claim_version_ids.insert(claim_version_id);
958 }
959 }
960 let mut visible_claim_version_ids = Vec::new();
961 let mut hidden_claim_version_ids = Vec::new();
962 for record in records {
963 let claim_version_id = value_string_field(record, "claim_version_id", case)?;
964 if candidate_claim_version_ids.contains(&claim_version_id)
965 && !superseded_claim_version_ids.contains(&claim_version_id)
966 {
967 visible_claim_version_ids.push(claim_version_id);
968 } else {
969 hidden_claim_version_ids.push(claim_version_id);
970 }
971 }
972
973 let stale_projection = projection_recorded_at
974 .zip(max_candidate_recorded_at)
975 .map(|(projection_recorded_at, max_recorded_at)| projection_recorded_at < max_recorded_at)
976 .unwrap_or(false);
977 let degraded = !temporal_index_exact || stale_projection;
978 let mut reason_codes = vec!["temporal-query-reference".to_string()];
979 if valid_at.is_some() {
980 reason_codes.push("valid-time-filter-applied".into());
981 }
982 if recorded_at_or_before.is_some() {
983 reason_codes.push("recorded-time-filter-applied".into());
984 }
985 if !superseded_claim_version_ids.is_empty() {
986 reason_codes.push("supersession-applied".into());
987 }
988 if stale_projection {
989 reason_codes.push("stale-projection-disclosed".into());
990 }
991 if !temporal_index_exact {
992 reason_codes.push("temporal-index-degraded-disclosed".into());
993 }
994 if !degraded {
995 reason_codes.push("temporal-query-exact-reference".into());
996 }
997 reason_codes.sort();
998 reason_codes.dedup();
999
1000 Ok(json!({
1001 "accepted": true,
1002 "deferred": false,
1003 "temporal_mode": if degraded { "degraded" } else { "exact" },
1004 "valid_as_of": valid_at.map(|valid_at| valid_at.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)),
1005 "recorded_as_of": recorded_at_or_before.map(|recorded_at_or_before| recorded_at_or_before.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)),
1006 "visible_claim_version_ids": sorted_strings(visible_claim_version_ids),
1007 "hidden_claim_version_ids": sorted_strings(hidden_claim_version_ids),
1008 "stale_projection": stale_projection,
1009 "view_disclosure_required": degraded,
1010 "degradation_record_required": degraded,
1011 "reason_codes": reason_codes
1012 }))
1013}
1014
1015fn reference_proof_debt_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
1016 let now = value_datetime_field(&case.input, "now", case)?;
1017 let requested_use = case
1018 .input
1019 .get("requested_use")
1020 .and_then(Value::as_str)
1021 .unwrap_or("local-advisory-display");
1022 let obligations = case
1023 .input
1024 .get("obligations")
1025 .and_then(Value::as_array)
1026 .ok_or_else(|| ReferenceError::MissingField {
1027 case_id: case.case_id.0.clone(),
1028 field: "obligations".into(),
1029 })?;
1030 let mut debt_ids = Vec::new();
1031 let mut restrictions = Vec::new();
1032 let mut expired_debt_ids = Vec::new();
1033 let mut escalated_debt_ids = Vec::new();
1034 let mut allowed_use_count = 0usize;
1035 let mut waiver_without_proof = false;
1036 for obligation in obligations {
1037 let obligation_id = value_string_field(obligation, "obligation_id", case)?;
1038 let satisfied = obligation
1039 .get("satisfied_by")
1040 .and_then(Value::as_array)
1041 .map(|items| !items.is_empty())
1042 .unwrap_or(false);
1043 if satisfied {
1044 continue;
1045 }
1046 let waived = obligation
1047 .get("waived_by")
1048 .and_then(Value::as_array)
1049 .map(|items| !items.is_empty())
1050 .unwrap_or(false);
1051 let debt_id = format!("proof-debt:{obligation_id}");
1052 let expires_at = optional_value_datetime_field(obligation, "expires_at", case)?;
1053 let expired = expires_at
1054 .map(|expires_at| now >= expires_at)
1055 .unwrap_or(false);
1056 if waived {
1057 waiver_without_proof = true;
1058 }
1059 if expired {
1060 expired_debt_ids.push(debt_id.clone());
1061 escalated_debt_ids.push(debt_id.clone());
1062 restrictions.push("block-release".to_string());
1063 } else if waived {
1064 restrictions.push("advisory-only".to_string());
1065 if requested_use == "local-advisory-display" || requested_use == "operator-triage" {
1066 allowed_use_count += 1;
1067 }
1068 } else {
1069 restrictions.push("block-release".to_string());
1070 }
1071 debt_ids.push(debt_id);
1072 }
1073 let blocks_promotion = !debt_ids.is_empty();
1074 Ok(json!({
1075 "debt_ids": sorted_strings(debt_ids),
1076 "restriction_kinds": sorted_strings(restrictions),
1077 "waiver_is_not_proof": true,
1078 "waiver_without_proof": waiver_without_proof,
1079 "blocks_promotion": blocks_promotion,
1080 "requested_use_allowed": allowed_use_count > 0 && escalated_debt_ids.is_empty(),
1081 "expired_debt_ids": sorted_strings(expired_debt_ids),
1082 "escalated_debt_ids": sorted_strings(escalated_debt_ids),
1083 "reason_codes": [
1084 "proof-debt-queryable",
1085 "proof-waiver-does-not-satisfy-obligation",
1086 "expired-proof-debt-escalates"
1087 ]
1088 }))
1089}
1090
1091fn reference_semantic_state_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
1092 let exactness = case
1093 .input
1094 .get("exactness")
1095 .and_then(Value::as_str)
1096 .ok_or_else(|| ReferenceError::MissingField {
1097 case_id: case.case_id.0.clone(),
1098 field: "exactness".into(),
1099 })?;
1100 let degradation_ids = optional_string_array(&case.input, "degradation_record_ids");
1101 let view_ids = optional_string_array(&case.input, "view_disclosure_ids");
1102 let contradiction_ids = optional_string_array(&case.input, "contradiction_record_ids");
1103 let contamination_ids = optional_string_array(&case.input, "execution_contamination_ids");
1104 let visible_degradation = !degradation_ids.is_empty();
1105 let visible_contradiction = !contradiction_ids.is_empty();
1106 let visible_contamination = !contamination_ids.is_empty();
1107 let can_answer_as_exact = exactness == "exact"
1108 && !visible_degradation
1109 && !visible_contradiction
1110 && !visible_contamination;
1111 Ok(json!({
1112 "can_answer_as_exact": can_answer_as_exact,
1113 "blocks_promotion": !can_answer_as_exact,
1114 "degradation_visible": visible_degradation,
1115 "view_disclosure_visible": !view_ids.is_empty(),
1116 "contradiction_visible": visible_contradiction,
1117 "execution_contamination_visible": visible_contamination,
1118 "effective_exactness": if visible_contradiction { "refuted" } else if can_answer_as_exact { "exact" } else { "degraded" },
1119 "reason_codes": [
1120 "semantic-state-queryable",
1121 "contradiction-blocks-promotion",
1122 "execution-contamination-disclosed"
1123 ]
1124 }))
1125}
1126
1127fn reference_view_disclosure_value(case: &ReferenceCaseV1) -> Result<Value, ReferenceError> {
1128 let widening_ids = optional_string_array(&case.input, "query_widening_receipt_ids");
1129 let degradation_ids = optional_string_array(&case.input, "degradation_event_ids");
1130 let separates_execution = case
1131 .input
1132 .get("separates_execution_from_domain_truth")
1133 .and_then(Value::as_bool)
1134 .unwrap_or(false);
1135 let has_visible_event = !widening_ids.is_empty() || !degradation_ids.is_empty();
1136 Ok(json!({
1137 "has_visible_widening_or_degradation": has_visible_event,
1138 "discloses_policy_events": separates_execution
1139 && widening_ids.iter().all(|id| !id.is_empty())
1140 && degradation_ids.iter().all(|id| !id.is_empty()),
1141 "result_exactness": if has_visible_event { "degraded" } else { "exact" },
1142 "query_widening_receipt_ids": sorted_strings(widening_ids),
1143 "degradation_event_ids": sorted_strings(degradation_ids),
1144 "reason_codes": [
1145 "view-disclosure-queryable",
1146 "widening-visible-before-results"
1147 ]
1148 }))
1149}
1150
1151struct ReferenceToolSpec<'a> {
1152 tool_id: &'a str,
1153 risk_class: &'a str,
1154 declared: bool,
1155 registered: bool,
1156 executable: bool,
1157 hidden: bool,
1158 allowed_risk: bool,
1159 matching_permit: bool,
1160 requires_native_tool_loop: bool,
1161}
1162
1163impl<'a> ReferenceToolSpec<'a> {
1164 fn new(tool_id: &'a str, risk_class: &'a str) -> Self {
1165 Self {
1166 tool_id,
1167 risk_class,
1168 declared: true,
1169 registered: false,
1170 executable: false,
1171 hidden: false,
1172 allowed_risk: false,
1173 matching_permit: false,
1174 requires_native_tool_loop: false,
1175 }
1176 }
1177
1178 fn registered(mut self) -> Self {
1179 self.registered = true;
1180 self
1181 }
1182
1183 fn executable(mut self) -> Self {
1184 self.executable = true;
1185 self
1186 }
1187
1188 fn allowed_risk(mut self) -> Self {
1189 self.allowed_risk = true;
1190 self
1191 }
1192}
1193
1194fn reference_tool(spec: ReferenceToolSpec<'_>) -> Value {
1195 json!({
1196 "tool_id": spec.tool_id,
1197 "risk_class": spec.risk_class,
1198 "declared": spec.declared,
1199 "registered": spec.registered,
1200 "executable": spec.executable,
1201 "hidden": spec.hidden,
1202 "allowed_risk": spec.allowed_risk,
1203 "matching_permit": spec.matching_permit,
1204 "requires_native_tool_loop": spec.requires_native_tool_loop
1205 })
1206}
1207
1208fn repaired_boundary_value(repair_kind: &str) -> Value {
1209 let reason_codes = sorted_strings(vec![
1210 "boundary-compile-accepted".into(),
1211 format!("boundary-repair:{repair_kind}"),
1212 format!("json-repair:{repair_kind}"),
1213 ]);
1214 json!({
1215 "accepted": true,
1216 "degraded": true,
1217 "repair_kind": repair_kind,
1218 "reason_codes": reason_codes
1219 })
1220}
1221
1222fn strip_markdown_json_fence(raw: &str) -> Option<String> {
1223 let trimmed = raw.trim();
1224 if !trimmed.starts_with("```") {
1225 return None;
1226 }
1227 let mut lines = trimmed.lines();
1228 let first = lines.next()?;
1229 if !first.trim_start_matches("```").trim().is_empty()
1230 && first.trim_start_matches("```").trim() != "json"
1231 {
1232 return None;
1233 }
1234 let mut body = lines.collect::<Vec<_>>();
1235 if body.last().is_some_and(|line| line.trim() == "```") {
1236 body.pop();
1237 }
1238 Some(body.join("\n"))
1239}
1240
1241fn extract_first_json_candidate(raw: &str) -> Option<String> {
1242 let object_start = raw.find('{');
1243 let array_start = raw.find('[');
1244 let start = match (object_start, array_start) {
1245 (Some(left), Some(right)) => left.min(right),
1246 (Some(left), None) => left,
1247 (None, Some(right)) => right,
1248 (None, None) => return None,
1249 };
1250 let end = raw.rfind('}').or_else(|| raw.rfind(']'))?;
1251 (end >= start).then(|| raw[start..=end].to_string())
1252}
1253
1254fn string_field(case: &ReferenceCaseV1, field: &str) -> Result<String, ReferenceError> {
1255 case.input
1256 .get(field)
1257 .and_then(Value::as_str)
1258 .map(str::to_string)
1259 .ok_or_else(|| ReferenceError::MissingField {
1260 case_id: case.case_id.0.clone(),
1261 field: field.into(),
1262 })
1263}
1264
1265fn bool_field(case: &ReferenceCaseV1, field: &str, default: bool) -> bool {
1266 case.input
1267 .get(field)
1268 .and_then(Value::as_bool)
1269 .unwrap_or(default)
1270}
1271
1272fn optional_datetime_field(
1273 case: &ReferenceCaseV1,
1274 field: &str,
1275) -> Result<Option<DateTime<Utc>>, ReferenceError> {
1276 match case.input.get(field) {
1277 None | Some(Value::Null) => Ok(None),
1278 Some(Value::String(raw)) => parse_datetime_field(raw, field, case).map(Some),
1279 Some(_) => Err(ReferenceError::InvalidField {
1280 case_id: case.case_id.0.clone(),
1281 field: field.into(),
1282 reason: "expected string or null".into(),
1283 }),
1284 }
1285}
1286
1287fn value_string_field(
1288 value: &Value,
1289 field: &str,
1290 case: &ReferenceCaseV1,
1291) -> Result<String, ReferenceError> {
1292 value
1293 .get(field)
1294 .and_then(Value::as_str)
1295 .map(str::to_string)
1296 .ok_or_else(|| ReferenceError::InvalidField {
1297 case_id: case.case_id.0.clone(),
1298 field: field.into(),
1299 reason: "expected string".into(),
1300 })
1301}
1302
1303fn value_datetime_field(
1304 value: &Value,
1305 field: &str,
1306 case: &ReferenceCaseV1,
1307) -> Result<DateTime<Utc>, ReferenceError> {
1308 parse_datetime_field(&value_string_field(value, field, case)?, field, case)
1309}
1310
1311fn optional_value_datetime_field(
1312 value: &Value,
1313 field: &str,
1314 case: &ReferenceCaseV1,
1315) -> Result<Option<DateTime<Utc>>, ReferenceError> {
1316 match value.get(field) {
1317 None | Some(Value::Null) => Ok(None),
1318 Some(Value::String(raw)) => parse_datetime_field(raw, field, case).map(Some),
1319 Some(_) => Err(ReferenceError::InvalidField {
1320 case_id: case.case_id.0.clone(),
1321 field: field.into(),
1322 reason: "expected string or null".into(),
1323 }),
1324 }
1325}
1326
1327fn optional_string_array(value: &Value, field: &str) -> Vec<String> {
1328 value
1329 .get(field)
1330 .and_then(Value::as_array)
1331 .into_iter()
1332 .flatten()
1333 .filter_map(Value::as_str)
1334 .map(str::to_string)
1335 .collect()
1336}
1337
1338fn parse_datetime_field(
1339 raw: &str,
1340 field: &str,
1341 case: &ReferenceCaseV1,
1342) -> Result<DateTime<Utc>, ReferenceError> {
1343 DateTime::parse_from_rfc3339(raw)
1344 .map(|value| value.with_timezone(&Utc))
1345 .map_err(|error| ReferenceError::InvalidField {
1346 case_id: case.case_id.0.clone(),
1347 field: field.into(),
1348 reason: error.to_string(),
1349 })
1350}
1351
1352fn tool_field(tool: &Value, field: &str) -> Result<String, ReferenceError> {
1353 tool.get(field)
1354 .and_then(Value::as_str)
1355 .map(str::to_string)
1356 .ok_or_else(|| ReferenceError::InvalidField {
1357 case_id: "tool-exposure".into(),
1358 field: field.into(),
1359 reason: "expected string".into(),
1360 })
1361}
1362
1363fn tool_bool(tool: &Value, field: &str, default: bool) -> bool {
1364 tool.get(field).and_then(Value::as_bool).unwrap_or(default)
1365}
1366
1367pub fn json_string<T: serde::Serialize>(value: &T) -> String {
1368 serde_json::to_value(value)
1369 .ok()
1370 .and_then(|value| value.as_str().map(str::to_string))
1371 .unwrap_or_default()
1372}
1373
1374fn sorted_strings(mut values: Vec<String>) -> Vec<String> {
1375 values.sort();
1376 values
1377}
1378
1379fn sorted_unique_strings(values: Vec<String>) -> Vec<String> {
1380 let mut set = BTreeSet::new();
1381 for value in values {
1382 set.insert(value);
1383 }
1384 set.into_iter().collect()
1385}
1386
1387#[cfg(test)]
1388mod tests {
1389 use super::*;
1390
1391 #[test]
1392 fn reference_cases_cover_required_catalogs() {
1393 let cases = reference_cases();
1394 let report = evaluate_reference_cases(&cases);
1395 let manifest = golden_fixture_manifest();
1396
1397 assert!(report.passed, "{:?}", report.findings);
1398 assert_eq!(manifest.provider_kinds, all_provider_kinds());
1399 assert_eq!(manifest.risk_classes, all_risk_classes());
1400 assert_eq!(manifest.memory_modes, all_memory_modes());
1401 assert_eq!(manifest.receipt_levels, all_receipt_levels());
1402 assert_eq!(manifest.tool_lifecycle_states, all_tool_lifecycle_states());
1403 }
1404
1405 #[test]
1406 fn mismatch_report_has_human_readable_diff() {
1407 let case = reference_permit_case(CanonicalToolSideEffectClass::Write);
1408 let report =
1409 compare_case_to_actual(&case, "unit-test:bad-permit", json!({"decision":"allow"}));
1410
1411 assert!(!report.passed);
1412 assert!(report.findings[0].human_diff.contains("expected"));
1413 assert!(report.findings[0].human_diff.contains("actual"));
1414 assert!(report.findings[0]
1415 .reason_codes
1416 .contains(&"reference-production-mismatch".into()));
1417 }
1418
1419 #[test]
1420 fn safe_coding_exposure_case_interprets_expected() {
1421 let case = reference_safe_coding_exposure_case();
1422 let actual = interpret_case(&case).unwrap();
1423
1424 assert_eq!(actual, case.expected);
1425 assert_eq!(
1426 actual["exposed_tool_ids"],
1427 json!([
1428 "aidens:file-stat:1",
1429 "aidens:patch-propose:1",
1430 "aidens:repo-list:1",
1431 "aidens:repo-read:1",
1432 "aidens:repo-search:1"
1433 ])
1434 );
1435 assert!(actual["blocked_tool_ids"]
1436 .as_array()
1437 .unwrap()
1438 .contains(&json!("aidens:patch-apply:1")));
1439 }
1440
1441 #[test]
1442 fn temporal_query_reference_case_interprets_as_of_semantics() {
1443 let case = reference_temporal_query_case();
1444 let actual = interpret_case(&case).unwrap();
1445
1446 assert_eq!(actual, case.expected);
1447 assert_eq!(actual["deferred"], json!(false));
1448 assert_eq!(actual["temporal_mode"], json!("exact"));
1449 assert_eq!(
1450 actual["visible_claim_version_ids"],
1451 json!(["claim-version-phase09-temporal-new"])
1452 );
1453 assert_eq!(
1454 actual["hidden_claim_version_ids"],
1455 json!(["claim-version-phase09-temporal-old"])
1456 );
1457 }
1458
1459 #[test]
1460 fn p28_bitemporal_reference_fixture_matrix_interprets_required_cases() {
1461 let cases = reference_bitemporal_fixture_cases();
1462
1463 assert_eq!(cases.len(), 5);
1464 for case in &cases {
1465 assert_eq!(interpret_case(case).unwrap(), case.expected);
1466 }
1467
1468 let valid_only = interpret_case(&cases[0]).unwrap();
1469 assert_eq!(
1470 valid_only["visible_claim_version_ids"],
1471 json!(["claim-version-phase09-valid-old"])
1472 );
1473 assert!(valid_only["recorded_as_of"].is_null());
1474
1475 let recorded_only = interpret_case(&cases[1]).unwrap();
1476 assert_eq!(
1477 recorded_only["visible_claim_version_ids"],
1478 json!(["claim-version-phase09-recorded-early"])
1479 );
1480 assert!(recorded_only["valid_as_of"].is_null());
1481
1482 let retro = interpret_case(&cases[2]).unwrap();
1483 assert_eq!(
1484 retro["visible_claim_version_ids"],
1485 json!(["claim-version-phase09-retro-correction"])
1486 );
1487 assert_eq!(
1488 retro["hidden_claim_version_ids"],
1489 json!(["claim-version-phase09-retro-original"])
1490 );
1491 assert!(retro["reason_codes"]
1492 .as_array()
1493 .unwrap()
1494 .contains(&json!("supersession-applied")));
1495
1496 let stale = interpret_case(&cases[3]).unwrap();
1497 assert_eq!(stale["temporal_mode"], json!("degraded"));
1498 assert_eq!(stale["stale_projection"], json!(true));
1499 assert_eq!(stale["view_disclosure_required"], json!(true));
1500
1501 let degraded = interpret_case(&cases[4]).unwrap();
1502 assert_eq!(degraded["temporal_mode"], json!("degraded"));
1503 assert_eq!(degraded["degradation_record_required"], json!(true));
1504 assert!(degraded["reason_codes"]
1505 .as_array()
1506 .unwrap()
1507 .contains(&json!("temporal-index-degraded-disclosed")));
1508 }
1509
1510 #[test]
1511 fn p09_proof_semantic_and_view_reference_cases_are_semantic_not_marker_checks() {
1512 let proof = interpret_case(&reference_proof_waiver_debt_case()).unwrap();
1513 assert_eq!(proof["blocks_promotion"], json!(true));
1514 assert_eq!(proof["waiver_is_not_proof"], json!(true));
1515 assert_eq!(
1516 proof["escalated_debt_ids"],
1517 json!(["proof-debt:proof-obligation:phase09-waiver"])
1518 );
1519 assert_eq!(proof["requested_use_allowed"], json!(false));
1520
1521 let semantic = interpret_case(&reference_semantic_state_contamination_case()).unwrap();
1522 assert_eq!(semantic["can_answer_as_exact"], json!(false));
1523 assert_eq!(semantic["effective_exactness"], json!("refuted"));
1524 assert_eq!(semantic["contradiction_visible"], json!(true));
1525 assert_eq!(semantic["execution_contamination_visible"], json!(true));
1526
1527 let view = interpret_case(&reference_view_widening_disclosure_case()).unwrap();
1528 assert_eq!(view["has_visible_widening_or_degradation"], json!(true));
1529 assert_eq!(view["discloses_policy_events"], json!(true));
1530 assert_eq!(view["result_exactness"], json!("degraded"));
1531 }
1532}