fix(claims): resolve long activity-feed slugs to canonical file stems
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
Activity feed emits slugs derived from PR description (the slugified claim title), which can be longer than the on-disk file stem (agents pick shorter hand-chosen filenames). Pure exact-stem lookup 404s on those. Three-tier resolution in handle_claim_detail: 1. Exact stem match (existing behavior) 2. Title fallback: normalize requested slug, look up via by_title index (already populated from frontmatter title during _build_indexes) 3. Prefix fallback: longest common prefix among stems, anchored at 32 chars to prevent spurious hits Response slug returns the canonical on-disk stem so frontend share-links and caches converge to one form. Repro: GET /api/claims/spacex-and-amazon-kuiper-non-endorsement-of-wef-debris- guidelines-demonstrates-systemic-voluntary-governance-failure-at-the-scale- where-it-matters-most was 404; now 200, returns shorter on-disk slug '...-governance-failure'. Negative case (nonsense slug) still 404s. Reported by Ship — Cory-facing demo path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1bc541ac93
commit
73880e138d
1 changed files with 33 additions and 2 deletions
|
|
@ -423,12 +423,43 @@ async def handle_claim_detail(request):
|
||||||
|
|
||||||
One round-trip, all data resolved server-side. Wikilinks pre-resolved.
|
One round-trip, all data resolved server-side. Wikilinks pre-resolved.
|
||||||
"""
|
"""
|
||||||
slug = request.match_info["slug"]
|
requested_slug = request.match_info["slug"]
|
||||||
by_title, by_stem = _build_indexes()
|
by_title, by_stem = _build_indexes()
|
||||||
|
|
||||||
|
# Resolution order: exact stem → title-normalized (handles description-derived
|
||||||
|
# slugs from /api/activity-feed that are longer than on-disk file stems) →
|
||||||
|
# stem-as-prefix (handles description-derived slugs that are shorter than the
|
||||||
|
# file stem because the description was truncated upstream).
|
||||||
|
slug = requested_slug
|
||||||
rel_path = by_stem.get(slug)
|
rel_path = by_stem.get(slug)
|
||||||
if not rel_path:
|
if not rel_path:
|
||||||
return web.json_response({"error": "claim not found", "slug": slug},
|
# Title fallback: requested slug = slugified frontmatter title
|
||||||
|
norm = _normalize_for_match(requested_slug)
|
||||||
|
resolved_stem = by_title.get(norm)
|
||||||
|
if resolved_stem:
|
||||||
|
slug = resolved_stem
|
||||||
|
rel_path = by_stem.get(resolved_stem)
|
||||||
|
if not rel_path:
|
||||||
|
# Prefix fallback: walk stems sharing a common prefix with the request,
|
||||||
|
# pick longest match. Anchored at 32 chars to avoid spurious hits.
|
||||||
|
norm_req = _normalize_for_match(requested_slug)
|
||||||
|
best_stem = None
|
||||||
|
best_len = 0
|
||||||
|
for stem in by_stem:
|
||||||
|
norm_stem = _normalize_for_match(stem)
|
||||||
|
common = 0
|
||||||
|
for a, b in zip(norm_req, norm_stem):
|
||||||
|
if a != b:
|
||||||
|
break
|
||||||
|
common += 1
|
||||||
|
if common >= 32 and common > best_len:
|
||||||
|
best_stem = stem
|
||||||
|
best_len = common
|
||||||
|
if best_stem:
|
||||||
|
slug = best_stem
|
||||||
|
rel_path = by_stem.get(best_stem)
|
||||||
|
if not rel_path:
|
||||||
|
return web.json_response({"error": "claim not found", "slug": requested_slug},
|
||||||
status=404, headers=CORS_HEADERS)
|
status=404, headers=CORS_HEADERS)
|
||||||
|
|
||||||
filepath = CODEX_BASE / rel_path
|
filepath = CODEX_BASE / rel_path
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue