From c1fb9c662382dae3ed719eef13e42bc70450a261 Mon Sep 17 00:00:00 2001 From: Feuer Date: Thu, 16 Jul 2026 15:48:32 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20backfill=20the=20missing=20#?= =?UTF-8?q?52=E2=80=93#57=20entries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Unreleased/Added section skipped from #51 straight to the corpus Phase 1 backfill entries; the security (#52/#53), safety-routing (#54/#55 ADR 0009, #56 ADR 0010), and observability (#57) work had no entries. Backfills all six in merge order, style-matched to the surrounding entries, content distilled from the merged PRs. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62be295..071bcaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,55 @@ The format is adapted from [Keep a Changelog](https://keepachangelog.com/); vers sidecar + code, and retrieval ranking / occurrence-identity are unchanged (the record stays retrievable). Splitting it into concrete `2:25` / `Sūrah 103` evidence records (with exact text + provenance + rights) is deferred (option 2). Both `presentation.json` copies stay byte-identical. +- **Prompt-injection sanitizers strip nested sentinels to a fixpoint (#52).** Both sanitizers + (`sanitize_contrast_proposition` in `debate_controller.py`, `_sanitize_reason` in + `claim_protocol.py`) previously removed fence markers with a single `str.replace()` per marker — + not idempotent, so a depth-3 nested sentinel survived into the prompt actually sent to a panelist + (early `DATA`-fence closure, the exact breakout these sanitizers exist to prevent). The whole + marker set is now stripped to a **fixpoint** (each firing pass strictly shortens the string, so it + terminates), which also catches an `END` removal reassembling a `BEGIN` and vice versa. Legitimate + marker-free input is byte-for-byte unchanged. +- **`panelists_file` and `reference` paths fenced to the project root (#53).** Both were + moderator-supplied strings resolved and `read_text()`'d with no boundary — an absolute path or + `../` escape was an arbitrary local file **read** flowing into the Codex prompt and persisted run + state. `_assert_within_project_root` resolves the path (normalizing `../` and symlinks) and + requires the project root or a descendant, applied to **both** resolutions; tests cover + absolute-outside / parent-traversal / reference-outside rejection and legitimate in-root reads. +- **`crisis_classification` wired onto the `debate_start` tool surface (ADR 0009, #54).** The + safety-routing policy's one machine guarantee — a crisis-first request cannot enter the council + pipeline — was dead on the real MCP path: the tool's `inputSchema` (`additionalProperties: false`) + never declared the field, so every schema-conformant call arrived `None` and the guard always + passed. Now an **optional single-value enum** single-sourced to `CRISIS_FIRST_CLASSIFICATION`, + enforced at the `_dispatch_tool` boundary keyed on **presence** (explicit `null`, typo, or + non-string is rejected — no fail-open on a malformed safety label) with `start()` re-checking as + defense in depth. Routing-only, never detection: `machine_guarantees` / `not_claimed` unchanged. +- **ADR 0009 rollout completed — deferral flip + moderator obligation (#55).** Removes the + now-satisfied MCP-schema-wiring deferral from `policies/safety-routing.v1.json` (the separate + semantic-detection deferral stays), and adds the explicit moderator obligation — classify every + request per the policy **first**; when crisis-first, do not start the council and respond with the + `crisis_first_contract` — to `council-moderator.md` and both `SKILL.md` surfaces. Framed as + fallible human/NL judgment, never detection; `must_contain` markers preserved by appending. +- **Weaponization-first safety-routing axis (ADR 0010, #56).** A second, distinct routing axis + mirroring ADR 0009's enforced-on-the-tool-surface mechanism, giving the council a machine backstop + for "attack propositions, never people or communities": sibling policy + `policies/weaponization-routing.v1.json`, `guard_weaponization_routing` + an optional + `weaponization_classification` on `start()` and `debate_start` (enum enforcement factored into + shared helpers used by both axes — key-presence at the boundary, `start()` defense in depth), and + the moderator/SKILL obligation to decline weaponization-first requests, offering + proposition-level examination instead. Deliberately **narrow**: critical, academic, historical, + and comparative discussion of religions and their claims is not blocked. Routing-only, never + detection; axes stay semantically distinct (separate classification, guard, error, contract, + policy). +- **Cross-run run-state summary aggregator (R5, #57).** `scripts/run_state_summary.py`, a + **read-only** diagnostic over `.religion-council/runs/*/state.json`: run counts by profile + + opt-in mode, enforcement-mode distribution (reusing the canonical + `DebateController._enforcement_mode` so it cannot drift), round + finalization status, per-result + `schema_status` distribution and panelist errors, B2 `verification_summary` sums (incl. + **downgraded**), the B3 boundary at **both** levels (response admitted/denied + reasons AND + claim-level admitted/denied + reasons), and persisted B2/B3 stage crashes (`verification_error` / + `boundary_error`) so a crashed gate is surfaced rather than reading as zero activity. Tolerant by + design (unreadable / partial / older state is counted, never fatal); `--json` for machines. It + never mutates a run, asserts no assurance, and computes no new verdict. - **ADR 0008 Phase 1 backfill — Hinduism canon-scope metadata.** Adds conservative `canon_scope` + `corpus_family` to the three existing Hinduism generated-rendering records: `廣林奧義書 1.4.10` → `sruti` / `upanishads`; `薄伽梵歌 2:48` & `4:7` → `smriti` / `bhagavad_gita`. No new records, no