diff --git a/hydra-gates/scripts/lib/check_spec_anchors.py b/hydra-gates/scripts/lib/check_spec_anchors.py index 343a018c..5e78b1bb 100644 --- a/hydra-gates/scripts/lib/check_spec_anchors.py +++ b/hydra-gates/scripts/lib/check_spec_anchors.py @@ -246,7 +246,24 @@ def has_anchor(md_path: str, fragment: str) -> bool: """True when *fragment* names a heading or task item in *md_path*.""" frag_slug = slugify(fragment) frag_alt = slugify(re.sub(r'^task-', '', fragment)) - frags = (frag_slug, frag_alt) + # The fragment VERBATIM, alongside its slugified forms. + # + # `heading_aliases` already produces both spellings of a heading — the + # kebab one and the one GitHub actually publishes — but the fragment was + # being pushed through `slugify()` first, and that rewrites `_` to `-`. + # A tag copied from a real GitHub link therefore stopped matching the + # gh alias it was copied FROM: + # + # heading ### Requirement: Trace-scoped call correlation via + # call_log.sessionId (REQ-011) + # gh alias …-via-call_logsessionid-req-011 ← what GitHub links to + # fragment …-via-call_logsessionid-req-011 ← what the author wrote + # slugified …-via-call-logsessionid-req-011 ← what was compared + # + # which matches neither alias: not the gh one (underscore rewritten) and + # not the kebab one (`call-log-sessionid`, split at the underscore). + # Reported as "anchor not found" on a tag whose link works. + frags = (frag_slug, frag_alt, fragment.lower()) pos_m = re.fullmatch(r'(?:task-)?(\d+)', fragment) positional = int(pos_m.group(1)) if pos_m else None item_count = 0 diff --git a/hydra-gates/scripts/lib/test_check_spec_anchors.py b/hydra-gates/scripts/lib/test_check_spec_anchors.py index 1ce5e84e..41b6184a 100644 --- a/hydra-gates/scripts/lib/test_check_spec_anchors.py +++ b/hydra-gates/scripts/lib/test_check_spec_anchors.py @@ -379,6 +379,52 @@ def test_a_wholly_invented_fragment_is_still_reported(self): self.assertIn("anchor not found", findings[0]) +class FragmentCopiedFromAGitHubLink(unittest.TestCase): + """A tag copied from a real GitHub link must resolve. + + ``heading_aliases`` already emits both spellings of a heading — the kebab + one and the one GitHub publishes — but the FRAGMENT was pushed through + ``slugify()`` before comparison, and that rewrites ``_`` to ``-``. So a + fragment matched neither alias: not the gh one (its underscore had been + rewritten) and not the kebab one (which splits at the underscore). + Observed on openconnector ``SynchronizationService`` (2026-08-07). + """ + + MD = ( + "# HTTP call engine\n\n" + "### Requirement: Trace-scoped call correlation via call_log.sessionId (REQ-011)\n\n" + "Body.\n" + ) + + def test_the_github_spelling_resolves(self): + # What GitHub links to: underscore kept, dot dropped. + self.assertTrue(_anchor( + self.MD, + "requirement-trace-scoped-call-correlation-via-call_logsessionid-req-011", + )) + + def test_the_kebab_spelling_still_resolves(self): + self.assertTrue(_anchor( + self.MD, + "requirement-trace-scoped-call-correlation-via-call-log-sessionid-req-011", + )) + + def test_a_near_miss_does_not_resolve(self): + # The control. Accepting the raw fragment must not become "accept any + # fragment that shares a prefix" — this one names a heading the file + # does not have. + self.assertFalse(_anchor( + self.MD, + "requirement-trace-scoped-call-correlation-via-call_logrequestid-req-011", + )) + + def test_an_underscore_fragment_against_a_file_without_it_does_not_resolve(self): + self.assertFalse(_anchor( + "# Other\n\n### Requirement: Something else entirely (REQ-012)\n", + "requirement-trace-scoped-call-correlation-via-call_logsessionid-req-011", + )) + + class GateIsNotBlind(unittest.TestCase): """One consolidated demonstration that the gate still has teeth.