Skip to content

wiki: escape brackets in raw article labels (#3547) - #3550

Open
ayushcodes10 wants to merge 4 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3547-wiki-label-bracket-escaping
Open

ayushcodes10 wants to merge 4 commits into
Graphify-Labs:v8from
ayushcodes10:fix-3547-wiki-label-bracket-escaping

Conversation

@ayushcodes10

Copy link
Copy Markdown
Contributor

Summary

Fixes the first of the two bugs in #3547 (literal placeholder/mis-encoded [[wikilinks]] in generated Community_N.md pages).

Bug 1 (literal placeholder wikilinks) — fixed here. _md_link already escapes [/] before a label becomes link text, because a node label is extracted source content and can literally contain a bracketed substring — most plausibly from a doc that discusses or demonstrates wikilink syntax. Three other sites print a label straight into an article body without going through that escaping at all: a community's own title, its "Key Concepts" node listing, and a god node's own title. Printed unescaped, a label like [[wikilink]] renders as a real (and always dead — the wiki export never writes bracket-style links) wikilink instead of the plain text it actually is. Pulled the escaping logic out of _md_link into a small shared helper (_escape_md_brackets) and applied it at all three sites.

Bug 2 (percent-encoded target vs. raw filename) — already fixed on v8. Traced this directly: _md_link's own docstring already documents removing quote()-based percent-encoding as issue #2597, and confirmed _safe_filename strips every character that would need encoding from a slug before it's used as either the link target or the on-disk filename — so raw emission and the actual filename are the same string by construction. Verified with the issue's own exact example (CMDS.md_(System Philosophy & Context Guide).md): no %28/%29/%26 anywhere in the current output, and tests/test_wiki_link_filename_parity.py (already in the suite, all passing) pins exactly this invariant for the same character classes the issue reports ((, ), &, em dash, %, CJK).

Test plan

  • New tests/test_wiki_label_bracket_escaping.py: the helper directly, each of the three fixed print sites individually, and an end-to-end to_wiki() export using the issue's own example placeholder text ([[link]], [[new_stem]], [[]]) confirmed absent from every generated page.
  • Ran the full existing wiki test suite (test_wiki.py, test_wiki_truncation_indicator.py, test_wiki_link_filename_parity.py) — 49 tests, all pass, no regressions.
  • Full suite: python3 -m pytest -q — 5491 passed, only the pre-existing unrelated failures (test_ollama_retry_cap.py missing openai in this env, one flaky timing assertion in test_ts_import_type_arguments.py).
  • python3 -m tools.skillgen --check — OK.

🤖 Generated with Claude Code

https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"''","nodes":"\[1, 2, 3\]","label":"'h\\u00e9llo w\\u00f6rld'","labels":"\{'a': 1, 'b': 2\}","cohesion":"100\.0","node\_community":"\{'a': None\}","resolver":"\{'a':…, the old code produced '\# héllo wörld\\n\\n\> 3 nodes · cohesion 100\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\- \*\*3\*\* \(2 connections\)\\n\\n\#\# Relationships… but the new code produces raises AttributeError. Paste that input straight into a regression test.


Graphify review — findings

Escapes [/] in every label printed directly into a generated wiki article — community titles, "Key Concepts" node listings, and god node titles — so source content that literally contains [[...]] renders as plain text instead of a dead wikilink. Factors the escaping already inlined in _md_link into a shared _escape_md_brackets helper and routes the three previously-unescaped print sites through it. Adds regression tests covering each site plus an end-to-end export check that no page retains a literal [[...]].

No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 249 functions depend on the 23 functions this change touches.

Health — this change adds coupling hotspots:

  • new: to_wiki() — 43 callers, 7 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _god_node_article() — 8 callers, 3 callees
  • new: _community_article() — 5 callers, 3 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 249 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 75 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

5 of 276 test file(s) selected (2%) via static blast radius.

  • tests/test_export_path_length.py — impact
  • tests/test_wiki.py — impact
  • tests/test_wiki_label_bracket_escaping.py — impact, changed-test
  • tests/test_wiki_link_filename_parity.py — impact
  • tests/test_wiki_truncation_indicator.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"''","nodes":"\[1, 2, 3\]","label":"'h\\u00e9llo w\\u00f6rld'","labels":"\{'a': 1, 'b': 2\}","cohesion":"100\.0","node\_community":"\{'a': None\}","resolver":"\{'a':…, the old code produced '\# héllo wörld\\n\\n\> 3 nodes · cohesion 100\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\- \*\*3\*\* \(2 connections\)\\n\\n\#\# Relationships… but the new code produces raises AttributeError. Paste that input straight into a regression test.

Could not verify: Could not verify \_god\_node\_article.

The verifier did not have enough to check \_god\_node\_article, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

No difference found (not proven): No behavior difference found in \_md\_link (not a proof).

The verifier ran both versions of \_md\_link on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/wiki.py
@@ -185,7 +199,7 @@ def _community_article(
def _god_node_article(G: nx.Graph, nid: str, labels: dict[int, str], node_community: dict[str, int] | None = None, resolver: dict[str, str] | None = None) -> str:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_god_node_article()

8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Thanks for the catch — the formal verifier's finding on _community_article was real. _escape_md_brackets called .replace() directly on its argument, but every caller passes d.get("label", nid), and a node whose dict has no label attribute falls back to its own networkx node id, which is not always a string (int and tuple ids are legal, e.g. a graph built with integer node ids). The previous code interpolated that fallback into an f-string, which stringifies anything; wrapping it in _escape_md_brackets turned that implicit coercion into a hard requirement the fallback doesn't always meet.

Fixed in 5d79e51 by coercing with str() first, restoring the old behavior for a non-string fallback while keeping the escaping for genuine string labels. Added a direct regression test reproducing the reported input shape (int node ids, no label attribute) plus a unit test on _escape_md_brackets itself.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"'/'","nodes":"\[3, 1, 2\]","label":"'\['","labels":"\{'k': 'v'\}","cohesion":"1\.0","node\_community":"\{'a': None\}","resolver":"\{'a': 1, 'b': 2\}"\}, the old code produced '\# \[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\- No… but the new code produces '\# \\\\\[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\…. Paste that input straight into a regression test.


Graphify review — findings

Escapes [/] in every label printed directly into a generated wiki article — community titles, "Key Concepts" node listings, and god-node titles — via a new _escape_md_brackets helper, so a label whose source content contains literal [[...]] renders as plain text rather than a dead Obsidian wikilink (#3547). The helper stringifies its input first, so the id-fallback path used when a node has no label handles non-string node ids (ints, tuples) without raising. Adds regression tests covering each site plus an end-to-end export check.

Worth a look

  • Backslash can bypass bracket escaping in Markdown linksgraphify/wiki.py:102 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 251 functions depend on the 25 functions this change touches.

Health — this change adds coupling hotspots:

  • new: to_wiki() — 43 callers, 7 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _god_node_article() — 8 callers, 3 callees
  • new: _community_article() — 6 callers, 3 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 251 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 77 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

5 of 276 test file(s) selected (2%) via static blast radius.

  • tests/test_export_path_length.py — impact
  • tests/test_wiki.py — impact
  • tests/test_wiki_label_bracket_escaping.py — impact, changed-test
  • tests/test_wiki_link_filename_parity.py — impact
  • tests/test_wiki_truncation_indicator.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"'/'","nodes":"\[3, 1, 2\]","label":"'\['","labels":"\{'k': 'v'\}","cohesion":"1\.0","node\_community":"\{'a': None\}","resolver":"\{'a': 1, 'b': 2\}"\}, the old code produced '\# \[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\- No… but the new code produces '\# \\\\\[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\…. Paste that input straight into a regression test.

Could not verify: Could not verify \_god\_node\_article.

The verifier did not have enough to check \_god\_node\_article, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

No difference found (not proven): No behavior difference found in \_md\_link (not a proof).

The verifier ran both versions of \_md\_link on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/wiki.py
@@ -185,7 +204,7 @@ def _community_article(
def _god_node_article(G: nx.Graph, nid: str, labels: dict[int, str], node_community: dict[str, int] | None = None, resolver: dict[str, str] | None = None) -> str:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_god_node_article()

8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Addressed the advisory finding on backslash bypassing the escaping in 64bfc3a. Source content can already contain a literal backslash right before a bracket (a doc excerpt showing a regex character class, \]+, for example) — escaping the bracket alone turned that into two backslashes then a bare bracket, and CommonMark reads a doubled backslash as one literal backslash, consuming the pair and leaving the bracket unescaped and live again. Now escaping every backslash in the source text first, before the brackets, so the bracket's own escape survives regardless of what preceded it.

Added a regression test with a small simulator of CommonMark's own left-to-right backslash-escape unwinding, so it checks the actual syntactic property (no bracket reachable as bare/unescaped) rather than just comparing output strings.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"'/'","nodes":"\[3, 1, 2\]","label":"'\['","labels":"\{'k': 'v'\}","cohesion":"1\.0","node\_community":"\{'a': None\}","resolver":"\{'a': 1, 'b': 2\}"\}, the old code produced '\# \[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\- No… but the new code produces '\# \\\\\[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\…. Paste that input straight into a regression test.


Graphify review — findings

Escapes [/] in node labels printed directly into generated wiki articles — community titles, "Key Concepts" node listings, and god-node titles — via a new _escape_md_brackets helper, so a label whose source content literally contains [[...]] renders as plain text rather than a dead wikilink. The helper escapes backslashes before brackets (so a pre-existing \] can't collapse the bracket's escape under CommonMark) and stringifies its input first, covering the non-string node ids callers fall back to when a node has no label.

Worth a look

  • Backslash doubling can reactivate escaped Markdown punctuationgraphify/wiki.py:82 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 254 functions depend on the 28 functions this change touches.

Health — this change adds coupling hotspots:

  • new: to_wiki() — 43 callers, 7 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: _god_node_article() — 8 callers, 3 callees
  • new: _community_article() — 6 callers, 3 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 254 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 80 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

5 of 276 test file(s) selected (2%) via static blast radius.

  • tests/test_export_path_length.py — impact
  • tests/test_wiki.py — impact
  • tests/test_wiki_label_bracket_escaping.py — impact, changed-test
  • tests/test_wiki_link_filename_parity.py — impact
  • tests/test_wiki_truncation_indicator.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"'/'","nodes":"\[3, 1, 2\]","label":"'\['","labels":"\{'k': 'v'\}","cohesion":"1\.0","node\_community":"\{'a': None\}","resolver":"\{'a': 1, 'b': 2\}"\}, the old code produced '\# \[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\- No… but the new code produces '\# \\\\\[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\…. Paste that input straight into a regression test.

