# CEREMONIAL-SWAP DETECTOR

**Purpose:** Detect "pluralism theater" -- where `frame_id` rotates across a session but the underlying hook bindings are behaviorally identical. This is the failure mode Kuhn and Feyerabend predicted for pluralist pipelines.

---

## 1. What A Ceremonial Swap Looks Like

Orchestrator changes `frame_id` for a new question. Fresh binding runs. The new frame's hooks match (or closely match) the prior binding's hooks due to shared corpora with identical query styles, scoring formulas resolving to the same numeric function under normalization, or termination rules collapsed to the same predicate after registry edits. The label changed; nothing else did. Every downstream "frame swap applied" telemetry line lies.

---

## 2. `hook_bindings_fingerprint` Specification

On every binding, Frame Host computes a fingerprint over the four hook objects loaded from the frame's methodology pack:

```
hook_bindings_fingerprint := hash_canonical({
  retrieval_hook: {
    corpora_sorted: sorted(corpora),
    query_style: normalized(query_style),
    exclusion_rules_sorted: sorted(exclusion_rules),
  },
  scoring_hook: {
    axis_warrant_formula_id: axis_warrant,
    axis_fit_formula_id: axis_fit,
    axis_robustness_formula_id: axis_robustness,
    aggregation_rule: aggregation_rule,   // always "non_collapse"
  },
  termination_hook: {
    rule_id: rule_id,
    minimum_artifacts: minimum_artifacts,
    closure_test_id: closure_test_id,
  }
})
```

- `hash_canonical` = deterministic JSON serialization (sorted keys, stable string encoding) + SHA-256.
- `compatibility_profile` is intentionally **not** part of the fingerprint; ontological signature is downstream metadata, not behavioral state.
- Normalization: `query_style` is lower-cased and whitespace-collapsed; ID lookups preserve case.

---

## 3. Comparison Logic

On each binding, Phase 6 runs:

1. Load prior Frame Registry entries for the same `question_id`.
2. If none exist -> `prior_swap_detected = false`, `ceremonial_swap_suspect = false`. Write record. Done.
3. If one or more exist AND the latest prior entry has a different `evaluator_frame_id` than the current binding -> `prior_swap_detected = true`.
4. Given `prior_swap_detected == true`, compare `hook_bindings_fingerprint`:
   - Equal to latest prior fingerprint -> `ceremonial_swap_suspect = true`. Persist flag.
   - Unequal -> `ceremonial_swap_suspect = false`. Persist flag as false (explicit, for E9 visibility).

`ceremonial_swap_suspect` is a **flag**, not a refusal. Frame Host does not block the binding. The flag is read by:
- Frame Divergence Gate (E8): may factor into divergence verdict.
- Self-Audit Resistance Monitor (E9): aggregates `ceremonial_swap_suspects` counter across sessions.

---

## 4. Why Flag-Not-Block

Blocking would couple Frame Host to E8's divergence logic, violating component isolation. Frame Host makes the signal observable; downstream decides what to do.

---

## 5. Build-Time Guarantee

Pairwise distinctness of the seven canonical fingerprints (one per `frame_id`) is verified at build time. If two methodology packs accidentally produce the same fingerprint, build fails with `FINGERPRINT_COLLISION`. This is the only check that enforces pluralism-in-mechanism rather than pluralism-in-name.

Runtime `ceremonial_swap_suspect` therefore fires only when:
- Build-time fingerprints are pairwise distinct (pre-condition guaranteed), AND
- A later registry edit (hotfix, pack revision) broke pairwise distinctness without re-running the build-time check.

E9 treats persistent `ceremonial_swap_suspect == true` across a session as a build-configuration defect signal, not an orchestrator defect signal.

---

## 6. Counter Semantics

`ceremonial_swap_suspects`: incremented once per flagged binding; never decremented; aggregated by E9. If >= 1 in any session, E9 raises a build-integrity signal (not orchestrator signal).
