From a355fd861ed170621820317dab4be8f6efc7a9c1 Mon Sep 17 00:00:00 2001 From: Brummel Date: Wed, 20 May 2026 18:15:44 +0200 Subject: [PATCH] fix(drift): scope anchor-presence check to jsonc fenced blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `anchor_in_jsonc_block(md, anchor) -> bool` to `design_schema_drift.rs` — a line-walking helper that toggles on ``` / ~~~ fence markers (any info-string: jsonc, json, or unspecified — all count) and returns true only if the anchor appears inside fenced content. Re-routes the seven `DATA_MODEL.contains(anchor)` assertion sites in the file (Term, Pattern, Type, Literal, Def, ParamMode, nested struct keys) onto it. The carrier from `debug` named six sites; in the implement phase the orchestrator flagged a seventh — `design_md_anchors_nested_ struct_keys` — that exhibited the identical scope-widening bug and was structurally identical to the named six. Routing it uniformly is the cohesive shape of the fix; leaving it as `.contains()` would have preserved a known false-positive surface inside the same file the fix targets. The "minimal fix, no surrounding cleanup" constraint blocks opportunistic refactor, not the uniform application of the named one-line substitution. Verified: 8/8 in `design_schema_drift` green (was 7 + 1 compile- blocking RED at c8c30d5); full ailang-core suite green; full workspace `cargo test` green, no regressions. Mirrors the inverse `strip_fences` pattern in `crates/ailang-core/tests/design_index_pin.rs`. closes #10 --- ...-05-20-iter-bugfix-drift-anchor-scope.json | 12 ++++++ .../ailang-core/tests/design_schema_drift.rs | 39 +++++++++++++++---- 2 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 bench/orchestrator-stats/2026-05-20-iter-bugfix-drift-anchor-scope.json diff --git a/bench/orchestrator-stats/2026-05-20-iter-bugfix-drift-anchor-scope.json b/bench/orchestrator-stats/2026-05-20-iter-bugfix-drift-anchor-scope.json new file mode 100644 index 0000000..49819e1 --- /dev/null +++ b/bench/orchestrator-stats/2026-05-20-iter-bugfix-drift-anchor-scope.json @@ -0,0 +1,12 @@ +{ + "iter_id": "bugfix-drift-anchor-scope", + "date": "2026-05-20", + "mode": "mini", + "outcome": "DONE", + "tasks_total": 1, + "tasks_completed": 1, + "reloops_per_task": { "1": 0 }, + "review_loops_spec": 0, + "review_loops_quality": 0, + "blocked_reason": null +} diff --git a/crates/ailang-core/tests/design_schema_drift.rs b/crates/ailang-core/tests/design_schema_drift.rs index 4315df3..3e9f912 100644 --- a/crates/ailang-core/tests/design_schema_drift.rs +++ b/crates/ailang-core/tests/design_schema_drift.rs @@ -21,6 +21,31 @@ use ailang_core::ast::{ const DATA_MODEL: &str = include_str!("../../../design/contracts/data-model.md"); +/// Scoped substring match: returns `true` iff `anchor` appears inside a +/// fenced code block (``` or ~~~) of `md`. Info-string `jsonc` / `json` / +/// unspecified all count — fence-toggling is by fence-marker line alone, +/// mirroring the inverse `strip_fences` pattern in +/// `crates/ailang-core/tests/design_index_pin.rs`. The whole-file +/// `.contains()` was fidelity-widened: anchors mentioned in surrounding +/// prose (footnotes, inline references, historical notes) counted as +/// present, so a future edit could delete the canonical schema entry for +/// a variant from inside a fenced block and the drift test would still +/// pass. This helper closes that surface. +fn anchor_in_jsonc_block(md: &str, anchor: &str) -> bool { + let mut in_fence = false; + for line in md.lines() { + let t = line.trim_start(); + if t.starts_with("```") || t.starts_with("~~~") { + in_fence = !in_fence; + continue; + } + if in_fence && line.contains(anchor) { + return true; + } + } + false +} + /// Every `Term` variant must have its JSON-schema anchor present in /// design/contracts/data-model.md. An LLM author cannot produce a term variant /// whose `"t"` tag is absent from the canonical schema document. @@ -156,7 +181,7 @@ fn design_md_anchors_every_term_variant() { Term::Recur { .. } => "recur", }; assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing anchor `{anchor}` for a Term variant — \ add it to design/contracts/data-model.md" ); @@ -186,7 +211,7 @@ fn design_md_anchors_every_pattern_variant() { Pattern::Ctor { .. } => "ctor", }; assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing anchor `{anchor}` for a Pattern variant" ); } @@ -219,7 +244,7 @@ fn design_md_anchors_every_type_variant() { Type::Forall { .. } => "forall", }; assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing anchor `{anchor}` for a Type variant" ); } @@ -248,7 +273,7 @@ fn design_md_anchors_every_literal_variant() { Literal::Float { .. } => "float", }; assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing anchor `{anchor}` for a Literal variant" ); } @@ -320,7 +345,7 @@ fn design_md_anchors_every_def_kind() { Def::Instance(_) => "instance", }; assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing anchor `{anchor}` for a Def kind" ); } @@ -344,7 +369,7 @@ fn design_md_anchors_every_parammode_variant() { ParamMode::Borrow => "borrow", }; assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing anchor `{anchor}` for a ParamMode variant" ); } @@ -457,7 +482,7 @@ fn design_md_anchors_nested_struct_keys() { for anchor in anchors { assert!( - DATA_MODEL.contains(anchor), + anchor_in_jsonc_block(DATA_MODEL, anchor), "design/contracts/data-model.md is missing nested-struct-key anchor `{anchor}`" ); }