Issue 21: The Till You Can't Read

Share
Non Xero Sum logo animation still
nxs maze disc 2

A county fair runs a raffle with a hard cap: one hundred tickets, one prize, no exceptions, because the license that lets them run it says so. Tickets sell three ways: cash at the booth, counted by a volunteer who can give the running total any time; a card processor whose dashboard updates the moment a payment clears; and a social club two towns over, which took a stack of paper tickets on consignment in July and has not answered a phone call since.

Ask the organizer how many are sold, and the honest answer has three parts. The booth and the processor can be added, because both count continuously and only go up. The club cannot, because nobody has looked inside that stack since July. The tempting move, the one almost everyone makes, is to guess: say the club probably moved ten, add ten, report a tidy number. That guess is a confident figure built on no defensible basis, offered with the same confidence as the two inputs genuinely counted.

There is a solved game underneath. Frank Knight drew the line in 1921: a risk is a bet over a distribution you can defend; an uncertainty is a bet over one you cannot, and treating the second as the first is the error. Abraham Wald gave the decision rule in 1950: when you cannot defend a distribution, choose the action safest against the worst state you cannot rule out, not the one that looks best against the state you'd guess if forced to. Gilboa and Schmeidler formalized why that is rational under ambiguity, not merely cautious, in 1989. None were thinking about raffle tickets; all three are what the organizer needs, because producing one number is an older instinct than asking whether it is defensible.

This week's free tool takes the problem as a general register: readable channels sum to a lower bound; unreadable ones carry a declared, defensible ceiling, added too, never a guess. A channel with no defensible ceiling is reported unknowable, not zero. The paid flagship gives the register a memory, so a ceiling quietly revised down becomes a finding rather than vanishing.


The Tape

Four from the wave this week. Each read through one question: when a number depends on a channel nobody can verify, does the claim say so, or does it just state the number.

  1. A dashboard tracking DKG Swarm's holder returns documents TRAC, an ERC-20 token with a fixed 500,000,000 supply, minting permanently disabled on-chain (contract 0xaa7a9ca87d3694b5755f213b5d04094b8d0f0a6f, mintingFinished() verified true). No blind till exists here by construction: every unit moves through one contract everyone can read, a fix available on-chain, not to a cap sold across platforms sharing no ledger.
  2. An X post claiming a four-agent stack took a business "from zero to $70k in its first month" names a leaked internal file as its source. Checked this week: no public repository, no leaked document, no independent confirmation of either. The channel the claim depends on is as unreadable as the fair's social club, and the post reports a confident total anyway.
  3. A companion post rebuilding $300 of paid subscriptions locally is the one item that disclosed its own blind spot on screen: captioned "DEMO DATA, SIMULATED NUMBERS" throughout. Worth citing for getting right what item 2 gets wrong.
  4. A reel demonstrating a personal Claude Code harness quotes exact token-caching figures as the build's headline evidence; checked this week, the same figures are illustrative for the demo, not a measured result. An illustrative number dressed as measured is the small version of the raffle guess.

Both tools follow The Read: the partial-observability cap auditor, free and runnable below, and Cap Memory, the paid flagship below the cut that catches a ceiling quietly lowered between audits.


The Read

The Read: the worst case is a decision, not a measurement

Split the raffle problem into two honest pieces. The booth and the processor are risk in Knight's sense: both report continuously, so you can state right now how many tickets each has sold. The club is uncertainty in Knight's sense: no distribution over "how many it has sold" is defensible, because nobody has looked, and that itself is the information. Knight's 1921 point, a century old and still mostly unobserved, is that these are different kinds of not-knowing: a risk has a number you can act on; an uncertainty does not, and pretending otherwise is the error.

Wald's answer, in Statistical Decision Functions (1950), is the minimax rule: against a state you cannot assign a probability to, choose the action least bad in the worst state you cannot rule out. The worst state the unreadable channel could hide is as many as it could possibly hold, and the decision that survives that is to assume exactly that many. Gilboa and Schmeidler's 1989 result is why this is rational under ambiguity, not merely cautious: maxmin expected utility is the utility an agent is axiomatically committed to once it admits it cannot pin down a single prior. Ellsberg showed the human half in 1961: offered a bet on a well-specified urn against an identical bet on one of unknown contents, people prefer the well-specified one at equal odds, which only makes sense if they already refuse to treat ambiguity as risk.

