# -*- coding: utf-8 -*-
"""
faith_gate.py — a faith cell may not be filled from language C4 excludes.

Faith is the weakest field in the index: the independent-coder validation put it at
alpha = 0.24, and three of its four disagreements were here. C4 of the evidentiary
standard therefore names exactly what does NOT fill the cell. Round-six review found
two cells filled on excluded grounds anyway, one of them three ways over, while the
identical fact pattern was correctly blanked one row away.

A rule that is applied by hand to 115 rows is applied inconsistently. This checks it.
A cell is FILLED only if it carries a first-person belief statement or documented
membership; if it carries an excluded marker instead, the build fails.

Run: python3 scripts/faith_gate.py
"""
import os, re, sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import phase2  # noqa: E402

# C4's own list of what is not a faith statement. Each entry carries a PER_SE flag.
#
# PER_SE=True is a C1/C2 identity bar: ethnicity, a parent's religion, a third party's
# characterisation, a wiki, a campaign slogan. These may NEVER fill the cell, and no
# admissible phrase in the same cell can rescue them. A red-teamer defeated the old design
# by writing "Roman Catholic — she described her Irish-American heritage and her mother's
# Catholic faith": the single admissible phrase "she described" switched off EVERY
# exclusion at once, heritage and parent-religion included, because the loop was
# `if match and not admissible`. Narrowing the admissible vocabulary had not changed that
# one phrase disarmed all bars. Now a per-se bar fires regardless of admissibility — the
# cell's basis must be the subject's own first-person statement and NOTHING that triggers a
# per-se bar, so the coder must remove the ethnicity/parent/third-party clause, not bury it
# beside an admissible verb. (Verified: none of the 16 currently-filled cells matches any
# exclusion, so no legitimate cell relied on the override this removes.)
#
# PER_SE=False is a soft bar — attendance, mission-work — where a POSITIVE documented
# membership legitimately supersedes it; those still yield to admissible().
EXCLUDED = [
    (r"values? faith|faith,? family,? and (freedom|future)|faith,? life and family",
     "campaign slogan / values language", True),
    (r"volunteer\w* (in|at) (his|her|their) church|mission[- ]work|serves? with faith-based",
     "charitable or mission-work activity", False),
    (r"described as \w+ \(|per [A-Z]\w+ News|infobox",
     "another person's characterization, or a third-party wiki infobox", True),
    # The lookahead used to be `(?!.*member)` -- so the EXCLUSION disarmed itself if the
    # word "member" appeared anywhere later, including in "no membership record found".
    # A rule that a sentence denying membership can switch off is not a rule. Membership
    # is now handled where it belongs, in admissible(), which requires a POSITIVE record.
    (r"attend(s|ed|ance)\b",
     "attendance rather than membership", False),
    # Added 2026-08-30 after a media lawyer found five filled cells this gate had cleared.
    (r"\bheritage\b|\b(Polish|Irish|Italian|German|Greek|Hispanic|Latino|Cuban|Mexican|"
     r"Armenian|Lebanese|Korean|Filipino)[- ]American\b",
     "ETHNIC HERITAGE — C1 expressly bars inferring faith from ethnicity", True),
    (r"\b(his|her|their) (father|mother|parents?|family) (was|were|pastored|served|is|are)\b"
     r"|\bson of a\b|\bdaughter of a\b|\bfamily business\b|"
     r"\b(his|her|their) (father|mother|parents?)'?s?\b.{0,20}\b(faith|religion|catholic|"
     r"jewish|christian|muslim|protestant|baptist|methodist|lutheran)\b",
     "A PARENT'S RELIGION — C1 forbids imputing it to the candidate, so it may not fill "
     "the cell whose purpose is to say it does not count", True),
    (r"\bcampaign bio\b.{0,80}\b(a man|a woman) of\b|\bhe draws strength\b"
     r"|\bshe draws strength\b|\bdeep faith\b",
     "THIRD-PERSON CAMPAIGN-BIO COPY — another person's characterisation of the subject", True),
    (r"\(Wikipedia|Wikipedia\)|per Wikipedia|Wikipedia (lists|says|describes)",
     "A THIRD-PARTY WIKI — C2 bars it as the basis for a faith cell, whatever it cites", True),
]
# What legitimately fills it: the subject's own documented statement about their own
# belief, or documented membership. C4 allows nothing else.
#
# This used to be a blanket veto over every exclusion -- any one of these words anywhere
# in the cell disarmed all four rules, so "campaign biography STATES faith, family and
# freedom" passed on the word "states", and a cell saying "no MEMBERSHIP record found"
# passed the membership test by containing its negation.
ADMISSIBLE = re.compile(
    r"(\bhe (has )?(publicly )?(stated|said|described)\b|"
    r"\bshe (has )?(publicly )?(stated|said|described)\b|"
    r"\bin (his|her|their) own words\b|"
    r"\bdocumented membership\b|\b(is|was) a (confirmed|professed|registered) member\b|"
    r"\bmembership (record|roll|register)s? (confirm|shows?|lists?)\b|"
    r"\bown statement\b|\bmy faith\b)", re.I)
# ...and never when the sentence is denying it. A negation beats an admissible phrase.
NEGATED = re.compile(
    r"\bno (membership|statement|record|direct statement)\b|"
    r"\bnever (stated|said|described|confirmed)\b|"
    r"\bnot located\b|\bcould not be\b|\bno .{0,24}located\b", re.I)


def admissible(cell):
    return bool(ADMISSIBLE.search(cell)) and not NEGATED.search(cell)


def main():
    bad = []
    for name, v in phase2.AGE_TENURE.items() if False else []:
        pass
    # Located by its own columns, not by being first. A decoy dict-of-dicts defined
    # above the real table used to redirect this gate onto the decoy, which then
    # certified an empty table while the real one shipped in violation.
    rows = None
    for v in vars(phase2).values():
        if (isinstance(v, dict) and len(v) > 50
                and all(isinstance(x, dict) for x in v.values())
                and any("faith" in x and "occ" in x for x in v.values())):
            rows = v
            break
    if rows is None:
        raise SystemExit("faith gate: could not locate the master coding table (a dict of "
                         ">50 rows carrying 'faith' and 'occ'). It is not optional: a gate "
                         "that cannot find its table must fail, not pass.")
    for name, rec in rows.items():
        f = (rec.get("faith") or "").strip()
        if not f or f.lower().startswith("blank"):
            continue                       # a blank cell cannot violate C4
        for pat, why, per_se in EXCLUDED:
            if re.search(pat, f, re.I) and (per_se or not admissible(f)):
                bad.append((name, why, f[:150]))
                break
    if bad:
        print("FAITH GATE FAILED — a cell is filled on grounds C4 excludes:\n")
        for n, why, f in bad:
            print(f"  {n}\n      excluded ground: {why}\n      cell: {f}…\n")
        print("Blank the cell with a recorded reason, or cite the subject's own")
        print("statement or documented membership.")
        raise SystemExit(1)
    filled = sum(1 for r in rows.values()
                 if (r.get("faith") or "").strip()
                 and not (r.get("faith") or "").lower().startswith("blank"))
    if filled == 0:
        raise SystemExit("faith gate: ZERO filled cells. Either the table is the wrong one "
                         "or every cell was blanked -- both are failures, and a gate that "
                         "prints a clean line over an empty table is the one that shipped "
                         "88 slogan cells in a red-team run.")
    print(f"    faith gate: {filled} filled cells across {len(rows)} rows, none on "
          f"excluded grounds")


if __name__ == "__main__":
    main()
