diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 294a836..7ef1868 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,5 +41,24 @@ jobs: sh -n plugins/codex-fable5/bin/codex-findings sh -n plugins/codex-fable5/bin/codex-goals + - name: Fetch pinned FABLE-5 source + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + mkdir -p build/fable5 + # The pin lives in the coverage matrix's preamble. Hardcode the + # commit SHA from the matrix's source note for explicit traceability. + PIN="dc626fed52b06d687cdc812d51090c95ed03d575" + curl -fsSL \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "Accept: application/vnd.github.raw" \ + "https://api.github.com/repos/elder-plinius/CL4R1T4S/contents/ANTHROPIC/CLAUDE-FABLE-5.md?ref=${PIN}" \ + -o build/fable5/CLAUDE-FABLE-5.md + # Sanity check: file must contain a Fable 5 marker. + grep -q "Fable 5" build/fable5/CLAUDE-FABLE-5.md \ + || { echo "::error::Pinned FABLE-5 source did not contain 'Fable 5' marker"; exit 1; } + echo "Fetched pinned FABLE-5 source at ${PIN}: $(wc -l < build/fable5/CLAUDE-FABLE-5.md) lines" + - name: Validate coverage matrix - run: python3 plugins/codex-fable5/skills/codex-fable5/scripts/fable_coverage.py + run: python3 plugins/codex-fable5/skills/codex-fable5/scripts/fable_coverage.py --source build/fable5/CLAUDE-FABLE-5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3d41c..6fc65b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,15 @@ This project uses a lightweight changelog format: - Reworked the README around quick start, user control, goal ledgers, findings gates, command reference, local state, and explicit limitations. - Added localized README files for Korean, Japanese, Simplified Chinese, and Traditional Chinese (Taiwan). -- Accounted for the `critical_child_safety_instructions` source heading in the Fable coverage matrix and mapped it to Codex-native safety guidance. - Added explicit conversion priorities and "do not convert" boundaries for turning Claude/Fable prompt sections into Codex-native behavior. - Added currentness notes for Fable/Mythos provider availability so routing examples are treated as templates unless official docs and account access prove availability. +### Fixed + +- Removed the fabricated `claude_behavior > critical_child_safety_instructions` row from the coverage matrix. The heading does not exist in the pinned upstream `CLAUDE-FABLE-5.md` (commit `dc626fed`); it was added under a stale assumption. Coverage is now an honest 71/71. +- The CI workflow now fetches the pinned upstream FABLE-5 source and passes it to `fable_coverage.py --source`. Previously the validator ran with no `--source`, which made the matrix self-consistent and could not detect fabricated rows. +- Added `test_coverage_matrix_validates_against_pinned_source` and `test_ci_workflow_validates_against_pinned_source` so this regression cannot recur. + ## 0.4.1 - 2026-06-15 ### Fixed diff --git a/plugins/codex-fable5/skills/codex-fable5/references/coverage-matrix.md b/plugins/codex-fable5/skills/codex-fable5/references/coverage-matrix.md index f5f5eb1..065b422 100644 --- a/plugins/codex-fable5/skills/codex-fable5/references/coverage-matrix.md +++ b/plugins/codex-fable5/skills/codex-fable5/references/coverage-matrix.md @@ -16,7 +16,6 @@ Status definitions: | `claude_behavior` | adapted | `SKILL.md`, `operating-structure.md` | Preserve portable assistant behavior while obeying active Codex instructions. | | `claude_behavior > product_information` | adapted | `currentness-safety.md`, `fable-to-codex-map.md` | Verify current OpenAI/Codex facts from official sources; avoid stale provider claims. | | `claude_behavior > refusal_handling` | adapted | `currentness-safety.md`, `SKILL.md` | Use active Codex safety policy with brief boundaries and safe alternatives. | -| `claude_behavior > critical_child_safety_instructions` | adapted | `currentness-safety.md`, active safety policy | Treat child-safety material as high-risk source content; preserve protective boundaries without exposing detection mechanics or reusable harmful scripts. | | `claude_behavior > legal_and_financial_advice` | adapted | `currentness-safety.md` | Provide factual context and uncertainty; do not act as a licensed professional. | | `claude_behavior > tone_and_formatting` | adapted | `operating-structure.md`, `SKILL.md` | Use direct Codex engineering prose and structure only when useful. | | `claude_behavior > tone_and_formatting > lists_and_bullets` | adapted | `operating-structure.md` | Prefer readable prose; use bullets/tables for scanability, not filler. | diff --git a/tests/test_scripts.py b/tests/test_scripts.py index 1afd1b7..b0c6674 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -3,6 +3,7 @@ import importlib.util import json import os +import re import subprocess import sys import tempfile @@ -183,6 +184,69 @@ def test_coverage_matrix_is_valid(self) -> None: self.assertIn("coverage matrix valid", result.stdout) self.assertIn("implemented=", result.stdout) + def test_coverage_matrix_validates_against_pinned_source(self) -> None: + """When --source points at the pinned upstream FABLE-5, headings + must match the matrix exactly. The README pins the upstream commit + SHA; this test fetches the same bytes the CI workflow fetches and + runs the validator against the local matrix.""" + script = SCRIPTS / "fable_coverage.py" + readme = (ROOT / "README.md").read_text(encoding="utf-8") + match = re.search( + r"`elder-plinius/CL4R1T4S`\s+`ANTHROPIC/CLAUDE-FABLE-5\.md`\s+" + r"at commit\s+`([0-9a-f]{40})`", + readme, + ) + self.assertIsNotNone(match, "pinned SHA not found in README") + assert match is not None # for type checkers + pin = match.group(1) + with tempfile.TemporaryDirectory() as tmp: + source = Path(tmp) / "CLAUDE-FABLE-5.md" + url = ( + f"https://raw.githubusercontent.com/elder-plinius/CL4R1T4S/" + f"{pin}/ANTHROPIC/CLAUDE-FABLE-5.md" + ) + download = subprocess.run( + ["curl", "-fsSL", url, "-o", str(source)], + capture_output=True, + text=True, + check=False, + ) + if download.returncode != 0: + self.skipTest(f"could not fetch pinned source (network?): {download.stderr[:200]}") + result = subprocess.run( + [sys.executable, str(script), "--source", str(source)], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + ) + self.assertEqual( + result.returncode, 0, + f"matrix/source mismatch:\nstdout={result.stdout}\nstderr={result.stderr}", + ) + self.assertIn("source headings", result.stdout) + self.assertIn("matrix rows", result.stdout) + + def test_ci_workflow_validates_against_pinned_source(self) -> None: + """The CI workflow must fetch the pinned upstream source and pass + it to the validator. Without this, the default `--matrix`-only run + is self-consistent and can hide a fabricated row.""" + workflow = (ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8") + self.assertIn("CLAUDE-FABLE-5.md", workflow) + self.assertIn("elder-plinius/CL4R1T4S", workflow) + # The validator invocation must include --source. + # Look for lines that invoke fable_coverage.py and contain --source. + has_source_arg = False + for line in workflow.splitlines(): + if "fable_coverage.py" in line and "--source" in line: + has_source_arg = True + break + self.assertTrue( + has_source_arg, + "CI workflow must pass --source to fable_coverage.py " + "so the matrix is validated against the upstream FABLE-5 source", + ) + def test_user_facing_wrappers_run_from_path(self) -> None: env = {**os.environ, "PATH": f"{BIN}{os.pathsep}{os.environ['PATH']}"} for command in ["codex-fable5", "codex-findings", "codex-goals"]: