diff --git a/crates/ailang-core/tests/design_schema_drift.rs b/crates/ailang-core/tests/design_schema_drift.rs index 0a6596b..4315df3 100644 --- a/crates/ailang-core/tests/design_schema_drift.rs +++ b/crates/ailang-core/tests/design_schema_drift.rs @@ -350,6 +350,82 @@ fn design_md_anchors_every_parammode_variant() { } } +/// The anchor-presence check must scope its substring match to ```jsonc``` +/// (and ``` / ```json) fenced code blocks. The whole-file `.contains()` is +/// fidelity-widened: a future edit can delete the canonical schema entry for +/// a variant from inside a fenced block while leaving the anchor string +/// mentioned in surrounding prose (e.g. a "design rationale" footnote, an +/// inline reference like `the "t": "lit" form`, or a historical-note +/// section). The drift test would silently pass, and downstream LLM authors +/// would lose the canonical schema for a variant the AST still produces. +/// +/// This test pins the property in two directions: +/// 1. A markdown string with the anchor present ONLY in prose (no fenced +/// block) must report ABSENT under the scoped helper. The unscoped +/// `.contains()` reports PRESENT for the same input — that mismatch is +/// the bug surface. +/// 2. A markdown string with the anchor present ONLY inside a ```jsonc``` +/// block must report PRESENT under the scoped helper. +/// +/// Gitea issue #10. The helper `anchor_in_jsonc_block` does not yet exist — +/// this test is RED until `skills/implement` mini-mode adds it and re-routes +/// the six existing call sites onto it. +#[test] +fn anchor_presence_check_is_scoped_to_jsonc_blocks() { + // Anchor mentioned only in prose — the false-pass surface. The whole + // sentence after the heading is plain markdown body text; no fenced + // block exists in this fixture at all. + let prose_only = r#"# Data model + +The `"t": "lit"` form used to be the canonical literal anchor. See +historical note below. + +## Historical note + +Older drafts pinned the literal schema as `"t": "lit"`; the current +schema uses a different shape (see commit log). +"#; + + // Anchor present only inside a fenced jsonc block — the true-positive + // surface. Prose outside the block does not mention the anchor. + let jsonc_only = r#"# Data model + +The literal form is canonical. + +```jsonc +{ "t": "lit", "lit": Literal } +``` + +End. +"#; + + // Unscoped substring match — what the file does today. Both inputs + // report PRESENT, and that is the bug: prose-only must not count. + assert!(prose_only.contains(r#""t": "lit""#)); + assert!(jsonc_only.contains(r#""t": "lit""#)); + + // Scoped helper — what the file must do. Prose-only is ABSENT, + // jsonc-only is PRESENT. This call is the RED: the helper does not + // yet exist, so this test fails to compile until `implement` mini-mode + // factors it out and re-routes the six call sites onto it. + assert!( + !anchor_in_jsonc_block(prose_only, r#""t": "lit""#), + "scoped helper must NOT count an anchor that lives only in prose" + ); + assert!( + anchor_in_jsonc_block(jsonc_only, r#""t": "lit""#), + "scoped helper MUST count an anchor that lives inside a ```jsonc``` block" + ); + + // Live document: the helper must also report PRESENT for the live + // data-model.md (proves the helper does not over-narrow and break + // the existing six checks). + assert!( + anchor_in_jsonc_block(DATA_MODEL, r#""t": "lit""#), + "scoped helper must find live anchors inside data-model.md ```jsonc``` blocks" + ); +} + /// Nested struct key anchors must be present in design/contracts/data-model.md. /// These keys appear inside `Suppress`, `ClassMethod`, `InstanceMethod`, /// and `Type::Forall` — they are not discriminators but they ARE part