From cfbfa533983328c532f0b08d84e1128202daa1bb Mon Sep 17 00:00:00 2001 From: Tarik Moussa Date: Mon, 15 Jun 2026 16:24:22 +0200 Subject: [PATCH] fix(wiki): protect math in cross-refs; drop no-op DB call; fix mark order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - R-9 (MED): _inject_cross_refs now protects LaTeX math spans ($…$, $$…$$, \(…\), \[…\]) like inline code, so a concept name inside a formula is no longer rewritten to a [[slug]] link and corrupting the LaTeX. Regression test added. - C-13 (LOW): mark ungrounded claims on the raw body BEFORE injecting cross-refs (render then inject), so a concept name inside a claim cannot stop the ⚠ regex from matching. - C-12 (LOW): _try_embed_formulas is now a true no-op — it previously issued a per-concept information_schema round-trip that did nothing. Co-Authored-By: Claude Fable 5 --- codex/wiki.py | 44 ++++++++++++++++++++------------------ tests/wiki/test_compile.py | 13 +++++++++++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/codex/wiki.py b/codex/wiki.py index b913258..e66024f 100644 --- a/codex/wiki.py +++ b/codex/wiki.py @@ -402,6 +402,13 @@ def _detect_conflicts( _INLINE_CODE_RE = re.compile(r"`[^`]+`") +# LaTeX math spans — protected from cross-ref injection so a concept name inside +# a formula is not rewritten to a [[slug]] link, corrupting the LaTeX (audit R-9). +_MATH_SPAN_RE = re.compile( + r"\$\$.*?\$\$|\$[^$\n]*?\$|\\\(.*?\\\)|\\\[.*?\\\]", + re.DOTALL, +) + def _inject_cross_refs( markdown: str, @@ -411,11 +418,12 @@ def _inject_cross_refs( """Replace occurrences of other concept titles/aliases with ``[[slug]]`` links. Only exact case-insensitive whole-word matches outside of existing - ``[[…]]`` blocks or inline code spans are replaced. - Inline-code spans (`` `…` ``) are temporarily protected by null-byte - placeholders and restored after injection. + ``[[…]]`` blocks, inline code spans, or LaTeX math spans are replaced. + Inline-code (`` `…` ``) and math (``$…$``, ``$$…$$``, ``\\(…\\)``, + ``\\[…\\]``) spans are temporarily protected by null-byte placeholders and + restored after injection. """ - # Step 1: protect inline-code spans from replacement + # Step 1: protect inline-code and math spans from replacement placeholders: dict[str, str] = {} def _protect(m: re.Match[str]) -> str: @@ -424,6 +432,7 @@ def _inject_cross_refs( return key markdown = _INLINE_CODE_RE.sub(_protect, markdown) + markdown = _MATH_SPAN_RE.sub(_protect, markdown) # don't rewrite names inside LaTeX (R-9) # Step 2: inject cross-refs on unprotected text for concept in all_concepts: @@ -602,14 +611,14 @@ def compile_concept( claims = _parse_claims(raw_output) claims = _run_grounding_guard(claims, chunks) - _all_concepts = all_concepts or [] - raw_output = _inject_cross_refs(raw_output, _all_concepts, concept.slug) - - # Graceful: try to embed formula chunks (F-09) — skip if table missing + # Graceful: formula-embedding hook (F-09) — currently a no-op (audit C-12). _try_embed_formulas(concept, chunks) compiled_at = datetime.now(UTC) + # Mark ungrounded claims on the raw body FIRST, then inject cross-refs: a + # concept name inside a claim must not stop the ⚠ regex from matching (C-13). markdown = _render_page_markdown(concept, raw_output, claims, compiled_at) + markdown = _inject_cross_refs(markdown, all_concepts or [], concept.slug) return ConceptPage( concept=concept, @@ -621,20 +630,13 @@ def compile_concept( def _try_embed_formulas(concept: Concept, chunks: list[dict[str, Any]]) -> None: - """Attempt to look up formula chunks for the concept — graceful no-op if F-09 absent.""" - try: - from codex.db import get_conn + """Reserved hook for embedding F-09 formula chunks into a page. - with get_conn() as conn: - # Check if formulas table exists - row = conn.execute( - "SELECT 1 FROM information_schema.tables WHERE table_name = 'formulas'" - ).fetchone() - if row is None: - return # F-09 not present - # (Future: embed relevant raw_latex into the page) - except Exception: # noqa: BLE001 - return # DB not reachable or other error — degrade gracefully + Currently a no-op — formula embedding is not implemented yet. Kept as a stable + seam for compile_concept's call site. The previous body issued a per-concept + DB round-trip (information_schema lookup) that did nothing (audit C-12). + """ + return # --------------------------------------------------------------------------- diff --git a/tests/wiki/test_compile.py b/tests/wiki/test_compile.py index 6835ec0..632905c 100644 --- a/tests/wiki/test_compile.py +++ b/tests/wiki/test_compile.py @@ -274,6 +274,19 @@ def test_inject_cross_refs_skips_inline_code() -> None: assert "[[circle-packing]]" in result # bare occurrence replaced +def test_inject_cross_refs_skips_math_spans() -> None: + """Concept names inside LaTeX math are NOT rewritten to [[slug]] links (audit R-9).""" + concepts = [ + Concept(slug="energy", title="Energy", aliases=[]), + FIXTURE_CONCEPT, + ] + text = "Prose Energy here. Math $x = Energy^2$ and inline \\(Energy\\) stay literal." + result = _inject_cross_refs(text, concepts, current_slug="lobachevsky-function") + assert "$x = Energy^2$" in result # display/inline $...$ math untouched + assert "\\(Energy\\)" in result # \(...\) math untouched + assert "Prose [[energy]] here." in result # prose occurrence still linked + + def test_inject_cross_refs_full_list_even_when_single_concept() -> None: """Cross-ref injection uses the full concept list, not just the compiled concept.""" concepts = [