# Enforcement reference — Harness Rules activation

How Harness Rules are put in front of the agent and the developer. Unlike
Business Rules (framed as post-implementation **memory**), HR are **constraints**
activated at the moment they matter. For the rule-file format, see `format.md`.
For the catalog conventions and rationale, see `docs/harness-rules/README.md`.

---

## The three activation layers

HR replicate the BR three-layer activation pattern, adapted to the distinct
nature of HR (constraints, not crystallized invariants).

```
              PROMPT-TIME                PROPOSE-TIME              WRITE-TIME
          (UserPromptSubmit)            (/opsx:propose)            (PreToolUse)
                  │                          │                         │
       inject-rules-context.ts      openspec/config.yaml      architecture-checkpoint.ts
       (loads HR index, top-N,      (HR guidance folded into  (existing structural gate —
        SEPARATE block, framed       context: + rules —        on block, APPENDS governing
        as CONSTRAINTS)              "Harness rules            HR-IDs to the message)
                                     applicable", HALT on
                  │                   VIOLATE of a blocking          +
                  │                   HR)                       harness-rules-content-guard.ts
                  │                          │                  (NEW hook — BLOCKS content
                  │                          │                   matching a rule's detect)
```

### Layer 1 — Prompt-time injection (`UserPromptSubmit`)

`.claude/hooks/inject-rules-context.ts` loads `cliter/harness-rules-index.json`
in addition to the BR index, scores active HR rules (primarily by bilingual
keyword), and injects the most relevant ones as a **SEPARATE labeled block** with
**CONSTRAINT framing** — distinct from the BR footer that frames rules as
"memory". HR get their own top-N budget so they do not compete with BR for slots.

