fix(wiki): protect math in cross-refs; drop no-op DB call; fix mark order

- 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 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-06-15 16:24:22 +02:00
parent ae5e9a528b
commit cfbfa53398
2 changed files with 36 additions and 21 deletions

View File

@@ -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
# ---------------------------------------------------------------------------

View File

@@ -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 = [