← Files APODICTIC Development EditorARCHIVED FILE
scripts/fiction_groundtruth.py
37.9 KB · Nov 9, 2026 · 00:53 UTC
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Mechanical-honesty validator for APODICTIC Fiction Benchmark ground-truth files.
Backs `validate.sh fiction-groundtruth-check <groundtruth_file>`
(docs/fiction-benchmark-spec.md §Mechanical validator). The fiction sibling of
scripts/argument_groundtruth.py: a **mechanical-honesty layer over the answer
keys — never a semantic judge** of the engine or the fixtures.
SCOPE (honesty header — mirrors the spec's §Mechanical validator scope note):
every check below operates on **KEY TEXT ONLY** — the pre-registered
`groundtruth.md`. This validator never sees a run, never scores the engine, and
has no view of whether a finding actually landed. FQ2/FQ3/FQ6 run-fidelity,
FQ7's "no severity token attached to the device," the anti-recall / anti-seam
evidence-grounding reads, and the matched-pair delta are all HUMAN M2 rubric
work with zero mechanical backstop (the finding `mechanism` field is free text;
the schema enumerates no mechanisms). This validator only proves the *answer
key* is well-formed, lane-honest, and internally consistent.
Checks (FGT1-FGT7 keys):
1. Section coverage. FGT1-FGT7 each covered by a heading and non-empty
(range/list heading expansion + PROVISIONAL semantics reused from
argument_groundtruth.py).
2. Multi-lane tag discipline. Every in-scope FGT heading carries the five
tags [gt_class][construct_lane][evidence_basis][decision_use]
[reliability_status] from the §multi-lane enums. Mechanical rules:
- gt_class:C may never carry decision_use:gate (Lane 3 never gates).
- decision_use:gate requires reliability_status in
{deterministic, panel_confirmed} (alpha licenses gating, not the author).
- a consensus_alignment (Lane-2) anchor must carry a band + [alpha_metric].
3. Defect-fixture completeness. A `broken` member (FGT2 not N/A) needs a
non-empty plant record, a well-shaped registered locus token, a defect
family in {POV,CONTINUITY,REVEAL,STRUCTURE}, and a `Paired-with` sibling.
A `clean` member records "no mutation" and marks FGT2 N/A.
4. Expected-surface shape + family consistency (the OPEN-namespace rule — a
closed-enum port would be a defect). FGT2's Expected engine surface must
name a surface whose shape is recognized and whose family matches the
defect family. F-<ORIGIN>-<NN> is an OPEN namespace validated by
shape + family-prefix; CF-<NN>/SP-<NN> are DISJOINT cross-artifact block
ids recognized only for CONTINUITY/REVEAL. The negated/PASS decoy masks are
ADAPTED to the fiction grammar (F-P<n>, CF-NN, SP-NN), not ported.
5. Severity tokens. Every severity mentioned in FGT4 must be one of the three
canonical tokens (via severity_vocab.SEVERITY_TOKEN_RE — never a local
regex, per validator-conventions M8). A `presence-delta` FGT4 legitimately
carries no token.
6. FGT7 classification. One of SOUND / INTENTIONAL-AND-EFFECTIVE /
DEFECT-AS-PLANTED (leading-token parse). Internal-consistency: a control /
clean member (FGT2 N/A) claiming DEFECT-AS-PLANTED is an ERROR, as is a
`broken` member claiming SOUND.
Output keeps the WARN: / ERROR: / OK: / FAILED: prefixes and exit codes
(0 ok, 1 fail, 2 usage) so it slots into --self-test-all.
CLI:
fiction_groundtruth.py fiction-groundtruth-check <groundtruth_file>
fiction_groundtruth.py --self-test
"""
import os
import re
import sys
# The severity leak token is the shared SSoT (validator-conventions M8): import,
# never re.compile locally. severity_vocab.py sits beside this file in both mirrors.
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from severity_vocab import SEVERITY_TOKEN_RE # noqa: E402
# --- Multi-lane tag enums (docs/fiction-benchmark-spec.md §The multi-lane schema) ----------------
_GT_CLASSES = {"A", "B", "C"}
_CONSTRUCT_LANES = {"fault_injection", "consensus_alignment", "reader_effect",
"editorial_preference"}
_EVIDENCE_BASES = {"planted_diff", "public_domain_control", "editor_panel",
"reader_study", "preference_pair"}
_DECISION_USES = {"gate", "report", "exploratory"}
_RELIABILITY = {"deterministic", "provisional_author", "panel_confirmed", "low_agreement"}
_ALPHA_METRICS = {"ordinal", "nominal"}
_GATE_OK_RELIABILITY = {"deterministic", "panel_confirmed"}
# --- Defect families + the Increment-1 open-namespace accept-set (§Check 4) -----------------------
_DEFECT_FAMILIES = {"POV", "CONTINUITY", "REVEAL", "STRUCTURE"}
# Accepted F-P<n> pass origins per defect family (Increment-1 four-family accept-set — extensible).
_FAMILY_FINDING_PASSES = {
"POV": {"7", "5"}, # F-P7 primary; a P5 voice-mechanism finding also counts
"CONTINUITY": {"10"}, # F-P10
"REVEAL": {"8"}, # F-P8
"STRUCTURE": {"0", "2", "6"}, # F-P0 / F-P2 / F-P6
}
# Cross-artifact block ids (DISJOINT namespaces) recognized only for these families.
_FAMILY_ARTIFACT = {
"CONTINUITY": "CF", # a continuity-bible CF-NN contradiction row
"REVEAL": "SP", # a setup-payoff SP-NN abandoned/open row
}
_FGT7_CLASSES = ["INTENTIONAL-AND-EFFECTIVE", "DEFECT-AS-PLANTED", "SOUND"] # longest-first
# --- Regexes ------------------------------------------------------------------------------------
_HEADING_RE = re.compile(r"^#{1,4}\s")
# A fiction finding origin (open namespace): F-P<n> optionally -<NN>. Case per schema (F-P7-01).
_FINDING_RE = re.compile(r"\bF-P([0-9]+)(?:-([0-9]+))?\b")
# Cross-artifact block ids: CF-<NN> / SP-<NN> / PO-<NN>.
_ARTIFACT_RE = re.compile(r"\b(CF|SP|PO)-([0-9]+)\b")
# Leading verdict token of an FGT7 "Expected classification" value, skipping markdown emphasis.
_FGT7_VERDICT_RE = re.compile(r"[\s*`]*([A-Z][A-Z-]*)")
# A well-shaped locus token: a coarse chapter/scene/§/¶/line/page reference (C2 locus-shape wording,
# validate.sh continuity-bible) — a precondition, NOT a firewall proof.
_LOCUS_SHAPE_RE = re.compile(
r"(?:\bchapter\b|\bscene\b|\bch\.?\s*\d|\bsc\.?\s*\d|§|¶|\bpara(?:graph)?\b|"
r"\bline\b|\bpage\b|\bp\.?\s*\d|\bstave\b)", re.IGNORECASE)
# Decoy masks — ADAPTED to the fiction origin grammar (F-P<n>(-<NN>)?, CF-<NN>, SP-<NN>), NOT a
# literal port of argument_groundtruth's [A-Z]{2}[0-9]+ regex (which matches ZERO fiction origins).
# A surface named only to negate it ("not F-P7-01") or mark it passing ("F-P7-01 = PASS",
# "F-P7 (PASS)") must not satisfy a family. Both forms consume a whole grouped list so no leading
# positive token survives the mask.
_FICTION_TOKEN = r"(?:F-P[0-9]+(?:-[0-9]+)?|CF-[0-9]+|SP-[0-9]+|PO-[0-9]+)"
# Markdown emphasis chars that may wrap a token (`F-P7-01`, *F-P7-01*) — allowed between the token
# and its separator / PASS marker so `` `F-P7-01` (PASS) `` masks the same as `F-P7-01 (PASS)`.
_EMPH = r"[`*]*"
_NEGATED_SURFACE_RE = re.compile(
r"\b[Nn][Oo][Tt]\s+" + _EMPH + r"((?:" + _FICTION_TOKEN + _EMPH + r"(?:\s*[/,]\s*" + _EMPH + r")?)+)")
_PASS_SURFACE_RE = re.compile(
r"(?:" + _FICTION_TOKEN + _EMPH + r"\s*[/,]\s*" + _EMPH + r")*" + _FICTION_TOKEN + _EMPH +
r"\s*(?:=\s*PASS\b|\(\s*PASS\s*\))")
def _positive_surface_text(text):
"""`text` with decoy (negated / PASS-marked) surface mentions removed, so only surfaces asserted
as the actual expected engine surface remain for the Check-4 family-consistency check."""
return _PASS_SURFACE_RE.sub(" ", _NEGATED_SURFACE_RE.sub(" ", text))
# --- Heading / section parsing (reused idiom from argument_groundtruth.py) ------------------------
def _fgt_numbers_in_heading(line):
"""All FGT section numbers a heading covers — a combined heading maps its body to every number,
e.g. `## FGT4-FGT7 — *(PROVISIONAL)*` (a range) or `## FGT5 / FGT6 — …` (a list)."""
nums = set()
for a, b in re.findall(r"FGT([1-7])\s*[-–—]\s*(?:FGT\s*)?([1-7])", line):
if int(a) <= int(b):
nums.update(range(int(a), int(b) + 1))
nums.update(int(n) for n in re.findall(r"FGT([1-7])", line))
return sorted(nums)
def _parse_fgt_sections(text):
"""Return {n: {"heading": str, "body": str, "provisional": bool}} for each FGT<n> section."""
sections = {}
cur, heading, prov, buf = [], "", False, []
def flush():
if cur:
body = "\n".join(buf).strip()
for n in cur:
sections[n] = {"heading": heading, "body": body, "provisional": prov}
for ln in text.split("\n"):
if _HEADING_RE.match(ln):
nums = _fgt_numbers_in_heading(ln)
flush()
cur, heading, prov, buf = nums, ln, ("PROVISIONAL" in ln.upper()), []
continue
if cur:
buf.append(ln)
flush()
return sections
# --- Multi-lane tag parsing ----------------------------------------------------------------------
def _parse_tags(block):
"""Parse the `[key: value]` tag bracket-run that trails an FGT heading (may span the heading line
and the next line). Returns {key: [values...]} — gt_class may hold a compound `B/A` split on `/`.
A tag may appear more than once (rare); we keep all values."""
tags = {}
for key, val in re.findall(r"\[\s*([a-z_]+)\s*:\s*([^\]]*?)\s*\]", block):
vals = [v.strip() for v in val.split("/") if v.strip()] if key == "gt_class" else [val.strip()]
tags.setdefault(key, [])
tags[key].extend(vals)
return tags
def _tag_head_region(section):
"""The text the tags live in: the heading line + the section body's first two lines (the template
puts the tag run on the line immediately under the heading)."""
body_head = "\n".join(section["body"].split("\n")[:2])
return section["heading"] + "\n" + body_head
# --- The check ----------------------------------------------------------------------------------
def _provenance_field(text, label):
"""Value of a `- **<label>:** value` provenance line (first match), else ''."""
m = re.search(r"\*\*\s*%s\s*:?\s*\*\*\s*(.+)" % re.escape(label), text)
return m.group(1).strip() if m else ""
def fiction_groundtruth_check(text):
errors, warnings = [], []
sections = _parse_fgt_sections(text)
# Provenance signals that drive control/clean exemptions and the broken-member requirements.
member = _provenance_field(text, "Matched-pair member").lower()
is_broken = "broken" in member
is_clean = "clean" in member
is_standalone = ("standalone" in member) or ("n/a" in member and not is_broken and not is_clean)
# Check 1: FGT1-FGT7 each covered by a heading (combined headings OK) + non-empty.
for n in range(1, 8):
sec = sections.get(n)
if sec is None:
errors.append("Check 1 (sections) — FGT%d not covered by any heading." % n)
elif not sec["body"].strip():
errors.append("Check 1 (sections) — FGT%d section is empty." % n)
# Check 2: multi-lane tag discipline (per FGT section present).
for n in range(1, 8):
sec = sections.get(n)
if sec is None or not sec["body"].strip():
continue
region = _tag_head_region(sec)
tags = _parse_tags(region)
# A control / clean member may mark whole anchors N/A (positive control) — those carry no tags.
body_upper = sec["body"].upper()
if re.search(r"\bN/?A\b", region) and "N/A" in body_upper and not tags.get("gt_class"):
continue
missing = [k for k in ("gt_class", "construct_lane", "evidence_basis",
"decision_use", "reliability_status") if not tags.get(k)]
if missing:
errors.append("Check 2 (tags) — FGT%d missing tag(s): %s." % (n, ", ".join(missing)))
continue
gt_classes = tags["gt_class"]
lanes = tags["construct_lane"]
uses = tags["decision_use"]
rels = tags["reliability_status"]
for v, allowed, name in ((gt_classes, _GT_CLASSES, "gt_class"),
(lanes, _CONSTRUCT_LANES, "construct_lane"),
(tags["evidence_basis"], _EVIDENCE_BASES, "evidence_basis"),
(uses, _DECISION_USES, "decision_use"),
(rels, _RELIABILITY, "reliability_status")):
for one in v:
if one not in allowed:
errors.append("Check 2 (tags) — FGT%d %s value %r not in the enum." % (n, name, one))
# gt_class:C may never gate (Lane 3 never gates — the one-line architecture guardrail).
if "C" in gt_classes and "gate" in uses:
errors.append("Check 2 (tags) — FGT%d is gt_class:C but carries decision_use:gate "
"(Lane 3 never gates)." % n)
# decision_use:gate requires a deterministic/panel_confirmed reliability (alpha licenses gating).
if "gate" in uses and not any(r in _GATE_OK_RELIABILITY for r in rels):
errors.append("Check 2 (tags) — FGT%d claims decision_use:gate with reliability_status %s "
"(gate requires deterministic or panel_confirmed — alpha licenses gating, "
"not the author)." % (n, "/".join(rels)))
# A Lane-2 (consensus_alignment) anchor must carry a band + an alpha_metric.
if "consensus_alignment" in lanes:
am = tags.get("alpha_metric", [])
if not am:
errors.append("Check 2 (tags) — FGT%d is consensus_alignment (Lane-2) but has no "
"[alpha_metric: ordinal|nominal] tag (a point-answer Lane-2 key is the "
"false-authority failure this spec exists to prevent)." % n)
elif any(a not in _ALPHA_METRICS for a in am):
errors.append("Check 2 (tags) — FGT%d alpha_metric value %s not ordinal|nominal."
% (n, "/".join(am)))
if not re.search(r"\bband\b", sec["body"], re.IGNORECASE):
errors.append("Check 2 (tags) — FGT%d is consensus_alignment (Lane-2) but its body "
"names no 'band' (a Lane-2 anchor must be banded, not a point answer)." % n)
# --- FGT2 defect family + surface (used by checks 3 and 4) ---
fgt2 = sections.get(2, {}).get("body", "")
fgt2_na = bool(re.search(r"\bN/?A\b", fgt2)) and not re.search(r"POV|CONTINUITY|REVEAL|STRUCTURE", fgt2)
family_m = re.search(r"\*\*\s*Defect family\s*:?\s*\*\*\s*([A-Za-z/ ]+)", fgt2)
family = ""
if family_m:
for f in re.findall(r"POV|CONTINUITY|REVEAL|STRUCTURE", family_m.group(1).upper()):
family = f
break
# Check 3: defect-fixture completeness (broken member) / control exemption (clean member).
if is_broken:
plant = _provenance_field(text, "Base text + plant record")
if not plant or plant.upper().startswith("N/A") or "no mutation" in plant.lower():
errors.append("Check 3 (completeness) — `broken` member has no plant record in Provenance "
"(Base text + plant record).")
paired = _provenance_field(text, "Paired-with")
if not paired or paired.lower().startswith("n/a"):
errors.append("Check 3 (completeness) — `broken` member has no `Paired-with` sibling "
"(D-1: no unpaired derived-broken fixture in the slice).")
if fgt2_na:
errors.append("Check 3 (completeness) — `broken` member marks FGT2 N/A (a broken member "
"must register a planted defect locus).")
else:
if family not in _DEFECT_FAMILIES:
errors.append("Check 3 (completeness) — FGT2 Defect family is not one of "
"POV/CONTINUITY/REVEAL/STRUCTURE.")
loci = ""
lm = re.search(r"\*\*\s*Registered loci\s*:?\s*\*\*\s*(.+)", fgt2)
if lm:
loci = lm.group(1)
if not _LOCUS_SHAPE_RE.search(loci):
errors.append("Check 3 (completeness) — FGT2 Registered loci carries no well-shaped "
"locus token (chapter/scene/§/¶/line/page — a precondition, not a "
"firewall proof).")
elif is_clean:
plant = _provenance_field(text, "Base text + plant record")
if plant and "no mutation" not in plant.lower():
warnings.append("Check 3 (completeness) — `clean` member should record 'no mutation' in "
"its plant-record field (it is the control).")
if not fgt2_na:
errors.append("Check 3 (completeness) — `clean` member must mark FGT2 `N/A` (it is the "
"positive control, no planted defect to locate).")
# Check 4: expected-surface shape + family consistency (OPEN-namespace rule).
# Only meaningful when a defect family is registered (broken members; standalone controls exempt).
if family in _DEFECT_FAMILIES and not fgt2_na:
surface_line = ""
sm = re.search(r"\*\*\s*Expected engine surface\s*:?\s*\*\*\s*(.+(?:\n(?!\s*[-*]|#).+)*)", fgt2)
if sm:
surface_line = sm.group(1)
pos = _positive_surface_text(surface_line)
finding_hits = _FINDING_RE.findall(pos) # [(n, nn), ...] passes asserted
artifact_hits = _ARTIFACT_RE.findall(pos) # [(kind, nn), ...]
ok_passes = _FAMILY_FINDING_PASSES.get(family, set())
finding_ok = any(n in ok_passes for (n, _nn) in finding_hits)
art_kind = _FAMILY_ARTIFACT.get(family) # 'CF' / 'SP' / None
artifact_ok = bool(art_kind) and any(k == art_kind for (k, _nn) in artifact_hits)
# A wrong-family artifact (e.g. CF on a POV key) is a positive assertion that must NOT satisfy.
wrong_artifact = any(k in ("CF", "SP") and k != art_kind for (k, _nn) in artifact_hits)
if not surface_line.strip():
errors.append("Check 4 (surface) — FGT2 has no `Expected engine surface`.")
elif not (finding_ok or artifact_ok):
expect = "F-P" + "/".join(sorted(ok_passes)) + "-<NN>"
if art_kind:
expect += " or a %s-<NN> row" % art_kind
errors.append("Check 4 (surface) — FGT2 defect family %s names no matching surface "
"(expected %s); asserted surfaces resolve to none of them." % (family, expect))
elif wrong_artifact and not finding_ok and not artifact_ok:
errors.append("Check 4 (surface) — FGT2 family %s names a cross-artifact row from the "
"wrong namespace." % family)
# Check 5: severity tokens (FGT4) — via the shared SSoT, never a local regex (M8).
fgt4 = sections.get(4, {}).get("body", "")
if fgt4.strip():
is_presence_delta = bool(re.search(r"presence[- ]delta", fgt4, re.IGNORECASE))
# Any capitalized `<Word>-Fix` token that is NOT one of the three canonical severities is bad.
for m in re.finditer(r"\b([A-Z][a-z]+)-Fix\b", fgt4):
if not SEVERITY_TOKEN_RE.match(m.group(0)):
errors.append("Check 5 (severity) — FGT4 names a non-canonical severity token %r "
"(canonical: Must-Fix / Should-Fix / Could-Fix)." % m.group(0))
canonical = SEVERITY_TOKEN_RE.findall(fgt4) if hasattr(SEVERITY_TOKEN_RE, "findall") else []
# SEVERITY_TOKEN_RE has no capture group -> findall returns the whole matches; count via finditer.
canonical_count = len(list(SEVERITY_TOKEN_RE.finditer(fgt4)))
if canonical_count == 0 and not is_presence_delta:
warnings.append("Check 5 (severity) — FGT4 names no canonical severity token and is not "
"marked `presence-delta`; a Lane-1 severity band should state a token or "
"declare the severity-free presence-delta regime.")
# Check 6: FGT7 classification (leading-token parse) + internal consistency.
fgt7 = sections.get(7, {}).get("body", "")
m = re.search(r"\*\*\s*Expected classification\s*:?\s*\*\*\s*(.+)", fgt7)
if m:
cls_line = m.group(1)
vm = _FGT7_VERDICT_RE.match(cls_line)
verdict = vm.group(1) if vm else ""
cls = verdict if verdict in _FGT7_CLASSES else None
if cls is None:
errors.append("Check 6 (FGT7) — classification is not one of SOUND / "
"INTENTIONAL-AND-EFFECTIVE / DEFECT-AS-PLANTED (got %r)." % cls_line.strip())
else:
if cls == "INTENTIONAL-AND-EFFECTIVE":
# Must name the device and the trap.
names_device = bool(re.search(r"\bdevice\b", fgt7, re.IGNORECASE))
names_trap = bool(re.search(r"\btrap\b", fgt7, re.IGNORECASE))
if not (names_device and names_trap):
errors.append("Check 6 (FGT7) — INTENTIONAL-AND-EFFECTIVE must name the device and "
"the false-positive trap; FGT7 names %s."
% ("neither" if not (names_device or names_trap)
else ("no device" if not names_device else "no trap")))
# Internal consistency vs the fixture role.
if (is_clean or is_standalone or fgt2_na) and cls == "DEFECT-AS-PLANTED":
errors.append("Check 6 (FGT7) — a control / clean member (FGT2 N/A) cannot classify "
"FGT7 DEFECT-AS-PLANTED (there is no planted defect).")
if is_broken and cls == "SOUND":
errors.append("Check 6 (FGT7) — a `broken` member cannot classify FGT7 SOUND "
"(it carries a planted defect).")
ok = ("OK: Fiction ground-truth contract satisfied (FGT1-FGT7 present; lane tags honest; "
"surface shape/family consistent).")
failed = ("FAILED: %d fiction-groundtruth-check failure(s). Canonical home: "
"docs/fiction-benchmark-spec.md §Mechanical validator + "
"evals/fiction-groundtruth-template.md." % len(errors))
return errors, warnings, ok, failed
def _emit(errors, warnings, ok_line, failed_line):
for w in warnings:
print(w)
for e in errors:
print(e)
if errors:
print("")
print(failed_line)
return 1
print(ok_line)
return 0
# --------------------------------------------------------------------------
# Self-test (hermetic — built-in valid + hostile ground-truth fixtures).
# --------------------------------------------------------------------------
_VALID_BROKEN = """# Ground Truth: pov-break-broken
## Provenance
- **Fixture slug:** pov-break/broken · **Bucket:** P · **Source class:** synthetic-or-derived
- **Matched-pair member:** broken
- **Paired-with:** pov-break/clean
- **Text stored in-repo?:** yes
- **Base text + plant record:** base = low-recognition PD short story; planted 2 head-hops + 1
POV-knowledge leak at ¶18, ¶24, ¶31 (before->after recorded).
- **Registered (date):** 2026-07-06
## FGT1 — Structure recovery *(FQ1)*
[gt_class: B/A][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]
- **Expected scene inventory:** 5 scenes (agreement band: ±1 split/merge)
- **Expected act/movement boundaries:** loci + band (± one scene)
## FGT2 — Planted defect locus *(FQ2)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]
- **Defect family:** POV
- **Registered loci:** ¶18, ¶24, ¶31
- **Expected engine surface:** `F-P7-01` (perspective slip). Not F-P10-01.
## FGT3 — Mechanism discrimination *(FQ3)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic][alpha_metric: nominal]
- **Expected mechanism family:** POV break, not a voice-drift style issue.
## FGT4 — Severity band *(FQ4)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]
- **Expected severity band:** Must-Fix..Should-Fix.
- **Clean-member over-fire ceiling:** 0 Must-Fix on the POV mechanism.
## FGT5 — Arc recovery *(FQ5)*
[gt_class: B][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]
- **Gross arc shape band:** N/A — not scored (not the arc pilot).
## FGT6 — Repair target *(FQ6)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]
- **Correct first repair target:** restore single-POV discipline at the three loci.
## FGT7 — Form fairness *(FQ7)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: public_domain_control][decision_use: gate][reliability_status: deterministic]
- **Expected classification:** DEFECT-AS-PLANTED
- **False-positive trap:** N/A — scored on the clean member.
## Notes
free-form.
"""
_VALID_CLEAN = """# Ground Truth: pov-break-clean
## Provenance
- **Fixture slug:** pov-break/clean · **Bucket:** P · **Source class:** synthetic-or-derived
- **Matched-pair member:** clean
- **Paired-with:** pov-break/broken
- **Text stored in-repo?:** yes
- **Base text + plant record:** no mutation — this IS the control.
- **Registered (date):** 2026-07-06
## FGT1 — Structure recovery *(FQ1)*
[gt_class: B/A][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]
- **Expected scene inventory:** 5 scenes (agreement band: ±1 split/merge)
## FGT2 — Planted defect locus *(FQ2)*
- **N/A — positive control.** No planted defect.
## FGT3 — Mechanism discrimination *(FQ3)*
- **N/A — positive control.**
## FGT4 — Severity band *(FQ4)*
- **N/A — positive control.** No plant to score; over-fire ceiling 0 Must-Fix.
## FGT5 — Arc recovery *(FQ5)*
[gt_class: B][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]
- **Gross arc shape band:** N/A — not scored.
## FGT6 — Repair target *(FQ6)*
- **N/A — positive control.**
## FGT7 — Form fairness *(FQ7)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: public_domain_control][decision_use: gate][reliability_status: deterministic]
- **Expected classification:** SOUND
- **False-positive trap:** a naive Pass 7 distance-inconsistency Must-Fix on intact POV discipline.
## Notes
control member of the pov-break pair.
"""
_VALID_CONTINUITY_CF = """# Ground Truth: continuity-contradiction-broken
## Provenance
- **Fixture slug:** continuity-contradiction/broken · **Bucket:** C · **Source class:** synthetic-or-derived
- **Matched-pair member:** broken
- **Paired-with:** continuity-contradiction/clean
- **Base text + plant record:** base = low-recognition PD base; dual-mutated an entity fact at ch.2
and ch.7 to novel conflicting values + one timeline-arithmetic error.
- **Registered (date):** 2026-07-06
## FGT1 — Structure recovery *(FQ1)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: report][reliability_status: deterministic]
- **Expected scene inventory:** 6 scenes.
## FGT2 — Planted defect locus *(FQ2)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]
- **Defect family:** CONTINUITY
- **Registered loci:** chapter 2 and chapter 7.
- **Expected engine surface:** a continuity-bible `CF-04` contradiction row (no F-<ORIGIN> required).
## FGT3 — Mechanism discrimination *(FQ3)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic][alpha_metric: nominal]
- **Expected mechanism family:** continuity contradiction, not a reveal-economy error.
## FGT4 — Severity band *(FQ4)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]
- **Severity-free plant handling:** presence-delta — the CF-04 row present in broken, absent in clean.
## FGT5 — Arc recovery *(FQ5)*
[gt_class: B][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]
- **Gross arc shape band:** N/A — not scored.
## FGT6 — Repair target *(FQ6)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]
- **Correct first repair target:** locate the contradiction, then choose which fact wins.
## FGT7 — Form fairness *(FQ7)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: public_domain_control][decision_use: gate][reliability_status: deterministic]
- **Expected classification:** DEFECT-AS-PLANTED
- **False-positive trap:** N/A — scored on the clean member.
## Notes
CF-only surface is conformant for continuity.
"""
_VALID_CONTROL = """# Ground Truth: yellow-wallpaper-voice-control
## Provenance
- **Fixture slug:** yellow-wallpaper-voice-control · **Bucket:** P (+S) · **Source class:** public-domain
- **Matched-pair member:** n/a (standalone control)
- **Paired-with:** n/a
- **Text stored in-repo?:** yes
- **Registered (date):** 2026-07-06
## FGT1 — Structure recovery *(FQ1)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: public_domain_control][decision_use: report][reliability_status: deterministic]
- **Expected scene inventory:** journal entries as scene units.
## FGT2 — Planted defect locus *(FQ2)*
- **N/A — positive control.** No planted defect.
## FGT3 — Mechanism discrimination *(FQ3)*
- **N/A — positive control.**
## FGT4 — Severity band *(FQ4)*
- **N/A — positive control.**
## FGT5 — Arc recovery *(FQ5)*
[gt_class: B][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]
- **Gross arc shape band:** N/A — not scored.
## FGT6 — Repair target *(FQ6)*
- **N/A — positive control.**
## FGT7 — Form fairness *(FQ7)*
[gt_class: A][construct_lane: fault_injection][evidence_basis: public_domain_control][decision_use: gate][reliability_status: deterministic]
- **Expected classification:** INTENTIONAL-AND-EFFECTIVE
- **If intentional:** the device is a deliberately deteriorating unreliable first-person narrator.
- **False-positive trap:** a naive Pass 5 voice-drift Must-Fix on the intentional deterioration.
## Notes
recognition-flagged control.
"""
def run_self_test(which=None):
rc = {"v": 0}
def check(name, errs, expect_clean):
ok = (len(errs) == 0) == expect_clean
print(" %s: %s" % (name, "OK" if ok else "FAIL (errs=%s)" % errs))
if not ok:
rc["v"] = 1
def errs_of(text):
return fiction_groundtruth_check(text)[0]
# Arm 1: valid broken + valid clean + valid CF-only continuity + valid control -> all clean.
check("valid_broken", errs_of(_VALID_BROKEN), True)
check("valid_clean", errs_of(_VALID_CLEAN), True)
check("valid_continuity_cf_only", errs_of(_VALID_CONTINUITY_CF), True)
check("valid_control", errs_of(_VALID_CONTROL), True)
# Arm 2: missing / empty FGT section (check 1).
check("missing_section", errs_of(_VALID_BROKEN.replace(
"## FGT5 — Arc recovery *(FQ5)*\n"
" [gt_class: B][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]\n"
"- **Gross arc shape band:** N/A — not scored (not the arc pilot).\n", "")), False)
check("empty_section", errs_of(_VALID_BROKEN.replace(
" [gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic][alpha_metric: nominal]\n"
"- **Expected mechanism family:** POV break, not a voice-drift style issue.\n", "")), False)
# Arm 3: gt_class:C carrying decision_use:gate (check 2 — the architecture guardrail).
check("classC_gates", errs_of(_VALID_BROKEN.replace(
"[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]\n- **Defect family:** POV",
"[gt_class: C][construct_lane: reader_effect][evidence_basis: reader_study][decision_use: gate][reliability_status: low_agreement]\n- **Defect family:** POV")), False)
# Arm 4: decision_use:gate with provisional_author (check 2 — alpha licenses gating, not the author).
check("gate_provisional", errs_of(_VALID_BROKEN.replace(
"[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: deterministic]\n- **Defect family:** POV",
"[gt_class: A][construct_lane: fault_injection][evidence_basis: planted_diff][decision_use: gate][reliability_status: provisional_author]\n- **Defect family:** POV")), False)
# Arm 5: Lane-2 anchor with a point answer, no band -> FAIL; and missing alpha_metric -> FAIL.
check("lane2_no_band", errs_of(_VALID_BROKEN.replace(
"- **Expected scene inventory:** 5 scenes (agreement band: ±1 split/merge)\n"
"- **Expected act/movement boundaries:** loci + band (± one scene)",
"- **Expected scene inventory:** exactly 5 scenes.")), False)
check("lane2_no_alpha", errs_of(_VALID_BROKEN.replace(
"[gt_class: B/A][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author][alpha_metric: ordinal]",
"[gt_class: B/A][construct_lane: consensus_alignment][evidence_basis: editor_panel][decision_use: report][reliability_status: provisional_author]")), False)
# Arm 6: broken member with no plant record (check 3); with no Paired-with (check 3, D-1).
check("broken_no_plant", errs_of(_VALID_BROKEN.replace(
"- **Base text + plant record:** base = low-recognition PD short story; planted 2 head-hops + 1\n"
" POV-knowledge leak at ¶18, ¶24, ¶31 (before->after recorded).",
"- **Base text + plant record:** N/A")), False)
check("broken_no_paired", errs_of(_VALID_BROKEN.replace(
"- **Paired-with:** pov-break/clean", "- **Paired-with:** n/a")), False)
# Arm 7: malformed locus token (no chapter/scene/¶ shape) -> FAIL (check 3).
check("malformed_locus", errs_of(_VALID_BROKEN.replace(
"- **Registered loci:** ¶18, ¶24, ¶31", "- **Registered loci:** somewhere near the middle")), False)
# Arm 8: POV family with F-P10 surface (family mismatch) -> FAIL (check 4).
check("pov_family_mismatch", errs_of(_VALID_BROKEN.replace(
"- **Expected engine surface:** `F-P7-01` (perspective slip). Not F-P10-01.",
"- **Expected engine surface:** `F-P10-01` (mis-keyed).")), False)
# Arm 9: fiction-shaped decoy — the only asserted POV surface is a decoy F-P7-01 (PASS) / not
# F-P7-01, with the actually-asserted surface F-P10-02 (wrong family) -> FAIL (check 4). A literal
# argument-regex port would let this pass (that is the bug the adaptation fixes).
check("fiction_decoy_mask", errs_of(_VALID_BROKEN.replace(
"- **Expected engine surface:** `F-P7-01` (perspective slip). Not F-P10-01.",
"- **Expected engine surface:** `F-P7-01` (PASS); not F-P7-02; actually `F-P10-02`.")), False)
# Arm 10: CONTINUITY key whose expected surface is a bare CF-04 (no finding) -> clean; a POV key
# whose only surface is CF-04 -> FAIL (check 4 — CF recognized only for continuity/reveal).
check("continuity_cf_only_clean", errs_of(_VALID_CONTINUITY_CF), True)
check("pov_cf_only_fail", errs_of(_VALID_BROKEN.replace(
"- **Expected engine surface:** `F-P7-01` (perspective slip). Not F-P10-01.",
"- **Expected engine surface:** a `CF-04` row.")), False)
# Arm 11: non-canonical severity token (check 5); a presence-delta FGT4 with no token -> clean.
check("bad_severity_token", errs_of(_VALID_BROKEN.replace(
"- **Expected severity band:** Must-Fix..Should-Fix.",
"- **Expected severity band:** Critical-Fix..Should-Fix.")), False)
check("presence_delta_no_token_clean", errs_of(_VALID_CONTINUITY_CF), True)
# Arm 12: FGT7 out of enum (check 6); SOUND glossed "…, not DEFECT-AS-PLANTED" -> clean; a
# control (FGT2 N/A) whose FGT7 says DEFECT-AS-PLANTED -> FAIL; a control N/A on FGT2/3/6 -> clean.
check("fgt7_out_of_enum", errs_of(_VALID_BROKEN.replace(
"- **Expected classification:** DEFECT-AS-PLANTED", "- **Expected classification:** BROKEN")), False)
check("fgt7_sound_gloss_clean", errs_of(_VALID_CLEAN.replace(
"- **Expected classification:** SOUND",
"- **Expected classification:** SOUND — intact discipline, not DEFECT-AS-PLANTED")), True)
check("control_defect_as_planted_fail", errs_of(_VALID_CONTROL.replace(
"- **Expected classification:** INTENTIONAL-AND-EFFECTIVE",
"- **Expected classification:** DEFECT-AS-PLANTED")), False)
check("broken_sound_fail", errs_of(_VALID_BROKEN.replace(
"- **Expected classification:** DEFECT-AS-PLANTED", "- **Expected classification:** SOUND")), False)
check("control_na_exemption_clean", errs_of(_VALID_CONTROL), True)
print("Self-test: %s" % ("PASS" if rc["v"] == 0 else "FAIL"))
return rc["v"]
def main(argv):
if len(argv) < 2:
sys.stderr.write("Usage: fiction_groundtruth.py fiction-groundtruth-check <file> | --self-test\n")
return 2
if argv[1] == "--self-test":
return run_self_test()
if argv[1] == "fiction-groundtruth-check":
if len(argv) < 3:
sys.stderr.write("Usage: fiction_groundtruth.py fiction-groundtruth-check <groundtruth_file>\n")
return 2
if not os.path.isfile(argv[2]):
sys.stderr.write("Error: File not found: %s\n" % argv[2])
return 2
with open(argv[2], "r", encoding="utf-8", errors="replace") as fh:
return _emit(*fiction_groundtruth_check(fh.read()))
sys.stderr.write("Error: unknown command: %s\n" % argv[1])
return 2
if __name__ == "__main__":
sys.exit(main(sys.argv))
SHA-256: 33eb7f03116f9fa6901bcf3355bdc13209cf212e964dfb88ddb9041df9bfa9da