`safeMatches` aceptaba `target.closest(selector)` como coincidencia, lo que
convertia toda regla de pack en regla de ancestro. Parecia inocuo porque los
`data-event-*` son transitorios: la regla necesita que el ancestro lleve puesto
justo el evento que ella nombra, y un hold dura ~240 ms.
Salvo cuando NO son transitorios. Un `signal.warn + risk` resuelve a
`persistence: 'untilFix'` (holds.ts), asi que un formulario que fallo la
validacion conserva `data-event="signal-warn-invalid"` INDEFINIDAMENTE — y
desde ese momento cada click y cada commit dentro del formulario casaba con la
regla del propio formulario y reproducia su earcon de error, hasta el siguiente
submit. El usuario oia un error mientras corregia el error.
Es el hallazgo S-01 de AUDIT-sema-2026-08-05, el unico de severidad ALTA, y los
256 tests de sema pasaban con la fuga puesta.
EL ARREGLO NO ROMPE EL ACOTADO POR DESCENDENCIA, porque ese vive en el SELECTOR
y `matches()` lo evalua contra el elemento en su arbol real. Es como
`media-player` acota sus reglas sobre los morfos de Button y Slider
(`[data-media-player] [data-button][data-event="…"]`). Medido antes de tocar:
CERO reglas del catalogo se declaran sin conjunto de evento y CERO usan el
matcher `ancestor`, asi que nada dependia del `closest()`.
GUARD `cascade-scope.test.ts`, nacido en ROJO con el escenario real (un
formulario con la estampa persistente + un boton dentro): la regla del ancestro
alcanzaba al descendiente. Fija las tres mitades — no alcanza al descendiente,
sigue honrando la descendencia escrita en el selector, y sigue casando con el
objetivo propio.
Y de paso se retira `__adv-verify-haptic-dead.test.ts`, andamio que dejo un
agente de la auditoria (autorizado por el usuario). `check` vuelve a 75 = la
baseline limpia, sin el +1.
VERIFICADO: sema+morfo+sound 369/369 · check 75 (baseline exacta) · navegador:
el transporte del player sigue callado, es decir la descendencia sigue
acotando · prettier limpio.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>