//! Regression pin: the canonical docs must stay present-tense-honest. //! //! The `design/` ledger (and `docs/PROSE_ROUNDTRIP.md`) must not assert //! something exists/works/changed that does not. This pin fails if any //! swept-and-corrected Wunschdenken / post-mortem string reappears, or //! if any present-tense corrected anchor (incl. the protected //! honest-reserved exceptions) is dropped. Matching is //! whitespace-normalised so the assertions are independent of the //! docs' hard line-wrap. Companion to `effect_doc_honesty_pin.rs` and //! to Sweep 5 of `bench/architect_sweeps.sh`. use ailang_core::FORM_A_SPEC; use std::fs; fn read(rel: &str) -> String { let p = concat!(env!("CARGO_MANIFEST_DIR"), "/../../"); fs::read_to_string(format!("{p}{rel}")) .unwrap_or_else(|e| panic!("read {rel}: {e}")) } /// Collapse every whitespace run (incl. newline + indent) to a single /// space, so a phrase split across the docs' hard line-wrap still /// matches a single-line assertion substring. fn norm(s: &str) -> String { s.split_whitespace().collect::>().join(" ") } /// The `design/` prose set the absent-pins span after the role-split /// (these pins formerly scanned the single canonical design doc): the /// RC + bump memory-model narrative /// (`models/rc-uniqueness.md`), float semantics /// (`contracts/float-semantics.md`), the typeclass contract incl. /// prelude classes & "does NOT commit to" /// (`contracts/typeclasses.md`), the effects model /// (`models/effects.md`), and the authoring-surface notation model /// (`models/authoring-surface.md`). A swept Wunschdenken / /// doc-archaeology string must be absent from **all** of them, so the /// reads are joined and a single absence assertion covers the set. fn design_corpus() -> String { [ read("design/models/rc-uniqueness.md"), read("design/contracts/float-semantics.md"), read("design/contracts/typeclasses.md"), read("design/contracts/method-dispatch.md"), read("design/contracts/prelude-classes.md"), read("design/contracts/language-constraints.md"), read("design/models/effects.md"), read("design/models/authoring-surface.md"), read("design/models/prose-projection.md"), ] .join("\n") } #[test] fn design_md_has_no_wunschdenken() { let d = norm(&design_corpus()); assert!(!d.contains("is on the path to retirement"), "design/: Boehm retirement is forward intent — it lives in the Gitea backlog, not the ledger"); assert!(!d.contains("Will back `Show Float`"), "design/: 'Will back Show Float' asserts a future capability as fact"); assert!(!d.contains("codegen ships in a future iter"), "design/: float_to_str codegen is reserved/not-yet-shipped, not a promised future ship"); assert!(!d.contains("may land in a future iteration"), "design/: auto-derivation 'may land in a future iteration' is Wunschdenken"); assert!(!d.contains("If/when concurrency arrives"), "design/: 'if/when concurrency arrives … will be' is forward conditional"); assert!(!d.contains("was deferred — all three layer additively"), "design/: deferred future integration paths are roadmap intent, not the ledger"); assert!(!d.contains("was deferred from milestone 22 entirely"), "design/: 'was deferred from milestone 22 entirely … A future iter ships' is history+Wunschdenken"); assert!(!d.contains("A future iter ships the full prose projection"), "design/: Form-B class/instance prose 'a future iter ships' is forward intent → roadmap"); assert!(!d.contains("transitional Boehm"), "design/: Boehm narrative is retired — must not re-emerge in the ledger"); assert!(!d.contains("parity oracle"), "design/: Boehm-as-parity-oracle is retired narrative — git log carries the history"); assert!(!d.contains("GC_malloc"), "design/: GC_malloc references are retired — RC + bump are the only allocators"); assert!(!d.contains("libgc"), "design/: libgc references are retired — no GC link dependency exists anymore"); } #[test] fn design_md_has_no_doc_archaeology() { let d = norm(&design_corpus()); assert!(!d.contains("An earlier draft of this Decision said"), "design/: doc-archaeology ('an earlier draft said … a follow-up replaced it') lives in git log"); assert!(!d.contains("previously all diagnostics were `Error`"), "design/: 'previously all diagnostics were Error' is project history → git log"); assert!(!d.contains("What did change in 2026-05-13 (this milestone) is"), "design/: 'what did change in (this milestone)' is post-mortem narrative"); assert!(!d.contains("primitives were retired 2026-05-14 in the per-type-print-op retirement"), "design/: 'X were retired in iter Y' is history → git log"); assert!(!d.contains("were retired in the per-type-print-op retirement (2026-05-14)"), "design/: the second per-type-print-op retirement-narrative sentence is history"); assert!(!d.contains("originally listed here as the eighth fixture, was retired"), "design/: 'originally listed … was retired by milestone X' is doc-archaeology"); } #[test] fn design_md_present_tense_anchors_present() { // Each anchor is read from its post-split home. Phrases that only // ever lived in the (now-removed) decision-records annex are // dropped — anything the design/ ledger truly needs lives in // design/contracts/ or design/models/. let honesty = norm(&read("design/contracts/honesty-rule.md")); let scope = norm(&read("design/contracts/scope-boundaries.md")); let memory = norm(&read("design/contracts/memory-model.md")); let str_abi = norm(&read("design/contracts/str-abi.md")); let prelude_classes = norm(&read("design/contracts/prelude-classes.md")); // the discriminator meta-subsection -> contracts/honesty-rule.md assert!(honesty.contains("the honesty rule it holds itself to"), "honesty-rule.md must carry the discriminator meta-subsection heading"); assert!(honesty.contains("whether the document asserts something exists, works, or changed that does not"), "honesty-rule.md discriminator must state the operational test"); // protected honest-reserved exceptions — over-correction guard assert!(scope.contains("`Diverge` is a reserved effect name with no op and no codegen"), "the gold-form honest-reserved anchor must remain in scope-boundaries.md (do not over-strip)"); assert!(memory.contains("a tiebreaker, not a rationale"), "the self-labelled tiebreaker is honest and stays in memory-model.md (do not over-strip)"); // corrected present-tense anchors assert!(str_abi.contains("type-installed; codegen is reserved and not yet shipped"), "float_to_str must be present-tense honest-reserved in str-abi.md"); assert!(prelude_classes.contains("`io/print_str` is the only built-in direct-output effect-op"), "the print-op set must be stated present-tense in prelude-classes.md (post-split home), not as a retirement narrative"); } #[test] fn prose_roundtrip_md_has_no_wunschdenken() { let p = norm(&read("docs/PROSE_ROUNDTRIP.md")); assert!(!p.contains("A future iter may layer a tool-use schema"), "PROSE_ROUNDTRIP.md: tool-use/MCP forward intent lives in the Gitea backlog"); assert!(p.contains("the lowest-common-denominator cycle and always works with any client"), "PROSE_ROUNDTRIP.md must close present-tense on the static-prompt path"); } #[test] fn form_a_scalar_param_carveout_present_and_old_rule_absent() { // form_a.md is read via the canonical include_str! const, not a // re-derived path: single source of truth, compile-checked, no // stale-path bug. norm() collapses the docs' hard line-wrap so // every asserted substring below is the single-spaced form // (planner Step-5 item 6: line-wrap is structurally discharged, // the substrings need not be contiguous in the .md source). let f = norm(FORM_A_SPEC); let d = norm(&read("design/contracts/embedding-abi.md")); // --- ABSENT: the four old unconditional phrasings must be gone --- assert!(!f.contains("Mode annotations on every `(fn ...)` parameter."), "form_a.md item 1 must no longer head with the unconditional 'every (fn ...) parameter' rule (site 3)"); assert!(!f.contains("All parameters of a `(fn ...)` MUST carry a mode annotation"), "form_a.md ### Function prose must no longer state the unconditional all-params rule (site 1)"); assert!(!f.contains("(DO NOT USE in new (fn ...) defs)"), "form_a.md grammar block must no longer blanket-forbid the bare (implicit-mode) form (site 2)"); assert!(!f.contains("Wrap every `(fn ...)` parameter in `(own ...)` or `(borrow ...)`."), "form_a.md Pitfalls bullet must no longer state the unconditional 'wrap every parameter' imperative (site 4)"); // --- PRESENT: the scalar-parameter carve-out, the four sites --- assert!(f.contains("A scalar (non-heap-shaped) parameter or return takes **no** mode: write it bare, e.g. `(con Int)`."), "form_a.md item 1 must state the scalar-parameter carve-out symmetrically with the return-type carve-out (site 3)"); assert!(f.contains("a scalar (non-heap-shaped: `(con Int)`, `(con Bool)`, `(con Unit)`, `(con Str)`) takes **no** mode and is written bare"), "form_a.md ### Function prose must carry the scalar carve-out (site 1)"); assert!(f.contains("; no mode — REQUIRED for scalars (Int/Bool/Unit/Str), rejected for heap-shaped"), "form_a.md grammar block comment must state the bare form is required for scalars (site 2)"); assert!(f.contains("Putting a mode on a scalar parameter."), "form_a.md Pitfalls must carry the inverse pitfall — over-wrapping a scalar (site 4)"); // --- PRESENT: the design/contracts/embedding-abi.md mirror --- assert!(d.contains("Export parameters are written **bare**: a scalar type carries no `own`/`borrow` mode"), "design/contracts/embedding-abi.md must mirror the canonical bare-scalar export-param rule"); }