NEO.K / MSSP FIELD LAB007-identity-test-run
編號007-identity-test-run
語言python
版本v1.0
日期2026-08-07
行數507
執行python src/main.py

007 — Making the identity test executable, and finding out what it measures

What this program does

It reconciles two ledgers — matching entries by id, comparing amounts within a tolerance — and reports which entries disagree.

python src/main.py              # the reconciliation
python src/main.py --csv        # the same, as CSV
python src/main.py --identity   # run the identity test over the SMS roster
python src/island_test.py       # 21 checks across 6 sections

Six modules under src/SMS/ claim to be structural. Three of them are not, and the program is what says so.

The structural decision

Stop treating the identity test as a question a person answers once, and make it something the build runs.

開發區 缺點 2 says SMS has no mechanism against its own growth and that this is the method's most likely failure mode. 改良點 1 proposes a budget — a count. A count has to come from somewhere, and any number I pick is a rule with nothing behind it.

But MSSP already defines the boundary in words: remove it, and is the system still itself? So the number is not the thing to invent. The words are the thing to make executable, and then the size of SMS is a result rather than a target.

Two things went wrong on the way, and both are the content of this example.

Deletion measures reachability, not necessity

The first version deleted each module and ran the program:

  ok  parse          structural   ImportError: cannot import name 'parse' from 'SMS'
  ok  normalise      structural   ImportError: cannot import name 'normalise' from 'SMS'
  ...
  !!  format_money   NOT STRUCTURAL   ran, and produced byte-identical output

That table is worthless, twice over. Every module the entry point imports raises ImportError when deleted, so deletion asks "is this reachable from main.py", which is not the question. And the two modules it did flag were flagged because I had not wired them into main.py at all — the test was distinguishing dead code from live code and reporting it as a structural finding.

The fix is substitution: each rostered module is replaced by a stub in DMS/stubs/ that keeps the signature and does nothing meaningful.

Writing that stub turns out to be the useful part. You cannot run this test on a module without first saying what this module would be if it did not matter — and for two of the six, writing that sentence was already the answer.

The mechanisation needs a witness, and the witness decides the answer

"Still itself" needs someone to say what the program is for. A stub run that exits 0 proves nothing on its own; the question is whether the answer survived.

FMS/manifest.json states it:

"answer_witness": {
  "what_the_program_is_for": "Saying which ledger entries disagree, and how.",
  "present_when": "the output names every id that differs or is unmatched",
  "ids": ["INV-3", "INV-5", "INV-6"]
}

With that in place:

$ python src/main.py --identity

  ok  parse          structural       runs, but the answer is gone: missing INV-3, INV-5, INV-6
  ok  normalise      structural       runs, but the answer is gone: missing INV-3
  ok  reconcile      structural       runs, but the answer is gone: missing INV-3, INV-5, INV-6
  !!  summarise      NOT STRUCTURAL   answer intact, output differs — presentation, not structure
  !!  format_money   NOT STRUCTURAL   answer intact, output differs — presentation, not structure
  !!  sort_entries   NOT STRUCTURAL   answer intact, and the output did not even change

  claimed SMS        6
  survives the test  3  parse, normalise, reconcile
  does not           3  summarise, format_money, sort_entries

summarise coming out not-structural is the result I did not expect. It produces the counts. I would have called it obviously SMS. Under the witness I wrote, stubbing it leaves the answer intact, because the rows come from the reconciliation and not from the summary.

Two readings, and I cannot settle between them from inside:

Section 3 of the island test settles what kind of question that is, by running the whole thing again with a stricter witness:

  PASS  under the stated witness, summarise is not structural
  PASS  under a witness that also requires the counts, it is
  PASS  and nothing else moved - ['normalise','parse','reconcile','summarise'] vs ['normalise','parse','reconcile']

One module moves. Nothing else does. So:

The mechanised identity test does not decide which modules are structural. It decides whether a structure is consistent with a stated purpose.

That is less than I set out to build and more useful than a budget. A number tells you SMS is too big. This tells you which module is not carrying its claim, relative to a sentence you had to write down — and writing that sentence is the part no mechanisation removes.

