Doctrina arquitectural sobre límites de los canales runtime:
- Canales built-in del framework: sound + haptic (los únicos).
- ARIA estructural: declarativo en morfo, NO canal.
- ARIA dinámico (live regions): soma escribe directo, NO canal.
- Visual (motion / color / presence): eidos CSS via data-event-*, NO canal.
- Regla operativa: algo es canal sólo si recibe SemanticSignal, acepta
modulación por intent.deltas, y tiene signature paramétrica análoga
a sound/haptic. ARIA dinámico falla la modulación.
- Channels extensibles: declaration merging del registry; opt-in
por app (voice, a11y formal, etc.). Framework no envía ninguno.
Cierra una tentación que surgió en D.7 — convertir ARIA en canal era
confusión categorial. Esta sección fija los límites para que nadie
reincida.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- **Implementación**: aplicar en `src/uix/sema/types.ts:SEMA_FAMILY_POLICY`. TypeScript deriva `IntentExpectedFamily` desde `intentRequirement === 'required'`. `intentGuidance` queda como campo doctrinal de documentación + posible lint en el futuro.
### D.8 Channels: qué es canal y qué no
- **Status**: **PROJECT_CANON** (regla arquitectural de límites)
- **Origen**: al revisar la sección D.7 emergió la tentación de promocionar ARIA a "canal a11y". Análisis honesto: era confusión categorial. Esta sección fija los límites para que nadie reincida.
**Canales runtime declarables en sema (built-in del framework):**
```
sound — SoundSignature (synth/sample, modulable por intent.deltas)
haptic — HapticSignature (vibration, modulable por intent.deltas)
```
**Lo que NO es canal (y no debe convertirse en canal):**
| Cosa | Dónde vive | Razón |
|---|---|---|
| ARIA estructural (`aria-label`, `aria-expanded`, `role`, ...) | `Morfo.parts[].aria` + `.role` | Declarativo. Resuelto desde props/states. Promocionarlo a canal sería convertir lo declarativo en post-hoc DOM manipulation. |
| ARIA dinámico (live regions `aria-live`) | Soma escribe directo en el live region DOM | Sólo un morfo lo necesita (`Announce`). Hacer canal añadiría engine surface sin caso plural. |
| Visual — motion (animaciones, transiciones) | Eidos CSS `@keyframes` reaccionando a `data-event-*` stampeado por `VisualChannel` | El stamp es la única responsabilidad de sema; el output visual es CSS. |
**Channel activation por familia** (en `SEMA_MAP`):
```
contact sound + haptic
commit sound + haptic
signal sound + haptic
emerge sound (sin haptic — apariciones no son táctiles)
shift sound (sin haptic — cambio de marco)
handle haptic (sin sound — feedback gestual es táctil)
sustain ninguno (puramente visual)
delegate ninguno (puramente estructural / visual)
```
Los packs DEBEN respetar el `activeChannels` del family. Añadir `haptic` a un pack que opera sobre family `emerge` es incoherente; añadir `sound` a `handle` también. El resolver no lo bloquea, pero la doctrina sí.
**Regla operativa**: algo es canal sólo si cumple las tres:
```
(a) recibe SemanticSignal y emite output perceptual,
(b) acepta modulación por intent.deltas (perceptual loading),
(c) tiene signature paramétrica análoga a SoundSignature / HapticSignature.
Si falla (b), no es canal — es declarativo o ad-hoc.
```
ARIA dinámico falla (b): `signal-announce + threat` produce el MISMO texto + el MISMO ARIA. La carga evaluativa del intent vive en sound + visual, no en el texto del anuncio. Por eso no es canal.
**Channels extensibles (no built-in, opt-in por la app):**
El registry `SemaChannelSignatures` es OPEN vía declaration merging. Una app puede:
a11y: { ariaPayload: string; politeness: 'polite' | 'assertive' }; // si la app lo quiere formal
}
}
```
E implementar un `Channel` con `prepare(signal, target)` + `play(effective)`. El framework no envía ninguno de éstos — son extensión de producto.
**Pendiente sin urgencia**: si en el futuro `Announce` necesita ser pluggable (apps que quieran enviar a logger, telemetría, voice UI), entonces vale convertirlo en canal formal. Hoy no.