So the arithmetic has two defensible terms and one that must never be a guess. Readable channels sum directly, a genuine lower bound even with a reporting lag. Unreadable ones carry a declared ceiling, the most a channel could possibly hold given what is known about its scale or contract, added too, never softened. A channel with no defensible ceiling gets a fourth state, not a number: unbounded, a materially different fact from being safely under cap.

The free tool below runs exactly this: a worked hundred-seat cap turns RED the moment the worst case, not the naive total, crosses it, ten over before a single reseller ticket is confirmed.

The twenty-minute version

Write your cap as a JSON register: {id, readable: true, count: N} for anything you read live, or {id, readable: false, ceiling: N} for anything you cannot, N being the most that channel could defensibly hold, not your best guess. Leave the ceiling null if you cannot bound it at all. Run the tool below. GREEN, AMBER, or RED means the worst case was computed honestly; BLIND means it hasn't been computed yet, the more useful thing to know.

A cap you must never breach is a decision about the worst case, not a measurement of the number you can see.


The Tool

The Tool: partialobservabilitycap_auditor.py

The free, from-scratch tool this week. Give it a cap and a list of channels, each a live-readable count or a declared ceiling, and it computes the worst case honestly: sum what you see, add every defensible ceiling, and refuse to call a ceilingless channel anything but what it is. Standard library only, runnable below.

#!/usr/bin/env python3
"""partial_observability_cap_auditor.py - audit a fixed cap sold across tills you
can read and tills you cannot.

A fixed supply (100 founder seats, a venue's capacity, a token's hard cap) is
often sold through several tills at once. Some you can meter in real time; at
least one you may not be able to, because it is structurally unreadable rather
than merely offline: a login path foreclosed behind a CAPTCHA, a partner selling
on their own system, a channel whose read API was never granted.

The tempting move is to estimate the unreadable till and add the estimate. That
produces one confident number that is wrong in an unknown direction. For a cap
you must never breach, wrong-low is the expensive direction: it sells seat 101.

Abraham Wald's minimax rule (Statistical Decision Functions, 1950) and Gilboa and
Schmeidler's maxmin expected utility under ambiguity (1989) point the same way
for a decision where you cannot put a probability on the unknown: act on the
WORST case you cannot rule out, not on a point estimate you cannot defend. Frank
Knight (1921) drew the line this tool lives on. Risk is a distribution you know;
uncertainty is one you do not, and a structurally-blind till is uncertainty.

So this tool never estimates a blind till. It does two honest things instead.

  1. It sums the READABLE tills in the direction that fails safe. A live count is
     a LOWER bound on true sales (overlaps double-counted on purpose), so the sum
     errs toward the cap, never away from it.
  2. It carries, for each BLIND till, a declared worst-case CEILING: the most
     sales that till could possibly hold, a bound you can defend. It adds the
     ceilings, not a guess. A blind till with no defensible ceiling is UNBOUNDED,
     and the honest verdict is then BLIND: the cap cannot be certified from the
     readable side, which is not the same as being under it.

      worst_case_total = sum(readable counts) + sum(blind ceilings)

It alerts the moment the WORST case crosses the cap, never the moment a point
estimate does. The naive number (readable only, blind treated as zero) is printed
beside the worst case so the size of the blind spot is visible.

Verdicts:
  GREEN   worst case is below the caution band. Safe to keep selling.
  AMBER   worst case is in the caution band (>= amber, < cap). Slow down.
  RED     worst case is at or over the cap. Stop: you may already have oversold.
  BLIND   a blind till has no ceiling, so the worst case is unbounded and the cap
          cannot be certified. Not GREEN. The count is unknowable, not zero.

Input: a JSON object {cap, amber_fraction?, channels:[...]}. Each channel is
{id, readable: true, count: N} or {id, readable: false, ceiling: N|null}.

Exit 0 only on GREEN. Exit 1 on AMBER, RED or BLIND. Exit 2 on read/parse error.
Standard library only.
"""
from __future__ import annotations