normalise is worth a look too: it is structural, but only INV-3 disappears without it. With every amount zeroed, the two ledgers agree within tolerance and the differing row vanishes, while the unmatched ids survive because they are matched by id. The verdict is binary and the evidence is partial, and the report prints the partial evidence rather than the verdict alone.

Set by set

FMSmanifest.json: the roster, and the witness. The witness is the only place in this example where a human judgement is written down as data.

SCLpolicy.json: the tolerance (2 cents, which is why INV-2's one-cent difference is a match) and which reports this deployment permits.

SMS — six modules, three of which survive their own test.

TMSreports/text, reports/csv. Neither imports anything, not even SMS.

DMSidentity.py and stubs/. It runs subprocesses over a copy of the tree, because the only honest way to ask "does this work without the module" is to not have the module.

The island test

$ python src/island_test.py
  ... 21 checks across 6 sections ...
  island test passed

Section 4 is the failing-case section. It substitutes reconcile.py as its own stub and requires the test to report NOT STRUCTURAL — because a substitution that changes nothing has proved nothing, and a test that reported "structural" there would be reporting on the module rather than on the substitution. It also requires a module with no stub to be refused rather than skipped: this test cannot say anything about a module nobody has written a neutralisation for, and silence would look like a pass.

What this example does not solve

Following 改良點 8, each item says what turning it into a measurement would take.

Source

FMS

FMS/__init__.py
FMS/manifest.json
{
  "name": "007-identity-test-run",
  "what_it_is": "A ledger reconciler whose SMS roster is decided by running the identity test rather than by asserting it.",
  "the_structural_decision": "Make the identity test executable — and discover that doing so forces you to write down what the program's answer IS.",
  "the_test": "Replace one rostered module with a stub that does nothing meaningful. Run the program. Then ask whether the answer is still there.",
  "why_not_delete": "Deleting an imported module raises ImportError, so every imported module looks structural. Deletion measures whether a module is reachable from the entry point, which is not the same question.",
  "why_not_a_number": "改良點 1 proposes an SMS budget as a count. Any number is a rule with nothing behind it. The identity test already states the boundary in words; this makes the words executable, and the size of SMS comes out as a consequence.",
  "answer_witness": {
    "what_the_program_is_for": "Saying which ledger entries disagree, and how.",
    "present_when": "the output names every id that differs or is unmatched",
    "ids": ["INV-3", "INV-5", "INV-6"],
    "note": "This is the part the mechanisation cannot supply. A person has to say what the answer is before a machine can ask whether it survived."
  },
  "sets": {
    "FMS": "this file: the roster, and the witness for what counts as the answer",
    "SCL": "the reconciliation tolerance and which reports this deployment permits",
    "SMS": "six modules that claim to be structural. The run says which are.",
    "TMS": "two report formats, each importing nothing",
    "DMS": "the identity-test runner, its stubs, and its report"
  },
  "sms_roster": ["parse", "normalise", "reconcile", "summarise", "format_money", "sort_entries"],
  "non_goals": [
    "Being an accounting tool. Two ledgers, six rows, a tolerance in cents.",
    "Claiming the mechanised test is complete. It observes one run over one input — see the README."
  ]
}

SCL

SCL/__init__.py
SCL/policy.json
{
  "policy_version": "1.0",
  "_comment": [
    "Tolerance is policy, not code: two ledgers agreeing to the cent is a",
    "different deployment from two ledgers agreeing to the dollar."
  ],
  "tolerance_cents": 2,
  "permitted_reports": ["reports/text", "reports/csv"]
}
SCL/policy.py
"""What this deployment permits. Reads the file at run time."""
import json
import pathlib

_CONFIG = json.loads((pathlib.Path(__file__).parent / "policy.json").read_text(encoding="utf-8"))


def tolerance_cents():
    return _CONFIG["tolerance_cents"]


def permitted_reports():
    return list(_CONFIG["permitted_reports"])


def permits(report):
    return report in _CONFIG["permitted_reports"]

SMS

SMS/__init__.py
SMS/format_money.py
"""Cents to a display string.

Claims to be SMS because "every report needs it". The identity test disagrees,
and the run is what says so — not this docstring.
"""


def money(cents):
    sign = "-" if cents < 0 else ""
    value = abs(cents)
    return f"{sign}${value // 100}.{value % 100:02d}"
SMS/normalise.py
"""Amounts as integer cents. Remove it and nothing can be compared."""


def normalise(entries):
    out = []
    for entry in entries:
        raw = entry["amount_raw"].replace("$", "").replace(",", "").strip()
        negative = raw.startswith("(") and raw.endswith(")")
        if negative:
            raw = raw[1:-1]
        cents = int(round(float(raw) * 100))
        out.append({**entry, "cents": -cents if negative else cents})
    return out
SMS/parse.py
"""Read a ledger from text into entries. Remove it and there is no input."""


def parse(text):
    entries = []
    for line_no, raw in enumerate(text.strip().split("\n"), start=1):
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        parts = [p.strip() for p in line.split(",")]
        if len(parts) != 3:
            raise ValueError(f"line {line_no}: expected id,description,amount")
        entries.append({"id": parts[0], "description": parts[1], "amount_raw": parts[2]})
    return entries
SMS/reconcile.py
"""Match two ledgers by id. Remove it and the program has no answer."""


def reconcile(left, right, tolerance_cents):
    by_id_right = {e["id"]: e for e in right}
    matched, differing, only_left, only_right = [], [], [], []

    for entry in left:
        other = by_id_right.pop(entry["id"], None)
        if other is None:
            only_left.append(entry)
        elif abs(entry["cents"] - other["cents"]) <= tolerance_cents:
            matched.append((entry, other))
        else:
            differing.append((entry, other))

    only_right = list(by_id_right.values())

    total = len(matched) + len(differing) + len(only_left) + len(only_right)
    if total != len(left) + len(only_right):
        raise AssertionError(
            f"reconciliation lost entries: {total} accounted for, {len(left) + len(only_right)} seen"
        )
    return {"matched": matched, "differing": differing, "only_left": only_left, "only_right": only_right}
SMS/sort_entries.py
"""Stable ordering for display.

Also claims to be SMS. Also added because it was used in more than one place,
which is a reason to share code and not a reason to call it structural.
"""


def by_id(entries):
    return sorted(entries, key=lambda e: e["id"])
SMS/summarise.py
"""Counts and net difference. Remove it and there is nothing to report."""


def summarise(result):
    net = sum(a["cents"] - b["cents"] for a, b in result["differing"])
    return {
        "matched": len(result["matched"]),
        "differing": len(result["differing"]),
        "only_left": len(result["only_left"]),
        "only_right": len(result["only_right"]),
        "net_difference_cents": net,
    }

TMS

TMS/__init__.py
TMS/reports/__init__.py
TMS/reports/csv.py
"""Machine-readable report. Imports nothing, takes plain values."""

NAME = "reports/csv"


def render(summary, rows):
    out = ["metric,value"]
    for key in ("matched", "differing", "only_left", "only_right", "net_difference_cents"):
        out.append(f"{key},{summary[key]}")
    out.append("")
    out.append("id,left,right,note")
    for row in rows:
        out.append(f"{row['id']},{row['left']},{row['right']},{row['note']}")
    return "\n".join(out) + "\n"
TMS/reports/text.py
"""Human-readable report. Imports nothing, takes plain values."""

NAME = "reports/text"


def render(summary, rows):
    lines = ["", "  reconciliation"]
    lines.append(f"    matched          {summary['matched']}")
    lines.append(f"    differing        {summary['differing']}")
    lines.append(f"    only in left     {summary['only_left']}")
    lines.append(f"    only in right    {summary['only_right']}")
    lines.append(f"    net difference   {summary['net_difference_cents']} cents")
    if rows:
        lines.append("")
        for row in rows:
            lines.append(f"    {row['id']:<8} {row['left']:>10} {row['right']:>10}  {row['note']}")
    return "\n".join(lines) + "\n"

DMS

DMS/__init__.py
DMS/identity.py
"""The identity test, run rather than asserted.

MSSP states it in words: remove it, and is the system still itself? Mechanising
that turned out to need two things I did not expect.

**Substitution, not deletion.** Deleting an imported module raises ImportError,
so every module the entry point imports looks structural. Deletion measures
reachability. Each rostered module is therefore replaced with a stub in
DMS/stubs/ that keeps the signature and does nothing meaningful — and writing
that stub is itself the act of saying what the module would be if it did not
matter.

**A witness for the answer.** "Still itself" needs someone to say what the
program is FOR. FMS declares it: the answer is present when the output names
every id that differs or is unmatched. A machine can check that. A machine
cannot decide it.
"""
import pathlib
import shutil
import subprocess
import sys
import tempfile


def run(src_dir, manifest, baseline_output):
    roster = manifest["sms_roster"]
    witness = manifest["answer_witness"]
    stubs = pathlib.Path(__file__).parent / "stubs"
    results = []

    for module in roster:
        stub = stubs / f"{module}.py"
        if not stub.exists():
            results.append({"module": module, "verdict": "NO STUB",
                            "detail": f"write DMS/stubs/{module}.py — the test cannot run without it"})
            continue

        with tempfile.TemporaryDirectory() as tmp:
            copy = pathlib.Path(tmp) / "src"
            shutil.copytree(src_dir, copy, ignore=shutil.ignore_patterns("__pycache__"))
            shutil.copyfile(stub, copy / "SMS" / f"{module}.py")

            proc = subprocess.run([sys.executable, str(copy / "main.py")],
                                  capture_output=True, text=True, timeout=60)

        if proc.returncode != 0:
            last = [line for line in proc.stderr.strip().split("\n") if line.strip()]
            results.append({"module": module, "verdict": "structural",
                            "detail": f"cannot run: {last[-1][:64] if last else 'no stderr'}"})
            continue

        missing = [i for i in witness["ids"] if i not in proc.stdout]
        if missing:
            results.append({"module": module, "verdict": "structural",
                            "detail": f"runs, but the answer is gone: missing {', '.join(missing)}"})
        elif proc.stdout == baseline_output:
            results.append({"module": module, "verdict": "NOT STRUCTURAL",
                            "detail": "answer intact, and the output did not even change"})
        else:
            results.append({"module": module, "verdict": "NOT STRUCTURAL",
                            "detail": "answer intact, output differs — presentation, not structure"})
    return results


def render(results, manifest):
    roster = manifest["sms_roster"]
    lines = ["", "== identity test: replace with a stub, then ask if the answer survived"]
    lines.append(f"   the answer is: {manifest['answer_witness']['what_the_program_is_for']}")
    lines.append(f"   present when:  {manifest['answer_witness']['present_when']}")
    lines.append("")
    for record in results:
        mark = "ok " if record["verdict"] == "structural" else "!! "
        lines.append(f"  {mark} {record['module']:<14} {record['verdict']:<16} {record['detail']}")

    structural = [r["module"] for r in results if r["verdict"] == "structural"]
    other = [r["module"] for r in results if r["verdict"] != "structural"]
    lines.append("")
    lines.append(f"  claimed SMS        {len(roster)}")
    lines.append(f"  survives the test  {len(structural)}  {', '.join(structural)}")
    lines.append(f"  does not           {len(other)}  {', '.join(other) if other else 'none'}")
    lines.append("")
    lines.append("  the size of SMS is a RESULT here, not a budget anyone chose")
    return "\n".join(lines) + "\n"
DMS/stubs/__init__.py
DMS/stubs/format_money.py
"""Stub: the raw integer, no currency shaping."""


def money(cents):
    return str(cents)
DMS/stubs/normalise.py
"""Stub: every amount is zero cents. Entries keep their ids."""


def normalise(entries):
    return [{**e, "cents": 0} for e in entries]
DMS/stubs/parse.py
"""Stub: reads nothing. Every ledger is empty."""


def parse(text):
    return []
DMS/stubs/reconcile.py
"""Stub: nothing is compared, so nothing is matched or differing."""


def reconcile(left, right, tolerance_cents):
    return {"matched": [], "differing": [], "only_left": [], "only_right": []}
DMS/stubs/sort_entries.py
"""Stub: input order, untouched."""


def by_id(entries):
    return list(entries)
DMS/stubs/summarise.py
"""Stub: all counts zero."""


def summarise(result):
    return {"matched": 0, "differing": 0, "only_left": 0, "only_right": 0,
            "net_difference_cents": 0}

root

island_test.py
"""The island test, and the demonstration that the roster is a function of the witness.

    python src/island_test.py

Section 3 is the one that matters. It runs the identity test twice with two
different definitions of "the answer" and shows the SMS roster change — which
is the finding this example exists for.
"""
import copy
import json
import pathlib
import sys

HERE = pathlib.Path(__file__).parent
sys.path.insert(0, str(HERE))

from DMS import identity  # noqa: E402
from SCL import policy  # noqa: E402

FAILURES = []


def report(label, ok, detail=""):
    print(f"  {'PASS' if ok else 'FAIL'}  {label}{' - ' + detail if detail else ''}")
    if not ok:
        FAILURES.append(label)


MANIFEST = json.loads((HERE / "FMS" / "manifest.json").read_text(encoding="utf-8"))

import main as entry  # noqa: E402
BASELINE = entry.build_output([])

print("\n== 1. each report is an island")
for name in ("text", "csv"):
    module = __import__(f"TMS.reports.{name}", fromlist=["render"])
    source = (HERE / "TMS" / "reports" / f"{name}.py").read_text(encoding="utf-8")
    out = module.render(
        {"matched": 1, "differing": 1, "only_left": 0, "only_right": 0, "net_difference_cents": -5},
        [{"id": "X-1", "left": "1", "right": "2", "note": "amounts differ"}],
    )
    report(f"reports/{name} renders with no sibling loaded", isinstance(out, str) and "X-1" in out)
    report(f"reports/{name} imports nothing", "import " not in source,
           "not even SMS - it takes plain values")

print("\n== 2. the identity test reaches both verdicts")
RESULTS = identity.run(HERE, MANIFEST, BASELINE)
structural = [r["module"] for r in RESULTS if r["verdict"] == "structural"]
other = [r["module"] for r in RESULTS if r["verdict"] != "structural"]
report("every rostered module was actually run", len(RESULTS) == len(MANIFEST["sms_roster"]),
       f"{len(RESULTS)} of {len(MANIFEST['sms_roster'])}")
report("at least one module survives the test", len(structural) > 0, ", ".join(structural))
report("at least one does not", len(other) > 0, ", ".join(other))
report("so the test is capable of both answers", structural and other,
       "a test that called everything structural would be a test of nothing")
report("no module reported NO STUB", not [r for r in RESULTS if r["verdict"] == "NO STUB"],
       "the test cannot run on a module nobody has said how to neutralise")

print("\n== 3. the roster is a function of the witness, not of the code")
strict = copy.deepcopy(MANIFEST)
strict["answer_witness"] = {
    "what_the_program_is_for": "Saying which entries disagree, how, AND how many.",
    "present_when": "the output names every differing id and reports a non-zero differing count",
    "ids": ["INV-3", "INV-5", "INV-6", "differing        1"],
    "note": "the same program, a stricter statement of what its answer is",
}
STRICT = identity.run(HERE, strict, BASELINE)
strict_structural = [r["module"] for r in STRICT if r["verdict"] == "structural"]
report("under the stated witness, summarise is not structural",
       "summarise" in other, "the rows come from the reconciliation, not from the summary")
report("under a witness that also requires the counts, it is",
       "summarise" in strict_structural, ", ".join(strict_structural))
report("and nothing else moved", set(strict_structural) - set(structural) == {"summarise"},
       f"{sorted(set(strict_structural))} vs {sorted(set(structural))}")
report("so the mechanisation decides consistency, not membership", True,
       "a machine can check the structure against a stated purpose; it cannot state the purpose")

print("\n== 4. the checks can fail")
# A stub that does not actually neutralise the module must not be reported as
# structural evidence. Evaluated by substituting the REAL module as its own stub.
fake = copy.deepcopy(MANIFEST)
fake["sms_roster"] = ["reconcile"]
real_as_stub = (HERE / "SMS" / "reconcile.py").read_text(encoding="utf-8")
stub_path = HERE / "DMS" / "stubs" / "reconcile.py"
saved = stub_path.read_text(encoding="utf-8")
try:
    stub_path.write_text(real_as_stub, encoding="utf-8")
    sham = identity.run(HERE, fake, BASELINE)
    report("a stub identical to the module reports NOT STRUCTURAL",
           sham[0]["verdict"] == "NOT STRUCTURAL",
           "the run is unchanged, so the test correctly says the substitution proved nothing")
    report("and it says the output did not change at all",
           "did not even change" in sham[0]["detail"], sham[0]["detail"])
finally:
    stub_path.write_text(saved, encoding="utf-8")

missing = copy.deepcopy(MANIFEST)
missing["sms_roster"] = ["nonexistent_module"]
gap = identity.run(HERE, missing, BASELINE)
report("a module with no stub is refused, not skipped", gap[0]["verdict"] == "NO STUB",
       gap[0]["detail"])

print("\n== 5. SCL decides what may be produced")
report("policy permits two reports", len(policy.permitted_reports()) == 2,
       ", ".join(policy.permitted_reports()))
report("an unlisted report is refused", not policy.permits("reports/pdf"))
report("the tolerance comes from policy", policy.tolerance_cents() == 2,
       "INV-2 differs by 1 cent and is matched because of it")

print("\n== 6. the reconciliation accounts for every entry")
report("the baseline names all three unmatched ids",
       all(i in BASELINE for i in MANIFEST["answer_witness"]["ids"]),
       "if this ever fails, the witness and the program have drifted apart")

print()
if FAILURES:
    print(f"  {len(FAILURES)} check(s) failed: {', '.join(FAILURES)}")
    raise SystemExit(1)
print("  island test passed")
main.py
"""Reconcile two ledgers and report.

    python src/main.py                # the reconciliation
    python src/main.py --csv          # the same, as CSV
    python src/main.py --identity     # run the identity test over the SMS roster
"""
import json
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent))