Could not verify: Could not verify \_god\_node\_article.

The verifier did not have enough to check \_god\_node\_article, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

No difference found (not proven): No behavior difference found in \_md\_link (not a proof).

The verifier ran both versions of \_md\_link on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/wiki.py
@@ -185,7 +218,7 @@ def _community_article(
def _god_node_article(G: nx.Graph, nid: str, labels: dict[int, str], node_community: dict[str, int] | None = None, resolver: dict[str, str] | None = None) -> str:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_god_node_article()

8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@ayushcodes10

Copy link
Copy Markdown
Contributor Author

Following up on the newest review pass's advisory ("Backslash doubling can reactivate escaped Markdown punctuation") — this is unverified/no reproducing input was given, unlike the earlier findings which did include a concrete counterexample. I checked it directly: the fix escapes every backslash in the source text first (single -> double) before escaping brackets, which is provably correct by induction — doubling always leaves an EVEN run of backslashes immediately before any bracket (0, 2, 4, ...), so the bracket-escaping step's one added backslash always makes an odd/pairing count that resolves as an escaped, inert bracket, regardless of how many backslashes preceded it or what surrounds them. Verified empirically against several adversarial cases (runs of 1-4 backslashes before/after/between brackets, mixed content) with a script that simulates CommonMark's left-to-right backslash-escape pairing — no case leaves a bare, live bracket. I believe this is the same concern the earlier finding already flagged and that 64bfc3a already fixed, not a new distinct gap; happy to add more test cases if a specific failing input turns up.

ayushcodes10 and others added 4 commits September 16, 2026 14:08
_md_link already escapes a label before it ever becomes link text,
because a node label is extracted source content and can literally
contain a bracketed substring, a doc that discusses or demonstrates
wikilink syntax being the obvious source. Three other places print a
label straight into an article body without going through that
escaping at all, a community's own title, its Key Concepts node
listing, and a god node's own title, so a label like double bracket
wikilink double bracket rendered as a real, and always dead since the
wiki export never writes that link form, wikilink instead of the
plain text it actually is. Pulls the escaping out of _md_link into a
small shared helper and applies it at all three sites. Fixes the
first of two bugs in Graphify-Labs#3547.

The second bug the report describes, a wikilink target left percent
encoded while the file written for it is not, was already fixed on
this branch's base by number 2597, confirmed directly: _safe_filename
strips everything that would need encoding from a slug before it is
ever used as either the link target or the filename, so raw emission
and the on disk name are the same string by construction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Covers the helper directly, each of the three fixed print sites, and
an end to end export using the exact placeholder text the report
listed, double bracket link double bracket and double bracket
new underscore stem double bracket among them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Addresses review feedback on PR 3550: the graphify review bot's
formal verifier found a concrete input on which _community_article
started raising AttributeError after this PR's escaping change.

_escape_md_brackets called replace directly on its argument. Every
caller passes d.get("label", nid), so a node whose dict has no
label attribute falls back to its own networkx node id, which is not
always a string (int and tuple ids are legal). The previous code
interpolated that fallback into an f string, which stringifies
anything; wrapping it in _escape_md_brackets turned that implicit
coercion into a hard requirement the fallback does not always meet.

Coercing with str() first restores the old behavior for a non string
fallback while keeping the escaping for genuine string labels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
Addresses a review finding on PR 3550: the graphify review bot
flagged that a pre existing backslash right before a bracket in
source content can bypass the bracket escaping.

_escape_md_brackets escaped only the two bracket characters. Source
content occasionally already contains a literal backslash right
before a bracket, a doc excerpt showing a regex character class like
backslash close bracket plus, for example. Escaping the bracket alone
turned that into two backslashes then a bare bracket, and CommonMark
reads a doubled backslash as one literal backslash, which consumes
the pair and leaves the following bracket unescaped and live as link
syntax again.

Escaping every backslash in the source text first, before the
brackets, keeps the bracket's own escape intact regardless of what
already preceded it. Added a regression test with a small simulator
of CommonMark's own left to right backslash escape unwinding, so the
assertion checks the actual syntactic property (no bracket reachable
as bare, unescaped) rather than just comparing strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
@ayushcodes10
ayushcodes10 force-pushed the fix-3547-wiki-label-bracket-escaping branch 2 times, most recently from 64bfc3a to 85d2122 Compare September 16, 2026 08:40

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 1 change(s) alter behavior, breaking input(s) attached.

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"'/'","nodes":"\[3, 1, 2\]","label":"'\['","labels":"\{'k': 'v'\}","cohesion":"1\.0","node\_community":"\{'a': None\}","resolver":"\{'a': 1, 'b': 2\}"\}, the old code produced '\# \[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\- No… but the new code produces '\# \\\\\[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\…. Paste that input straight into a regression test.


