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"`[^`]+`") _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( def _inject_cross_refs(
markdown: str, markdown: str,
@@ -411,11 +418,12 @@ def _inject_cross_refs(
"""Replace occurrences of other concept titles/aliases with ``[[slug]]`` links. """Replace occurrences of other concept titles/aliases with ``[[slug]]`` links.
Only exact case-insensitive whole-word matches outside of existing Only exact case-insensitive whole-word matches outside of existing
``[[…]]`` blocks or inline code spans are replaced. ``[[…]]`` blocks, inline code spans, or LaTeX math spans are replaced.
Inline-code spans (`` `…` ``) are temporarily protected by null-byte Inline-code (`` `…` ``) and math (``$…$``, ``$$…$$``, ``\\(…\\)``,
placeholders and restored after injection. ``\\[…\\]``) 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] = {} placeholders: dict[str, str] = {}
def _protect(m: re.Match[str]) -> str: def _protect(m: re.Match[str]) -> str:
@@ -424,6 +432,7 @@ def _inject_cross_refs(
return key return key
markdown = _INLINE_CODE_RE.sub(_protect, markdown) 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 # Step 2: inject cross-refs on unprotected text
for concept in all_concepts: for concept in all_concepts:
@@ -602,14 +611,14 @@ def compile_concept(
claims = _parse_claims(raw_output) claims = _parse_claims(raw_output)
claims = _run_grounding_guard(claims, chunks) claims = _run_grounding_guard(claims, chunks)
_all_concepts = all_concepts or [] # Graceful: formula-embedding hook (F-09) — currently a no-op (audit C-12).
raw_output = _inject_cross_refs(raw_output, _all_concepts, concept.slug)
# Graceful: try to embed formula chunks (F-09) — skip if table missing
_try_embed_formulas(concept, chunks) _try_embed_formulas(concept, chunks)
compiled_at = datetime.now(UTC) 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 = _render_page_markdown(concept, raw_output, claims, compiled_at)
markdown = _inject_cross_refs(markdown, all_concepts or [], concept.slug)
return ConceptPage( return ConceptPage(
concept=concept, concept=concept,
@@ -621,20 +630,13 @@ def compile_concept(
def _try_embed_formulas(concept: Concept, chunks: list[dict[str, Any]]) -> None: 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.""" """Reserved hook for embedding F-09 formula chunks into a page.
try:
from codex.db import get_conn
with get_conn() as conn: Currently a no-op — formula embedding is not implemented yet. Kept as a stable
# Check if formulas table exists seam for compile_concept's call site. The previous body issued a per-concept
row = conn.execute( DB round-trip (information_schema lookup) that did nothing (audit C-12).
"SELECT 1 FROM information_schema.tables WHERE table_name = 'formulas'" """
).fetchone() return
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
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -274,6 +274,19 @@ def test_inject_cross_refs_skips_inline_code() -> None:
assert "[[circle-packing]]" in result # bare occurrence replaced 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: 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.""" """Cross-ref injection uses the full concept list, not just the compiled concept."""
concepts = [ concepts = [