Two bugs:
1. Sentinel used double-underscore delimiters (__DESC:id__) — fragile regex
[^_]+ would break on IDs containing underscores, and Pass-1 output
containing sentinels would leak through Pass 2 unstripped.
2. After Pass 2 substitution, sentinels inside substituted text (from
Pass-1 output of the referenced id) were never cleaned up.
Fix: switch to null-byte delimiters (\x00DESC:id\x00) which cannot appear
in user text, and add a second .replace(SENTINEL_RE, '') pass to strip any
sentinels that survive after substitution (self-refs, nested cycles).