Graphify review — findings

Escapes [/] (and backslashes ahead of them) in every label printed directly into a generated wiki article — community titles, "Key Concepts" node listings, and god-node titles — via a new _escape_md_brackets, so source content that literally contains [[...]] renders as plain text rather than a dead Obsidian wikilink (#3547). Backslashes are escaped before brackets so a pre-existing \] in source doesn't un-escape back into live link syntax, and non-string node ids used as label fallbacks are stringified rather than raised on. _md_link now routes its own escaping through the same helper.

No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 254 functions depend on the 28 functions this change touches.

Health — this change adds coupling hotspots:

  • new: to_wiki() — 43 callers, 7 callees
  • new: dispatch_command() — 2 callers, 125 callees
  • new: _god_node_article() — 8 callers, 3 callees
  • new: _community_article() — 6 callers, 3 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 254 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 80 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

5 of 284 test file(s) selected (2%) via static blast radius.

  • tests/test_export_path_length.py — impact
  • tests/test_wiki.py — impact
  • tests/test_wiki_label_bracket_escaping.py — impact, changed-test
  • tests/test_wiki_link_filename_parity.py — impact
  • tests/test_wiki_truncation_indicator.py — impact

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Behavior changes: \_community\_article changes behavior, here is the input that shows it.

The verifier found a concrete input on which \_community\_article behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.

Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.

Evidence: On input \{"G":"\(lambda \_g: \(\_g\.add\_nodes\_from\(\[\(1, \{\}\), \(2, \{\}\), \(3, \{\}\)\]\), \_g\.add\_edges\_from\(\[\(1, 2, \{\}\), \(1, 3, \{\}\), \(2, 3, \{\}\)\]\), \_g\)\[\-1\]\)\(\_\_import\_\_\('networkx'\)\.Graph\(\)\)","cid":"'/'","nodes":"\[3, 1, 2\]","label":"'\['","labels":"\{'k': 'v'\}","cohesion":"1\.0","node\_community":"\{'a': None\}","resolver":"\{'a': 1, 'b': 2\}"\}, the old code produced '\# \[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\- No… but the new code produces '\# \\\\\[\\n\\n\> 3 nodes · cohesion 1\.00\\n\\n\#\# Key Concepts\\n\\n\- \*\*3\*\* \(2 connections\)\\n\- \*\*1\*\* \(2 connections\)\\n\- \*\*2\*\* \(2 connections\)\\n\\n\#\# Relationships\\n\\n\…. Paste that input straight into a regression test.

Could not verify: Could not verify \_god\_node\_article.

The verifier did not have enough to check \_god\_node\_article, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

No difference found (not proven): No behavior difference found in \_md\_link (not a proof).

The verifier ran both versions of \_md\_link on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 1 grounded finding(s) anchored inline below; 4 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/wiki.py
@@ -185,7 +218,7 @@ def _community_article(
def _god_node_article(G: nx.Graph, nid: str, labels: dict[int, str], node_community: dict[str, int] | None = None, resolver: dict[str, str] | None = None) -> str:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_god_node_article()

8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant