# Format reference — Harness Rules Catalog

Exact, mandatory format of an HR rule file. For rationale, scope semantics, and
the HR-vs-BR differences, see `docs/harness-rules/README.md` (the canonical
convention doc). For how activation works, see `enforcement.md`.

---

## Layout del archivo

```
cliter/harness-rules/<topic>.md              # core (scope: project)
cliter/<bounded-context>/harness-rules/<topic>.md   # bc-bound / multi-bc
```

- Un archivo por **topic**, no por agregado. El nombre describe el tema
  (`modules`, `bridges`, `ports-adapters`, `generated-code`, `dev-modes`,
  `types-acl`) porque las HR son mayoritariamente procedurales.
- Una regla vive en **un único archivo de topic**.
- El parser globbea **ambos** patterns: `cliter/harness-rules/*.md` (core) y
  `cliter/*/harness-rules/*.md` (bc-bound / multi-bc). Si un BC no se instala,
  su carpeta no existe y sus HR no aparecen en el índice.

---

## Frontmatter file-level (obligatorio)

```yaml
---
scope: project # project | bc-bound | multi-bc
bounded_contexts: [] # vacío para project; kebab-lowercase para bc-bound/multi-bc
last_updated: 2026-05-28 # ISO YYYY-MM-DD
keywords: # ≥1, BILINGÜE deliberado (español + inglés)
  - shadow module
  - módulo paralelo
paths: # ≥1; cada uno empieza por backend/ frontend/ cliter/ scripts/ docs/
  - backend/src/@app/**/*.module.ts
detect: # OPCIONAL — solo para reglas mecánicamente detectables por contenido
  pattern: 'as unknown as'
  message: 'HR-ACL-001 (blocking): double cast prohibido. …'
---
```

| Campo              | Tipo            | Notas                                                                                          |
| ------------------ | --------------- | ---------------------------------------------------------------------------------------------- |
| `scope`            | enum (string)   | **Obligatorio, HR-only.** `project` (core, `bounded_contexts: []`), `bc-bound` (1 BC), `multi-bc` (≥2 BCs). BR no tiene este campo. |
| `bounded_contexts` | string[]        | `minItems: 0`. Vacío para `project`; kebab-lowercase para bc-bound/multi-bc. (BR exige ≥1.)    |
| `aggregates`       | string[]        | **Opcional** en HR (BR lo exige). Las HR procedurales NO declaran `aggregates`.                |
| `last_updated`     | string (date)   | `YYYY-MM-DD`. Refrescar tras cualquier cambio significativo.                                   |
| `keywords`         | string[]        | ≥1, bilingüe deliberado. Lo usa el inject hook (scoring por keyword) y el índice `byKeyword`.  |
| `paths`            | string[]        | ≥1, globs respecto a la raíz. Top-level: `backend/`, `frontend/`, `cliter/`, `scripts/`, `docs/`. Alimenta `byPath`. |
| `detect`           | object          | **OPCIONAL.** `{ pattern: string, message: string }`, `additionalProperties: false`. Presente solo en reglas content-blocked. Ver abajo. |

Validado contra `docs/harness-rules/_schema.json` por `pnpm hr:validate`
(`additionalProperties: false`: no metas otras keys).

### El campo `detect` (content-blocked rules)

`detect` es lo que hace una regla **content-blocked** (ver `enforcement.md`).
Solo declarado en reglas cuyo patrón es mecánicamente detectable sin ambigüedad.

```yaml
detect:
  pattern: 'as unknown as' # substring literal por defecto (sin sorpresas de regex)
  message: 'HR-ACL-001 (blocking): …mensaje completo que el content-guard escribe a stderr al bloquear…'
```

- `pattern` se trata como **substring literal** por defecto: seguro, sin
  comportamiento regex inesperado.
- `message` es el texto exacto que el content-guard emite cuando bloquea — debe
  citar el id y explicar la alternativa correcta.
- Se propaga al índice como `byId[id].detect` vía `pnpm hr:generate`.
- **Hoy la única regla con `detect` es `HR-ACL-001`** (`as unknown as`). Las
  otras cinco lo omiten y son advisory.
- Añadir una regla mecánica nueva = añadir `detect` a su `.md` (catalog-driven);
  el content-guard NO hardcodea reglas.

---

## Encabezado humano

Bajo el frontmatter, antes de las reglas:

```markdown
# <título humano del topic>

> **Glosario**:
>
> - **<término>**: <definición breve>
> - **<término>**: <definición breve>

---
```

---

## Sección por regla (canónica)

Cada regla sigue este orden:

````markdown
## HR-<TOPIC>-<NNN> — <Título corto en español>

| Campo     | Valor                                 |
| --------- | ------------------------------------- |
| Estado    | `active` \| `derogated` \| `proposed` |
| Severidad | `blocking` \| `informational`         |
| Origen    | <cita de fuente: memo o spec>         |

### Enunciado

<Una o dos frases en español describiendo la convención.>

### Casos exhaustivos

<!-- DECISION MATRIX: situación → ¿permitido? → acción correcta -->

| #   | Situación | ¿Permitido? | Acción correcta |
| --- | --------- | ----------- | --------------- |
| 1   | …         | SÍ          | …               |
| 2   | …         | **NO**      | …               |

### Anchor patterns

<!-- globs / heurísticas que el content-guard o un futuro guard vigila -->

- <glob o heurística>

### Implementa

<!-- OPCIONAL. Si lo incluyes, cada `path` en backticks DEBE resolver en disco
     o `hr:validate` sale con exit 2 (warning). Si dudas, OMÍTELO y referencia
     la fuente en prosa. -->

### Tests

<!-- OPCIONAL -->

### Reglas relacionadas

- **HR-XXX-NNN** — <relación corta>
````

---

## Reglas estilísticas

- **Heading de regla**: `## HR-<...> — <título>`. El separador entre id y título
  es el **EM-DASH** `—` (U+2014) con un espacio a cada lado, no `-`. El parser
  compartido (`scripts/_catalog-runtime/`) lo usa para detectar reglas; su regex
  admite tanto `BR-...` como `HR-...`.
- **Tabla de metadatos**: va INMEDIATAMENTE tras el heading. Dos primeras filas
  cabecera (`| Campo | Valor |`) y separador. Los valores enum entre backticks
  (`` `active` ``, `` `blocking` ``); `Origen` en texto libre sin backticks.
- **Casos exhaustivos = decision matrix**: columnas `# | Situación | ¿Permitido?
  | Acción correcta`. `¿Permitido?` es `SÍ` / `**NO**` / `—`. (En BR esta tabla
  es rangos input/output → resultado; en HR es la decision matrix.) **No menos de
  dos casos** — incluye al menos un caso negativo.
- **Anchor patterns**: globs o heurísticas concretas. Para una regla
  content-blocked, deben describir el mismo patrón que su `detect.pattern`.

---

## Secciones obligatorias y opcionales

- **Obligatorias** en toda regla: tabla de metadata (Estado/Severidad/Origen),
  `### Enunciado`, `### Casos exhaustivos`, `### Anchor patterns`.
- **Opcionales**, según la naturaleza de la regla:
  - `### Implementa`: cuando la regla ancla a líneas concretas verificadas.
    (En BR es obligatorio; en HR opcional — muchas HR no anclan a un fichero.)
  - `### Tests`: cuando hay tests linkeables.
  - `### Reglas relacionadas`: cuando hay relación semántica con otras HR.
  - `### Derogación`: cuando `Estado: derogated`.
- **NO existen en HR** (sí en BR): `### Diagrama de cascada` (las HR no describen
  cascadas de negocio) ni `### Asunciones a verificar`.

---

## Diferencias de formato HR vs BR (resumen)

| Aspecto                 | Business Rules (BR)                | Harness Rules (HR)                                  |
| ----------------------- | ---------------------------------- | --------------------------------------------------- |
| `scope`                 | No existe.                         | Obligatorio (`project \| bc-bound \| multi-bc`).    |
| `bounded_contexts`      | ≥1 obligatorio.                    | `minItems: 0` (vacío para `project`).               |
| `aggregates`            | Obligatorio.                       | **Opcional.**                                       |
| `detect`                | No existe.                         | **Opcional** (content-blocked rules; hoy HR-ACL-001). |
| `### Implementa`        | Obligatorio.                       | **Opcional.**                                       |
| `### Anchor patterns`   | No existe.                         | **Añadido** (globs/heurísticas para el guard).      |
| `### Casos exhaustivos` | Rangos input/output → resultado.   | **Decision matrix**: situación → ¿permitido? → acción. |
| `### Diagrama de cascada` | Disponible.                      | No aplica.                                          |

---

## Estado y severidad

### Estado

| Valor       | Significado                                                 |
| ----------- | ----------------------------------------------------------- |
| `active`    | Convención vigente. (Las seis seed rules son `active`.)     |
| `derogated` | Reemplazada o retirada. Su id queda permanentemente tomado. |
| `proposed`  | Documentada pero aún no vigente.                            |

Solo las reglas `active` gobiernan (inject, propose, content-guard las filtran).

### Severidad

| Valor           | Significado                                                                   |
| --------------- | ----------------------------------------------------------------------------- |
| `blocking`      | Romperla rompe la arquitectura o introduce un anti-patrón estructural grave.  |
| `informational` | Orientación de criterio. Su violación genera fricción, no rotura estructural. |

Solo dos niveles, sin "warning" intermedio. `HR-MODES-001` (Solution mode =
YAGNI) es la única seed rule `informational`. La severidad gobierna el HALT del
propose: solo un VIOLATE de una regla `blocking` detiene `/opsx:propose`.

---

## Errores comunes y cómo evitarlos

| Error                                                   | Síntoma                                          | Solución                                                  |
| ------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------- |
| Heading con `-` en vez de `—`                           | El parser no detecta la regla                    | Usar siempre `—` (U+2014).                                |
| Id con minúsculas o sin guiones                         | `hr:validate` rechaza el archivo (exit 1)        | `HR-MODULES-001`, no `Hr-modules-1`.                      |
| Declarar `aggregates` en una HR procedural por costumbre | Ruido; campo innecesario                         | Omitir `aggregates` salvo regla aggregate-specific.       |
| `detect.message` sin citar el id                        | El bloqueo del content-guard no explica la regla | Empezar el `message` por `HR-<...> (<severidad>):`.       |
| Path inexistente en `### Implementa`                    | `hr:validate` exit 2 (warning)                   | Citar solo paths verificados o referenciar la fuente.     |
| Editar el índice o `INDEX.md` a mano                    | Drift con el catálogo                            | `pnpm hr:generate`.                                       |
| "Armonizar" la mezcla de idiomas                        | Pérdida de keywords bilingües y convención       | NO armonizar; es deliberada.                              |