import argparse
import json
import math
import sys

VERDICTS = ("GREEN", "AMBER", "RED", "BLIND")

def _non_negative_int(value, field):
    """Return value as a non-negative int, or raise ValueError naming the field.
    A count or ceiling that is negative, fractional or non-numeric is a bad
    register, not a zero: refuse it rather than silently coerce."""
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise ValueError("%s must be a number, got %r" % (field, value))
    if isinstance(value, float) and not value.is_integer():
        raise ValueError("%s must be a whole number, got %r" % (field, value))
    n = int(value)
    if n < 0:
        raise ValueError("%s must be >= 0, got %d" % (field, n))
    return n

def audit(config):
    """Classify one cap register. Returns a result dict. Raises ValueError on a
    malformed register (missing cap, a readable till with no count, a bad number)."""
    if not isinstance(config, dict):
        raise ValueError("register must be a JSON object")
    cap = config.get("cap")
    if cap is None:
        raise ValueError("register has no 'cap'")
    cap = _non_negative_int(cap, "cap")
    if cap == 0:
        raise ValueError("cap must be > 0")

    amber_fraction = config.get("amber_fraction", 0.9)
    if not isinstance(amber_fraction, (int, float)) or not (0 < amber_fraction <= 1):
        raise ValueError("amber_fraction must be in (0, 1], got %r" % (amber_fraction,))
    amber_at = math.ceil(cap * amber_fraction)

    channels = config.get("channels")
    if not isinstance(channels, list) or not channels:
        raise ValueError("register needs a non-empty 'channels' list")

    readable_total = 0
    blind_ceiling_total = 0
    readable = []
    blind_bounded = []
    blind_unbounded = []

    for ch in channels:
        if not isinstance(ch, dict) or "id" not in ch:
            raise ValueError("each channel needs an 'id'")
        cid = str(ch["id"])
        if ch.get("readable") is True:
            if "count" not in ch:
                raise ValueError("readable till %r has no 'count'" % cid)
            n = _non_negative_int(ch["count"], "count for %s" % cid)
            readable_total += n
            readable.append((cid, n))
        elif ch.get("readable") is False:
            ceiling = ch.get("ceiling", None)
            if ceiling is None:
                blind_unbounded.append(cid)
            else:
                c = _non_negative_int(ceiling, "ceiling for %s" % cid)
                blind_ceiling_total += c
                blind_bounded.append((cid, c))
        else:
            raise ValueError("channel %r must set 'readable' true or false" % cid)

    unbounded = bool(blind_unbounded)
    worst_case = readable_total + blind_ceiling_total  # a lower bound when unbounded

    if unbounded:
        verdict = "BLIND"
    elif worst_case >= cap:
        verdict = "RED"
    elif worst_case >= amber_at:
        verdict = "AMBER"
    else:
        verdict = "GREEN"

    return {
        "cap": cap,
        "amber_at": amber_at,
        "readable_total": readable_total,
        "naive_total": readable_total,          # blind treated as zero: the trap
        "blind_ceiling_total": blind_ceiling_total,
        "worst_case_total": worst_case,
        "worst_case_is_lower_bound": unbounded,  # true worst case is unbounded
        "verdict": verdict,
        "readable": readable,
        "blind_bounded": blind_bounded,
        "blind_unbounded": blind_unbounded,
    }

