Jump to content

The Principia Machina/TPC-PY/tpc record.py

From The Invariance Itself
Revision as of 02:11, 30 September 2026 by BFP Rauch (talk | contribs) (1 revision imported)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
# -*- coding: utf-8 -*-
"""
tpc_record 0.1.0 (proposed, increment 12): the Research Register's records
=========================================================================
TPC-PY residence. The closed table of tpm_record_schema 0.4.0 (section 7,
embedded below as its text and read by the table's own line grammar, 7.1)
and the functions of tpm_record_functions 0.3.0 that tpc_record carries.
Both texts are the specification; this module states them as code
(D192.2-D202.1 as the user's selections of 2026-09-28 fix them, read under
RD-PT-10 to RD-PT-16; D214.1, D215.2 and D218.1 as the user's answers of
2026-09-29 fix them, tpm_post_tpcpy_selections 0.4.0). No rule rests on a
drafted item and none carries a mark (rulings S105, S114).
D214.1: STATUS UNMARKED at landing, one closed set UNMARKED, HELD or
REFUTED, on the classes an account authors (schema 6.4-6.8).
D215.2: INHERITS (RID, TPX-written, inside the digest) on a record a
COPY-FORWARD writes; every inherited record lands at its class's landing
grade (REC-32 whatever INHERITS; REC-40 retired, never raised); the copy
keeps a predecessor's REFUTED or WITHDRAWN grade by a WITHDRAWAL of cause
INHERITED (functions 6.2, 6.3); schema_json's GRADE the landing grade alone.
D218.1: a RESULT's U_C, whose table line carries PREC, enters the record
preimage in its precision form (prec_decimal, functions 7.3), and
budget_check reads that form, so U_C is checked at its last written digit
and the record digest keeps that digit.

THE TABLE'S FUNCTIONS (tpm_record_schema 0.4.0)
  record_check(record, tfk, setting, stage) -> ["OK"] | ["REFUSED", problems]
  record_preimage(record) -> ["OK", fields] | ["REFUSED", problems]
  record_digest(record)   -> ["OK", hex]    | ["REFUSED", problems]
  record_view(record, view, grade) -> ["OK", shape] | ["REFUSED", problems]
  schema_json() -> ["OK", text]       (non-normative; RD-PT-15)
problems: [[code, field, message], ...] sorted in code point order, codes
REC-01 to REC-40 (schema 12) and REC-50 to REC-97 (functions 17).

run_case(fn, args) renders a result in TPM's canonical JSON (schema 11.4):
keys sorted in code point order, no white space, only the quotation mark,
the backslash and U+0000-U+001F escaped (lowercase, as u00xx after a
backslash), every other
character as itself; the corpora record_corpus, record_view_corpus,
conditions_corpus (tpc_strike's new functions) and settlement_corpus
(tpc_eom's) are answered here.

Standard library only; Python 3.8 or later. Imports tpc_digest, tpc_zone,
tpc_units, tpc_form, tpc_tests, tpc_strike (and tpc_eom for run_case);
none of those imports tpc_record (RD-RS-33).
"""
import unicodedata

import tpc_digest as G
import tpc_form as F
import tpc_strike as S
import tpc_tests as TT
import tpc_units as U
import tpc_zone as Z

TPC_RECORD_VERSION = "0.1.0"      # proposed: new module (increment 12), unreleased until H-DECL declares TPC-PY 0.2.0
VERSION = TPC_RECORD_VERSION
SCHEMA_TEXT_VERSION = "0.4.0"     # tpm_record_schema (0.2.1: schema_json's GRADE enumeration, S100; 0.3.0: U_C's PREC; 0.4.0: D215.2, the LANDGRADE alone)

# ---- the table: tpm_record_schema 0.4.0 section 7.5 (0.3.0's line for line; 0.2.1's but U_C's PREC), BEGIN to END RECORD TABLE, verbatim
TABLE_TEXT = """BEGIN RECORD TABLE
ENUM CLASS = RESULT | ASSUMPTION | CORROBORATION | VERIFICATION
  | WITHDRAWAL | NAMED_GAP | ENTRY_STATUS | PROMOTION | MASTER | HOLD
  | RELEASE
ENUM GRADE_VALUES = PROVISIONAL | ESTABLISHED | REFUTED | WITHDRAWN
  | NOT GRADED
ENUM REGIME = L | M
ENUM REGIME_M = M
ENUM FALSIFIER_CLASS = F1 | F2 | F3
ENUM SITUATION = INITIAL | BOUNDARY
ENUM OUTCOME = MATCH | MATCH WITH DIFFERENCE NOTED
ENUM TARGET_STATUS = PROVISIONAL | ESTABLISHED | HELD
ENUM EVALUATION = A | B
ENUM ORIGIN = SAME BLOCK | REPRODUCTION BLOCK
ENUM GAP_SOURCE = IC2 | UNDER | SR5_LOCUS | SR7_ASSUMPTIONS | SR7_DOMAIN
ENUM ROLE_WORDS = CONSTRAINT | CONSTITUTIVE | INVARIANT | DEFINITION

CLASS RESULT KEYED PUSH
CLASS ASSUMPTION KEYED PUSH
CLASS CORROBORATION KEYED PUSH
CLASS VERIFICATION KEYED PUSH
CLASS WITHDRAWAL LIST TPX
CLASS NAMED_GAP KEYED TPX
CLASS ENTRY_STATUS KEYED PUSH
CLASS PROMOTION LIST TPX
CLASS MASTER KEYED TPX
CLASS HOLD LIST TPX
CLASS RELEASE LIST TPX

LANDGRADE RESULT = PROVISIONAL
LANDGRADE ASSUMPTION = PROVISIONAL
LANDGRADE CORROBORATION = PROVISIONAL
LANDGRADE VERIFICATION = PROVISIONAL
LANDGRADE WITHDRAWAL = NOT GRADED
LANDGRADE NAMED_GAP = NOT GRADED
LANDGRADE ENTRY_STATUS = NOT GRADED
LANDGRADE PROMOTION = NOT GRADED
LANDGRADE MASTER = NOT GRADED
LANDGRADE HOLD = NOT GRADED
LANDGRADE RELEASE = NOT GRADED

GRADES RESULT = PROVISIONAL | ESTABLISHED | REFUTED | WITHDRAWN
GRADES ASSUMPTION = PROVISIONAL | WITHDRAWN
GRADES CORROBORATION = PROVISIONAL | REFUTED | WITHDRAWN
GRADES VERIFICATION = PROVISIONAL | WITHDRAWN
GRADES WITHDRAWAL = NOT GRADED
GRADES NAMED_GAP = NOT GRADED
GRADES ENTRY_STATUS = NOT GRADED
GRADES PROMOTION = NOT GRADED
GRADES MASTER = NOT GRADED
GRADES HOLD = NOT GRADED
GRADES RELEASE = NOT GRADED

STATUSES RESULT = UNMARKED | HELD | REFUTED
STATUSES ASSUMPTION = UNMARKED | HELD
STATUSES CORROBORATION = UNMARKED | HELD | REFUTED
STATUSES VERIFICATION = UNMARKED | HELD
STATUSES ENTRY_STATUS = UNMARKED | HELD

SUB DERIVATION KEY -
SUB COMPONENT KEY NAME
SUB POINT KEY DOF
SUB INDEPENDENCE KEY -
SUB EXTERNAL KEY -
SUB DIFFERENCE KEY -

FIELD RESULT ID RID ONE R LEAD D - -
FIELD RESULT CLASS ENUM ONE R AUTHOR D - ENUM=CLASS
FIELD RESULT GRADE ENUM ONE R TPX D - GRADE
FIELD RESULT STATUS ENUM ONE R TPX X - STATUS
FIELD RESULT BLOCK TEXT ONE R TPX D - NOTBLANK
FIELD RESULT AUTHOR TEXT ONE R TPX D - NOTBLANK
FIELD RESULT LANDED_BY TEXT ONE R TPX D - NOTBLANK
FIELD RESULT LANDED TEXT ONE R TPX D - UTC
FIELD RESULT VETTED_IN TEXT ONE O TPX D - NOTBLANK
FIELD RESULT CITES IDSET SET R AUTHOR D - RID
FIELD RESULT ASSUMPTIONS IDSET SET R AUTHOR D - RID
FIELD RESULT LITERATURE IDSET SET O AUTHOR D - BIBID,SETTING
FIELD RESULT SUPERSEDES RID ONE O AUTHOR D - -
FIELD RESULT INHERITS RID ONE O TPX D - -
FIELD RESULT TPS TEXT ONE R TPX D - VERSION
FIELD RESULT D_IS TEXT ONE R TPX D - HEX64
FIELD RESULT STATEMENT TEXT ONE R AUTHOR D - NOTBLANK
FIELD RESULT REGIME ENUM ONE R AUTHOR D - ENUM=REGIME
FIELD RESULT DERIVATION OBJECT ONE R AUTHOR D - SUB=DERIVATION
FIELD RESULT FALSIFIER TEXT ONE R AUTHOR D - NOTBLANK
FIELD RESULT FALSIFIER_CLASS ENUM ONE R AUTHOR D - ENUM=FALSIFIER_CLASS
FIELD RESULT DOMAIN TEXT ONE O AUTHOR D - NOTBLANK
FIELD RESULT DOMAIN_CONDITION PREDICATE ONE O AUTHOR D - -
FIELD RESULT REFS IDENT SET C AUTHOR D - TPKNAME
FIELD RESULT EQUATION FORM ONE C AUTHOR D - -
FIELD RESULT ROLE ENUM ONE C AUTHOR D - ROLE
FIELD RESULT ENTRIES IDENT SET O AUTHOR D - TRMENTRY
FIELD RESULT QUANTITY IDENT ONE C AUTHOR D - TRMENTRY
FIELD RESULT VALUE DECIMAL ONE C AUTHOR D - -
FIELD RESULT UNIT UNIT ONE C AUTHOR D - DIM=QUANTITY
FIELD RESULT U_C DECIMAL ONE C AUTHOR D - POSITIVE,PREC
FIELD RESULT BUDGET OBJECT LIST C AUTHOR D - SUB=COMPONENT,NONEMPTY
FIELD RESULT CORRELATED BOOL ONE C AUTHOR D FALSE -
FIELD RESULT AT OBJECT LIST C AUTHOR D EMPTY SUB=POINT
FIELD RESULT DIGEST TEXT ONE O TPX X - HEX64,DIGEST
RULE RESULT | IF REGIME = M | REQUIRE | QUANTITY, VALUE, UNIT, U_C, BUDGET
RULE RESULT | IF REGIME = M | FORBID | EQUATION, ROLE
RULE RESULT | IF REGIME = L | FORBID | U_C, BUDGET
RULE RESULT | IF REGIME = L | ALL OR NONE | QUANTITY, VALUE, UNIT
RULE RESULT | IF BUDGET ABSENT | FORBID | CORRELATED
RULE RESULT | IF VALUE ABSENT | FORBID | AT
RULE RESULT | IF ROLE PRESENT | REQUIRE | EQUATION
RULE RESULT | IF EQUATION PRESENT | REQUIRE | REFS
RULE RESULT | IF DOMAIN_CONDITION PRESENT | REQUIRE | REFS

FIELD ASSUMPTION ID RID ONE R LEAD D - -
FIELD ASSUMPTION CLASS ENUM ONE R AUTHOR D - ENUM=CLASS
FIELD ASSUMPTION GRADE ENUM ONE R TPX D - GRADE
FIELD ASSUMPTION STATUS ENUM ONE R TPX X - STATUS
FIELD ASSUMPTION BLOCK TEXT ONE R TPX D - NOTBLANK
FIELD ASSUMPTION AUTHOR TEXT ONE R TPX D - NOTBLANK
FIELD ASSUMPTION LANDED_BY TEXT ONE R TPX D - NOTBLANK
FIELD ASSUMPTION LANDED TEXT ONE R TPX D - UTC
FIELD ASSUMPTION VETTED_IN TEXT ONE O TPX D - NOTBLANK
FIELD ASSUMPTION CITES IDSET SET R AUTHOR D - RID
FIELD ASSUMPTION ASSUMPTIONS IDSET SET R AUTHOR D - RID
FIELD ASSUMPTION LITERATURE IDSET SET O AUTHOR D - BIBID,SETTING
FIELD ASSUMPTION SUPERSEDES RID ONE O AUTHOR D - -
FIELD ASSUMPTION INHERITS RID ONE O TPX D - -
FIELD ASSUMPTION TPS TEXT ONE R TPX D - VERSION
FIELD ASSUMPTION D_IS TEXT ONE R TPX D - HEX64
FIELD ASSUMPTION PROPOSITION TEXT ONE R AUTHOR D - NOTBLANK,T2
FIELD ASSUMPTION REFS IDENT SET R AUTHOR D - TPKNAME
FIELD ASSUMPTION SITUATION ENUM ONE O AUTHOR D - ENUM=SITUATION
FIELD ASSUMPTION DOF IDENT ONE C AUTHOR D - TPKDOF
FIELD ASSUMPTION VALUE DECIMAL ONE C AUTHOR D - -
FIELD ASSUMPTION UNIT UNIT ONE C AUTHOR D - DIM=DOF
FIELD ASSUMPTION AT OBJECT LIST C AUTHOR D EMPTY SUB=POINT
FIELD ASSUMPTION EQUATION FORM ONE C AUTHOR D - -
FIELD ASSUMPTION ROLE ENUM ONE C AUTHOR D - ROLE
FIELD ASSUMPTION DIGEST TEXT ONE O TPX X - HEX64,DIGEST
RULE ASSUMPTION | IF SITUATION PRESENT | REQUIRE | DOF, VALUE, UNIT
RULE ASSUMPTION | IF SITUATION ABSENT | FORBID | DOF, VALUE, UNIT, AT
RULE ASSUMPTION | IF SITUATION PRESENT | FORBID | EQUATION, ROLE
RULE ASSUMPTION | IF ROLE PRESENT | REQUIRE | EQUATION

FIELD CORROBORATION ID RID ONE R LEAD D - -
FIELD CORROBORATION CLASS ENUM ONE R AUTHOR D - ENUM=CLASS
FIELD CORROBORATION GRADE ENUM ONE R TPX D - GRADE
FIELD CORROBORATION STATUS ENUM ONE R TPX X - STATUS
FIELD CORROBORATION BLOCK TEXT ONE R TPX D - NOTBLANK
FIELD CORROBORATION AUTHOR TEXT ONE R TPX D - NOTBLANK
FIELD CORROBORATION LANDED_BY TEXT ONE R TPX D - NOTBLANK
FIELD CORROBORATION LANDED TEXT ONE R TPX D - UTC
FIELD CORROBORATION VETTED_IN TEXT ONE O TPX D - NOTBLANK
FIELD CORROBORATION CITES IDSET SET R AUTHOR D - RID
FIELD CORROBORATION ASSUMPTIONS IDSET SET R AUTHOR D - RID
FIELD CORROBORATION LITERATURE IDSET SET O AUTHOR D - BIBID,SETTING
FIELD CORROBORATION SUPERSEDES RID ONE O AUTHOR D - -
FIELD CORROBORATION INHERITS RID ONE O TPX D - -
FIELD CORROBORATION TPS TEXT ONE R TPX D - VERSION
FIELD CORROBORATION D_IS TEXT ONE R TPX D - HEX64
FIELD CORROBORATION TARGET RID ONE R AUTHOR D - -
FIELD CORROBORATION OUTCOME ENUM ONE R TPX D - ENUM=OUTCOME
FIELD CORROBORATION DIFFERENCE TEXT ONE C TPX D - NOTBLANK
FIELD CORROBORATION INDEPENDENCE OBJECT ONE R TPX D - SUB=INDEPENDENCE
FIELD CORROBORATION TARGET_STATUS ENUM ONE R TPX D - ENUM=TARGET_STATUS
FIELD CORROBORATION SELF_REPORTED TEXT ONE O AUTHOR D - NOTBLANK
FIELD CORROBORATION DIGEST TEXT ONE O TPX X - HEX64,DIGEST
RULE CORROBORATION | IF OUTCOME = MATCH WITH DIFFERENCE NOTED
  | REQUIRE | DIFFERENCE
RULE CORROBORATION | IF OUTCOME = MATCH | FORBID | DIFFERENCE

FIELD VERIFICATION ID RID ONE R LEAD D - -
FIELD VERIFICATION CLASS ENUM ONE R AUTHOR D - ENUM=CLASS
FIELD VERIFICATION GRADE ENUM ONE R TPX D - GRADE
FIELD VERIFICATION STATUS ENUM ONE R TPX X - STATUS
FIELD VERIFICATION BLOCK TEXT ONE R TPX D - NOTBLANK
FIELD VERIFICATION AUTHOR TEXT ONE R TPX D - NOTBLANK
FIELD VERIFICATION LANDED_BY TEXT ONE R TPX D - NOTBLANK
FIELD VERIFICATION LANDED TEXT ONE R TPX D - UTC
FIELD VERIFICATION VETTED_IN TEXT ONE O TPX D - NOTBLANK
FIELD VERIFICATION CITES IDSET SET R AUTHOR D - RID
FIELD VERIFICATION ASSUMPTIONS IDSET SET R AUTHOR D - RID
FIELD VERIFICATION LITERATURE IDSET SET O AUTHOR D - BIBID,SETTING
FIELD VERIFICATION SUPERSEDES RID ONE O AUTHOR D - -
FIELD VERIFICATION INHERITS RID ONE O TPX D - -
FIELD VERIFICATION TPS TEXT ONE R TPX D - VERSION
FIELD VERIFICATION D_IS TEXT ONE R TPX D - HEX64
FIELD VERIFICATION REGIME ENUM ONE R AUTHOR D - ENUM=REGIME_M
FIELD VERIFICATION TARGET RID ONE R AUTHOR D - -
FIELD VERIFICATION EXTERNAL OBJECT ONE R AUTHOR D - SUB=EXTERNAL
FIELD VERIFICATION SOURCE TEXT ONE R AUTHOR D - NOTBLANK,BIBSOURCE
FIELD VERIFICATION AT OBJECT LIST O AUTHOR D EMPTY SUB=POINT
FIELD VERIFICATION DIFFERENCE OBJECT ONE R AUTHOR D - SUB=DIFFERENCE
FIELD VERIFICATION DIGEST TEXT ONE O TPX X - HEX64,DIGEST

FIELD NAMED_GAP ID RID ONE R TPX D - -
FIELD NAMED_GAP CLASS ENUM ONE R TPX D - ENUM=CLASS
FIELD NAMED_GAP GRADE ENUM ONE R TPX D - GRADE
FIELD NAMED_GAP LANDED_BY TEXT ONE R TPX D - TPXONLY
FIELD NAMED_GAP LANDED TEXT ONE R TPX D - UTC
FIELD NAMED_GAP TPS TEXT ONE R TPX D - VERSION
FIELD NAMED_GAP INHERITS RID ONE O TPX D - -
FIELD NAMED_GAP SOURCE ENUM ONE R TPX D - ENUM=GAP_SOURCE
FIELD NAMED_GAP AGAINST TEXT SET R TPX D - AGAINST
FIELD NAMED_GAP TRIGGER TEXT ONE R TPX D - NOTBLANK,TRIGGER
FIELD NAMED_GAP STATEMENT TEXT ONE R TPX D - NOTBLANK
FIELD NAMED_GAP STATE TEXT ONE R TPX X - STATELINE
FIELD NAMED_GAP DIGEST TEXT ONE O TPX X - HEX64,DIGEST

FIELD ENTRY_STATUS ID RID ONE R LEAD D - -
FIELD ENTRY_STATUS CLASS ENUM ONE R AUTHOR D - ENUM=CLASS
FIELD ENTRY_STATUS GRADE ENUM ONE R TPX D - GRADE
FIELD ENTRY_STATUS STATUS ENUM ONE R TPX X - STATUS
FIELD ENTRY_STATUS BLOCK TEXT ONE R TPX D - NOTBLANK
FIELD ENTRY_STATUS AUTHOR TEXT ONE R TPX D - NOTBLANK
FIELD ENTRY_STATUS LANDED_BY TEXT ONE R TPX D - NOTBLANK
FIELD ENTRY_STATUS LANDED TEXT ONE R TPX D - UTC
FIELD ENTRY_STATUS VETTED_IN TEXT ONE O TPX D - NOTBLANK
FIELD ENTRY_STATUS CITES IDSET SET R AUTHOR D - RID
FIELD ENTRY_STATUS ASSUMPTIONS IDSET SET R AUTHOR D - RID
FIELD ENTRY_STATUS LITERATURE IDSET SET O AUTHOR D - BIBID,SETTING
FIELD ENTRY_STATUS INHERITS RID ONE O TPX D - -
FIELD ENTRY_STATUS TPS TEXT ONE R TPX D - VERSION
FIELD ENTRY_STATUS D_IS TEXT ONE R TPX D - HEX64
FIELD ENTRY_STATUS ENTRY IDENT ONE R AUTHOR D - TRMENTRY
FIELD ENTRY_STATUS RIDER TEXT ONE O AUTHOR D - NOTBLANK
FIELD ENTRY_STATUS NOTE TEXT ONE O AUTHOR D - NOTBLANK
FIELD ENTRY_STATUS SCRUTINY TEXT ONE O AUTHOR D - NOTBLANK
FIELD ENTRY_STATUS CHECKS_OWED TEXT ONE O AUTHOR D - NOTBLANK
FIELD ENTRY_STATUS DIGEST TEXT ONE O TPX X - HEX64,DIGEST
RULE ENTRY_STATUS | ALWAYS | AT LEAST ONE | RIDER, NOTE, SCRUTINY, CHECKS_OWED

FIELD MASTER ID RID ONE R TPX D - MASTERID
FIELD MASTER CLASS ENUM ONE R TPX D - ENUM=CLASS
FIELD MASTER GRADE ENUM ONE R TPX D - GRADE
FIELD MASTER LANDED_BY TEXT ONE R TPX D - TPXONLY
FIELD MASTER LANDED TEXT ONE R TPX D - UTC
FIELD MASTER TPS TEXT ONE R TPX D - VERSION
FIELD MASTER UUID TEXT ONE R TPX D - UUID4
FIELD MASTER PREFIX IDENT ONE R TPX D - -
FIELD MASTER D_TPK TEXT ONE R TPX D - HEX64,ZONE=TPK
FIELD MASTER D_TRM TEXT ONE R TPX D - HEX64,ZONE=TrM
FIELD MASTER D_TMS TEXT ONE R TPX D - HEX64,ZONE=TMS
FIELD MASTER D_TFK TEXT ONE R TPX D - HEX64,DTFK
FIELD MASTER CERTIFICATE TEXT ONE R TPX D - NOTBLANK
FIELD MASTER STRUCTURE_LINE TEXT ONE R TPX D - NOTBLANK
FIELD MASTER MODULES_USED TEXT ONE R TPX D - NOTBLANK
FIELD MASTER DIGEST TEXT ONE O TPX X - HEX64,DIGEST

FIELD SUB:DERIVATION STEPS INT ONE R AUTHOR D - STEPS
FIELD SUB:DERIVATION TEXT TEXT ONE R AUTHOR D - NOTBLANK
FIELD SUB:COMPONENT NAME IDENT ONE R AUTHOR D - KEY
FIELD SUB:COMPONENT U DECIMAL ONE R AUTHOR D - NONNEG
FIELD SUB:COMPONENT UNIT UNIT ONE R AUTHOR D - -
FIELD SUB:COMPONENT EVALUATION ENUM ONE R AUTHOR D - ENUM=EVALUATION
FIELD SUB:COMPONENT BASIS TEXT ONE R AUTHOR D - NOTBLANK
FIELD SUB:POINT DOF IDENT ONE R AUTHOR D - KEY,TPKDOF
FIELD SUB:POINT VALUE DECIMAL ONE R AUTHOR D - -
FIELD SUB:POINT UNIT UNIT ONE R AUTHOR D - DIM=DOF
FIELD SUB:INDEPENDENCE ACCOUNT TEXT ONE R TPX D - NOTBLANK
FIELD SUB:INDEPENDENCE SNAPSHOT TEXT ONE R TPX D - NOTBLANK
FIELD SUB:INDEPENDENCE VENDOR TEXT ONE R TPX D - NOTBLANK
FIELD SUB:INDEPENDENCE IMPLEMENTATION TEXT ONE R TPX D - NOTBLANK
FIELD SUB:INDEPENDENCE ORIGIN ENUM ONE R TPX D - ENUM=ORIGIN
FIELD SUB:INDEPENDENCE NOT_SERVED_BEFORE BOOL ONE R TPX D - -
FIELD SUB:INDEPENDENCE SAME_SPONSOR BOOL ONE R TPX D - -
FIELD SUB:INDEPENDENCE SAME_MODEL BOOL ONE R TPX D - -
FIELD SUB:EXTERNAL VALUE DECIMAL ONE R AUTHOR D - -
FIELD SUB:EXTERNAL U_C DECIMAL ONE R AUTHOR D - NONNEG
FIELD SUB:EXTERNAL UNIT UNIT ONE R AUTHOR D - DIM=QUANTITY
FIELD SUB:EXTERNAL QUANTITY IDENT ONE R AUTHOR D - TRMENTRY
FIELD SUB:DIFFERENCE VALUE DECIMAL ONE R AUTHOR D - -
FIELD SUB:DIFFERENCE U DECIMAL ONE R AUTHOR D - POSITIVE
FIELD SUB:DIFFERENCE ZETA DECIMAL ONE R AUTHOR D - -
END RECORD TABLE
"""


