← Files APODICTIC Development EditorARCHIVED FILE
scripts/stance_calibration.py
21.9 KB · Nov 9, 2026 · 00:53 UTC
#!/usr/bin/env python3
"""Validate argument-register / rhetorical-stance calibration records.
This is a shape-and-join gate. It never decides whether a rhetorical move is earned.
stance_calibration.py stance-calibration <ledger.md> --argument-state <Argument_State.md>
stance_calibration.py --self-test
"""
import re
import sys
from pathlib import Path
try:
import apodictic_artifacts as art
except ImportError:
art = None
from argument_groundtruth import _GT8_ROW_FLAG_TYPES as _PREMISE_FLAG_MECHANISMS
_REGISTER_RE = re.compile(r"^Register:\s*(asserted|generative)\s*$", re.MULTILINE)
_CONFIRM_RE = re.compile(
r"^Register confirmation:\s*(DEFAULT-ASSERTED|WRITER-CONFIRMED|FORCED-ASSERTED)\s*$",
re.MULTILINE,
)
_HIGH_RE = re.compile(
r"^High-stakes gate:\s*(ACTIVE|INACTIVE)\s+[—-]\s*(\S.*)$", re.MULTILINE
)
_CO_ROW_RE = re.compile(
r"^\s*-\s*(CO[1-9][0-9]*)\s*\|\s*Location:\s*(\S.*?)\s*\|\s*"
r"Kind:\s*(ASSERTION|PRESCRIPTION)\s*\|\s*Press:\s*(LIGHT|MEDIUM|HARD)\s*\|\s*"
r"Consequence:\s*(LOW|MEDIUM|HIGH)\s*$",
re.MULTILINE,
)
_REGISTER_FLOOR_MECHANISM_RE = re.compile(
r"^(?:WR|SM|BP)(?:\d|[-\s]|$)", re.IGNORECASE
)
_STANCE_MECHANISM_RE = re.compile(
r"^(?:CL4|SM4|BP4|FM-A8|FM-A12|FM-A14|FM-A17|FM-A19|FM-A13|"
r"DI4(?:/FM-A13)?|WR-ON-ANALOGY)\b",
re.IGNORECASE,
)
_EARNED = {"earned", "earned-by-frame"}
_EFFECTS = {
"register-floor",
"stance-demotion",
"blocked-high-stakes",
"blocked-cash-out",
}
def _is_premise_flag_mechanism(mechanism):
if not isinstance(mechanism, str):
return False
token = mechanism.strip()
return token.upper() in _PREMISE_FLAG_MECHANISMS or re.match(
r"^GT8\b", token, re.IGNORECASE
) is not None
def _is_register_floor_mechanism(mechanism):
return isinstance(mechanism, str) and _REGISTER_FLOOR_MECHANISM_RE.match(
mechanism.strip()
) is not None
def _is_stance_mechanism(mechanism):
return isinstance(mechanism, str) and _STANCE_MECHANISM_RE.match(
mechanism.strip()
) is not None
def _parse_cashouts(text):
"""Parse the exact single-line cash-out grammar inside its named section."""
errors = []
lines = text.splitlines()
headers = [i for i, line in enumerate(lines) if line.rstrip() == "Cash-out inventory:"]
if len(headers) > 1:
return {}, False, ["S2 cash-out: expected at most one Cash-out inventory section"]
if not headers:
return {}, False, errors
section_lines = []
for line in lines[headers[0] + 1:]:
if not line.strip():
break
if not line.startswith((" ", "\t")):
break
section_lines.append(line)
cashouts = {}
none_count = 0
for line in section_lines:
if line == " - NONE":
none_count += 1
continue
row = _CO_ROW_RE.fullmatch(line)
if row is None:
errors.append(
"S2 cash-out: malformed row; expected exact '- NONE' or "
"CO# | Location | Kind | Press | Consequence"
)
continue
cid, location, kind, press, consequence = row.groups()
if cid in cashouts:
errors.append("S2 cash-out: duplicate id %s" % cid)
cashouts[cid] = {
"location": location,
"kind": kind,
"press": press,
"consequence": consequence,
}
if none_count > 1:
errors.append("S2 cash-out: NONE sentinel must appear exactly once")
if none_count and cashouts:
errors.append("S2 cash-out: NONE sentinel cannot coexist with CO rows")
return cashouts, none_count == 1, errors
def _read(path):
try:
return Path(path).read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError) as exc:
raise ValueError("cannot read %s as UTF-8: %s" % (path, exc))
def parse_state(text):
errors = []
reg = _REGISTER_RE.findall(text)
conf = _CONFIRM_RE.findall(text)
high = _HIGH_RE.findall(text)
if len(reg) != 1:
errors.append("S1 state: expected exactly one Register: asserted|generative field")
if len(conf) != 1:
errors.append("S1 state: expected exactly one Register confirmation field")
if len(high) != 1:
errors.append("S1 state: expected exactly one High-stakes gate: ACTIVE|INACTIVE — source field")
register = reg[0] if len(reg) == 1 else None
confirmation = conf[0] if len(conf) == 1 else None
high_gate = high[0][0] if len(high) == 1 else None
if register == "generative" and confirmation != "WRITER-CONFIRMED":
errors.append("S1 state: generative register requires WRITER-CONFIRMED")
if register == "generative" and high_gate == "ACTIVE":
errors.append("S1 state: an ACTIVE high-stakes gate forces asserted register")
if high_gate == "ACTIVE" and confirmation != "FORCED-ASSERTED":
errors.append("S1 state: ACTIVE high-stakes gate requires FORCED-ASSERTED")
if confirmation == "FORCED-ASSERTED" and register != "asserted":
errors.append("S1 state: FORCED-ASSERTED requires asserted register")
if confirmation == "FORCED-ASSERTED" and high_gate != "ACTIVE":
errors.append("S1 state: FORCED-ASSERTED requires an ACTIVE high-stakes gate")
cashouts, none_sentinel, cashout_errors = _parse_cashouts(text)
errors.extend(cashout_errors)
if register == "generative" and not cashouts and not none_sentinel:
errors.append("S2 cash-out: generative state requires an exact inventory or '- NONE'")
if high_gate == "ACTIVE" and len(high) == 1:
source = high[0][1].strip()
if re.match(r"^NONE(?:\b|[^A-Z0-9])", source, re.IGNORECASE):
errors.append("S1 state: ACTIVE high-stakes gate requires a non-NONE intake source")
return {
"register": register,
"confirmation": confirmation,
"high_gate": high_gate,
"cashouts": cashouts,
}, errors
def check(ledger_text, state_text=None):
errors = []
if art is None:
return ["S0 runtime: apodictic_artifacts.py unavailable"]
schema = art.load_schema("apodictic.finding.v1")
if schema is None:
return ["S0 runtime: apodictic.finding.v1 schema unavailable"]
state = None
if state_text is not None:
state, state_errors = parse_state(state_text)
errors.extend(state_errors)
findings = []
for btype, obj, jerr in art.parse_blocks(ledger_text):
if btype != "finding":
continue
if jerr:
errors.append("S3 finding: malformed JSON: %s" % jerr)
continue
where = "finding %s" % (obj.get("id", "<missing>") if isinstance(obj, dict) else "<invalid>")
shape = art.validate_obj(obj, schema, where)
errors.extend("S3 schema: %s" % err for err in shape)
if isinstance(obj, dict):
findings.append(obj)
if not findings:
errors.append("S3 finding: no apodictic:finding blocks found")
return errors
for f in findings:
fid = str(f.get("id", "<missing>"))
register = f.get("register")
stance = f.get("stance")
verdict = f.get("stance_verdict")
effect = f.get("calibration_effect")
co_ref = f.get("cash_out_ref")
severity = f.get("severity")
mechanism = f.get("mechanism")
if (stance is None) != (verdict is None):
errors.append("S4 %s: stance and stance_verdict must appear together" % fid)
if effect is not None and effect not in _EFFECTS:
errors.append("S4 %s: unknown calibration_effect %r" % (fid, effect))
if effect is not None and state is None:
errors.append("S4 %s: calibration_effect requires --argument-state" % fid)
continue
if _is_premise_flag_mechanism(mechanism) and any(
value is not None for value in (register, stance, verdict, effect, co_ref)):
errors.append("S4 %s: GT8 premise-plausibility flags cannot carry stance calibration" % fid)
continue
co = None
if co_ref is not None:
if state is None:
errors.append("S5 %s: cash_out_ref requires --argument-state" % fid)
elif co_ref not in state["cashouts"]:
errors.append("S5 %s: cash_out_ref %s does not resolve" % (fid, co_ref))
else:
co = state["cashouts"][co_ref]
if state is not None and state["high_gate"] == "ACTIVE" and register == "generative":
errors.append("S6 %s: ACTIVE high-stakes gate forbids register=generative" % fid)
if state is not None and register == "generative" and state["register"] != "generative":
errors.append("S6 %s: finding register=generative contradicts the asserted document register" % fid)
would_demote = verdict in _EARNED or effect in {"register-floor", "stance-demotion"}
recorded_register_floor = register == "generative" and severity == "Could-Fix"
expected_block = None
if state is not None and state["high_gate"] == "ACTIVE" and would_demote:
expected_block = "blocked-high-stakes"
elif co is not None and co["kind"] == "PRESCRIPTION" and (
would_demote or recorded_register_floor):
expected_block = "blocked-cash-out"
if expected_block:
if effect != expected_block:
errors.append("S6 %s: precedence requires calibration_effect=%s" % (fid, expected_block))
if register == "generative" and severity == "Could-Fix":
errors.append("S6 %s: blocked finding cannot use the generative Could-Fix floor" % fid)
elif effect == "blocked-high-stakes":
errors.append("S6 %s: blocked-high-stakes requires an earned would-be demotion under an ACTIVE gate" % fid)
elif effect == "blocked-cash-out":
errors.append("S6 %s: blocked-cash-out requires an earned would-be demotion at a joined PRESCRIPTION row" % fid)
if effect == "blocked-high-stakes" and (stance is None or verdict not in _EARNED):
errors.append("S6 %s: blocked-high-stakes requires stance + earned verdict" % fid)
if effect == "blocked-cash-out" and not (register == "generative" or verdict in _EARNED):
errors.append("S6 %s: blocked-cash-out requires generative register or an earned stance verdict" % fid)
if effect == "register-floor":
if register != "generative" or severity != "Could-Fix":
errors.append("S7 %s: register-floor requires register=generative and severity=Could-Fix" % fid)
if state is None or state["register"] != "generative":
errors.append("S7 %s: register-floor requires a generative Argument_State" % fid)
if not _is_register_floor_mechanism(mechanism):
errors.append("S7 %s: register-floor is limited to WR/SM/BP mechanism families" % fid)
if co is not None:
errors.append("S7 %s: register-floor is a non-cash-out mechanism and forbids cash_out_ref" % fid)
if effect == "stance-demotion":
if stance is None or verdict not in _EARNED or severity != "Could-Fix":
errors.append("S7 %s: stance-demotion requires stance + earned verdict + Could-Fix" % fid)
if not _is_stance_mechanism(mechanism):
errors.append("S7 %s: stance-demotion requires an enumerated overstatement mechanism" % fid)
if verdict in _EARNED and not expected_block:
applied_register = register or (state["register"] if state is not None else None)
expected_effect = (
"register-floor" if applied_register == "generative" and co is None
else "stance-demotion"
)
if severity != "Could-Fix" or effect != expected_effect:
errors.append("S7 %s: earned verdict requires severity=Could-Fix and calibration_effect=%s" %
(fid, expected_effect))
if register == "generative" and severity == "Could-Fix" and co is None and not expected_block \
and effect != "register-floor":
errors.append("S7 %s: generative Could-Fix requires calibration_effect=register-floor" % fid)
return errors
def run_self_test():
failures = []
def finding(**updates):
obj = {
"schema": "apodictic.finding.v1", "id": "F-DC-01", "mechanism": "WR0",
"severity": "Could-Fix", "confidence": "HIGH", "evidence_refs": ["Ch 1"],
"fix_class": "structural", "risk_if_fixed": "flattening",
"register": "generative", "stance": "S2", "stance_verdict": "earned-by-frame",
"calibration_effect": "register-floor",
}
obj.update(updates)
obj = {key: value for key, value in obj.items() if value is not None}
import json
return "<!-- apodictic:finding\n%s\n-->" % json.dumps(obj)
base_state = """## 1. Context and Classification
Register: generative
Register confirmation: WRITER-CONFIRMED
High-stakes gate: INACTIVE — NONE
Cash-out inventory:
- CO1 | Location: paragraph 9 | Kind: PRESCRIPTION | Press: HARD | Consequence: HIGH
"""
def expect(name, ledger, state, passes):
errs = check(ledger, state)
ok = not errs
print(" %s: %s%s" % (name, "OK" if ok == passes else "FAIL",
"" if ok == passes else " (%s)" % "; ".join(errs)))
if ok != passes:
failures.append(name)
expect("register_floor_valid", finding(), base_state, True)
expect("missing_stance_rejected", finding(stance=None), base_state, False)
expect("bad_enum_rejected", finding(stance="S9"), base_state, False)
expect("unresolved_cashout_rejected", finding(cash_out_ref="CO9"), base_state, False)
expect("prescriptive_demotion_rejected", finding(cash_out_ref="CO1"), base_state, False)
expect("bare_generative_floor_at_prescriptive_cashout_rejected",
finding(stance=None, stance_verdict=None, calibration_effect=None,
cash_out_ref="CO1"), base_state, False)
expect("prescriptive_block_valid",
finding(severity="Should-Fix", cash_out_ref="CO1", calibration_effect="blocked-cash-out"),
base_state, True)
asserted_prescriptive_state = base_state.replace("Register: generative", "Register: asserted").replace(
"WRITER-CONFIRMED", "DEFAULT-ASSERTED")
expect("asserted_unearned_prescriptive_full_strength_valid",
finding(severity="Should-Fix", register="asserted", stance="S3",
stance_verdict="unearned", calibration_effect=None, cash_out_ref="CO1"),
asserted_prescriptive_state, True)
expect("asserted_earned_prescriptive_block_valid",
finding(severity="Should-Fix", register="asserted", stance="S3",
stance_verdict="earned", calibration_effect="blocked-cash-out",
cash_out_ref="CO1"), asserted_prescriptive_state, True)
high_state = base_state.replace("Register: generative", "Register: asserted").replace(
"WRITER-CONFIRMED", "FORCED-ASSERTED").replace("INACTIVE — NONE", "ACTIVE — testimony")
expect("high_gate_block_valid",
finding(severity="Should-Fix", register="asserted", calibration_effect="blocked-high-stakes"),
high_state, True)
expect("high_gate_wrong_effect_rejected", finding(), high_state, False)
expect("high_gate_unearned_full_strength_valid",
finding(severity="Should-Fix", register="asserted", stance="S3",
stance_verdict="unearned", calibration_effect=None), high_state, True)
expect("high_gate_divergent_full_strength_valid",
finding(severity="Should-Fix", register="asserted", stance="S3",
stance_verdict="divergent", calibration_effect=None), high_state, True)
expect("high_gate_native_could_block_valid",
finding(severity="Could-Fix", register="asserted", stance="S3",
stance_verdict="earned", calibration_effect="blocked-high-stakes"),
high_state, True)
expect("high_gate_generative_finding_rejected",
finding(severity="Should-Fix", register="generative",
calibration_effect="blocked-high-stakes"), high_state, False)
expect("generative_unconfirmed_rejected", finding(), base_state.replace(
"WRITER-CONFIRMED", "DEFAULT-ASSERTED"), False)
expect("forced_asserted_inactive_rejected", finding(severity="Should-Fix", register="asserted",
stance_verdict="unearned", calibration_effect=None), asserted_prescriptive_state.replace(
"DEFAULT-ASSERTED", "FORCED-ASSERTED"), False)
expect("active_lowercase_none_source_rejected", finding(severity="Should-Fix",
register="asserted", calibration_effect="blocked-high-stakes"),
high_state.replace("testimony", "none"), False)
expect("active_none_suffix_source_rejected", finding(severity="Should-Fix",
register="asserted", calibration_effect="blocked-high-stakes"),
high_state.replace("testimony", "NONE (unspecified)"), False)
expect("malformed_cashout_rejected", finding(), base_state.replace("Kind: PRESCRIPTION", "PRESCRIPTION"), False)
expect("malformed_none_suffix_rejected", finding(), base_state.replace(
" - CO1 | Location: paragraph 9 | Kind: PRESCRIPTION | Press: HARD | Consequence: HIGH",
" - NONE-extra"), False)
expect("mistyped_cashout_id_rejected", finding(), base_state.replace("CO1", "COX"), False)
expect("duplicate_cashout_rejected", finding(), base_state +
" - CO1 | Location: paragraph 10 | Kind: ASSERTION | Press: LIGHT | Consequence: LOW\n", False)
expect("none_plus_row_rejected", finding(), base_state.replace(
"Cash-out inventory:", "Cash-out inventory:\n - NONE"), False)
expect("lookalike_cashout_id_rejected", finding(cash_out_ref="CO1-extra"), base_state, False)
assertion_state = base_state.replace("Register: generative", "Register: asserted").replace(
"WRITER-CONFIRMED", "DEFAULT-ASSERTED").replace("Kind: PRESCRIPTION", "Kind: ASSERTION")
expect("assertion_stance_demotion_valid",
finding(mechanism="FM-A8 false precision", register="asserted", cash_out_ref="CO1",
calibration_effect="stance-demotion"),
assertion_state, True)
expect("asserted_generative_floor_rejected", finding(), asserted_prescriptive_state.replace(
"Kind: PRESCRIPTION", "Kind: ASSERTION"), False)
expect("ineligible_register_floor_rejected",
finding(mechanism="missing primary-source evidence"), base_state, False)
expect("ineligible_stance_demotion_rejected",
finding(mechanism="missing primary-source evidence", register="asserted",
cash_out_ref="CO1", calibration_effect="stance-demotion"),
assertion_state, False)
expect("earned_should_without_demotion_rejected",
finding(severity="Should-Fix", register="asserted", calibration_effect=None),
assertion_state, False)
expect("earned_must_without_demotion_rejected",
finding(severity="Must-Fix", register="asserted", calibration_effect=None),
assertion_state, False)
expect("earned_by_frame_generative_should_without_floor_rejected",
finding(severity="Should-Fix", calibration_effect=None), base_state, False)
expect("register_floor_with_cashout_ref_rejected",
finding(cash_out_ref="CO1"), assertion_state, False)
expect("gt8_premise_flag_stance_rejected",
finding(mechanism="CONTESTABLE"), base_state, False)
expect("gt8_prefixed_mechanism_stance_rejected",
finding(mechanism="GT8 — premise plausibility: contestable ground"),
base_state, False)
expect("generative_unearned_register_floor_valid",
finding(stance_verdict="unearned"), base_state, True)
expect("generative_earned_missing_register_rejected",
finding(register=None, calibration_effect="stance-demotion"), base_state, False)
expect("effect_without_state_rejected", finding(), None, False)
expect("cashout_ref_without_state_rejected",
finding(severity="Should-Fix", register="asserted", stance="S3",
stance_verdict="unearned", calibration_effect=None, cash_out_ref="CO1"),
None, False)
legacy = finding()
for field in ("register", "stance", "stance_verdict", "calibration_effect"):
import json as _json
marker = art.parse_blocks(legacy)[0][1]
marker.pop(field, None)
legacy = "<!-- apodictic:finding\n%s\n-->" % _json.dumps(marker)
expect("legacy_finding_compatible", legacy, assertion_state, True)
if failures:
print("Self-test: FAIL (%d): %s" % (len(failures), ", ".join(failures)))
return 1
print("Self-test: PASS (stance-calibration; schema + joins + precedence + blocking)")
return 0
def main(argv):
if "--self-test" in argv:
return run_self_test()
args = list(argv[1:])
if args and args[0] == "stance-calibration":
args.pop(0)
if not args:
sys.stderr.write("Usage: stance_calibration.py stance-calibration <ledger.md> --argument-state <Argument_State.md> | --self-test\n")
return 2
ledger = args.pop(0)
state_path = None
if "--argument-state" in args:
i = args.index("--argument-state")
if i + 1 >= len(args):
sys.stderr.write("ERROR: --argument-state requires a path\n")
return 2
state_path = args[i + 1]
del args[i:i + 2]
if args:
sys.stderr.write("ERROR: unknown arguments: %s\n" % " ".join(args))
return 2
try:
ledger_text = _read(ledger)
state_text = _read(state_path) if state_path else None
except ValueError as exc:
sys.stderr.write("stance-calibration: FAIL — %s\n" % exc)
return 2
errors = check(ledger_text, state_text)
if errors:
print("stance-calibration: FAIL (%d)" % len(errors))
for error in errors:
print(" - %s" % error)
return 1
print("stance-calibration: PASS (%s)" % ledger)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))
SHA-256: 0a336b00d2a6ff0b2c824fefa012bfbe36fc650e59a1e25b74b6ddbd2f7c5608