def render(result):
    lines = []
    lines.append("PARTIAL-OBSERVABILITY CAP AUDITOR")
    lines.append("  cap            : %d  (caution band from %d)"
                 % (result["cap"], result["amber_at"]))
    lines.append("  readable tills :")
    for cid, n in result["readable"]:
        lines.append("    %-22s %d (live count, a lower bound)" % (cid, n))
    if result["blind_bounded"]:
        lines.append("  blind tills (declared ceiling, never estimated):")
        for cid, c in result["blind_bounded"]:
            lines.append("    %-22s <= %d (worst case it could hold)" % (cid, c))
    if result["blind_unbounded"]:
        lines.append("  blind tills WITH NO CEILING (unbounded, uncertifiable):")
        for cid in result["blind_unbounded"]:
            lines.append("    %-22s <= ?  (count unknowable, not zero)" % cid)
    lines.append("")
    lines.append("  naive total    : %d   (blind tills counted as zero: the trap)"
                 % result["naive_total"])
    if result["worst_case_is_lower_bound"]:
        lines.append("  worst case     : >= %d and UNBOUNDED above"
                     % result["worst_case_total"])
    else:
        lines.append("  worst case     : %d   (readable + every blind ceiling)"
                     % result["worst_case_total"])
    lines.append("")

    v = result["verdict"]
    if v == "GREEN":
        lines.append("VERDICT: GREEN. Even the worst case is under the caution band.")
    elif v == "AMBER":
        lines.append("VERDICT: AMBER. The worst case is in the caution band; slow the sell.")
    elif v == "RED":
        over = result["worst_case_total"] - result["cap"]
        lines.append("VERDICT: RED. The worst case is at or over the cap by %d." % over)
        lines.append("         The naive total says %d seats are free. They may not be."
                     % (result["cap"] - result["naive_total"]))
        lines.append("         Wald minimax: a cap you must never breach is a decision")
        lines.append("         on the worst case, not on the number you can read.")
    else:  # BLIND
        lines.append("VERDICT: BLIND. A till has no ceiling, so the worst case is unbounded.")
        lines.append("         The cap cannot be certified from the readable side. This is")
        lines.append("         not GREEN. An unreadable till is Knightian uncertainty, and")
        lines.append("         its count is unknowable, not zero.")
    return "\n".join(lines)

def run(path, as_json):
    try:
        with open(path, encoding="utf-8") as fh:
            config = json.load(fh)
    except OSError as exc:
        print("cannot read %s: %s" % (path, exc), file=sys.stderr)
        return 2
    except json.JSONDecodeError as exc:
        print("cannot parse %s: %s" % (path, exc), file=sys.stderr)
        return 2
    try:
        result = audit(config)
    except ValueError as exc:
        print("bad register in %s: %s" % (path, exc), file=sys.stderr)
        return 2

    if as_json:
        printable = dict(result)
        printable["readable"] = [{"id": c, "count": n} for c, n in result["readable"]]
        printable["blind_bounded"] = [{"id": c, "ceiling": n} for c, n in result["blind_bounded"]]
        print(json.dumps(printable, indent=2))
    else:
        print(render(result))

    return 0 if result["verdict"] == "GREEN" else 1

# (register, expected verdict). Each is a fact the classifier must get right.
SELF_TESTS = [
    # readable only, well under: GREEN
    ({"cap": 100, "channels": [
        {"id": "a", "readable": True, "count": 10},
        {"id": "b", "readable": True, "count": 20}]}, "GREEN"),
    # a blind ceiling pushes the worst case over, though the naive total is low: RED
    ({"cap": 100, "channels": [
        {"id": "a", "readable": True, "count": 70},
        {"id": "blind", "readable": False, "ceiling": 40}]}, "RED"),
    # naive total alone would read GREEN here; the honest verdict is RED
    ({"cap": 100, "channels": [
        {"id": "a", "readable": True, "count": 0},
        {"id": "b", "readable": True, "count": 0},
        {"id": "blind", "readable": False, "ceiling": 100}]}, "RED"),
    # worst case lands in the caution band: AMBER
    ({"cap": 100, "channels": [
        {"id": "a", "readable": True, "count": 80},
        {"id": "blind", "readable": False, "ceiling": 12}]}, "AMBER"),
    # a blind till with no ceiling is unbounded: BLIND, never GREEN
    ({"cap": 100, "channels": [
        {"id": "a", "readable": True, "count": 1},
        {"id": "blind", "readable": False, "ceiling": None}]}, "BLIND"),
    # a blind till with a missing ceiling key is also unbounded
    ({"cap": 100, "channels": [
        {"id": "a", "readable": True, "count": 1},
        {"id": "blind", "readable": False}]}, "BLIND"),
    # exactly at the cap is RED, not AMBER (a cap is never-exceed)
    ({"cap": 50, "channels": [
        {"id": "a", "readable": True, "count": 50}]}, "RED"),
    # one under the cap, outside a tight caution band: GREEN
    ({"cap": 50, "amber_fraction": 1.0, "channels": [
        {"id": "a", "readable": True, "count": 49}]}, "GREEN"),
]