def _parse_table(text):
    """The table's line grammar (schema 7.1): continuation lines joined; the
    ENUM, CLASS, LANDGRADE, GRADES, STATUSES, SUB, FIELD and RULE lines read."""
    lines = text.split("\n")
    i0 = lines.index("BEGIN RECORD TABLE")
    i1 = lines.index("END RECORD TABLE")
    joined = []
    for ln in lines[i0 + 1:i1]:
        if ln.startswith("  "):
            joined[-1] = joined[-1] + " " + ln.strip()
        elif ln.strip() != "":
            joined.append(ln)
    T = {"enums": {}, "classes": [], "form": {}, "writer": {}, "landgrade": {},
         "grades": {}, "statuses": {}, "subs": [], "subkey": {}, "fields": {}, "rules": {}}
    for ln in joined:
        kw = ln.split(" ", 1)[0]
        if kw == "ENUM":
            name, vals = ln[len("ENUM "):].split(" = ", 1)
            T["enums"][name] = vals.split(" | ")
        elif kw == "CLASS":
            _, name, form, writer = ln.split(" ")
            T["classes"].append(name)
            T["form"][name] = form
            T["writer"][name] = writer
        elif kw in ("LANDGRADE", "GRADES", "STATUSES"):
            name, vals = ln[len(kw) + 1:].split(" = ", 1)
            if kw == "LANDGRADE":
                T["landgrade"][name] = vals
            else:
                T[kw.lower()][name] = vals.split(" | ")
        elif kw == "SUB":
            _, name, _key, key = ln.split(" ")
            T["subs"].append(name)
            T["subkey"][name] = None if key == "-" else key
        elif kw == "FIELD":
            p = ln.split(" ")
            if len(p) != 10:
                raise ValueError("table: FIELD line of %d words" % len(p))
            T["fields"].setdefault(p[1], []).append(
                {"name": p[2], "type": p[3], "card": p[4], "req": p[5], "writer": p[6],
                 "dig": p[7], "default": p[8], "cons": [] if p[9] == "-" else p[9].split(",")})
        elif kw == "RULE":
            parts = ln.split(" | ")
            cls = parts[0].split(" ")[1]
            cond = parts[1]
            if cond == "ALWAYS":
                c = None
            elif cond.endswith(" PRESENT") or cond.endswith(" ABSENT"):
                w = cond.split(" ")
                c = {"field": w[1], "op": w[2]}
            else:
                f, v = cond[len("IF "):].split(" = ", 1)
                c = {"field": f, "op": "=", "value": v}
            T["rules"].setdefault(cls, []).append(
                {"if": c, "action": parts[2], "fields": parts[3].split(", ")})
        else:
            raise ValueError("table: unknown line")
    return T


T = _parse_table(TABLE_TEXT)
KEYED = [c for c in T["classes"] if T["form"][c] == "KEYED"]
VIEWS = ["HOLD", "STRIKE", "SUITE", "PROBE_L", "PROBE_M", "PROBE_LOAD", "LITERATURE", "EOM"]
GRADE_VALUES = T["enums"]["GRADE_VALUES"]
STATUS_WORDS = ["PROVISIONAL", "ESTABLISHED", "REFUTED", "WITHDRAWN", "HELD", "NOT GRADED"]
STATUS_VALUES = ["UNMARKED", "HELD", "REFUTED"]


# ---- small helpers --------------------------------------------------------------------
def _fullmatch_rid(s):
    """[A-Za-z_][A-Za-z0-9_]*-[1-9][0-9]* over the whole string (\\Z: a final line
    feed never matches)."""
    return _RID.match(s) is not None


import re                                                   # noqa: E402

_RID = re.compile(r"[A-Za-z_][A-Za-z0-9_]*-[1-9][0-9]*\Z")
_BIB = re.compile(r"[A-Za-z_][A-Za-z0-9_]*-BIB[1-9][0-9]*\Z")
_IDENT = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z")
_HEX64 = re.compile(r"[0-9a-f]{64}\Z")
_UUID4 = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\Z")
_VERSION = re.compile(r"(0|[1-9][0-9]{0,3})\.(0|[1-9][0-9]{0,3})\.(0|[1-9][0-9]{0,3})\Z")
_UTC = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z\Z")
_DATE = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})\Z")
_EOM = re.compile(r"EOM\(([A-Za-z_][A-Za-z0-9_]*)\)\Z")
_SETTLED = re.compile(r"SETTLED ([0-9]{4}-[0-9]{2}-[0-9]{2}) BY (.+)\Z", re.S)
_STANDING = re.compile(r"STANDING ([0-9]{4}-[0-9]{2}-[0-9]{2}) BY (.+)\Z", re.S)


