diff --git a/diagnostics/claims_api.py b/diagnostics/claims_api.py index 8808e45..712a46b 100644 --- a/diagnostics/claims_api.py +++ b/diagnostics/claims_api.py @@ -68,8 +68,22 @@ def _normalize_for_match(s): # ─── Frontmatter parse ───────────────────────────────────────────────────── +_CODE_FENCE_WRAPPER_RE = re.compile(r"^\s*```(?:markdown|md)?\s*\n(.*?)\n```\s*$", re.DOTALL) + + def _split_frontmatter(text): - """Return (frontmatter_dict, body_str) or (None, None) if not a claim file.""" + """Return (frontmatter_dict, body_str) or (None, None) if not a claim file. + + Tolerates files wrapped in a top-level ```markdown ... ``` code fence — + some agents have produced these (e.g. Montreal Protocol claim from Astra, + 2024-12-09). Unwrap once before frontmatter detection. + """ + if not text: + return None, None + m = _CODE_FENCE_WRAPPER_RE.match(text) + if m: + text = m.group(1) + text = text.lstrip() if not text.startswith("---"): return None, None try: @@ -465,8 +479,13 @@ async def handle_claim_detail(request): filepath = CODEX_BASE / rel_path fm, body = _read_claim_file(filepath) if not fm: - return web.json_response({"error": "frontmatter parse failed", "slug": slug}, - status=500, headers=CORS_HEADERS) + # File exists at this stem but has no parseable frontmatter — almost + # always a stray enrichment fragment that landed in domains/ without + # being merged into a parent claim. Surfacing as 404 (no claim here) + # not 500: the caller can't act on it differently anyway. + return web.json_response({"error": "claim not found", "slug": slug, + "reason": "file_no_frontmatter"}, + status=404, headers=CORS_HEADERS) # Open read-only DB connection for this request conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)