- The block is headed as constraints that GOVERN the work (e.g. "harness rules
  que gobiernan este trabajo — constraints arquitectónicos, no solo memoria").
- Only `state: active` rules are injected.
- If the HR index is absent or empty, the hook degrades cleanly to pure BR
  behavior (BR injection is byte-identical to before this change).

### Layer 2 — Propose-time conflict detection (`/opsx:propose`)

`openspec/config.yaml` carries the Harness Rules guidance **folded into the
`context:` block and the `rules.proposal` list** (the only keys OpenSpec injects
into `openspec instructions`; an unknown top-level `harness_rules:` key would be
silently ignored). The Business Rules `context`/`rules` are preserved verbatim.
During `/opsx:propose` the agent:

1. Loads `cliter/harness-rules-index.json`.
2. Uses `byPath` to find active rules whose glob `pattern` matches the change's
   file paths (and `byKeyword` / `byId` as secondary lookups).
3. Records a `## Harness rules applicable` section in `proposal.md`, placed
   **immediately AFTER `## Business rules affected`**, declaring **COMPLY /
   VIOLATE / DEROGATE** per governing rule. Writes `None` if no active rule
   governs any touched path.
4. **HALTs only** when the approach would VIOLATE a rule of severity `blocking`,
   presenting the resolution options (derogate the rule / create an exception /
   justify no conflict) and recording the choice. A VIOLATE of an
   `informational` rule is recorded but does not halt.

This block uses COMPLY/VIOLATE/DEROGATE, **never** the BR `mantiene/deroga/
extiende` vocabulary, and **never** schedules a `/harness-rules:promote` task —
there is no such lifecycle (see "No promote lifecycle" below).

### Layer 3 — Write-time messaging + content-guard (`PreToolUse`)

Two hooks operate at write time. They are independent; either one exiting 2
blocks the write.

**(a) Rule-aware checkpoint messaging** — `.claude/hooks/architecture-checkpoint.ts`
is the EXISTING structural gate that blocks the first `Write` to a NEW file under
`backend/src/@(api|app|aurora|bridges)/...`. This change made its block message
**rule-aware**: when it already blocks, it resolves the active HR ids whose
`byPath` matches the target file (reusing `rulesByPath`/`globToRegex` from
`scripts/_catalog-runtime/glob.ts`) and APPENDS them to the message ("Reglas
harness que gobiernan este path: HR-BRIDGES-001 — consúltalas"). It introduces
**no new blocking condition** — the `exit 2` is still governed solely by the
existing structural gate. If the HR index is missing, the message degrades to the
prior structural text.

**(b) Content-guard** — `.claude/hooks/harness-rules-content-guard.ts` is a NEW
`PreToolUse` hook on `Write`/`Edit`/`MultiEdit`. It is **catalog-driven**: it
reads the active rules' `detect` fields from the index, and BLOCKS (exit 2,
emitting `detect.message`) when the tool's content contains a rule's
`detect.pattern` AND the target file matches that rule's `paths`. It hardcodes no
rule knowledge.

---

## Advisory vs content-blocked — the two-level split

HR are NOT enforced uniformly. A rule's enforcement level is decided by its
**detectability**, not by its severity.

| Level               | Which rules                                                              | How enforced                                                          |
| ------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| **Content-blocked** | Rules with a `detect` field. **Today: `HR-ACL-001` only** (`as unknown as`) | The content-guard BLOCKS the write (exit 2). A hard VIOLATE.          |
| **Advisory**        | The other five: `HR-MODULES-001`, `HR-BRIDGES-001`, `HR-PORTS-001`, `HR-CODEGEN-001`, `HR-MODES-001` | Surfaced via the skill, prompt injection, propose detection, and checkpoint messaging. The agent applies judgment. **Never auto-blocked.** |

### Why the semantic rules are advisory, not blocked by path

A path glob cannot encode intent:

- `HR-MODULES-001` prohibits a **shadow module**, not the creation of
  `*.module.ts` (catalyst generates them; devs add legitimate flows). A glob
  cannot tell a legitimate new flow from a shadow module.
- `HR-BRIDGES-001` prohibits cross-BC code in `@app/`, but legitimate within-BC
  adapters also live there. A glob cannot tell them apart.

Blocking these by path would over-block normal operations (false positives). Their
enforcement therefore stays advisory — the agent, guided by this skill and the
injected constraints, makes the call. Only rules whose violation is **mechanically
detectable without ambiguity** (a literal content pattern, like `HR-ACL-001`'s
`as unknown as`) get blocking teeth.

### Adding a new content-blocked rule (catalog-driven)

Because the content-guard is catalog-driven, giving a rule blocking teeth is a
**catalog edit, not a code edit**: add a `detect: { pattern, message }` to the
rule's `.md` frontmatter, run `pnpm hr:generate`, and the guard picks it up from
`byId[id].detect`. Keep `pattern` a substring literal unless an unambiguous regex
is genuinely needed; keep `message` citing the id and the correct alternative.

---

## Slash commands: audit exists; promote / document do NOT

There IS one HR slash command — `/harness-rules:audit` — and the propose-time
block **never** schedules a promote. The promote/document absence is the
deliberate, defining difference from Business Rules:

- **`/harness-rules:audit` EXISTS** — a **read-only** health/drift report
  (`scripts/harness-rules/audit.ts` + `.claude/commands/harness-rules/audit.md`)
  following the structural-script + LLM-continuation pattern: the script emits
  sections **A (health)** and **B (mechanical drift)** and exits `0` (clean) /
  `2` (warnings); Claude then continues the session with section **E (semantic
  tensions)** over the rule bodies. It is NOT exposed as a `pnpm hr:*` alias
  (matching BR audit) — invoke it via the slash command, or run
  `npx tsx scripts/harness-rules/audit.ts` (`--json` for tooling) directly. It
  never mutates the catalog or the index.
- **`/harness-rules:promote` does NOT exist (by design).** A BR is crystallized
  POST-archive: the proposal declares future rules and `/business-rules:promote`
  writes them after archive. HR are **stable constraints**, NOT crystallized
  per-change — there is no promote lifecycle (S3 decision, encoded in
  `openspec/config.yaml`). An HR changes ONLY through a deliberate architectural
  decision (its own change): derogate the old rule, create a new one with a NEW
  id (ids never reused). An ordinary proposal records COMPLY/VIOLATE/DEROGATE
  against the EXISTING catalog; it does not crystallize anything later.
- **`/harness-rules:document` does NOT exist (by design).** A BR is documented
  retroactively FROM an aggregate's code; HR are cross-cutting architectural
  patterns authored BEFORE they govern code — there is no aggregate to bootstrap
  from. Documenting an HR is writing the `.md` directly + `pnpm hr:generate`.

To create or change a rule, edit the catalog manually (Workflow: derogate +
create new) and run `pnpm hr:generate && pnpm hr:validate`.

---

## Degradation guarantees (regression safety)

- If `cliter/harness-rules-index.json` is absent or empty, all three layers
  degrade to pure BR behavior: prompt injection emits only the BR block, the
  checkpoint emits only its structural message, the content-guard finds no
  patterns and blocks nothing.
- Extending the shared hooks (`inject-rules-context.ts`,
  `architecture-checkpoint.ts`) preserves 100% of the observable BR behavior —
  this is a verified invariant of the change.