def _m(rx, s):
    return type(s) is str and rx.match(s) is not None


def _is_int(v):
    return type(v) is int


def _shown_char(c):
    return unicodedata.category(c)[0] == "C" or c in ("
", "
")


def _show(v):
    """A slot value in a message (schema 12.1): every code point of general
    category C, and U+2028 and U+2029, written <U+XXXX>, TAB and LF included."""
    return "".join("<U+%04X>" % ord(c) if _shown_char(c) else c for c in v)


def cj(v):
    """TPM canonical JSON (schema 11.4). A float, which no result of this module
    holds, is written by tpc_strike canon so the writer never fails."""
    if v is True:
        return "true"
    if v is False:
        return "false"
    if v is None:
        return "null"
    if type(v) is int:
        return str(v)
    if type(v) is float:
        return S.canon(v)
    if type(v) is str:
        out = ['"']
        for ch in v:
            if ch == '"':
                out.append('\\"')
            elif ch == "\\":
                out.append("\\\\")
            elif ord(ch) < 0x20:
                out.append("\\u%04x" % ord(ch))
            else:
                out.append(ch)
        out.append('"')
        return "".join(out)
    if type(v) in (list, tuple):
        return "[" + ",".join(cj(x) for x in v) + "]"
    if type(v) is dict:
        return "{" + ",".join(cj(k) + ":" + cj(v[k]) for k in sorted(v)) + "}"
    raise TypeError("cj: unsupported type")


def _or_list(xs):
    return xs[0] if len(xs) == 1 else ", ".join(xs[:-1]) + " or " + xs[-1]


def _and_list(xs):
    return xs[0] if len(xs) == 1 else ", ".join(xs[:-1]) + " and " + xs[-1]


def _date_ok(y, mo, d):
    y, mo, d = int(y), int(mo), int(d)
    if mo < 1 or mo > 12 or d < 1:
        return False
    leap = (y % 4 == 0 and y % 100 != 0) or y % 400 == 0
    days = [31, 29 if leap else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    return d <= days[mo - 1]


def _utc_ok(s):
    m = _UTC.match(s)
    if not m:
        return False
    y, mo, d, h, mi, se = m.groups()
    return _date_ok(y, mo, d) and int(h) <= 23 and int(mi) <= 59 and int(se) <= 59


def _day_ok(s):
    m = _DATE.match(s) if type(s) is str else None
    return m is not None and _date_ok(*m.groups())


def _stateline_ok(s):
    if s == "OPEN":
        return True
    m = _SETTLED.match(s)
    if m:
        return _day_ok(m.group(1)) and all(_m(_RID, x) for x in m.group(2).split(", "))
    m = _STANDING.match(s)
    if m:
        return _day_ok(m.group(1)) and not Z._blank(m.group(2))
    return False


def _problems(p):
    """Sorted by code, field, message in code point order; no duplicate triple."""
    return [list(x) for x in sorted(set(tuple(x) for x in p))]


def _refused(p):
    return ["REFUSED", _problems(p)]


def _one(code, field, msg):
    return ["REFUSED", [[code, field, msg]]]


TYPE_PHRASE = {("RID", "ONE"): "a record identifier", ("IDSET", "SET"): "a set of record identifiers",
               ("IDENT", "ONE"): "an identifier", ("IDENT", "SET"): "a set of identifiers",
               ("TEXT", "ONE"): "a text", ("TEXT", "SET"): "a set of texts",
               ("DECIMAL", "ONE"): "a decimal string", ("UNIT", "ONE"): "a unit string",
               ("DIMENSION", "ONE"): "a dimension string", ("FORM", "ONE"): "a form string",
               ("PREDICATE", "ONE"): "a predicate string", ("ENUM", "ONE"): "a string",
               ("INT", "ONE"): "an integer", ("BOOL", "ONE"): "a boolean",
               ("OBJECT", "ONE"): "an object", ("OBJECT", "LIST"): "a list of objects"}
FORM_PHRASE = {"RID": "a record identifier", "BIBID": "a BIB ID", "IDENT": "an identifier",
               "HEX64": "64 lowercase hexadecimal characters", "UUID4": "a version-4 UUID",
               "VERSION": "a version n.n.n", "UTC": "a UTC time YYYY-MM-DDTHH:MM:SSZ",
               "STEPS": "an integer from 1 to 1000000", "TPXONLY": "TPX",
               "STATELINE": "a STATE line",
               "ROLE": "EOM(<DOF>), CONSTRAINT, CONSTITUTIVE, INVARIANT or DEFINITION",
               "DIMENSION": "a dimension"}
TEXT_FORMS = [("HEX64", lambda s: _m(_HEX64, s)), ("UUID4", lambda s: _m(_UUID4, s)),
              ("VERSION", lambda s: _m(_VERSION, s)), ("UTC", _utc_ok),
              ("STATELINE", _stateline_ok), ("TPXONLY", lambda s: s == "TPX")]


def _cons_value(f, prefix):
    for c in f["cons"]:
        if c.startswith(prefix):
            return c[len(prefix):]
    return None


def _field(fields, name):
    for f in fields:
        if f["name"] == name:
            return f
    return None


# ---- record_check (schema 8) ---------------------------------------------------------
class _Ctx(object):
    def __init__(self, cls, stage, tfk):
        self.cls, self.stage, self.tfk = cls, stage, tfk
        self.p = []
        self.ok = set()              # paths that passed S2

    def add(self, code, field, msg):
        self.p.append([code, field, msg])


def _check_value(ctx, f, v, path):
    """S2 (c) and (d) for one value; True when it passed."""
    t, card, cons = f["type"], f["card"], f["cons"]
    bad_type = False
    if card == "SET":
        bad_type = type(v) is not list or any(type(x) is not str for x in v)
    elif t == "OBJECT" and card == "LIST":
        bad_type = type(v) is not list or any(type(x) is not dict for x in v)
    elif t == "OBJECT":
        bad_type = type(v) is not dict
    elif t == "INT":
        bad_type = not _is_int(v)
    elif t == "BOOL":
        bad_type = type(v) is not bool
    else:
        bad_type = type(v) is not str
    if bad_type:
        ctx.add("REC-09", path, "%s is not %s" % (path, TYPE_PHRASE[(t, card)]))
        return False
    if card == "SET":
        seen = set()
        for x in v:
            if t == "IDSET":
                form = "BIBID" if "BIBID" in cons else "RID"
                if not _m(_BIB if form == "BIBID" else _RID, x):
                    ctx.add("REC-19", path, "%s member %s is not %s" % (path, _show(x), FORM_PHRASE[form]))
                    return False
                c = x
            elif t == "IDENT":
                if not _m(_IDENT, x):
                    ctx.add("REC-19", path, "%s member %s is not %s" % (path, _show(x), FORM_PHRASE["IDENT"]))
                    return False
                c = x
            else:                                   # TEXT
                b = Z._first_bad(x)
                if b is not None:
                    ctx.add("REC-10", path, "%s holds the disallowed code point %s" % (path, b))
                    return False
                if "NOTBLANK" in cons and Z._blank(x):
                    ctx.add("REC-11", path, "%s is blank" % path)
                    return False
                c = Z._text(x)
            if c in seen:
                ctx.add("REC-13", path, "%s repeats %s" % (path, _show(c)))
                return False
            seen.add(c)
        return True
    if t == "OBJECT":
        sub = _cons_value(f, "SUB=")
        sfields = T["fields"]["SUB:" + sub]
        if card == "ONE":
            _check_object(ctx, sfields, v, path)
            return True
        if "NONEMPTY" in cons and len(v) == 0:
            ctx.add("REC-35", path, "%s is empty" % path)
            return False
        key = T["subkey"][sub]
        keys = []
        for i, o in enumerate(v, 1):
            _check_object(ctx, sfields, o, "%s[%d]" % (path, i))
            if key is not None and ("%s[%d].%s" % (path, i, key)) in ctx.ok:
                keys.append(_canon_scalar(_field(sfields, key), o[key]))
        seen = set()
        for kv in keys:
            if kv in seen:
                ctx.add("REC-13", path, "%s repeats %s" % (path, _show(kv)))
                return False
            seen.add(kv)
        return True
    if t in ("RID", "IDENT"):
        if not _m(_RID if t == "RID" else _IDENT, v):
            ctx.add("REC-19", path, "%s %s is not %s" % (path, _show(v), FORM_PHRASE[t]))
            return False
        return True
    if t == "TEXT":
        b = Z._first_bad(v)
        if b is not None:
            ctx.add("REC-10", path, "%s holds the disallowed code point %s" % (path, b))
            return False
        if "NOTBLANK" in cons and Z._blank(v):
            ctx.add("REC-11", path, "%s is blank" % path)
            return False
        for form, test in TEXT_FORMS:
            if form in cons and not test(v):
                ctx.add("REC-19", path, "%s %s is not %s" % (path, _show(v), FORM_PHRASE[form]))
                return False
        return True
    if t == "ENUM":
        if "ROLE" in cons:
            if v in T["enums"]["ROLE_WORDS"] or _m(_EOM, v):
                return True
            ctx.add("REC-19", path, "%s %s is not %s" % (path, _show(v), FORM_PHRASE["ROLE"]))
            return False
        if "GRADE" in cons:
            if v not in GRADE_VALUES:
                ctx.add("REC-12", path, "%s %s is not one of %s" % (path, _show(v), _or_list(GRADE_VALUES)))
                return False
            lg = T["landgrade"][ctx.cls]      # D215.2: INHERITS or not (REC-40 retired)
            if v != lg:
                ctx.add("REC-32", path, "GRADE %s is not %s, the landing grade of %s"
                        % (_show(v), lg, ctx.cls))
                return False
            return True
        vals = T["statuses"][ctx.cls] if "STATUS" in cons else T["enums"][_cons_value(f, "ENUM=")]
        if v not in vals:
            ctx.add("REC-12", path, "%s %s is not one of %s" % (path, _show(v), _or_list(vals)))
            return False
        return True
    if t == "DECIMAL":
        c = Z.canon_decimal(v)
        if c is None:
            ctx.add("REC-14", path, "%s %s is not a decimal" % (path, _show(v)))
            return False
        if "POSITIVE" in cons and (c == "0" or c.startswith("-")):
            ctx.add("REC-15", path, "%s %s is not positive" % (path, _show(v)))
            return False
        if "NONNEG" in cons and c.startswith("-"):
            ctx.add("REC-16", path, "%s %s is negative" % (path, _show(v)))
            return False
        return True
    if t == "UNIT":
        r = U.unit_parse(v)
        if r[0] != "OK":
            ctx.add("REC-17", path, "%s: %s" % (path, r[1]))
            return False
        return True
    if t in ("FORM", "PREDICATE"):
        r = F.parse_form(v, "equation" if t == "FORM" else "predicate")
        if r[0] != "OK":
            ctx.add("REC-18", path, "%s: %s" % (path, r[1]))
            return False
        return True
    if t == "DIMENSION":
        if Z.normalize_dimension(v) is None:
            ctx.add("REC-19", path, "%s %s is not %s" % (path, _show(v), FORM_PHRASE["DIMENSION"]))
            return False
        return True
    if t == "INT":
        if "STEPS" in cons and not (1 <= v <= 1000000):
            ctx.add("REC-19", path, "%s %d is not %s" % (path, v, FORM_PHRASE["STEPS"]))
            return False
        return True
    return True                                     # BOOL


def _check_object(ctx, fields, obj, path):
    names = [f["name"] for f in fields]
    for k in obj:
        if k not in names:
            ctx.add("REC-06", "%s.%s" % (path, _show(k)), "unknown field %s.%s" % (path, _show(k)))
    for f in fields:
        p = "%s.%s" % (path, f["name"])
        if f["name"] in obj:
            if _check_value(ctx, f, obj[f["name"]], p):
                ctx.ok.add(p)
        elif f["req"] == "R":
            ctx.add("REC-08", p, "%s is missing" % p)


def _cond_true(c, rec):
    if c is None:
        return True
    if c["op"] == "PRESENT":
        return c["field"] in rec
    if c["op"] == "ABSENT":
        return c["field"] not in rec
    return c["field"] in rec and rec[c["field"]] == c["value"]


def _cond_text(c):
    if c["op"] == "PRESENT":
        return "%s is present" % c["field"]
    if c["op"] == "ABSENT":
        return "%s is absent" % c["field"]
    return "%s is %s" % (c["field"], c["value"])


def _tfk_ok(tfk):
    """8.2: null, or an object whose TPK and TrM are objects, the TPK's DOF and
    CONSTANT (absent meaning empty) lists of objects with a string NAME (a DOF
    also a DIMENSION normalize_dimension reads), the TrM's ENTRIES a list of
    objects with a string NAME and a DIMENSION normalize_dimension reads."""
    if tfk is None:
        return True
    if type(tfk) is not dict or type(tfk.get("TPK")) is not dict or type(tfk.get("TrM")) is not dict:
        return False
    tpk, trm = tfk["TPK"], tfk["TrM"]
    for kind in ("DOF", "CONSTANT"):
        lst = tpk.get(kind, [])
        if type(lst) is not list:
            return False
        for e in lst:
            if type(e) is not dict or type(e.get("NAME")) is not str:
                return False
            if kind == "DOF" and (type(e.get("DIMENSION")) is not str
                                  or Z.normalize_dimension(e["DIMENSION"]) is None):
                return False
    ents = trm.get("ENTRIES")
    if type(ents) is not list:
        return False
    for e in ents:
        if type(e) is not dict or type(e.get("NAME")) is not str:
            return False
        if type(e.get("DIMENSION")) is not str or Z.normalize_dimension(e["DIMENSION"]) is None:
            return False
    return True


def _tfk_checks(ctx, obj, fields, prefix, tfk):
    """S5 over the fields of one object that passed S2."""
    tpk, trm = tfk["TPK"], tfk["TrM"]
    dofs = dict((e["NAME"], e["DIMENSION"]) for e in tpk.get("DOF", []))
    consts = set(e["NAME"] for e in tpk.get("CONSTANT", []))
    ents = dict((e["NAME"], e["DIMENSION"]) for e in trm["ENTRIES"])
    resolved = {}
    for f in fields:
        n = f["name"]
        p = prefix + n
        if p not in ctx.ok:
            continue
        v = obj[n]
        cons = f["cons"]
        if "TPKNAME" in cons:
            for x in (v if f["card"] == "SET" else [v]):
                if x not in dofs and x not in consts:
                    ctx.add("REC-25", p, "%s %s%s is not a DOF or CONSTANT of the TPK"
                            % (p, "member " if f["card"] == "SET" else "", _show(x)))
                    break
        if "TPKDOF" in cons:
            if v in dofs:
                resolved[n] = dofs[v]
            else:
                ctx.add("REC-25", p, "%s %s is not a DOF of the TPK" % (p, _show(v)))
        if "TRMENTRY" in cons:
            if f["card"] == "SET":
                for x in v:
                    if x not in ents:
                        ctx.add("REC-25", p, "%s member %s is not an entry of the TrM" % (p, _show(x)))
                        break
            elif v in ents:
                resolved[n] = ents[v]
            else:
                ctx.add("REC-25", p, "%s %s is not an entry of the TrM" % (p, _show(v)))
    for f in fields:
        n = f["name"]
        p = prefix + n
        if p not in ctx.ok:
            continue
        for c in f["cons"]:
            if c in ("DIM=QUANTITY", "DIM=DOF") and c[4:] in resolved:
                want = Z.normalize_dimension(resolved[c[4:]])
                got = U.unit_parse(obj[n])[2]
                if got != want:
                    ctx.add("REC-26", p, "%s %s has dimension %s, not %s" % (p, _show(obj[n]), got, want))
    for f in fields:
        if f["type"] == "OBJECT" and (prefix + f["name"]) in ctx.ok:
            sf = T["fields"]["SUB:" + _cons_value(f, "SUB=")]
            v = obj[f["name"]]
            if f["card"] == "ONE":
                _tfk_checks(ctx, v, sf, prefix + f["name"] + ".", tfk)
            else:
                for i, o in enumerate(v, 1):
                    _tfk_checks(ctx, o, sf, "%s%s[%d]." % (prefix, f["name"], i), tfk)


def _zone_digest(tfk, zn, fn):
    """8.2's "a zone tpc_zone cannot digest", read by the zone checks (8.2 since 0.2.1, C-RS-18):
    the zone's digest when the zone is an object the zone check passes
    (t1_well_formed; trm_well_formed with the TPK; tms_well_formed), else
    None, so both residences answer alike on a malformed zone."""
    zone = tfk.get(zn)
    if type(zone) is not dict:
        return None
    if zn == "TPK":
        good = Z.t1_well_formed(zone)[0]
    elif zn == "TrM":
        good = type(tfk.get("TPK")) is dict and Z.trm_well_formed(zone, tfk["TPK"])[0]
    else:
        good = Z.tms_well_formed(zone)[0]
    return fn(zone) if good else None


def record_check(record, tfk, setting, stage):
    """tpm_record_schema 0.4.0 section 8."""
    if stage not in ("PUSH", "LANDED"):
        return _one("REC-36", "-", "stage %s is not PUSH or LANDED" % _show(cj(stage)))
    if setting not in ("OFF", "ON", "CLOSED", None):
        return _one("REC-36", "-", "setting %s is not OFF, ON, CLOSED or null" % _show(cj(setting)))
    if not _tfk_ok(tfk):
        return _one("REC-39", "-", "the TFK is not readable")
    # S1
    if type(record) is not dict:
        return _one("REC-01", "-", "the record is not an object")
    if "CLASS" not in record:
        return _one("REC-02", "CLASS", "CLASS is missing")
    cv = record["CLASS"]
    if type(cv) is not str:
        return _one("REC-09", "CLASS", "CLASS is not a string")
    if cv not in T["classes"]:
        return _one("REC-03", "CLASS", "CLASS %s is not a record class" % _show(cv))
    if T["form"][cv] == "LIST":
        return _one("REC-04", "CLASS", "CLASS %s is written by TPX in its own form" % cv)
    if stage == "PUSH" and T["writer"][cv] == "TPX":
        return _one("REC-05", "CLASS", "CLASS %s is written by TPX and never pushed" % cv)
    ctx = _Ctx(cv, stage, tfk)
    fields = T["fields"][cv]
    names = [f["name"] for f in fields]
    # S2
    for k in record:
        if k not in names:
            ctx.add("REC-06", _show(k), "unknown field %s" % _show(k))
    for f in fields:
        n = f["name"]
        if n in record:
            if stage == "PUSH" and f["writer"] == "TPX":
                ctx.add("REC-07", n, "%s is written by TPX" % n)
                continue
            if _check_value(ctx, f, record[n], n):
                ctx.ok.add(n)
        elif f["req"] == "R" and (stage == "LANDED" or f["writer"] != "TPX"):
            ctx.add("REC-08", n, "%s is missing" % n)
    # S3 rules
    for r in T["rules"].get(cv, []):
        if not _cond_true(r["if"], record):
            continue
        a, fl = r["action"], r["fields"]
        if a == "REQUIRE":
            for x in fl:
                if stage == "PUSH" and _field(fields, x)["writer"] == "TPX":
                    continue
                if x not in record:
                    ctx.add("REC-20", x, "%s is required when %s" % (x, _cond_text(r["if"])))
        elif a == "FORBID":
            for x in fl:
                if x in record:
                    ctx.add("REC-21", x, "%s is not allowed when %s" % (x, _cond_text(r["if"])))
        elif a == "ALL OR NONE":
            pres = [x for x in fl if x in record]
            if 0 < len(pres) < len(fl):
                first = [x for x in fl if x not in record][0]
                ctx.add("REC-22", first, "%s are given together or not at all" % _and_list(fl))
        elif a == "AT LEAST ONE":
            if not any(x in record for x in fl):
                ctx.add("REC-23", "-", "one of %s is required" % _or_list(fl))
    ok = ctx.ok
    if cv == "RESULT" and {"REGIME", "QUANTITY", "ENTRIES"} <= ok and record["REGIME"] == "M":
        if record["QUANTITY"] not in record["ENTRIES"]:
            ctx.add("REC-27", "ENTRIES", "ENTRIES does not name QUANTITY %s" % _show(record["QUANTITY"]))
    if cv == "MASTER":
        if {"ID", "PREFIX"} <= ok and record["ID"] != record["PREFIX"] + "-1":
            ctx.add("REC-28", "ID", "ID %s is not %s-1" % (_show(record["ID"]), record["PREFIX"]))
        if {"D_TFK", "D_TPK", "D_TRM", "D_TMS"} <= ok:
            if G.d_tfk(record["D_TPK"], record["D_TRM"], record["D_TMS"]) != record["D_TFK"]:
                ctx.add("REC-33", "D_TFK", "D_TFK %s is not the combination of D_TPK, D_TRM and D_TMS"
                        % record["D_TFK"])
    if setting == "OFF" and "LITERATURE" in ok:
        ctx.add("REC-30", "LITERATURE", "LITERATURE is not kept under BIBLIOGRAPHY SETTING OFF")
    if setting in ("ON", "CLOSED") and "SOURCE" in ok and cv == "VERIFICATION" \
            and ("LITERATURE" in ok or "LITERATURE" not in record):
        s = record["SOURCE"]
        if not (_m(_BIB, s) and s in record.get("LITERATURE", [])):
            ctx.add("REC-31", "SOURCE", "SOURCE %s is not a BIB ID in LITERATURE" % _show(s))
    if cv == "NAMED_GAP" and "SOURCE" in ok:
        src = record["SOURCE"]
        if "AGAINST" in ok:
            ag = record["AGAINST"]
            if src == "SR7_DOMAIN":
                if ag != ["TFK DOMAIN"]:
                    ctx.add("REC-19", "AGAINST", "AGAINST is not the one member TFK DOMAIN")
                    ok.discard("AGAINST")
            else:
                rx, form = (_IDENT, "IDENT") if src in ("IC2", "UNDER") else (_RID, "RID")
                for x in ag:
                    if not _m(rx, x):
                        ctx.add("REC-19", "AGAINST", "AGAINST member %s is not %s" % (_show(x), FORM_PHRASE[form]))
                        ok.discard("AGAINST")
                        break
        if "TRIGGER" in ok and src in ("SR5_LOCUS", "SR7_ASSUMPTIONS", "SR7_DOMAIN"):
            if not _m(_RID, record["TRIGGER"]):
                ctx.add("REC-19", "TRIGGER", "TRIGGER %s is not %s" % (_show(record["TRIGGER"]), FORM_PHRASE["RID"]))
    # S4 T2
    if cv == "ASSUMPTION" and "PROPOSITION" in ok:
        for fnd in TT.t2_findings(record["PROPOSITION"]):
            ctx.add("REC-24", "PROPOSITION", "PROPOSITION " + fnd)
    # S5 the TFK
    if tfk is not None:
        _tfk_checks(ctx, record, fields, "", tfk)
        if cv == "NAMED_GAP" and {"SOURCE", "AGAINST"} <= ok and record["SOURCE"] in ("IC2", "UNDER"):
            dofs = set(e["NAME"] for e in tfk["TPK"].get("DOF", []))
            for x in record["AGAINST"]:
                if x not in dofs:
                    ctx.add("REC-25", "AGAINST", "AGAINST member %s is not a DOF of the TPK" % _show(x))
                    break
        if cv == "MASTER":
            for fld, zn, fn in (("D_TPK", "TPK", Z.d_tpk), ("D_TRM", "TrM", Z.d_trm), ("D_TMS", "TMS", Z.d_tms)):
                if fld not in ok:
                    continue
                d = _zone_digest(tfk, zn, fn)
                if d is None:
                    ctx.add("REC-39", "-", "the TFK is not readable")
                elif d != record[fld]:
                    ctx.add("REC-34", fld, "%s %s is not the TFK's D(%s)" % (fld, record[fld], zn))
    # S6 the digest
    if stage == "LANDED" and "DIGEST" in ok and not ctx.p:
        r2 = dict(record)
        del r2["DIGEST"]
        d = record_digest(r2)
        if d[0] == "OK" and d[1] != record["DIGEST"]:
            ctx.add("REC-29", "DIGEST", "DIGEST %s is not the record's digest %s" % (record["DIGEST"], d[1]))
    if not ctx.p:
        return ["OK"]
    return _refused(ctx.p)


# ---- the canonical form and the record digest (schema 9) ---------------------------
def _canon_scalar(f, v):
    t = f["type"]
    if t == "TEXT":
        return Z._text(v)
    if t == "DECIMAL":
        if "PREC" in f["cons"]:                   # a RESULT's U_C: its precision form
            return prec_decimal(v)[1]
        return Z.canon_decimal(v)
    if t == "UNIT":
        return U.unit_parse(v)[1]
    if t == "FORM":
        return F.parse_form(v, "equation")[1]
    if t == "PREDICATE":
        return F.parse_form(v, "predicate")[1]
    if t == "DIMENSION":
        return Z.normalize_dimension(v)
    if t == "BOOL":
        return "true" if v else "false"
    return v


def _forbidden(cls, rec):
    out = set()
    for r in T["rules"].get(cls, []):
        if r["action"] == "FORBID" and _cond_true(r["if"], rec):
            out.update(r["fields"])
    return out


def _encode(f, v, out):
    t, card = f["type"], f["card"]
    if card == "SET":
        mem = sorted(Z._text(x) if t == "TEXT" else x for x in v)
        out.append(len(mem))
        out.extend(mem)
    elif t == "OBJECT":
        sub = _cons_value(f, "SUB=")
        sf = T["fields"]["SUB:" + sub]
        if card == "ONE":
            _encode_obj(sf, v, out)
        else:
            kf = _field(sf, T["subkey"][sub])
            mem = sorted(v, key=lambda o: _canon_scalar(kf, o[kf["name"]]))
            out.append(len(mem))
            for o in mem:
                _encode_obj(sf, o, out)
    elif t == "INT":
        out.append(v)
    else:
        out.append(_canon_scalar(f, v))


def _encode_obj(sf, o, out):
    pres = [f for f in sf if f["name"] in o]
    out.append(len(pres))
    for f in pres:
        out.append(f["name"])
        _encode(f, o[f["name"]], out)


def record_preimage(record):
    """["OK", the "TPC-RECORD" form 1 field list] (schema 9.2-9.4)."""
    rec = record
    if type(record) is dict and "DIGEST" in record:
        rec = dict(record)
        del rec["DIGEST"]
    r = record_check(rec, None, None, "LANDED")
    if r[0] != "OK":
        return r
    cls = rec["CLASS"]
    forb = _forbidden(cls, rec)
    body, n = [], 0
    for f in T["fields"][cls]:
        if f["dig"] != "D":
            continue
        nm = f["name"]
        if nm in rec:
            v = rec[nm]
        elif f["default"] == "EMPTY" and nm not in forb:
            v = []
        elif f["default"] == "FALSE" and nm not in forb:
            v = False
        else:
            continue
        n += 1
        body.append(nm)
        _encode(f, v, body)
    return ["OK", ["TPC-RECORD", "1", n] + body]


def record_digest(record):
    r = record_preimage(record)
    if r[0] != "OK":
        return r
    return ["OK", G.digest_fields(r[1])]


# ---- the views (schema 10) --------------------------------------------------------------
def record_view(record, view, grade):
    """A view maps, it does not check (10.2); the status word W of 10.3."""
    if type(record) is not dict:
        return _one("REC-01", "-", "the record is not an object")
    if type(view) is not str or view not in VIEWS:
        return _one("REC-37", "-", "view %s is not a view" % _show(cj(view)))
    if grade is not None and (type(grade) is not str or grade not in GRADE_VALUES):
        return _one("REC-38", "-", "grade %s is not a grade" % _show(cj(grade)))
    r = record
    st = r.get("STATUS")
    if type(st) is str and st in ("HELD", "REFUTED"):
        hw, w = True, st
    elif grade is not None:
        hw, w = True, grade
    elif "GRADE" in r:
        hw, w = True, r["GRADE"]
    else:
        hw, w = False, None
    o = {}

    def put(k, src):
        if src in r:
            o[k] = r[src]

    def steps():
        d = r.get("DERIVATION")
        if type(d) is dict and "STEPS" in d:
            o["steps"] = d["STEPS"]

    tpx_corr = r.get("CLASS") == "CORROBORATION" and r.get("LANDED_BY") == "TPX"
    if view == "HOLD":
        put("id", "ID")
        put("block_id", "BLOCK")
        if tpx_corr:
            o["author"] = "TPX"
        else:
            put("author", "AUTHOR")
        put("cites", "CITES")
        put("assumptions", "ASSUMPTIONS")
        put("kind", "CLASS")
        if hw:
            o["status"] = w
        if tpx_corr and "AUTHOR" in r:
            o["supplied_by"] = r["AUTHOR"]
        if "LANDED_BY" in r and r["LANDED_BY"] != "TPX" and ("AUTHOR" not in r or r["LANDED_BY"] != r["AUTHOR"]):
            o["vetted_by"] = r["LANDED_BY"]
        put("vetted_in", "VETTED_IN")
    elif view == "STRIKE":
        put("assumptions", "ASSUMPTIONS")
        put("cites", "CITES")
        steps()
    elif view == "SUITE":
        put("regime", "REGIME")
        put("assumptions", "ASSUMPTIONS")
        put("cites", "CITES")
        steps()
        put("domain", "DOMAIN")
    elif view == "PROBE_L":
        if "EQUATION" in r:
            o["finding"] = {"type": "EQUATION", "value": r["EQUATION"]}
        elif "VALUE" in r:
            o["finding"] = {"type": "DECIMAL", "value": r["VALUE"]}
        elif "STATEMENT" in r:
            o["finding"] = {"type": "TEXT", "value": r["STATEMENT"]}
    elif view == "PROBE_M":
        put("x", "VALUE")
        put("u", "U_C")
        put("unit", "UNIT")
    elif view == "PROBE_LOAD":
        put("cites", "CITES")
        put("assumptions", "ASSUMPTIONS")
        if hw:
            o["standing"] = w in ("PROVISIONAL", "ESTABLISHED")
    elif view == "LITERATURE":
        put("id", "ID")
        if hw:
            o["status"] = w
        o["literature"] = r["LITERATURE"] if "LITERATURE" in r else []
    else:                                                    # EOM
        put("ID", "ID")
        if hw:
            o["STATUS"] = w
        put("ROLE", "ROLE")
        put("EQUATION", "EQUATION")
    return ["OK", o]


# ---- schema_json (schema 11; non-normative, RD-PT-15) ---------------------------------
_DESC = ("Generated by TPC tpc_record schema_json from tpm_record_schema " + SCHEMA_TEXT_VERSION
         + ". Non-normative: the table governs and TPC record_check decides; a validator run on "
           "this document can pass a record TPX refuses.")
_QUIET = ("NOTBLANK", "KEY", "NONEMPTY", "SETTING", "TPKNAME", "TPKDOF", "TRMENTRY", "DTFK",
          "DIGEST", "T2", "GRADE", "STATUS")
_TYPE_FN = {"FORM": "tpc_form parse_form", "PREDICATE": "tpc_form parse_form",
            "DECIMAL": "tpc_zone canon_decimal", "UNIT": "tpc_units unit_parse",
            "DIMENSION": "tpc_zone normalize_dimension"}


def _desc(f):
    t, cons = f["type"], f["cons"]
    d = "%s (%s), written by %s" % (t, f["card"], f["writer"])
    fn = _TYPE_FN.get(t)
    if fn is None:
        other = [c for c in cons if c not in _QUIET and not c.startswith("ENUM=")
                 and not c.startswith("SUB=") and not c.startswith("DIM=") and not c.startswith("ZONE=")]
        if other or t in ("RID", "IDSET") or "ROLE" in cons:
            fn = "tpc_record record_check"
    if fn is not None:
        d += "; checked by TPC " + fn
    return d + "."


def _fs(cls, f):
    t, card, cons = f["type"], f["card"], f["cons"]
    D = _desc(f)
    if card == "SET":
        return {"type": "array", "items": {"type": "string"}, "uniqueItems": True, "description": D}
    if t == "OBJECT":
        ref = "#/$defs/" + _cons_value(f, "SUB=")
        if card == "ONE":
            return {"$ref": ref, "description": D}
        return {"type": "array", "items": {"$ref": ref}, "description": D}
    if t == "ENUM" and "ROLE" not in cons:
        if "GRADE" in cons:                  # 11.3 (S100; D215.2): the class's LANDGRADE alone
            return {"enum": [T["landgrade"][cls]], "description": D}
        if "STATUS" in cons:
            return {"enum": list(T["statuses"][cls]), "description": D}
        return {"enum": list(T["enums"][_cons_value(f, "ENUM=")]), "description": D}
    if t == "INT":
        return {"type": "integer", "minimum": 1, "maximum": 1000000, "description": D}
    if t == "BOOL":
        return {"type": "boolean", "description": D}
    return {"type": "string", "description": D}


def schema_json():
    """["OK", the JSON Schema 2020-12 document generated from the table]."""
    branches = []
    for c in KEYED:
        fl = T["fields"][c]
        props = {}
        for f in fl:
            props[f["name"]] = _fs(c, f)
        branches.append({"if": {"properties": {"CLASS": {"const": c}}, "required": ["CLASS"]},
                         "then": {"type": "object", "properties": props,
                                  "required": [f["name"] for f in fl
                                               if f["req"] == "R" and f["writer"] in ("AUTHOR", "LEAD")],
                                  "additionalProperties": False}})
    defs = {}
    for s in T["subs"]:
        fl = T["fields"]["SUB:" + s]
        props = {}
        for f in fl:
            props[f["name"]] = _fs(None, f)
        defs[s] = {"type": "object", "properties": props,
                   "required": [f["name"] for f in fl if f["req"] == "R"],
                   "additionalProperties": False}
    doc = {"$schema": "https://json-schema.org/draft/2020-12/schema",
           "title": "TPM register record", "description": _DESC, "type": "object",
           "required": ["CLASS"], "properties": {"CLASS": {"enum": list(KEYED)}},
           "allOf": branches, "$defs": defs}
    return ["OK", cj(doc)]


# =========================================================================================
# tpm_record_functions 0.3.0
# =========================================================================================
def _v(v):
    """<v> of an argument error: a string shown as given, any other value as its
    canonical JSON (RD-TQ-31)."""
    return _show(v) if type(v) is str else _show(cj(v))


def _vj(v):
    """<v> as REC-36's: the value's canonical JSON, shown."""
    return _show(cj(v))


def _bad_arg(argument, value, what):
    return _one("REC-50", "-", "%s %s is not %s" % (argument, _vj(value), what))


RANK = {"REFUTED": 3, "WITHDRAWN": 2, "ESTABLISHED": 1}
LIST_CLASSES = ("HOLD", "RELEASE", "WITHDRAWAL", "PROMOTION")


def _pairs_ok(v):
    return type(v) is list and all(type(p) is list and len(p) == 2 and type(p[0]) is str
                                   and type(p[1]) is str for p in v)


def _register(register, argument="register"):
    """(keyed {id: record} in order, list-form records) or a REC-50 refusal (0.3)."""
    bad = _bad_arg(argument, register, "a register")
    if type(register) is not list:
        return None, None, bad
    keyed, lists = {}, []
    for r in register:
        if type(r) is dict:
            if r.get("CLASS") not in KEYED or type(r.get("ID")) is not str or r["ID"] in keyed \
                    or r.get("GRADE") not in GRADE_VALUES:
                return None, None, bad
            if "STATUS" in r and type(r["STATUS"]) is not str:
                return None, None, bad
            keyed[r["ID"]] = r
        elif type(r) is list:
            if len(r) < 2 or r[1] not in LIST_CLASSES:
                return None, None, bad
            if r[1] == "WITHDRAWAL" and not (len(r) == 14 and type(r[3]) is str and r[5] in ("REFUTED", "WITHDRAWN")
                                             and _pairs_ok(r[10])):
                return None, None, bad
            if r[1] == "PROMOTION" and not (len(r) == 5 and type(r[3]) is list and all(
                    type(t) is list and len(t) == 3 and type(t[0]) is str for t in r[3])):
                return None, None, bad
            lists.append(r)
        else:
            return None, None, bad
    return keyed, lists, None


def _grade_now(keyed, lists, rid):
    g0 = keyed[rid]["GRADE"]
    if g0 == "NOT GRADED":
        return g0
    best = g0
    for r in lists:
        given = []
        if r[1] == "WITHDRAWAL":
            if r[3] == rid:
                given.append(r[5])
            if rid in [p[0] for p in r[10]]:
                given.append("WITHDRAWN")
        elif r[1] == "PROMOTION":
            if rid in [t[0] for t in r[3]]:
                given.append("ESTABLISHED")
        for g in given:
            if RANK.get(g, 0) > RANK.get(best, 0):
                best = g
    return best


def _words(keyed, lists):
    out = {}
    for rid, r in keyed.items():
        st = r.get("STATUS")
        out[rid] = st if st in ("HELD", "REFUTED") else _grade_now(keyed, lists, rid)
    return out


def _standing(w):
    return w in ("PROVISIONAL", "ESTABLISHED")


def _class_arg(c):
    if type(c) is not str or c not in T["classes"]:
        return _one("REC-51", "-", "class %s is not a record class" % _vj(c))
    return None


# ---- 4 grade and status ----------------------------------------------------------------
def grade_values(cls):
    r = _class_arg(cls)
    return r if r else ["OK", list(T["grades"][cls])]


def status_values(cls):
    r = _class_arg(cls)
    return r if r else ["OK", list(T["statuses"].get(cls, []))]


def grade_now(register, rid):
    keyed, lists, bad = _register(register)
    if bad:
        return bad
    if type(rid) is not str or rid not in keyed:
        return _one("REC-52", "-", "record %s is not in the register" % _v(rid))
    return ["OK", _grade_now(keyed, lists, rid)]


def status_words(register):
    keyed, lists, bad = _register(register)
    if bad:
        return bad
    return ["OK", _words(keyed, lists)]


def is_standing(word):
    if type(word) is not str or word not in STATUS_WORDS:
        return _one("REC-53", "-", "word %s is not a status word" % _v(word))
    return ["OK", _standing(word)]


def status_word(grade, status):
    if type(grade) is not str or grade not in GRADE_VALUES:
        return _one("REC-38", "-", "grade %s is not a grade" % _vj(grade))
    if status is not None and (type(status) is not str or status not in STATUS_VALUES):
        return _one("REC-53", "-", "word %s is not a status word" % _v(status))
    return ["OK", status if status in ("HELD", "REFUTED") else grade]


def grade_next(cls, grade, event):
    r = _class_arg(cls)
    if r:
        return r
    if type(grade) is not str or grade not in GRADE_VALUES:
        return _one("REC-38", "-", "grade %s is not a grade" % _vj(grade))
    if type(event) is not str or event not in ("PROMOTE", "STRIKE", "WITHDRAW"):
        return _one("REC-55", "-", "event %s is not PROMOTE, STRIKE or WITHDRAW" % _v(event))
    grades = T["grades"][cls]
    out = None
    if event == "PROMOTE" and cls == "RESULT" and grade == "PROVISIONAL":
        out = "ESTABLISHED"
    elif event == "STRIKE" and cls in ("RESULT", "CORROBORATION") and grade in ("PROVISIONAL", "ESTABLISHED") \
            and grade in grades:
        out = "REFUTED"
    elif event == "WITHDRAW" and grades != ["NOT GRADED"] and grade in ("PROVISIONAL", "ESTABLISHED") \
            and grade in grades:
        out = "WITHDRAWN"
    if out is None:
        return _one("REC-54", "-", "%s does not apply to %s %s of grade %s" % (event, _art(cls), cls, grade))
    return ["OK", out]


def _art(cls):
    """REC-54's article (F-GV2-11): "an" before ASSUMPTION and ENTRY_STATUS, "a" before
    every other class of the table."""
    return "an" if cls in ("ASSUMPTION", "ENTRY_STATUS") else "a"


def status_next(cls, status, event):
    """functions 4.7 (D214.1)."""
    r = _class_arg(cls)
    if r:
        return r
    statuses = T["statuses"].get(cls, [])
    if not statuses:
        return _one("REC-56", "-", "%s carries no STATUS" % cls)
    ok_event = type(event) is str and (event in ("HOLD", "STRIKE") or (
        event.startswith("RELEASE:") and event[len("RELEASE:"):] in STATUS_WORDS))
    if not ok_event:
        return _one("REC-55", "-", "event %s is not HOLD, STRIKE or RELEASE:<word>" % _v(event))
    out = None
    if type(status) is str and status in statuses:
        if event == "HOLD":
            out = "REFUTED" if status == "REFUTED" else "HELD"
        elif event == "STRIKE":
            if cls in ("RESULT", "CORROBORATION") and status in ("UNMARKED", "HELD"):
                out = "REFUTED"
        else:
            w = event[len("RELEASE:"):]
            out = w if w in ("HELD", "REFUTED") else "UNMARKED"
            if out not in statuses:
                out = None
    if out is None:
        return _one("REC-54", "-", "%s does not apply to %s %s of STATUS %s" % (event, _art(cls), cls, _v(status)))
    return ["OK", out]


def inherit_grade(cls, grade):
    """functions 4.8 (D215.2): the grade the inherited record holds once the copy is
    written; its landing GRADE is its class's LANDGRADE in every case (RD-RS-50)."""
    r = _class_arg(cls)
    if r:
        return r
    if type(grade) is not str or grade not in GRADE_VALUES:
        return _one("REC-38", "-", "grade %s is not a grade" % _vj(grade))
    if T["grades"][cls] == ["NOT GRADED"]:
        return ["OK", "NOT GRADED"]
    return ["OK", grade if grade in ("REFUTED", "WITHDRAWN") else "PROVISIONAL"]


# ---- 5 citations --------------------------------------------------------------------------
def _members(v):
    return [x for x in v if type(x) is str] if type(v) is list else []


def citation_check(record, register, entries, supersede_decided):
    keyed, lists, bad = _register(register)
    if bad:
        return bad
    if type(entries) is not list or not all(_m(_BIB, x) for x in entries):
        return _bad_arg("entries", entries, "a list of BIB IDs")
    if type(supersede_decided) is not bool:
        return _bad_arg("supersede_decided", supersede_decided, "a boolean")
    if type(record) is not dict:
        return _one("REC-01", "-", "the record is not an object")
    words = _words(keyed, lists)
    p = []
    if type(record.get("ID")) is str and record["ID"] in keyed:      # an ID that is not a string: not looked up (PHP's is_string)
        p.append(["REC-57", "ID", "ID %s is already in the register" % _show(record["ID"])])
    for field, want, article in (("CITES", "RESULT", "a RESULT"), ("ASSUMPTIONS", "ASSUMPTION", "an ASSUMPTION")):
        for x in _members(record.get(field)):
            if x not in keyed or keyed[x]["CLASS"] != want:
                p.append(["REC-58", field, "%s member %s is not %s of the register" % (field, _show(x), article)])
                break
            if not _standing(words[x]):
                p.append(["REC-59", field, "%s member %s does not stand (%s)" % (field, _show(x), words[x])])
                break
    cls = record.get("CLASS")
    if cls in ("CORROBORATION", "VERIFICATION") and type(record.get("TARGET")) is str:
        t = record["TARGET"]
        if t not in keyed or keyed[t]["CLASS"] != "RESULT":
            p.append(["REC-58", "TARGET", "TARGET %s is not a RESULT of the register" % _show(t)])
        elif words[t] not in ("PROVISIONAL", "ESTABLISHED", "HELD"):
            p.append(["REC-59", "TARGET", "TARGET %s does not stand (%s)" % (_show(t), words[t])])
    if type(record.get("SUPERSEDES")) is str:
        s = record["SUPERSEDES"]
        if s not in keyed or keyed[s]["CLASS"] != cls:
            p.append(["REC-58", "SUPERSEDES", "SUPERSEDES %s is not a %s of the register" % (_show(s), _v(cls))])
        elif not _standing(words[s]):
            p.append(["REC-59", "SUPERSEDES", "SUPERSEDES %s does not stand (%s)" % (_show(s), words[s])])
        elif not supersede_decided and keyed[s].get("AUTHOR") != record.get("AUTHOR"):
            p.append(["REC-60", "SUPERSEDES", "SUPERSEDES %s was authored by %s, not %s"
                      % (_show(s), _v(keyed[s].get("AUTHOR")), _v(record.get("AUTHOR")))])
    for b in _members(record.get("LITERATURE")):
        if b not in entries:
            p.append(["REC-61", "LITERATURE", "LITERATURE member %s is not a landed entry of the UWC" % _show(b)])
            break
    return _refused(p) if p else ["OK"]


# ---- 6 the withdrawal -------------------------------------------------------------------------
def _rid_list(v):
    return type(v) is list and all(_m(_RID, x) for x in v) and len(set(v)) == len(v)


def withdrawal_scope(register, root, still_stands):
    keyed, lists, bad = _register(register)
    if bad:
        return bad
    if type(root) is not str or root not in keyed:
        return _one("REC-52", "-", "record %s is not in the register" % _v(root))
    if not _rid_list(still_stands):
        return _bad_arg("still_stands", still_stands, "a list of record identifiers")
    ss = set(still_stands)
    reached, frontier = set(), [root]
    while frontier:
        cur = frontier.pop()
        for rid, r in keyed.items():
            if rid in reached or rid == root:
                continue
            if cur in _members(r.get("CITES")) or cur in _members(r.get("ASSUMPTIONS")):
                reached.add(rid)
                if rid not in ss:
                    frontier.append(rid)
    probs = [["REC-62", "-", "STILL STANDS member %s is not reached by the withdrawal" % x]
             for x in still_stands if x not in reached]
    if probs:
        return _refused(probs)
    words = _words(keyed, lists)
    casc = [[rid, words[rid]] for rid in sorted(reached - ss)
            if _grade_now(keyed, lists, rid) in ("PROVISIONAL", "ESTABLISHED")]
    return ["OK", casc]


ACT_KEYS = ["cause", "withdrawn", "withdrawn_by", "status_set", "prior_status", "superseded_by", "locus_gap",
            "still_stands", "cascade", "tps_version", "d_tms"]
CAUSES = ["SR1", "SR5", "AUDIT", "AUTHOR", "SUPERSEDED", "INHERITED"]
CAUSE_SETS = {"SR1": ["REFUTED"], "SR5": ["REFUTED"], "AUTHOR": ["WITHDRAWN"], "SUPERSEDED": ["WITHDRAWN"],
              "AUDIT": ["REFUTED", "WITHDRAWN"], "INHERITED": ["REFUTED", "WITHDRAWN"]}


def _pair_list_ok(v):
    if not _pairs_ok(v):
        return False
    ids = [q[0] for q in v]
    return all(_m(_RID, i) for i in ids) and len(set(ids)) == len(ids) and ids == sorted(ids) \
        and all(q[1] in STATUS_WORDS for q in v)


def _act(act):
    """The act's problems (REC-63 alone, or REC-64 per key and REC-65)."""
    if type(act) is not dict or sorted(act) != sorted(ACT_KEYS):
        return [["REC-63", "-", "act is not a withdrawal act"]]
    p = []

    def bad(key, form):
        p.append(["REC-64", key, "%s %s is not %s" % (key, _v(act[key]), form)])

    cause = act["cause"]
    cause_ok = cause in CAUSES
    if not cause_ok:
        bad("cause", "one of SR1, SR5, AUDIT, AUTHOR, SUPERSEDED or INHERITED")
    if not _m(_RID, act["withdrawn"]):
        bad("withdrawn", "a record identifier")
    wb = act["withdrawn_by"]
    if type(wb) is not str or Z._first_bad(wb) is not None or Z._blank(wb):
        bad("withdrawn_by", "a text")
    if cause_ok and act["status_set"] not in CAUSE_SETS[cause]:
        bad("status_set", "the grade cause %s sets" % cause)
    if cause == "INHERITED":
        if act["prior_status"] != "PROVISIONAL":
            bad("prior_status", "PROVISIONAL")
    elif act["prior_status"] not in ("PROVISIONAL", "ESTABLISHED", "HELD"):
        bad("prior_status", "a status word of a record that stands or is held")
    if cause == "SUPERSEDED":
        if not _m(_RID, act["superseded_by"]):
            bad("superseded_by", "a record identifier")
    elif act["superseded_by"] != "":
        bad("superseded_by", "empty")
    if cause == "SR5":
        if act["locus_gap"] != "" and not _m(_RID, act["locus_gap"]):
            bad("locus_gap", "a record identifier")
    elif act["locus_gap"] != "":
        bad("locus_gap", "empty")
    if cause == "AUDIT":
        if not _pair_list_ok(act["still_stands"]):
            bad("still_stands", "a sorted list of [id, word] pairs")
    elif act["still_stands"] != []:
        bad("still_stands", "empty")
    if cause == "INHERITED":
        if act["cascade"] != []:
            bad("cascade", "empty")
    elif not _pair_list_ok(act["cascade"]):
        bad("cascade", "a sorted list of [id, word] pairs")
    if not _m(_VERSION, act["tps_version"]):
        bad("tps_version", "a version n.n.n")
    if not _m(_HEX64, act["d_tms"]):
        bad("d_tms", "64 lowercase hexadecimal characters")
    if not p:
        ss = [q[0] for q in act["still_stands"]]
        cs = [q[0] for q in act["cascade"]]
        for i in sorted(set([act["withdrawn"]] + ss + cs)):
            if ([act["withdrawn"]] + ss + cs).count(i) > 1:
                p.append(["REC-65", "-", "%s is named twice" % i])
    return p


def _act_fields(act):
    f = ["TPC-WITHDRAW", "1", act["cause"], act["withdrawn"], act["withdrawn_by"], act["status_set"],
         act["prior_status"], act["superseded_by"], act["locus_gap"], len(act["still_stands"])]
    for i, w in act["still_stands"]:
        f += [i, w]
    f.append(len(act["cascade"]))
    for i, w in act["cascade"]:
        f += [i, w]
    return f + [act["tps_version"], act["d_tms"]]


def withdrawal_preimage(act):
    p = _act(act)
    if p:
        return _refused(p)
    return ["OK", _act_fields(act)]


def withdrawal_record(withdrawal_id, act):
    p = _act(act)
    if p and p[0][0] == "REC-63":
        return _refused(p)
    if not _m(_RID, withdrawal_id):
        p.append(["REC-64", "withdrawal_id", "withdrawal_id %s is not a record identifier" % _v(withdrawal_id)])
    if p:
        return _refused(p)
    return ["OK", [withdrawal_id, "WITHDRAWAL", act["cause"], act["withdrawn"], act["withdrawn_by"],
                   act["status_set"], act["prior_status"], act["superseded_by"], act["locus_gap"],
                   act["still_stands"], act["cascade"], act["tps_version"], act["d_tms"],
                   G.digest_fields(_act_fields(act))]]


# ---- 2, 7 exact decimal arithmetic and budget_check -------------------------------------
def _nat_add(a, b):
    a, b = a[::-1], b[::-1]
    out, carry = [], 0
    for i in range(max(len(a), len(b))):
        s = carry + (ord(a[i]) - 48 if i < len(a) else 0) + (ord(b[i]) - 48 if i < len(b) else 0)
        out.append(chr(48 + s % 10))
        carry = s // 10
    if carry:
        out.append(chr(48 + carry))
    r = "".join(reversed(out)).lstrip("0")
    return r if r else "0"


def _nat_mul(a, b):
    if a == "0" or b == "0":
        return "0"
    res = [0] * (len(a) + len(b))
    for i in range(len(a) - 1, -1, -1):
        for j in range(len(b) - 1, -1, -1):
            res[i + j + 1] += (ord(a[i]) - 48) * (ord(b[j]) - 48)
        for k in range(len(res) - 1, 0, -1):     # carry after each row keeps every cell small
            if res[k] >= 10:
                res[k - 1] += res[k] // 10
                res[k] %= 10
    r = "".join(chr(48 + d) for d in res).lstrip("0")
    return r if r else "0"


def _nat_dec1(a):
    d = list(a)
    i = len(d) - 1
    while d[i] == "0":
        d[i] = "9"
        i -= 1
    d[i] = chr(ord(d[i]) - 1)
    r = "".join(d).lstrip("0")
    return r if r else "0"


def _nat_cmp(a, b):
    if len(a) != len(b):
        return -1 if len(a) < len(b) else 1
    return -1 if a < b else (1 if a > b else 0)


def _nat_shift(a, k):
    return a if a == "0" else a + "0" * k


def _mexp(c):
    """A canonical decimal's (m, e); the sign is dropped (callers pass values
    that are not negative)."""
    c = c[1:] if c.startswith("-") else c
    if "e" in c:
        m, e = c.split("e")
        return m, int(e)
    return c, 0


def _m_result(record):
    """An M RESULT whose measured fields budget_check reads."""
    if type(record) is not dict or record.get("CLASS") != "RESULT" or record.get("REGIME") != "M":
        return False
    u_c = Z.canon_decimal(record.get("U_C"))
    if u_c is None or u_c == "0" or u_c.startswith("-") or U.unit_parse(record.get("UNIT"))[0] != "OK":
        return False
    b = record.get("BUDGET")
    if type(b) is not list or not b:
        return False
    for c in b:
        if type(c) is not dict or U.unit_parse(c.get("UNIT"))[0] != "OK":
            return False
        cu = Z.canon_decimal(c.get("U"))
        if cu is None or cu.startswith("-"):
            return False
    return True


def budget_check(record):
    if not _m_result(record):
        return _one("REC-66", "-", "budget_check reads an M RESULT")
    if record.get("CORRELATED") is True:
        return ["INSPECT", "BUDGET is correlated: its consistency is INSPECTION"]
    ru = U.unit_parse(record["UNIT"])
    rtok = U._tokens(record["UNIT"])
    pre = dict(U.PREFIXES)
    first_dim, first_conv, first_insp, shifts = None, None, None, []
    for i, c in enumerate(record["BUDGET"], 1):
        cu = U.unit_parse(c["UNIT"])
        if cu[2] != ru[2]:
            if first_dim is None:
                first_dim = _one("REC-67", "BUDGET[%d].UNIT" % i, "BUDGET[%d].UNIT %s has dimension %s, not %s"
                                 % (i, _show(c["UNIT"]), cu[2], ru[2]))
            shifts.append(None)
            continue
        if cu[3] != ru[3]:
            if first_conv is None:
                first_conv = ["CONVENTION", "units of different kind: %s and %s" % (cu[3], ru[3])]
            shifts.append(None)
            continue
        ctok = U._tokens(c["UNIT"])
        if len(ctok) != len(rtok) or any(a[1] != b[1] or a[2] != b[2] for a, b in zip(ctok, rtok)):
            if first_insp is None:
                first_insp = ["INSPECT", "BUDGET[%d].UNIT %s is not a power of ten of UNIT %s"
                              % (i, _show(c["UNIT"]), _show(record["UNIT"]))]
            shifts.append(None)
            continue
        shifts.append(sum(((pre[a[0]] if a[0] else 0) - (pre[b[0]] if b[0] else 0)) * a[2]
                          for a, b in zip(ctok, rtok)))
    for outcome in (first_dim, first_conv, first_insp):
        if outcome is not None:
            return outcome
    mc, ec = _mexp(prec_decimal(record["U_C"])[1])       # U_C at its last written digit
    d1 = _nat_dec1(mc)
    lowf = "5" if d1 == "0" else d1 + "5"
    low, high = _nat_mul(lowf, lowf), _nat_mul(mc + "5", mc + "5")
    eb = 2 * ec - 2
    terms = []
    for c, s in zip(record["BUDGET"], shifts):
        m, e = _mexp(Z.canon_decimal(c["U"]))
        if m != "0":
            terms.append((_nat_mul(m, m), 2 * (e + s)))
    exps = [eb] + [t[1] for t in terms]
    b = min(exps)
    if max(exps) - b > 20000:
        return ["INSPECT", "BUDGET spans more than 20000 decimal digits"]
    total = "0"
    for m2, e in terms:
        total = _nat_add(total, _nat_shift(m2, e - b))
    if _nat_cmp(_nat_shift(low, eb - b), total) <= 0 and _nat_cmp(total, _nat_shift(high, eb - b)) <= 0:
        return ["OK"]
    return _one("REC-68", "U_C", "U_C %s is not the root sum of squares of BUDGET at its last digit"
                % _show(record["U_C"]))


def _cd(x):
    return Z.canon_decimal(S.canon(x))


def prec_decimal(s):
    """D218.1 (functions 0.3.0 7.3): s of tpc_zone's DECIMAL grammar read as sign,
    integer digits I, fraction digits F and exponent X; D = I then F, leading zeros
    removed; "0" when D is empty, else sign, D (trailing zeros kept) and "e" X - len(F)
    when that is not 0. The value canon_decimal(s) names, at s's last written digit."""
    m = Z._DECIMAL.match(s) if type(s) is str else None
    if not m:
        return _bad_arg("s", s, "a decimal string")
    sign, ip, fp, ex = m.group(1), m.group(2), m.group(3) or "", m.group(4)
    d = (ip + fp).lstrip("0")
    if d == "":
        return ["OK", "0"]
    x = (int(ex) if ex else 0) - len(fp)
    return ["OK", sign + d + ("e" + str(x) if x != 0 else "")]


def field_values(record):
    if type(record) is not dict or record.get("CLASS") != "RESULT":
        return _one("REC-69", "-", "field_values reads a RESULT")
    if record.get("REGIME") != "M":
        return ["OK", {"U_C": "0"}]
    n = U.normalize_measurement(record.get("VALUE"), record.get("U_C"), record.get("UNIT"))
    if n[0] != "OK":
        return _one("REC-70", "U_C", "U_C: %s" % n[1])
    return ["OK", {"U_C": _cd(n[2])}]


# ---- 8 the parrot comparison's pair ---------------------------------------------------------
def _l_result(r):
    return type(r) is dict and r.get("CLASS") == "RESULT" and r.get("REGIME") == "L"


def l_finding_pair(original, reproduction):
    if not (_l_result(original) and _l_result(reproduction)):
        return _one("REC-71", "-", "l_finding_pair reads two L RESULTs")
    pair = (original, reproduction)
    if all(type(x.get("EQUATION")) is str for x in pair):
        out = []
        for x in pair:
            f = F.parse_form(x["EQUATION"], "equation")
            if f[0] != "OK":
                return _one("REC-71", "-", "l_finding_pair reads two L RESULTs")
            out.append({"finding": {"type": "EQUATION", "value": f[1]}})
        return ["OK"] + out
    if all("VALUE" in x and "UNIT" in x for x in pair):
        a = U.normalize_measurement(original["VALUE"], "0", original["UNIT"])
        b = U.normalize_measurement(reproduction["VALUE"], "0", reproduction["UNIT"])
        if a[0] != "OK" or b[0] != "OK":
            return _one("REC-71", "-", "l_finding_pair reads two L RESULTs")
        if a[3] != b[3]:
            return _one("REC-72", "-", "units of different dimension: %s and %s" % (a[3], b[3]))
        if a[4] != b[4]:
            return ["CONVENTION", "units of different kind: %s and %s" % (a[4], b[4])]
        return ["OK", {"finding": {"type": "DECIMAL", "value": _cd(a[1])}},
                {"finding": {"type": "DECIMAL", "value": _cd(b[1])}}]
    if not all(type(x.get("STATEMENT")) is str for x in pair):
        return _one("REC-71", "-", "l_finding_pair reads two L RESULTs")
    return ["OK"] + [{"finding": {"type": "TEXT", "value": Z._text(x["STATEMENT"])}} for x in pair]


# ---- 11 the declared form on a record -----------------------------------------------------
def _tpk_dims(tfk):
    tpk = tfk["TPK"]
    dims = {}
    for kind in ("DOF", "CONSTANT"):
        for e in tpk.get(kind, []):
            dims[e["NAME"]] = e.get("DIMENSION")
    return dims


def _dofs(tfk):
    return [e["NAME"] for e in tfk["TPK"].get("DOF", [])]


def _names(text, mode):
    r = F.form_names(text, mode) if type(text) is str else ["REFUSED", ""]
    return r[1] if r[0] == "OK" else []


def record_form_check(record, tfk):
    if type(record) is not dict or record.get("CLASS") not in ("RESULT", "ASSUMPTION"):
        return _one("REC-75", "-", "record_form_check reads a RESULT or an ASSUMPTION")
    if tfk is None or not _tfk_ok(tfk):
        return _one("REC-39", "-", "the TFK is not readable")
    if "EQUATION" not in record:
        return ["OK"]
    eq = record["EQUATION"]
    refs = _members(record.get("REFS"))
    en = _names(eq, "equation")
    dn = _names(record.get("DOMAIN_CONDITION"), "predicate") if "DOMAIN_CONDITION" in record else []
    p = []
    for n in en:
        if n not in refs:
            p.append(["REC-76", "EQUATION", "EQUATION names %s, which is not in REFS" % n])
    for n in refs:
        if n not in en and n not in dn:
            p.append(["REC-77", "REFS", "REFS %s is named in neither EQUATION nor DOMAIN_CONDITION" % _show(n)])
    if p:
        return _refused(p)
    r = F.form_dimension(eq, "equation", _tpk_dims(tfk))
    if r[0] != "OK":
        return _one("REC-78", "EQUATION", "EQUATION %s (T4)" % r[1])
    tree = F.form_tree(eq, "equation")[1]
    dv, stack = set(), [tree]
    while stack:
        n = stack.pop()
        if type(n) is list:
            if n and n[0] == "D":
                dv.add(n[2])
            stack.extend(n)
    dofs = _dofs(tfk)
    cons = tfk["TPK"].get("CONSTRAINT", [])
    if type(cons) is not list:                    # not a list: none read, as TPC-PHP reads it
        cons = []
    for s in sorted(dv):
        if s not in dofs:
            p.append(["REC-79", "EQUATION", "EQUATION's D variable %s is not a DOF" % s])
        for c in sorted(c.get("NAME") for c in cons if type(c) is dict and c.get("TARGET") == s
                        and type(c.get("NAME")) is str):
            p.append(["REC-79", "EQUATION", "EQUATION's D variable %s is the TARGET of CONSTRAINT:%s" % (s, c)])
    params = tfk["TPK"].get("PARAMETERS")
    one = not (type(params) is dict and params.get("D_VARIABLES") == "SEVERAL")
    tdv = F.d_variables(tfk["TPK"])
    if one and len(set(tdv) | dv) > 1:
        p.append(["REC-80", "EQUATION", "EQUATION's D variables %s and the TPK's %s are more than one under "
                  "D_VARIABLES ONE" % (", ".join(sorted(dv)) or "none", ", ".join(tdv) or "none")])
    if p:
        return _refused(p)
    role = record.get("ROLE")
    m = _EOM.match(role) if type(role) is str else None
    if m and m.group(1) not in dofs:
        return _one("REC-81", "ROLE", "ROLE EOM(%s): %s is not a DOF of the TPK" % (m.group(1), m.group(1)))
    return ["OK"]


def domain_check(record, tfk):
    if type(record) is not dict:
        return _one("REC-01", "-", "the record is not an object")
    if tfk is None or not _tfk_ok(tfk):
        return _one("REC-39", "-", "the TFK is not readable")
    if "DOMAIN_CONDITION" not in record:
        return ["OK"]
    dc = record["DOMAIN_CONDITION"]
    refs = _members(record.get("REFS"))
    dn = _names(dc, "predicate")
    p = []
    for n in dn:
        if n not in refs:
            p.append(["REC-82", "DOMAIN_CONDITION", "DOMAIN_CONDITION names %s, which is not in REFS" % n])
    if "EQUATION" not in record:
        for n in refs:
            if n not in dn:
                p.append(["REC-77", "REFS", "REFS %s is named in neither EQUATION nor DOMAIN_CONDITION" % _show(n)])
    if p:
        return _refused(p)
    r = F.form_totality(dc, _tpk_dims(tfk))
    if r[0] != "OK":
        return _one("REC-83", "DOMAIN_CONDITION", "DOMAIN_CONDITION %s (T6)" % r[1])
    p = [["REC-83", "DOMAIN_CONDITION", "DOMAIN_CONDITION %s (T6)" % q] for q in r[1]]
    return _refused(p) if p else ["OK"]


# ---- 13 named gaps ---------------------------------------------------------------------------
GAP_TEMPLATES = {"IC2": "The DOF {d} is tied by no TrM entry (IC2).",
                 "UNDER": "The unknown DOF {d} is determined by no form (UNDER).",
                 "SR5_LOCUS": "The ASSUMPTIONS {ids} lie outside the closer record's closure in the withdrawal {t} (SR5).",
                 "SR7_ASSUMPTIONS": "The ASSUMPTIONS {ids} of an L record are contradicted by {t} (SR7).",
                 "SR7_DOMAIN": "{t} contradicts an L record whose a(r) is 0: a finding against the TFK DOMAIN (SR7)."}


def _gap(rid, source, against, trigger, landed, tps):
    r = {"ID": rid, "CLASS": "NAMED_GAP", "GRADE": "NOT GRADED", "LANDED_BY": "TPX", "LANDED": landed,
         "TPS": tps, "SOURCE": source, "AGAINST": sorted(against) if _members(against) == against else against,
         "TRIGGER": trigger, "STATE": "OPEN"}
    if type(source) is str and source in GAP_TEMPLATES and type(trigger) is str and _members(against) == against:
        ids = ", ".join(sorted(against))
        r["STATEMENT"] = GAP_TEMPLATES[source].replace("{d}", ids).replace("{ids}", ids).replace("{t}", trigger)
    c = record_check(r, None, None, "LANDED")
    if c[0] != "OK":
        return c
    r["DIGEST"] = record_digest(r)[1]
    return ["OK", r]


def _ident_list(v):
    return type(v) is list and all(_m(_IDENT, x) for x in v)


def gap_records(prefix, ic2, under, trigger, landed, tps):
    for name, v in (("ic2", ic2), ("under", under)):
        if not _ident_list(v):
            return _bad_arg(name, v, "a list of identifiers")
    if not (type(landed) is str and _utc_ok(landed)):
        return _bad_arg("landed", landed, "a UTC time")
    if not _m(_VERSION, tps):
        return _bad_arg("tps", tps, "a version n.n.n")
    p = []
    for name, v in (("ic2", ic2), ("under", under)):
        for d in sorted(set(v)):
            if v.count(d) > 1:
                p.append(["REC-89", "-", "%s repeats %s" % (name, d)])
    if p:
        return _refused(p)
    out, n = [], 2
    for src, lst in (("IC2", ic2), ("UNDER", under)):
        for d in sorted(lst):
            g = _gap("%s-%d" % (prefix if type(prefix) is str else "", n), src, [d], trigger, landed, tps)
            if g[0] != "OK":
                return g
            out.append(g[1])
            n += 1
    return ["OK", out]


def gap_record(rid, source, against, trigger, landed, tps):
    return _gap(rid, source, against, trigger, landed, tps)


def _closure(keyed, rid):
    """The ASSUMPTIONS closure of rid: its own and those of every record it
    CITES, transitively; identifiers outside the register ignored."""
    acc, seen, stack = set(), set(), [rid]
    while stack:
        cur = stack.pop()
        if cur in seen or cur not in keyed:
            continue
        seen.add(cur)
        r = keyed[cur]
        acc.update(_members(r.get("ASSUMPTIONS")))
        stack.extend(_members(r.get("CITES")))
    return acc


def _trm_entries(tfk):
    return dict((e["NAME"], e) for e in tfk["TrM"]["ENTRIES"])


def gap_view(gap, records, tfk):
    keyed, lists, bad = _register(records, "records")
    if bad:
        return bad
    if type(gap) is not dict or gap.get("CLASS") != "NAMED_GAP" or type(gap.get("AGAINST")) is not list:
        return _bad_arg("gap", gap, "a NAMED_GAP record")
    if tfk is None or not _tfk_ok(tfk):
        return _one("REC-39", "-", "the TFK is not readable")
    words = _words(keyed, lists)
    against = _members(gap["AGAINST"])
    ents = _trm_entries(tfk)
    out = []
    src = gap.get("SOURCE")
    for rid in sorted(keyed):
        if not _standing(words[rid]):
            continue
        r = keyed[rid]
        hit = False
        if src in ("IC2", "UNDER"):
            if any(d in _members(r.get("REFS")) for d in against):
                hit = True
            named = _members(r.get("ENTRIES")) + ([r["QUANTITY"]] if type(r.get("QUANTITY")) is str else [])
            if any(n in ents and ents[n].get("DOF") in against for n in named):
                hit = True
        elif src in ("SR5_LOCUS", "SR7_ASSUMPTIONS"):
            hit = bool(_closure(keyed, rid) & set(against))
        if hit:
            out.append(rid)
    return ["OK", out]


def gap_state(state, date, what):
    if type(state) is not str or state not in ("OPEN", "SETTLED", "STANDING"):
        return _one("REC-90", "-", "state %s is not OPEN, SETTLED or STANDING" % _v(state))
    if state == "OPEN":
        return ["OK", "OPEN"]
    p = []
    if not _day_ok(date):
        p.append(["REC-50", "-", "date %s is not a date YYYY-MM-DD" % _vj(date)])
    if state == "SETTLED":
        if not (_rid_list(what) and what):
            p.append(["REC-50", "-", "what %s is not a list of record identifiers" % _vj(what)])
    elif type(what) is not str or Z._first_bad(what) is not None or Z._blank(what):
        p.append(["REC-50", "-", "what %s is not a text" % _vj(what)])
    if p:
        return _refused(p)
    if state == "SETTLED":
        return ["OK", "SETTLED %s BY %s" % (date, ", ".join(sorted(what)))]
    return ["OK", "STANDING %s BY %s" % (date, what)]


def eom0_open(records):
    keyed, lists, bad = _register(records, "records")
    if bad:
        return bad
    return ["OK", any(r["CLASS"] == "NAMED_GAP" and r.get("SOURCE") == "UNDER" and r.get("STATE") == "OPEN"
                      for r in keyed.values())]


# ---- 14 verification ---------------------------------------------------------------------------
def verification_order(target_push, verification_push, source_push):
    def nat(x):
        return _is_int(x) and x >= 0
    if not (nat(target_push) and nat(verification_push) and (source_push is None or nat(source_push))):
        return _bad_arg("pushes", [target_push, verification_push, source_push], "integers from 0")
    if verification_push <= target_push:
        return _one("REC-91", "-", "the VERIFICATION lands in its TARGET's push or before it")
    if source_push is not None and source_push < target_push:
        return ["INSPECT", "the SOURCE entry landed before the TARGET"]
    return ["OK"]


def _at_list(at):
    """9.1's form [DOF, v, kind] per point, sorted by DOF; None when a point does
    not normalize."""
    out = []
    for q in (at if type(at) is list else []):
        if type(q) is not dict:
            return None
        n = U.normalize_measurement(q.get("VALUE"), "0", q.get("UNIT"))
        if n[0] != "OK" or type(q.get("DOF")) is not str:
            return None
        out.append([q["DOF"], _cd(n[1]), n[4]])
    return sorted(out)


def verification_difference(target, verification, k):
    if not (type(target) is dict and target.get("CLASS") == "RESULT"
            and all(type(target.get(f)) is str for f in ("ID", "QUANTITY", "VALUE", "UNIT"))
            and type(verification) is dict and verification.get("CLASS") == "VERIFICATION"
            and verification.get("TARGET") == target["ID"] and type(verification.get("EXTERNAL")) is dict):
        return _one("REC-92", "-", "verification_difference reads a RESULT and a VERIFICATION of it")
    if not (type(k) is str and Z.canon_decimal(k) == k and Z._is_pos_finite(k)):
        return _bad_arg("k", k, "a positive canonical decimal")
    ext = verification["EXTERNAL"]
    p = []
    if ext.get("QUANTITY") != target["QUANTITY"]:
        p.append(["REC-93", "EXTERNAL.QUANTITY", "EXTERNAL.QUANTITY %s is not the TARGET's QUANTITY %s"
                  % (_v(ext.get("QUANTITY")), _show(target["QUANTITY"]))])
    at_t, at_v = _at_list(target.get("AT")), _at_list(verification.get("AT"))
    if at_t is None or at_v is None or [q[:2] for q in at_t] != [q[:2] for q in at_v]:
        p.append(["REC-94", "AT", "AT is not the TARGET's AT"])
    if p:
        return _refused(p)
    nt = U.normalize_measurement(target["VALUE"], target.get("U_C", "0") if target.get("REGIME") == "M" else "0",
                                 target["UNIT"])
    ne = U.normalize_measurement(ext.get("VALUE"), ext.get("U_C"), ext.get("UNIT"))
    if nt[0] != "OK" or ne[0] != "OK":
        return _one("REC-92", "-", "verification_difference reads a RESULT and a VERIFICATION of it")
    if nt[3] != ne[3]:
        return _one("REC-72", "-", "units of different dimension: %s and %s" % (nt[3], ne[3]))
    if nt[4] != ne[4]:
        return ["CONVENTION", "units of different kind: %s and %s" % (nt[4], ne[4])]
    import math
    val = nt[1] - ne[1]
    uu = math.sqrt(nt[2] * nt[2] + ne[2] * ne[2])
    if uu == 0.0:
        return _one("REC-95", "-", "U is 0: both uncertainties are 0")
    z = val / uu
    got = {"VALUE": _cd(val), "U": _cd(uu), "ZETA": _cd(z)}
    contra = S.mm_contradicts(nt[1], nt[2], ne[1], ne[2], float(k), 1.0)
    pres = verification.get("DIFFERENCE")
    pres = pres if type(pres) is dict else {}
    for f in ("VALUE", "U", "ZETA"):
        if Z.canon_decimal(pres.get(f)) != got[f]:
            p.append(["REC-96", "DIFFERENCE." + f, "DIFFERENCE.%s %s is not the recomputed %s"
                      % (f, _v(pres.get(f)), got[f])])
    if p:
        return _refused(p)
    return ["OK", got, contra]


# ---- 15 promotion, the entry view, successors ------------------------------------------------
FCLASS = ("F1", "F2", "F3")


def _floor(tfk):
    tms = tfk.get("TMS")
    params = tms.get("PARAMETERS") if type(tms) is dict else None
    f = params.get("PROMOTION_FLOOR", "F2") if type(params) is dict else "F2"
    return f if f in FCLASS else "F2"


def _words_arg(words):
    return type(words) is dict and all(type(v) is str for v in words.values())


def _eligible(keyed, words, ents, floor, audits, rid):
    r = keyed[rid]
    if r["CLASS"] != "RESULT":
        return ["NOT", ["not a RESULT"]]
    reasons = []
    w = words.get(rid, "NOT GRADED")
    if w != "PROVISIONAL":
        reasons.append("does not stand as PROVISIONAL (%s)" % _v(w))
    if "DOMAIN" not in r:
        reasons.append("DOMAIN is absent")
    if "DOMAIN_CONDITION" not in r:
        reasons.append("DOMAIN_CONDITION is absent")
    entries = _members(r.get("ENTRIES"))
    if not entries:
        reasons.append("ENTRIES is absent or empty")
    classes = [r.get("FALSIFIER_CLASS")] + [ents[e].get("FALSIFIER_CLASS") for e in entries if e in ents]
    classes = [c for c in classes if c in FCLASS]
    class_read = min(classes) if classes else "F1"
    if class_read < floor:
        reasons.append("FALSIFIER_CLASS %s is below PROMOTION_FLOOR %s" % (class_read, floor))
    for c in sorted(_members(r.get("CITES"))):
        if words.get(c, "NOT GRADED") != "ESTABLISHED":
            reasons.append("cites %s, which is not ESTABLISHED" % c)
    closure = set([rid])
    seen, stack = set(), [rid]
    while stack:
        cur = stack.pop()
        if cur in seen or cur not in keyed:
            continue
        seen.add(cur)
        nxt = _members(keyed[cur].get("CITES")) + _members(keyed[cur].get("ASSUMPTIONS"))
        closure.update(nxt)
        stack.extend(nxt)
    for a in sorted(closure & set(audits)):
        reasons.append("an audit block is open on %s" % a)
    if reasons:
        return ["NOT", reasons]
    return ["ELIGIBLE", class_read, floor]


def _promotion_args(records, words, tfk, open_audits):
    keyed, lists, bad = _register(records, "records")
    if bad:
        return None, bad
    if not _words_arg(words):
        return None, _bad_arg("words", words, "a map of status words")
    if tfk is None or not _tfk_ok(tfk):
        return None, _one("REC-39", "-", "the TFK is not readable")
    if not _rid_list(open_audits):
        return None, _bad_arg("open_audits", open_audits, "a list of record identifiers")
    return keyed, None


def promotion_eligible(records, words, tfk, open_audits, rid):
    keyed, bad = _promotion_args(records, words, tfk, open_audits)
    if bad:
        return bad
    if type(rid) is not str or rid not in keyed:
        return _one("REC-52", "-", "record %s is not in the register" % _v(rid))
    return _eligible(keyed, words, _trm_entries(tfk), _floor(tfk), open_audits, rid)


def _fixed_point(keyed, words, tfk, audits, candidates):
    words = dict(words)
    ents, floor = _trm_entries(tfk), _floor(tfk)
    promoted = []
    while True:
        rnd = []
        for rid in sorted(candidates):
            if rid in [p[0] for p in promoted]:
                continue
            e = _eligible(keyed, words, ents, floor, audits, rid)
            if e[0] == "ELIGIBLE":
                rnd.append([rid, e[1], e[2]])
        if not rnd:
            return sorted(promoted)
        for t in rnd:
            words[t[0]] = "ESTABLISHED"
        promoted.extend(rnd)


def promotion_pass(records, words, tfk, open_audits):
    keyed, bad = _promotion_args(records, words, tfk, open_audits)
    if bad:
        return bad
    return ["OK", _fixed_point(keyed, words, tfk, open_audits,
                               [rid for rid, r in keyed.items() if r["CLASS"] == "RESULT"])]


def promotion_record(promotion_id, landing_block, promoted):
    if not _m(_RID, promotion_id):
        return _bad_arg("promotion_id", promotion_id, "a record identifier")
    if type(landing_block) is not str or Z._first_bad(landing_block) is not None or Z._blank(landing_block):
        return _bad_arg("landing_block", landing_block, "a text")
    ok_list = type(promoted) is list and all(
        type(t) is list and len(t) == 3 and _m(_RID, t[0]) and t[1] in FCLASS and t[2] in FCLASS for t in promoted)
    if ok_list and len(set(t[0] for t in promoted)) != len(promoted):
        ok_list = False
    if not ok_list:
        return _bad_arg("promoted", promoted, "a list of [id, class_read, floor] triples")
    if not promoted:
        return _one("REC-97", "-", "a PROMOTION with nothing promoted is never written")
    pr = sorted(promoted)
    f = ["TPC-PROMOTE", "1", landing_block, len(pr)]
    for t in pr:
        f += t
    return ["OK", [promotion_id, "PROMOTION", landing_block, pr, G.digest_fields(f)]]


def entry_view(records, words, tfk, entry):
    keyed, lists, bad = _register(records, "records")
    if bad:
        return bad
    if not _words_arg(words):
        return _bad_arg("words", words, "a map of status words")
    if tfk is None or not _tfk_ok(tfk):
        return _one("REC-39", "-", "the TFK is not readable")
    ents = _trm_entries(tfk)
    if type(entry) is not str or entry not in ents:
        return _one("REC-74", "-", "quantity %s is not an entry of the TrM" % _v(entry))
    dof = ents[entry].get("DOF")
    recs, gaps, vers, es = [], [], [], None
    for rid in sorted(keyed):
        r = keyed[rid]
        c = r["CLASS"]
        if c == "RESULT" and (entry in _members(r.get("ENTRIES")) or r.get("QUANTITY") == entry):
            recs.append([rid, words.get(rid, "NOT GRADED")])
        elif c == "NAMED_GAP" and r.get("SOURCE") in ("IC2", "UNDER") and r.get("STATE") == "OPEN" \
                and dof in _members(r.get("AGAINST")):
            gaps.append(rid)
        elif c == "VERIFICATION" and type(r.get("EXTERNAL")) is dict and r["EXTERNAL"].get("QUANTITY") == entry:
            vers.append(rid)
        elif c == "ENTRY_STATUS" and r.get("ENTRY") == entry:
            key = (r.get("LANDED") if type(r.get("LANDED")) is str else "", rid)
            if es is None or key > es:
                es = key
    return ["OK", {"ENTRY": entry, "RECORDS": recs, "GAPS": gaps, "VERIFICATIONS": vers,
                   "ENTRY_STATUS": es[1] if es else None}]


def _zone_sub(tms, part, fields):
    return Z._node([part, Z._sorted_entries(tms.get(part, []), fields)])


def reaudit_scope(src, dst):
    for name, v in (("src", src), ("dst", dst)):
        if not (type(v) is dict and all(k in v for k in ("D_TPK", "D_TRM", "TMS")) and type(v["TMS"]) is dict
                and type(v["TMS"].get("PARAMETERS")) is dict and Z.tms_well_formed(v["TMS"])[0]):
            return _bad_arg(name, v, "an object with D_TPK, D_TRM and TMS")
    if src["D_TPK"] != dst["D_TPK"] or src["D_TRM"] != dst["D_TRM"]:
        return ["OK", ["ALL"]]
    a, b = src["TMS"], dst["TMS"]
    pa, pb = a["PARAMETERS"], b["PARAMETERS"]

    def canon_param(pm, name):
        v = pm.get(name)
        if name == "K":
            return Z.canon_decimal(v) if type(v) is str else cj(v)
        if name == "STEP_CONVENTION":
            return Z._text(v) if type(v) is str else cj(v)
        defaults = {"IC2_POLICY": "GAP", "PROMOTION_FLOOR": "F2", "STRUCTURE_CHECK": "ON"}
        if name in defaults and name not in pm:
            v = defaults[name]
        return cj(v)

    checks = set()
    table = [("K", ["SR3", "SR8"]), ("N_X", ["SR6"]), ("STEP_CONVENTION", ["SR4", "SR5"]),
             ("PROMOTION_FLOOR", ["PROMOTION"]), ("STRUCTURE_CHECK", ["STRUCTURE", "EOM-0"]),
             ("S_MAX", ["T3"]), ("IC2_POLICY", ["IC2"])]
    for name, adds in table:
        if canon_param(pa, name) != canon_param(pb, name):
            checks.update(adds)
    cz = _zone_sub(a, "CONSTANTS", Z.TMSC_FIELDS[0]) != _zone_sub(b, "CONSTANTS", Z.TMSC_FIELDS[0])
    yz = _zone_sub(a, "YARDSTICKS", Z.YARD_FIELDS[0]) != _zone_sub(b, "YARDSTICKS", Z.YARD_FIELDS[0])
    if cz or yz:
        checks.update(["SR4", "SR5"])
    return ["OK", sorted(checks)]


def repromote_batch(src_records, src_words, dst_records, dst_words, tfk, open_audits):
    """functions 15.6 (D202.1; D215.2: a copy the successor withdraws is never a
    candidate, RD-RS-49)."""
    skeyed, _l, bad = _register(src_records, "src_records")
    if bad:
        return bad
    if not _words_arg(src_words):
        return _bad_arg("src_words", src_words, "a map of status words")
    keyed, bad = _promotion_args(dst_records, dst_words, tfk, open_audits)
    if bad:
        return bad
    cands = [rid for rid, r in keyed.items() if r["CLASS"] == "RESULT" and type(r.get("INHERITS")) is str
             and src_words.get(r["INHERITS"]) == "ESTABLISHED"]
    return ["OK", _fixed_point(keyed, dst_words, tfk, open_audits, cands)]


# ---- run_case: one corpus case, rendered in canonical JSON ------------------------------
def _a(args, k):
    return args.get(k) if type(args) is dict else None


def _strike(fn):
    return lambda a: fn(_a(a, "records"), _a(a, "words"), _a(a, "tfk"), _a(a, "quantity"))


FNS = {
    "record_check": lambda a: record_check(_a(a, "record"), _a(a, "tfk"), _a(a, "setting"), _a(a, "stage")),
    "record_preimage": lambda a: record_preimage(_a(a, "record")),
    "record_digest": lambda a: record_digest(_a(a, "record")),
    "record_view": lambda a: record_view(_a(a, "record"), _a(a, "view"), _a(a, "grade")),
    "schema_json": lambda a: schema_json(),
    "grade_values": lambda a: grade_values(_a(a, "class")),
    "status_values": lambda a: status_values(_a(a, "class")),
    "grade_now": lambda a: grade_now(_a(a, "register"), _a(a, "id")),
    "status_words": lambda a: status_words(_a(a, "register")),
    "is_standing": lambda a: is_standing(_a(a, "word")),
    "status_word": lambda a: status_word(_a(a, "grade"), _a(a, "status")),
    "grade_next": lambda a: grade_next(_a(a, "class"), _a(a, "grade"), _a(a, "event")),
    "status_next": lambda a: status_next(_a(a, "class"), _a(a, "status"), _a(a, "event")),
    "inherit_grade": lambda a: inherit_grade(_a(a, "class"), _a(a, "grade")),
    "citation_check": lambda a: citation_check(_a(a, "record"), _a(a, "register"), _a(a, "entries"),
                                               _a(a, "supersede_decided")),
    "withdrawal_scope": lambda a: withdrawal_scope(_a(a, "register"), _a(a, "root"), _a(a, "still_stands")),
    "withdrawal_preimage": lambda a: withdrawal_preimage(_a(a, "act")),
    "withdrawal_record": lambda a: withdrawal_record(_a(a, "withdrawal_id"), _a(a, "act")),
    "budget_check": lambda a: budget_check(_a(a, "record")),
    "field_values": lambda a: field_values(_a(a, "record")),
    "prec_decimal": lambda a: prec_decimal(_a(a, "s")),
    "l_finding_pair": lambda a: l_finding_pair(_a(a, "original"), _a(a, "reproduction")),
    "record_form_check": lambda a: record_form_check(_a(a, "record"), _a(a, "tfk")),
    "domain_check": lambda a: domain_check(_a(a, "record"), _a(a, "tfk")),
    "gap_records": lambda a: gap_records(_a(a, "prefix"), _a(a, "ic2"), _a(a, "under"), _a(a, "trigger"),
                                         _a(a, "landed"), _a(a, "tps")),
    "gap_record": lambda a: gap_record(_a(a, "id"), _a(a, "source"), _a(a, "against"), _a(a, "trigger"),
                                       _a(a, "landed"), _a(a, "tps")),
    "gap_view": lambda a: gap_view(_a(a, "gap"), _a(a, "records"), _a(a, "tfk")),
    "gap_state": lambda a: gap_state(_a(a, "state"), _a(a, "date"), _a(a, "what")),
    "eom0_open": lambda a: eom0_open(_a(a, "records")),
    "verification_order": lambda a: verification_order(_a(a, "target_push"), _a(a, "verification_push"),
                                                       _a(a, "source_push")),
    "verification_difference": lambda a: verification_difference(_a(a, "target"), _a(a, "verification"),
                                                                 _a(a, "k")),
    "promotion_eligible": lambda a: promotion_eligible(_a(a, "records"), _a(a, "words"), _a(a, "tfk"),
                                                       _a(a, "open_audits"), _a(a, "id")),
    "promotion_pass": lambda a: promotion_pass(_a(a, "records"), _a(a, "words"), _a(a, "tfk"),
                                               _a(a, "open_audits")),
    "promotion_record": lambda a: promotion_record(_a(a, "promotion_id"), _a(a, "landing_block"),
                                                   _a(a, "promoted")),
    "entry_view": lambda a: entry_view(_a(a, "records"), _a(a, "words"), _a(a, "tfk"), _a(a, "entry")),
    "reaudit_scope": lambda a: reaudit_scope(_a(a, "src"), _a(a, "dst")),
    "repromote_batch": lambda a: repromote_batch(_a(a, "src_records"), _a(a, "src_words"),
                                                 _a(a, "dst_records"), _a(a, "dst_words"), _a(a, "tfk"),
                                                 _a(a, "open_audits")),
    # tpc_strike 0.3.0 (D196.1) and tpc_eom 0.2.0 (D198.1 (b)), whose corpora tpc_record answers
    "conditions_of": lambda a: S.conditions_of(_a(a, "records"), _a(a, "id")),
    "comparison_set": _strike(S.comparison_set),
    "sr8_groups": _strike(S.sr8_groups),
    "settlement_check": lambda a: _eom().settlement_check(_a(a, "tfk"), _a(a, "records"), _a(a, "words"),
                                                          _a(a, "naming")),
}


def _eom():
    import tpc_eom
    return tpc_eom


def run_case(fn, args):
    """The rendered result of one corpus case. An unknown fn raises: a corpus
    error, not a vector."""
    if fn not in FNS:
        raise ValueError("fn")
    return cj(FNS[fn](args))