def self_test():
    passed = 0
    for i, (register, expected) in enumerate(SELF_TESTS):
        try:
            got = audit(register)["verdict"]
        except ValueError as exc:
            got = "ERROR(%s)" % exc
        ok = got == expected
        passed += ok
        print("  %-4s case %-2d expected %-6s got %-6s"
              % ("PASS" if ok else "FAIL", i, expected, got))

    # a malformed register must raise, not return a verdict
    for bad in ({"channels": [{"id": "a", "readable": True, "count": 1}]},   # no cap
                {"cap": 100, "channels": [{"id": "a", "readable": True}]},   # no count
                {"cap": 100, "channels": [{"id": "a", "readable": True, "count": -1}]}):
        try:
            audit(bad)
            print("  FAIL malformed register did not raise: %r" % (bad,))
        except ValueError:
            passed += 1
            print("  PASS malformed register refused: %s" % next(iter(bad)))
    total = len(SELF_TESTS) + 3
    print()
    print("SELF-TEST: %d/%d checks passed" % (passed, total))
    return 0 if passed == total else 1

def main():
    ap = argparse.ArgumentParser(
        description="Audit a fixed cap across readable and structurally-blind tills.")
    ap.add_argument("register", nargs="?", help="JSON object {cap, channels:[...]}")
    ap.add_argument("--json", action="store_true", help="machine-readable output")
    ap.add_argument("--self-test", action="store_true", help="run built-in checks and exit")
    args = ap.parse_args()

    if args.self_test:
        return self_test()
    if not args.register:
        ap.error("a register path is required unless --self-test is given")
    return run(args.register, args.json)

if __name__ == "__main__":
    sys.exit(main())

Feed it a hundred-seat register with two readable tills and one honestly bounded blind till:

PARTIAL-OBSERVABILITY CAP AUDITOR
  cap            : 100  (caution band from 90)
  readable tills :
    web-checkout           38 (live count, a lower bound)
    box-office             14 (live count, a lower bound)
  blind tills (declared ceiling, never estimated):
    partner-reseller       <= 20 (worst case it could hold)

  naive total    : 52   (blind tills counted as zero: the trap)
  worst case     : 72   (readable + every blind ceiling)

VERDICT: GREEN. Even the worst case is under the caution band.

Raise the two readable counts and the reseller's ceiling and the same arithmetic turns RED at a worst case of 110, ten over cap, while the naive total still reads 70 and still says 30 are free. Drop the ceiling entirely, and the verdict is not GREEN either; it is BLIND, because an unbounded till is evidence that nobody has bounded it yet, not evidence of safety.

Exit one on the oversold register, exit one again on the unbounded one, exit zero only when the worst case itself clears the caution band. --self-test runs eleven planted cases, three of which must raise rather than return a verdict, and all eleven pass.


Founder offer

If you sell a fixed, legally-capped resource through more than one channel, run this register against your own numbers today. The free tool tells you where you stand now; the paid flagship tells you whether the ground is shifting under a number you trusted last week.

Pro is $15 a month or $250 a year: the Brief, the paid flagship tool, the paid Feed payload, and the archive. Founder is $300 a year, capped at 100 seats, with a founders-only MCP server built once the hundredth seat sells. Terms: nonxerosum.studio/mcp.

No pitch beyond that. The worst case is the argument.

founder bar issue 21