You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/CONTINUE-audit-queue.md

438 lines
27 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CONTINUE — la cola de auditorías, y el protocolo con el que se trabaja
> **Kickoff para la sesión siguiente**: _«Lee `docs/process/CONTINUE-audit-queue.md`
> y empieza por §1 (S-33, firmado y sin ejecutar).»_
> **Fecha**: 2026-08-13 · Rama `alpha-0.1-dir-prefs`, pusheada en `eda3b977b`.
> ⚠️ **COMPARTIDA**: otra sesión trabaja en `blocks` **y** en un eje de
> dismissal (los `types.ts` de color-picker / time-picker / time-range-picker).
> `git reset -q` + stagear por lista explícita, y clasificar **por contenido del
> diff, nunca por nombre**.
> **Fuentes de los hallazgos**: [`AUDIT-sema-2026-08-05.md`](./AUDIT-sema-2026-08-05.md)
> (44 confirmados con refutación adversarial) ·
> [`CONTINUE-perceptual-surface.md`](./CONTINUE-perceptual-surface.md) §3 (la
> cola larga, con el detalle de cada eje). Este documento dice **cómo se
> trabaja, qué se firmó y por dónde se sigue**.
---
## 0 · EL PROTOCOLO — va primero porque cambió hoy, y no es opcional
**Por CADA modificación o acción, y ANTES de tocar nada**, se responden las
cinco preguntas del autor:
1. ¿Qué es lo que se va a solucionar?
2. ¿Has leído toda la documentación al respecto, o sólo estás presuponiendo?
3. ¿Qué afecta a otros componentes?
4. ¿Afecta a las doctrinas del ecosistema?
5. ¿Qué modificaciones planteas?
**Un item por mensaje, y se PARA a esperar firma.** Hoy propuse una «puerta»
que me dejaba auto-autorizar lo mecánico y presenté cuatro decisiones juntas en
una hoja resumida; el autor lo cortó en seco. No hay puerta: se expone y se
espera.
Y dos precondiciones que hoy costaron **dos rectificaciones completas**:
- **El SPEC del COMPONENTE es doctrina, y se busca ANTES de diagnosticar.** El
item de la «6ª fila fantasma» de chronos llevaba un mes en la cola y llegó a
estar FIRMADO; al ir a ejecutarlo apareció `SPEC.md` del componente diciendo
lo contrario en tres sitios coordinados. Se revocó. Una asimetría con la
familia NO es evidencia de deriva: puede ser la única decisión escrita.
- **Barrer `docs/` ANTES de proponer retirar nada.** Recomendé retirar
`intentRequirement: 'forbidden'` «igual que `collapse` y `lock`». Falso:
aquellos no tenían una línea en `docs/`; éste está en `CANON.md` §4, en
`architecture/sema.md` §, y en la tabla de **D.3**, decisión firmada del autor
en `decisions/book-deviations.md:472`.
Regla que resume las dos: **el código nunca corrige al canon; se le pone al
autor la CONTRADICCIÓN delante y decide él.**
---
## 1 · Mañana lo primero: S-33, FIRMADO el 2026-08-13 y sin ejecutar
**El defecto es `Exclude`, no «falta una rama».** `IntentOptionalFamily =
Exclude<SemaFamily, IntentRequiredFamily>` es una derivación de mundo abierto
—«todo lo que no es required»— así que una familia marcada `'forbidden'` cae en
el cubo OPCIONAL, donde el intent está permitido. Los dos guardianes de runtime
tienen el mismo hueco: `validation.ts:39` sólo comprueba `=== 'required'`, y
`event.ts:104-110` (`isSemaEvent`), si el intent está presente, devuelve
`isIntent(...) || isIntentBinding(...)` sin mirar la política.
**Medido con el `tsc` del proyecto** (probe con los dos estados del const, hoy y
endurecido). Un único error en todo el probe, y es el control de la derivación
actual:
```
intent-probe.ts(87,1): error TS2578: Unused '@ts-expect-error' directive.
```
es decir: con `contact` en `'forbidden'`, `{ family: 'contact', intent: 'threat' }`
**compila**. Lo demás del probe demostró las tres cosas que hacían falta:
| Probe | Resultado |
| --- | --- |
| `{contact,intent}` · `{contact}` · `{commit,intent}` con la tercera rama presente | compilan → **una rama tecleada sobre `never` no cuesta nada hoy** |
| `@ts-expect-error` sobre `{family:'commit'}` | se CONSUME → la puerta de `required` sigue intacta |
| `@ts-expect-error` sobre `{contact,intent}` con derivación POSITIVA y contact forbidden | se CONSUME → **flipar una línea del const cierra la puerta solo** |
| `{family:'contact'}` a secas, misma situación | compila → `'forbidden'` significa «familia usable, intent prohibido», no «familia inutilizable» |
**La receta firmada:**
1. `sema/types.ts` — derivar los tres cubos **positivamente** (`extends 'optional'`
/ `extends 'required'` / `extends 'forbidden'`), matando el `Exclude`, y añadir
la rama `{ family: IntentForbiddenFamily; intent?: never }` a `SemaEvent` y a
`MorfoEventSemantic` (`morfo/types.ts`).
2. `sema/event.ts:104` y `sema/validation.ts:39` — la condición simétrica en los
dos guardianes de runtime.
3. El esquema sium de `morfo/schema.ts` **no necesita nada hoy** (no hay literal
que añadir mientras ninguna familia sea `'forbidden'`), y si alguien flipa el
const **salta el guard de S-34** que se metió hoy y le dice que lo añada. Las
dos mitades cubiertas: los tipos se activan solos, el runtime avisa.
4. El probe se convierte en **test** (`sema/` o `contracts.test.ts`), para que
el día que alguien reintroduzca un `Exclude` el `@ts-expect-error` deje de
consumirse y falle.
Delta de conducta hoy: **cero, medido**. Lo que cambia es que la promesa del
canon («editing the const reshapes the discriminated unions») pasa a ser cierta.
⚠️ Y la razón por la que se descartó la alternativa que yo mismo había
recomendado —un guard que fallara si alguna familia declara `'forbidden'`— es
doctrinal, no de gusto: es **lint donde el tipo puede hablar**, y `CLAUDE.md`
§*Eidos drift defense* dice que cuando una capa PUEDE expresar la restricción en
TypeScript, hacerlo con un test es una violación de arquitectura. La misma
doctrina con la que ayer se cerró F3 borrando `targetOverride`.
---
## 2 · Lo cerrado hoy (6 commits, todos pusheados)
| Commit | Qué |
| --- | --- |
| `b8aa333fd` | **F3 CERRADA** — `targetOverride` BORRADO de `TriggerOptions` + `assertTargetOverride`; el compilador, ya como censo, cazó 9 usos en tests que el grep del scratch nunca escaneó |
| `c00e97b91` | **F5 CERRADA** — los tres sitios de las tres auditorías, medidos DESPUÉS de la campaña |
| `a15745491` | **chronos `fixedWeeks`: decisión REVOCADA** — el SPEC manda; no era un defecto |
| `44af38e6e` | **S-10** — el canal visual honra los servicios de su propia bolsa (`anidado ?? raíz`, como los otros tres) |
| `14a169357` | **guard D9** — mira también la vista de eidos, que en los componentes-vista ES el emisor |
| `eda3b977b` | **S-34** — el validador de runtime queda atado al const de política de intent, con el guard visto fallar |
Con F5 cerrada, **las seis fases del eje perceptual (F1–F6) están completas**.
---
## 3 · Decisiones ABIERTAS — esperan firma, no se tocan
Cada una necesita su exposición de cinco preguntas cuando se abra. Aquí sólo el
titular y la evidencia ya medida, para no re-descubrirla.
### S-35 · la forma etiqueta regala lo que la estructurada exige
`SemaActionEvent = SemaEvent | SemaEventLabel` y la etiqueta incluye la familia
pelada: `'commit'` compila aunque `commit` sea `required`, y
`normalizeSemaEvent('commit')` devuelve `intent: null` — el documento que la
política declara inválido. `validateSemaEvent` remata devolviendo temprano ante
cualquier etiqueta (`validation.ts:29`). **Alcance medido**: `SemaActionEvent` no
tiene consumidores fuera de los tres ficheros del propio sema. Va pegado a S-33:
la misma unión, las mismas líneas.
### ~~S-30/S-38~~ · EJECUTADO 2026-08-13 — las cinco reglas ABREN LA PUERTA
**La decision se INVIRTIO al contrastarla con el mundo real y con el autor.** Mi
recomendacion era borrar los bloques; el autor pregunto que hacen iOS/Android y
quien decide — y la verificacion en las fuentes (Apple HIG: haptic de warning
para alertas; tick de seleccion para cambios de valor; Android: «less is more»
pero con constantes CONFIRM/REJECT) mas un hecho del propio framework (la
intencion estaba ESCRITA y comentada en los packs, y la haptica entera es
opt-in del producto) cambiaron el veredicto: las reglas llevaban la intencion
del disenador y solo habian olvidado la puerta. Arreglo: `channels: ['sound',
'haptic']` en las cinco, con la razon perceptual escrita en cada una; los 5
waivers de `CHANNEL_EXCEPTIONS` fuera (el guard del censo, re-armado, ES el
test que avisa si vuelve a pasar); y un e2e nuevo en `resolver.test.ts` que
prueba resolucion + `vibrate()` con valores FINITOS (la defensa S-31 cubre los
`{kind:'tick'}` parciales — el aviso del waiver ya estaba pagado). Visto en
rojo dos veces: el censo sin waivers listando las 5, y el e2e contra los packs
sin ensanchar.
### S-30/S-38 · (ficha original, cerrada)
Dialog ×2 (alertdialog y sheet), editable, stepper y timeline escriben
`haptic: {…}` sobre eventos de familia `emerge`/`shift`, cuyo `activeChannels`
es `['sound']`. Medido por el verificador con resolver y canal reales: firma
háptica instalada, `vibrate()` **0 veces**. Y el mapa es coherente en su diseño:
las familias que vibran declaran **base háptica Y canal** (contact, commit,
signal, handle); emerge y shift no declaran ninguna de las dos.
**Opciones**: (a) `channels: ['sound','haptic']` en las cinco (patrón canónico en
`slider`, `css-field`, `drag-drop`) · (b) borrar los bloques y escribir que
emerge/shift no vibran. **Mi criterio: (b)** — el mapa ya lo dice en dos sitios
independientes, y (a) reabriría por la puerta de atrás una decisión perceptual
(que un overlay te vibre en la mano) que nadie ha tomado. Con guard que cruce
cada regla contra el `activeChannels` alcanzable.
### ~~S-19~~ · EJECUTADO PARCIAL 2026-08-13 — la firma alineada; encenderlo sigue ABIERTO
**Mi primer analisis estaba mal y el autor lo mando revisar.** Dije que el canal
estaba muerto porque el runtime de soma no mete `message` en la señal; es cierto
que no lo mete, pero **es diseño, no defecto**: son DOS emisores para DOS
publicos (soma cubre sus componentes; el canal cubre a quien usa
`EngineSemantic` SIN soma y escribe su propia señal con el texto). Justo por eso
nada se anuncia dos veces, y mi opcion (b) —fusionarlos— habria roto el diseño.
Retirada.
HECHO: `AnnounceFn` pasa a prioridad POSICIONAL, como el sumidero y como la
fuente de soma, asi que `{ announce: uix.announce }` compila literal y sobra el
adaptador. Visto en rojo antes: un sumidero con la forma de `uix.announce`
recibia `{priority:'assertive'}` en el hueco de la prioridad — y ahi
`liveRegionIds[obj]` es `undefined`, o sea que TODO caia en la region polite y un
`threat` dejaba de interrumpir. 3 aserciones de test migradas de bolsa a
posicional. `sema.md` §announce reescrito: fuera el aviso del adaptador, dentro
la razon de los dos publicos.
QUEDA ABIERTO (decision, no bug): **ninguna raiz enchufa el canal**. Con el
puerto ya alineado es UNA linea en la raiz, pero encenderlo sin inyectar el
anunciador cae en las regiones propias del canal y añade un SEGUNDO par
`role=status`/`role=alert` junto al compartido.
### S-19 · (ficha original)
`AnnounceFn` pide `(message, { priority })` y `uix.announce` es posicional
`(message, priority?, timeout?)`: el cableado canónico de `sema.md:762` es un
error de tipos, no hay adaptador, y **ninguna de las dos raíces** pasa `announce`
al engine. Si alguien fuerza el cast, `priority` llega como objeto,
`liveRegionIds[priority]` es `undefined` y todo cae en la región polite — un
`threat` deja de interrumpir.
**Son DOS decisiones y conviene no mezclarlas**: (i) alinear la firma + corregir
el doc — no cambia conducta, porque hoy nadie lo pasa; (ii) enchufar el canal en
las raíces, que **sí** cambia conducta y arriesga el «dos sumideros» que la
propia doctrina prohíbe, porque soma tiene su propio puerto de anuncio
funcionando (`soma.svelte.ts:99`, el de metrics/menu-dial/onion-menu).
**Mi criterio**: (i) ahora, (ii) como ítem con censo previo de quién anunciaría
por los dos caminos.
---
## 4 · La cola, en orden
1. ~~**S-33**~~ EJECUTADO 2026-08-13 (`1a174d5a6`): los tres cubos derivados en
positivo + rama `intent?: never` + `intentRequirementOf()` (leer el const
directo ESTRECHA y el gate no compila — arista que la ficha no vio).
Desviacion declarada: `validation.ts` NO se toco (su rama de politica es
inalcanzable — hallazgo nuevo, abierto).
2. ~~**S-35**~~ EJECUTADO 2026-08-13 (`6a7554415`): `SemaEventKey` derivado de la
politica (bare solo sin required; pareja sin forbidden), `SEMA_EVENT_LABELS`
56→54, `isSemaEventLabel('commit')` false → `validateSemaEvent` lanza. Dos
hallazgos ABIERTOS de su paso: el vocabulario de etiquetas no tiene NINGUN
consumidor en produccion (¿se gana el sitio?), y la rama `required` de
`validation.ts:39-46` es codigo muerto (inalcanzable tras `isSemaEvent`).
3. ~~**S-30/S-38**~~ EJECUTADO · ~~**S-19**~~ EJECUTADO PARCIAL (firma alineada; encender el canal sigue abierto, ver §3).
4. **§3.5 fable** — ~~`M1 emission`~~ **PARTE (i) EJECUTADA 2026-08-13**: el
flag `emission: 'runtime' | 'host' | 'external' | 'declared-only'` de D.2
existe en `MorfoEvent` + schema (ausente = `'runtime'`, las ~250
declaraciones intactas), el guard D9 exime por declaracion (y si el evento
eximido esta en la deuda, EXIGE limpiarla — visto con sonda temporal sobre
tooltip), el pack-census rechaza reglas sobre eventos `declared-only`
(fixture 2 casos; `host` NO es muerto), y `morfo.md` §Step 5.5 lleva el
apendice tecnico con la nota doctrinal de D.2. La sonda destapo y cerro un
agujero: el flag entre `name` y `semantic` hacia INVISIBLE el evento al
regex del guard — ahora lo tolera. ⚠️ **Parte (ii): 3 de 7 resueltas** — ~~tooltip ×3~~ **CABLEADO 2026-08-13**
(la re-analisis del autor cazo lo que faltaba: la firma global
`present-rise` de base.css:6520 y que la entrada por `data-state` era el
WORKAROUND de la emision ausente, con la semantica focus=sin-entrada
colgando de ella). Receta F4: `present` pre→post SIN su commit (el commit
fijaba 'delayed-open' y pisaria el 'instant-open' del focus — los overlays
binarios si lo conservan), dismiss ×2 + `targetFallback: [trigger]`,
triggers solo en transiciones REALES (un timer cancelado no emite; un
re-hover abierto no re-emite), y la causa escape distinguida. MEDIDO en
navegador los 3 caminos: hover→present en CONTENT con la entrada autorizada
corriendo (scale-in+fade-in, present-rise NO — el preset gana la cascada);
focus→present con `instant-open` y present-rise CORRIENDO = la fuga exacta
→ silencio visual MINIMO en la receta, scoped a
`[data-state='instant-open'][data-event-phase='active']`, re-medido: anims
[]; leave→dismiss en CONTENT via Presence con la salida autorizada
(scale-out+fade-out, dismiss-fade no); Escape→dismiss-escape. El SILENT del
pack por fin CASA y la puerta de temas funciona. Deudas limpiadas: 3 ids
del D9 + 3 excepciones del censo. ~~virtual-list/grid ×3~~ **RESUELTOS
2026-08-13 con `emission: 'host'`** — la resolucion YA estaba escrita en los
dos providers como decision con su porque («apps wire it themselves with
their own throttle policy»; per-pixel + familia handle = trinquete continuo
bajo inercia) y solo le faltaba un sitio DECLARABLE: es el caso de diseño
del valor `host` de D.2. La cita desfasada del comentario
(`activeChannels=['haptic']` — hoy es ['sound','haptic']) corregida al
tocarlo; la promesa del host es viable (`provider.runtime` es publico).
~~gradient-picker commit-reset~~ **RETIRADO
2026-08-13 (S-14 cerrado)** — y la ficha se corrigio con medida: los
hermanos color/date/time-picker SI emiten su reset (cableados 2026-08-11),
pero NO componen el Picker generico — son dueños de su transaccion.
Gradient-picker la DELEGA entera (su docblock lo declara), asi que el Clear
ya suena con el `commit-reset` del picker compuesto, y cablear el gemelo
habria dado DOBLE firma por un gesto (la clase file-upload). Retirados:
evento del morfo (con la razon en su lugar) + regla del pack + filas de los
dos README + la excepcion del censo + el ultimo id de la deuda.
**Con esto la parte (ii) esta COMPLETA y M1 CERRADO ENTERO: las dos listas
de deuda (INERT_EVENT_DEBT y EMISSION_EXCEPTIONS) quedan VACIAS por primera
vez**, cada una con su lapida explicando las 7 resoluciones. La
instancia original de la ficha (5 pickers con close inerte) esta MUERTA
desde la normalizacion de nombres · ~~`M4`~~ **EJECUTADO 2026-08-13, con
el DIAGNOSTICO CORREGIDO dos veces**: la premisa de la ficha era falsa (hay
12 declaraciones con enum de 3+, censadas: aura·checkbox·image·meter·
progress·tooltip) y su arreglo («exigir exactamente 2 valores») las habria
proscrito — son legitimas porque bindean STRINGS y el modo hace passthrough.
El peligro real es booleano↔enum-no-binario, hoy inexistente. Arreglo: la
pareja solo se infiere con enum BINARIO (exacta por construccion), y un
booleano llegando a 3+ LANZA nombrando la salida declarativa (`v.mapRef`,
que YA existia en el vocabulario — ni campo nuevo ni adivinanza), doctrina
S-09. En mi re-analisis cai en dos: el «guard de contratos» de mi primera
propuesta NO existia, y mi censo de bindings verifico 2 de 12 — la suite
entera de navegador (1278✓) es la que probo que ninguno bindea booleano.
Tests nacidos en rojo 2/3 (el tercero paso porque ES la conducta binaria de
hoy) · ~~`M5`~~ **EJECUTADO 2026-08-13, y la ficha era INCOMPLETA**: acertaba en que
se puede tipar ya, pero no vio que 2 de los 5 usos casan attrs que el morfo
NO declara A PROPOSITO (`data-size`/`data-sheet`, eidos-only por docblock) ni
que un solo slot no puede imponer enum Y quedar abierto (la clase S-11). La
solucion es el precedente de DOS EJES del framework: `state` tipado con
`DataPairOf<M>` (attr declarado + valor del enum; el typo no compila) +
`undeclaredState` abierto y auto-documentado para los eidos-only, con el
builder lanzando en runtime si un attr declarado se cuela por la puerta
abierta (y viceversa) — gate en el builder y no en el censo porque lanza al
IMPORTAR el pack. `aria` queda abierto con la razon escrita (su unico uso
casa un role deliberadamente no declarado). Mi primera propuesta era un
guard de censo — el MISMO error de S-33, corregido en re-analisis. Probes
`@ts-expect-error` + 5 tests runtime nacidos en rojo (4/4) ·
~~`M6`~~ **EJECUTADO
2026-08-13**: los alias `IntentExpectedFamily`/`SemaEventLabel` BORRADOS
(no-shims; D.3 los nombra como HISTORIA, no como prescripción), 13 usos
migrados a los canónicos, `semaIntentExpectedFamilySchema` renombrado
`…Required…` CON el guard S-34 actualizado en el mismo commit, los 2
`as never` de `compile.ts` retirados (vestigio pre-`DepSink`), y
`_resetCompileCache` resultó YA borrado — fuera de la cola ·
~~`E2`~~ · ~~`E3`~~ **EJECUTADOS 2026-08-13** — E2: el mode-source de sistema
consulta `dom.getWindow().matchMedia` y se suscribe por `dom.listen`
(ventana CORRECTA en iframe/popup; fallback crudo sin dom); descartado el
tracker en adom por la regla de ≥2 consumidores (los 6 boots reales de web/
pasan todos su `modeSource` — el camino de sistema tiene hoy CERO
consumidores vivos). E3: `#lastSchemeCss` — un re-derive que falla conserva
el ultimo bloque bueno y avisa por `#uix?.logger` en vez de DESVESTIR la
pagina (`apply()` leia '' como «sin esquema» y borraba el `<style>`);
`applyColorScheme` sigue lanzando eager en la cara del llamador. Hallazgo
del rojo: el validador de config es FAIL-CLOSED y rechaza escalas rotas en
la puerta — la inyeccion del test va sobre `buildScheme` mismo, passthrough
real hasta que el flag lo revienta. Ambos tests vistos morder el codigo
viejo via stash.
5. **§3.6 blocks** — **`A-47` SIGUE ABIERTO — el arreglo del 2026-08-13 fue
REVERTIDO el 2026-08-14** (`acda70f1e` deshecho). No llegaba a 5 recetas
(alias congelados en `:root`, la violacion que `canon/tsc.md` nombra y que
no se leyo antes de proponer), no cubria los lienzos solidos de Card ni
Banner, y EMPEORABA el anillo en subarboles que repintan lienzo claro. Lo
que el analisis dejo en claro y sirve para quien lo retome: el anillo
pertenece al CONTEXTO DE TINTA (`[data-on]`), no al variant de un
componente; y el eje evaluativo (`--focus-ring-color-error`) NO puede
aplanarse a la tinta de contraste — el rojo es el mensaje; su problema real
es que es translucido (`color-mix(risk-8 52%, transparent)`) y se lo come
cualquier lienzo saturado. NADA de esto esta verificado por el autor.
[texto anterior, ahora invalido:] ~~ARREGLADO en la CLASE, no en la
instancia**: el anillo global es primario translucido (suave a proposito) y
sobre un `Surface` solido primario media 1,02:1 (peor aun que el 1,43 del
ledger; misma pareja rgb(142,78,198)). Una declaracion en la regla solida de
`surface.css` rescopa `--focus-ring-color` a la tinta del slot
`contrast(on-solid)` (pick APCA + suelo WCAG, rfc-color-engine §8; §32 = un
solo modelo outline, todos los consumidores beben del var). Medido en la
demo real: 1,02 → **5,18:1** en el `outlineColor` del boton enfocado; el
token global fuera del panel INTACTO (2,17, sin cambios). A plena fuerza,
sin mix — aritmetica sobre token garantizado re-pierde la garantia.
Hallazgos anotados sin decidir: `--focus-ring-color-error` sobre solido
(semantica de error, caso aparte) y el 2,17:1 del anillo global contra la
PAGINA (el ledger no lo señalo; el contraste puede satisfacerse contra el
boton adyacente — decision de diseño si se abre). Fila del ledger →
ARREGLADO, SIN commitear (viaja con la otra sesion, protocolo A-85) ·
~~`A-69`~~ **ARREGLADO 2026-08-13 via la disposicion del ledger, tras
re-analisis del autor** (mi primera propuesta sustituia el muelle por un
ease-out — cambio de DISEÑO de tapadillo: la caida exponencial es la
semilla portada; y mi cita de motion.md §drivers estaba fuera de
jurisdiccion — gobierna presets CSS, no numero→formatter). Ejecutado:
λ derivada de (recorrido, umbral, duration) con ζ fija en 2√2 — misma
FORMA, reloj recalibrado; `onEnd` desde la parada REAL del muelle (antes:
timer ciego que disparaba 5,9 s antes); guard de recorrido no-mostrable.
MEDIDO con Playwright (sonda espejo de la del ledger, muestreo directo de
textContent): 12.500/340/48 asientan TODOS en 1977 ms (duration 2000,
±23 ms) con dispersion 0 ms — antes 7867/5083/3567 y 4310 de dispersion.
Fila del ledger → ARREGLADO sin commitear (protocolo A-85). NO tocados:
A-68 (arranques vs entrada — item propio) ni el driver spring de $motion
(sustrato distinto). **§3.6 blocks COMPLETO.**
6. **§3.3 eje B** — 4 continuos sin throttle (gradient-builder, color-picker
área, number-field, css-field) · `sequence: 'coincident'` sin implementar
con 21 eventos declarándolo (**decisión**: implementar o marcar reservado) ·
las 68 demos emitiendo sin `verb` · medir `knob.handle-drop` (`pre` con
handler) y el retraso de 120 ms de `button.contact-activate`.
7. **Firmadas que quedan** — el chip bloqueado vía `DragDrop.Draggable`
(transversal, con su letra pequeña ya medida en
`CONTINUE-perceptual-surface.md` §4) · retirar `mode`/`scope` (+ la hermana
`SemaCause`, que sigue ABIERTA).
8. **§3.2 restitución de persistentes** — eje nuevo del engine; afecta form,
file-upload, textarea, media-player, announce, aura.
9. **C5 rebanadas 3-4 de chronos** — subir a soma el comportamiento del chip
(teclado, captura de puntero, drag-to-create) y sólo después extraer
`MonthView`/`WeekView`/`AgendaView`. Con D14 dentro. El grande.
---
## 4.1 · Hallazgos del 2026-08-13 — RESUELTOS por el autor
Los tres que salieron mientras se cerraban §3.4 y §3.5, decididos el mismo día:
- **La rama `required` de `validation.ts` es código muerto** → **SE QUEDA, y
documentada** en el docblock de `validateSemaEvent`: `isSemaEvent` es
policy-aware, así que una violación sale por el throw genérico y el mensaje
específico nunca dispara (medido). Se conserva porque los dos guards
responden preguntas distintas y sólo su ORDEN hace redundante a uno;
reordenar sería cambiar el mensaje lanzado sin que nadie lo pida.
- **El vocabulario de etiquetas sin consumidor interno** (`SemaActionEvent`,
`normalizeSemaEvent`, `toSemaEventLabel`, `validateSemaEvent`) → **SE GANA
EL SITIO, y documentado** en el docblock de `SemaActionEvent`: no es falta
de consumidor, es AUDIENCIA — la app que conduce `EngineSemantic` sin soma,
la misma que sirve el canal announce (dos emisores, dos públicos). Soma
nunca toma ese camino porque baja la declaración estructurada del morfo.
- ~~**Encender el canal announce en las raíces (S-19 parte ii)**~~ →
**EJECUTADO 2026-08-13, encendido POR DEFECTO en las dos raíces.** El
análisis destapó que era la raíz o nadie: la opción `announce` se pasa al
construir el engine DENTRO de `createActiveUix`, cuando `uix.announce` aún
no existe — el único cableado escribible por un app caía en las regiones
propias del canal (el segundo par que AUX-1 prohíbe). El impl registra el
canal post-construcción con cierre tardío, igual en standalone y attach.
Default ON porque announce es SUSTITUCIÓN, no ornamento (la norma del campo:
Angular CDK LiveAnnouncer, React Aria — el anunciador a11y es ambiente).
Opt-outs: `events: { announce: false }` (el engine gana el flag legible
`announceOptedOut`) y un `announce` explícito del app gana. Cero doble
anuncio: soma no mete `message` en la señal. Visto en rojo vía stash del
impl; probado que el mensaje aterriza en la región COMPARTIDA (ids de uix,
UN solo `[role='alert']` en el documento). `sema.md` §announce actualizado.
## 5 · Base de verificación (mídete contra esto, no contra memoria)
```
npx vitest run src/uix/sema src/uix/morfo src/uix/contracts.test.ts --project=server
npx vitest run src/uix/soma # proyecto navegador
npm run check # 69 errores = base
npm run docs:check # 0 / 623
```
- `contracts.test.ts` tiene **6 fallos AJENOS y preexistentes** (barrel de
waveform · nombres de fichero de provider · MOR-4 · escrituras DOM de menubar ·
data-attrs · namespaces de aura). Hoy llegó a haber 7: el séptimo era **mío y
de otro commit**, y se cerró (`14a169357`).
- soma navegador: **1278 ✓ / 1**, el timeout ajeno preexistente de
`soma-attr-audit`.
- sema+morfo: **532 ✓**.
---
## 6 · Trampas medidas hoy — no re-descubrirlas
- **El guard D9 es laxo A PROPÓSITO.** Compara `includes("'nombre'")` y no
`trigger('nombre')` porque el nombre llega a la llamada por VARIABLE en media
docena de patrones. Censado: endurecerlo convierte **27 emisiones reales** en
violaciones. Su precio —un nombre entrecomillado en un comentario lo
satisface— queda escrito en el propio colector.
- **El panel del navegador oculto**: `rAF` suspendido (los ticks continuos no
estampan nunca) y la línea de tiempo de animaciones congelada, así que el
desestampado cae SIEMPRE en el tope de 1500 ms de la espera de expresión.
`getAnimations()` dice QUÉ corre sobre QUÉ, nunca cuánto dura.
- **Prettier**: hay ficheros sucios en HEAD (`errors.ts`, `test-fixtures.ts`,
`runtime.svelte.ts`, `runtime.svelte.test.ts`, `contracts.test.ts`). Probar
contra HEAD antes de `--write`, y no reformatear lo que ya estaba sucio.
- **`git stash push <ruta>` + `stash pop`** es la forma limpia de comprobar si un
fallo es tuyo o de HEAD. Hoy demostró que el D9 fallaba sin mis cambios.

Powered by TurnKey Linux.