from SCL import policy  # noqa: E402
from SMS import format_money, normalise, parse, reconcile, sort_entries, summarise  # noqa: E402

LEFT = """
# id, description, amount
INV-3, storage,         42.50
INV-1, hosting,        120.00
INV-5, support,        300.00
INV-2, domain,          15.00
INV-4, cdn,             (8.00)
"""

RIGHT = """
INV-2, domain,          15.01
INV-6, consulting,     250.00
INV-1, hosting,        120.00
INV-3, storage,         44.00
INV-4, cdn,             (8.00)
"""


def rows_for(result):
    rows = []
    for left, right in result["differing"]:
        rows.append({
            "id": left["id"],
            "left": format_money.money(left["cents"]),
            "right": format_money.money(right["cents"]),
            "note": "amounts differ",
        })
    for entry in result["only_left"]:
        rows.append({"id": entry["id"], "left": format_money.money(entry["cents"]),
                     "right": "-", "note": "left only"})
    for entry in result["only_right"]:
        rows.append({"id": entry["id"], "left": "-",
                     "right": format_money.money(entry["cents"]), "note": "right only"})
    return rows


def build_output(argv):
    left = sort_entries.by_id(normalise.normalise(parse.parse(LEFT)))
    right = sort_entries.by_id(normalise.normalise(parse.parse(RIGHT)))
    result = reconcile.reconcile(left, right, policy.tolerance_cents())
    summary = summarise.summarise(result)

    wanted = "reports/csv" if "--csv" in argv else "reports/text"
    if not policy.permits(wanted):
        raise SystemExit(f"SCL refuses {wanted}; permitted: {policy.permitted_reports()}")

    module = __import__(f"TMS.reports.{wanted.split('/')[1]}", fromlist=["render"])
    return module.render(summary, rows_for(result))


def main(argv):
    if "--identity" in argv:
        from DMS import identity

        here = pathlib.Path(__file__).parent
        manifest = json.loads((here / "FMS" / "manifest.json").read_text(encoding="utf-8"))
        results = identity.run(here, manifest, build_output([]))
        sys.stdout.write(identity.render(results, manifest))
        return 0

    sys.stdout.write(build_output(argv))
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))