Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 17s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 17s
wai-app and Create / wai-app · does this change touch it (push) Successful in 29s
WAI operation catalogue / does this change touch it (push) Successful in 28s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 37s
wai-fleet service / does this change touch it (push) Successful in 29s
wai-audit collector and player integration / does this change touch it (push) Successful in 41s
wai-quantum for JavaScript / does this change touch it (push) Successful in 21s
wai-ledger service / does this change touch it (push) Successful in 23s
wai-key-proxy / does this change touch it (push) Successful in 35s
wai-quantum studio and desktop / does this change touch it (push) Successful in 15s
wai-sink-android native sink library / does this change touch it (push) Successful in 16s
wai-sink-swift native sink package / does this change touch it (push) Successful in 14s
WAI Studio / does this change touch it (push) Successful in 14s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Successful in 9m21s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 1m30s
wai-ledger service / service · macos (push) Successful in 1m0s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m24s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 7m3s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 9m34s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 14m21s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 6s
wai-audit collector and player integration / collector · linux (push) Successful in 2m6s
wai-key-proxy / proxy · linux (push) Successful in 2m9s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m53s
wai-ledger service / service · linux (push) Successful in 3m14s
wai-quantum for JavaScript / javascript · linux (push) Successful in 3m26s
wai-quantum studio and desktop / studio · browser (push) Successful in 6m12s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 8m52s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 4m49s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 5m3s
WAI Studio / Studio in WebKit · linux (push) Successful in 12m24s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
A statement in symbols that is not an object, with a bearer_id that is not a string, or with a malformed group or index is now symbols_invalid, at plan level and as run check 7, where the reader used to return an error. Section 7.1 states the rule for group and index: 64 lowercase hexadecimal digits, and an integer from 0 to 2^53 - 1. A new reason, wanted_invalid, is the last of section 7.1's checks. It refuses a wanted that is not a list, an entry that is not an object, a malformed group and an index that is not an integer in that range. A run raises it at check 2. One object wanted twice stays plans_invalid, and an entry section 7.1 refuses names no object, so two of them alike are wanted_invalid. No case in any corpus changes. A position past 2^53 - 1, which no case carries and which read as deadline_not_stated, is now refused. Both readers derive these outcomes from the JSON, and the generator writes the forms the typed inputs cannot hold over a built case's JSON. Existing cases are byte-identical. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1714 lines
78 KiB
Python
1714 lines
78 KiB
Python
#!/usr/bin/env python3
|
|
"""Independent verifier of the timeliness corpus (extensions/timeliness.md).
|
|
|
|
Written from the extension's text. It shares no code with wai-rs, which generates the corpus,
|
|
and never runs it. It takes from the second implementations of the texts this extension builds
|
|
on what they define: group and object receipts from the JWP corpus's
|
|
(`../jwp-conformance/jwp_verify.py`, jwp-receipts §2.2, §3); a claim's steps 1 to 5 from the
|
|
claim corpus's (`../claim-conformance/claim_verify.py`, jwp-receipts §3.1), whose step 4 holds
|
|
§4.2's rules; a transmission claim's step 6, a schedule's, and a receiver's passes from the
|
|
carousel corpus's (`carousel_verify.py`: bearer-receipts §4.3, carousel §4.3 and §7); a holdings
|
|
record's form, signature and holdings from the holdings corpus's (`holdings_verify.py`, holdings
|
|
§4.4, §5.4); and a WAI1 envelope's manifest from the staged corpus's reader (SPEC §2, §3).
|
|
Everything else this extension adds is written here:
|
|
|
|
- §4.3: a timeliness claim against its group, the object receipts and envelopes held, and each
|
|
rung's group and object receipt;
|
|
- §5: an entry's value at an instant, and its end;
|
|
- §6: the intents that stand, the records that stand and the held-at statements they give
|
|
(`held_from_records`, with §2's `before_group`), each intent's lines (`lines`), and a
|
|
statement offered;
|
|
- §7: the plan's inputs, each source's arrival, and the choice, the ladder's rung included;
|
|
- §7.5: a sink's run of its plans against its clock (`run`), each plan reading the claims and records
|
|
held by its instant.
|
|
|
|
For every case it requires the outcome the corpus states, field for field; that every reason
|
|
of §9's table, every outcome of §6.3's and every action §7.5.3 states appears in the corpus; then
|
|
it mutates copies of the
|
|
corpus, one defect each, and requires every copy to fail; and it drops each of its own checks in
|
|
turn and requires the corpus to catch the weakened verifier. A bound no vector carries is checked
|
|
directly.
|
|
|
|
python3 timeliness_verify.py <live-conformance/timeliness>
|
|
|
|
Exit status 0 only if every case verifies, every mutated copy fails, every weakened verifier is
|
|
caught and every direct check holds. Python 3.8 or later; the `cryptography` package, which the
|
|
claim verifier imports.
|
|
"""
|
|
|
|
import copy
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
for d in ("claim-conformance", "jwp-conformance"):
|
|
sys.path.insert(0, os.path.join(HERE, "..", d))
|
|
import claim_verify as CV # noqa: E402
|
|
import jwp_verify as JV # noqa: E402
|
|
import carousel_verify as KV # noqa: E402
|
|
import holdings_verify as HV # noqa: E402
|
|
|
|
SV = CV.SV # the staged corpus's strict WAI1 reader, which the claim verifier loads
|
|
|
|
EXTENSION = os.path.join(HERE, "..", "extensions", "timeliness.md")
|
|
SPEC = os.path.join(HERE, "..", "SPEC.md")
|
|
JWP_RECEIPTS = os.path.join(HERE, "..", "extensions", "jwp-receipts.md")
|
|
TELEMETRY = os.path.join(HERE, "..", "extensions", "delivery-telemetry.md")
|
|
FULL = 1_000_000 # §2: a value in parts per million of the full value
|
|
LAST_INSTANT = 253_402_300_799_999 # §2: 9999-12-31T23:59:59.999Z
|
|
MAX_INT = (1 << 53) - 1
|
|
ACTIONS = ("fetch", "abandon", "present", "passed", "ended") # §7.5.3
|
|
T0_PLUS_LATER = 1_793_664_000_000 + 3_600_000 # An instant past every plan of the corpus's runs.
|
|
|
|
# A weakened run drops one of these checks (see `WEAKENINGS`); None checks everything.
|
|
DROPPED = None
|
|
|
|
|
|
def drops(name):
|
|
return DROPPED == name
|
|
|
|
|
|
def text_of(path):
|
|
with open(path, encoding="utf-8") as f:
|
|
return f.read()
|
|
|
|
|
|
def table_rows(text, heading):
|
|
"""The first backticked cell of each row of the table under `heading`, up to the next heading
|
|
of its level or above."""
|
|
at = text.index("\n" + heading)
|
|
level = heading.split(" ")[0]
|
|
ends = [text.find("\n" + h + " ", at + 1) for h in ("#" * n for n in range(2, len(level) + 1))]
|
|
ends = [e for e in ends if e >= 0]
|
|
rows = text[at:min(ends) if ends else len(text)].splitlines()
|
|
return [r.split("`")[1] for r in rows if r.startswith("| `")]
|
|
|
|
|
|
def reasons_table():
|
|
"""§9's codes, in order, each with the section the table says raises it."""
|
|
text = text_of(EXTENSION)
|
|
at = text.index("\n## 9. Reasons")
|
|
end = text.find("\n## ", at + 1)
|
|
rows = text[at:end].splitlines()
|
|
return [(r.split("`")[1], r.split("|")[2].strip()) for r in rows if r.startswith("| `")]
|
|
|
|
|
|
def outcomes_table():
|
|
"""§6.3's outcomes, in order."""
|
|
return table_rows(text_of(EXTENSION), "### 6.3 Lines")
|
|
|
|
|
|
def refinement_only():
|
|
"""SPEC §5, *Refinement capabilities*: the capabilities that are refinement-only."""
|
|
return set(table_rows(text_of(SPEC), "### Refinement capabilities (staged delivery)"))
|
|
|
|
|
|
REFINEMENT_ONLY = refinement_only()
|
|
|
|
|
|
def need(ok, reason):
|
|
"""jwp-receipts §3.1 step 6."""
|
|
if not ok:
|
|
raise CV.Reject(6, reason)
|
|
|
|
|
|
# ------------------------------------------------------------------ what the verifier holds
|
|
|
|
# Steps 1 to 4 of a claim, a group's steps 1 and 2 with its objects', and a record's reading are
|
|
# pure in their bytes: each is computed once and kept, which nothing a weakened run drops reads.
|
|
_CLAIMS, _GROUPS, _RECORDS = {}, {}, {}
|
|
|
|
|
|
def claim_steps(wire):
|
|
"""jwp-receipts §3.1 steps 1 to 4 (claim_verify.py). Returns the claim; raises CV.Reject."""
|
|
if wire not in _CLAIMS:
|
|
try:
|
|
_CLAIMS[wire] = CV.verify_claim(wire)
|
|
except CV.Reject as r:
|
|
_CLAIMS[wire] = r
|
|
v = _CLAIMS[wire]
|
|
if isinstance(v, CV.Reject):
|
|
raise v
|
|
return v
|
|
|
|
|
|
def read_record(b):
|
|
"""holdings §4.4 (holdings_verify.py). Returns the record; raises HV.Refused."""
|
|
if b not in _RECORDS:
|
|
try:
|
|
_RECORDS[b] = HV.read(b)
|
|
except HV.Refused as r:
|
|
_RECORDS[b] = r
|
|
v = _RECORDS[b]
|
|
if isinstance(v, HV.Refused):
|
|
raise v
|
|
return v
|
|
|
|
|
|
class Group:
|
|
"""A group given, after jwp-receipts §3 steps 1 and 2, with its object receipts and envelopes."""
|
|
|
|
def __init__(self, item):
|
|
self.receipt = JV.read_group(item["group"])
|
|
JV.verify_group(self.receipt)
|
|
self.link = JV.link(self.receipt).hex()
|
|
self.count = self.receipt["leaf_count"]
|
|
self.signer = self.receipt["signer_pubkey"]
|
|
self.start = JV.seconds(self.receipt["group_start"])
|
|
self.verdicts = [] # bearer-receipts §6.2 step 2, one for each object receipt given
|
|
self.pairs = [] # [(object receipt, envelope or None)] for each that reads
|
|
self.included = {} # position -> content hash of each object receipt that passes step 5
|
|
self.envelopes = {} # content hash -> envelope bytes held
|
|
for o in item["objects"]:
|
|
try:
|
|
ob = JV.read_object(o["receipt"])
|
|
except JV.Refuse as r:
|
|
self.verdicts.append("reject reason=" + r.reason)
|
|
continue
|
|
env = None if o["envelope"] is None else bytes.fromhex(o["envelope"])
|
|
self.pairs.append((ob, env))
|
|
try:
|
|
if env is None:
|
|
JV.inclusion(ob, self.receipt)
|
|
self.verdicts.append("inclusion-verified")
|
|
else:
|
|
JV.accept(ob, self.receipt, env)
|
|
self.verdicts.append("delivered-verified")
|
|
except JV.Refuse as r:
|
|
self.verdicts.append("reject reason=" + r.reason)
|
|
try:
|
|
JV.inclusion(ob, self.receipt)
|
|
self.included[ob["object_id"]] = ob["content_hash"].hex()
|
|
except JV.Refuse:
|
|
pass
|
|
if env is not None:
|
|
self.envelopes[JV.blake3(JV.OBJECT + env).hex()] = env
|
|
|
|
|
|
def read_groups(items):
|
|
"""§6.2 step 1: each group under jwp-receipts §3 steps 1 and 2, and each of its object receipts
|
|
as bearer-receipts §6.2 step 2 checks it. Raises JV.Refuse for a group that fails."""
|
|
out = []
|
|
for item in items:
|
|
key = json.dumps(item, sort_keys=True)
|
|
if key not in _GROUPS:
|
|
try:
|
|
_GROUPS[key] = Group(item)
|
|
except JV.Refuse as r:
|
|
_GROUPS[key] = r
|
|
g = _GROUPS[key]
|
|
if isinstance(g, JV.Refuse):
|
|
raise g
|
|
out.append(g)
|
|
return out
|
|
|
|
|
|
def manifest(env):
|
|
"""A WAI1 envelope's manifest (SPEC §2, §3), or None for bytes that are not one."""
|
|
try:
|
|
m, _, _ = SV.read_wai1(env)
|
|
except SV.NotWai1:
|
|
return None
|
|
return m
|
|
|
|
|
|
def rungs(entry):
|
|
"""§4.1: an entry's ladder, in its order: none for `null`, the one object, or the array's."""
|
|
fb = entry["fallback"]
|
|
return [] if fb is None else fb if isinstance(fb, list) else [fb]
|
|
|
|
|
|
def checked_rungs(entry, check):
|
|
"""The rungs §4.3 reads, every one; a weakened run reads only the first."""
|
|
return rungs(entry)[:1] if drops(check) else rungs(entry)
|
|
|
|
|
|
def is_layer(env):
|
|
"""§4.3 step 6: a WAI1 whose capability is refinement-only (SPEC §5)."""
|
|
m = manifest(env)
|
|
return m is not None and m["model_requirement"]["capability"] in REFINEMENT_ONLY
|
|
|
|
|
|
def derived_from(env, motif):
|
|
"""§4.3 step 8: a WAI1 whose manifest carries `derived_from` (SPEC §7.1, *Derived renditions*:
|
|
its four members in their forms) with `motif` the primary's content hash. A WAI2 is none."""
|
|
m = manifest(env)
|
|
df = None if m is None else m.get("derived_from")
|
|
return (isinstance(df, dict) and all(isinstance(df.get(k), str) and df[k] for k in
|
|
("motif", "motif_capability", "renderer", "settings"))
|
|
and CV.is_hex(df["motif"], 32) and CV.is_hex(df["settings"], 32) and df["motif"] == motif)
|
|
|
|
|
|
# ------------------------------------------------------------------ §2: the held-at statement
|
|
|
|
def before_group(word_s, start):
|
|
"""§2: a held-at statement is before its group when the whole seconds of the time its word
|
|
states, any fraction dropped (`word_s`), are below the seconds jwp-receipts §2.4.3 reads from
|
|
the group's `group_start` (`start`, None where it is not a date-time, which nothing is before).
|
|
§4.3 step 4 reads an entry's `from_ms` by the same rule."""
|
|
if start is None:
|
|
return False
|
|
return word_s <= start if drops("same_second") else word_s < start
|
|
|
|
|
|
# ------------------------------------------------------------------ §4.3: step 6
|
|
|
|
def check_timeliness(c, g, groups):
|
|
"""§4.3 against the group `g` the claim is checked against, which passed jwp-receipts §3 steps
|
|
1 and 2, and every group given (`groups`, by link hash) with the object receipts and envelopes
|
|
held under it."""
|
|
need(g.start is not None, "context_invalid")
|
|
entries = c["body"]["objects"]
|
|
envelopes = {}
|
|
for x in groups.values():
|
|
envelopes.update(x.envelopes)
|
|
need(drops("subject") or g.link == c["subject"], "timeliness_subject_mismatch")
|
|
need(drops("signer") or g.signer == c["signer_pubkey"], "timeliness_signer_mismatch")
|
|
need(drops("range") or all(e["leaf_index"] < g.count for e in entries), "timeliness_leaf_out_of_range")
|
|
need(drops("before") or not any(before_group(e["from_ms"] // 1000, g.start) for e in entries),
|
|
"timeliness_before_group")
|
|
need(drops("content") or all(g.included.get(e["leaf_index"], e["content_hash"]) == e["content_hash"]
|
|
for e in entries), "timeliness_content_mismatch")
|
|
named = [e["content_hash"] for e in entries] + [f["content_hash"] for e in entries for f in checked_rungs(e, "rung_layer")]
|
|
need(drops("layer") or not any(h in envelopes and is_layer(envelopes[h]) for h in named), "timeliness_on_layer")
|
|
for f in (f for e in entries for f in checked_rungs(e, "rung_position")):
|
|
fg = groups.get(f["group"])
|
|
if fg is not None and not drops("fallback_position"):
|
|
need(f["leaf_index"] < fg.count and fg.included.get(f["leaf_index"], f["content_hash"]) == f["content_hash"],
|
|
"timeliness_fallback_mismatch")
|
|
for e in entries:
|
|
for f in checked_rungs(e, "rung_derived"):
|
|
if f["content_hash"] in envelopes and not drops("derived"):
|
|
need(derived_from(envelopes[f["content_hash"]], e["content_hash"]), "timeliness_fallback_not_derived")
|
|
|
|
|
|
# ------------------------------------------------------------------ §5: value over time
|
|
|
|
def value(entry, h):
|
|
"""§5.1: the entry's value at the instant h, floored."""
|
|
by = entry["by_ms"]
|
|
if h <= by:
|
|
return FULL
|
|
pts = [(by, FULL)] + [(p["at_ms"], p["value_ppm"]) for p in entry["decay"]]
|
|
for (ta, va), (tb, vb) in zip(pts, pts[1:]):
|
|
if ta < h <= tb:
|
|
num, den = va * (tb - h) + vb * (h - ta), tb - ta
|
|
return (2 * num + den) // (2 * den) if drops("floor") else num // den
|
|
return 0
|
|
|
|
|
|
def end(entry):
|
|
"""§5.2: the `at_ms` of its last point."""
|
|
return entry["decay"][-1]["at_ms"]
|
|
|
|
|
|
# ------------------------------------------------------------------ §6: judging delivery
|
|
|
|
def claims_step(wires, groups, plan, chained=True):
|
|
"""§6.2 step 2, and §7.2's (`plan`): each claim under jwp-receipts §3.1 steps 1 to 5, each
|
|
signer's previous claim the last of its claims held before it, then step 6 against the group
|
|
whose link hash is its subject, or the first group. Returns each claim's label, and each
|
|
claim accepted (None for one refused). `chained` is False only in a weakened run."""
|
|
by_link = {}
|
|
for g in groups:
|
|
by_link.setdefault(g.link, g)
|
|
held, labels, accepted = {}, [], []
|
|
for wire in wires:
|
|
try:
|
|
c = claim_steps(wire)
|
|
except CV.Reject as r:
|
|
labels.append(f"reject step={r.step} reason={r.reason}")
|
|
accepted.append(None)
|
|
continue
|
|
previous = held.get(c["signer_pubkey"])
|
|
held[c["signer_pubkey"]] = c
|
|
try:
|
|
if chained:
|
|
CV.check_chain(c, previous)
|
|
g = by_link.get(c["subject"], groups[0] if groups else None)
|
|
if c["class"] == "timeliness":
|
|
need(g is not None, "timeliness_subject_mismatch")
|
|
check_timeliness(c, g, by_link)
|
|
elif plan and c["class"] == "transmission" and g is not None:
|
|
KV.check_transmission(c, g.receipt)
|
|
elif plan and c["class"] == "schedule" and g is not None:
|
|
KV.check_schedule(c, g.receipt, g.pairs)
|
|
elif not drops("context"):
|
|
raise CV.Reject(6, "context_mismatch")
|
|
except CV.Reject as r:
|
|
labels.append(f"reject step={r.step} reason={r.reason}")
|
|
accepted.append(None)
|
|
continue
|
|
labels.append("accepted")
|
|
accepted.append(c)
|
|
return labels, accepted
|
|
|
|
|
|
def intents_step(accepted, groups):
|
|
"""§6.2 step 3: the entries of the accepted claims by group and `leaf_index`, in the order of
|
|
the groups given and then of `leaf_index`: [(group, leaf_index, entry, or None when set
|
|
aside)]."""
|
|
stated = {}
|
|
for c in accepted:
|
|
if c is not None and c["class"] == "timeliness":
|
|
for e in c["body"]["objects"]:
|
|
stated.setdefault((c["subject"], e["leaf_index"]), []).append(e)
|
|
out, seen = [], set()
|
|
for g in groups:
|
|
if g.link in seen:
|
|
continue
|
|
seen.add(g.link)
|
|
for i in sorted(i for link, i in stated if link == g.link):
|
|
es = stated[(g.link, i)]
|
|
stands = drops("intent_contradiction") or all(e == es[0] for e in es)
|
|
out.append((g.link, i, es[0] if stands else None))
|
|
return out
|
|
|
|
|
|
def records_step(records, groups):
|
|
"""§6.2 step 4: each record as holdings §4.4 reads and verifies it, refused whole when its
|
|
entry for a group given misstates the leaf count or is before the group, two of one holder at
|
|
one `as_of_ms` that differ set aside. Returns each record's label and the records that are
|
|
not refused (index -> record)."""
|
|
by_link = {g.link: g for g in groups}
|
|
labels, kept = [], {}
|
|
for k, hexed in enumerate(records):
|
|
try:
|
|
rec = read_record(bytes.fromhex(hexed))
|
|
mine = [(by_link[g.hex()], n) for g, n, _ in rec[3] if g.hex() in by_link]
|
|
if not drops("leaf_count") and any(g.count != n for g, n in mine):
|
|
raise HV.Refused("holdings_leaf_count_mismatch")
|
|
if not drops("held_before") and any(before_group(rec[1] // 1000, g.start) for g, _ in mine):
|
|
raise HV.Refused("held_before_group")
|
|
kept[k] = rec
|
|
labels.append("stands")
|
|
except HV.Refused as r:
|
|
labels.append("reject reason=" + r.reason)
|
|
at = {}
|
|
for k, rec in kept.items():
|
|
at.setdefault((rec[0], rec[1]), []).append(k)
|
|
for ks in at.values():
|
|
if len({HV.digest(kept[k]) for k in ks}) > 1 and not drops("record_contradiction"):
|
|
for k in ks:
|
|
labels[k] = "set-aside reason=holdings_contradicted"
|
|
return labels, kept
|
|
|
|
|
|
def held_from_records(standing, group, index):
|
|
"""§6.2 step 5: the held-at statements the records that stand give for object `index` of
|
|
`group` (a link hash in hexadecimal): (holder, group, index, instant) for each record that holds
|
|
it (holdings §5.4), its instant the record's `as_of_ms`."""
|
|
g = bytes.fromhex(group)
|
|
return [(rec[0].hex(), group, index, rec[1]) for rec in standing if HV.holds(rec, g, index) is None]
|
|
|
|
|
|
def has_entry(rec, group):
|
|
return any(g.hex() == group for g, _, _ in rec[3])
|
|
|
|
|
|
def line_holders(group, entry, standing):
|
|
"""§6.3: the holders with a record that stands and has an entry for the intent's group or a
|
|
rung's group, in the order of their first such record."""
|
|
rs = checked_rungs(entry, "rung_holders")
|
|
out = []
|
|
for rec in standing:
|
|
h = rec[0].hex()
|
|
if h not in out and (drops("line_holders") or has_entry(rec, group)
|
|
or any(has_entry(rec, r["group"]) for r in rs)):
|
|
out.append(h)
|
|
return out
|
|
|
|
|
|
def grade(entry, p, fs):
|
|
"""§6.3's table, the first outcome that holds, for a holder whose least instants for the
|
|
primary and for each rung of the ladder are p and fs (None where none)."""
|
|
by = entry["by_ms"]
|
|
if p is not None and (p < by if drops("deadline_inclusive") else p <= by):
|
|
return "on_time", FULL
|
|
# The first rung, in the ladder's order, held by the deadline.
|
|
in_time = [r for r, f in zip(rungs(entry), fs) if f is not None and (f <= by or drops("fallback_deadline"))]
|
|
if in_time:
|
|
r = in_time[-1] if drops("rung_order") else in_time[0]
|
|
# §5.3: what arrived after the deadline grades nothing, so a later primary of more value
|
|
# does not displace a rung held by then.
|
|
if not (drops("sink_rule") and p is not None and value(entry, p) > r["value_ppm"]):
|
|
return "replaced", r["value_ppm"]
|
|
if p is not None:
|
|
return "after_deadline", value(entry, p)
|
|
return "not_shown", 0
|
|
|
|
|
|
def lines(entry, holders, primary, fallback):
|
|
"""§6.3: an intent's line for each of `holders` (keys in hexadecimal), in increasing order of
|
|
key bytes, from that holder's own held-at statements for its primary and for the rungs of its
|
|
fallback, each statement naming its object. *f* is one instant for a ladder of one rung, and
|
|
the list of them for an array."""
|
|
def least(statements, holder, latest=False):
|
|
instants = [s[3] for s in statements if drops("own_statements") or s[0] == holder]
|
|
if not instants:
|
|
return None
|
|
return max(instants) if latest or drops("least") else min(instants)
|
|
|
|
out = []
|
|
for h in (holders if drops("key_order") else sorted(holders, key=bytes.fromhex)):
|
|
p = least(primary, h)
|
|
fs = [least([s for s in fallback if (s[1], s[2]) == (r["group"], r["leaf_index"])], h, drops("least_fallback"))
|
|
for r in rungs(entry)]
|
|
outcome, v = grade(entry, p, fs)
|
|
f = fs if isinstance(entry["fallback"], list) else (fs[0] if fs else None)
|
|
out.append({"holder": h, "held_ms": p, "fallback_ms": f, "outcome": outcome, "value_ppm": v})
|
|
return out
|
|
|
|
|
|
def intent_lines(link, entry, standing):
|
|
primary = held_from_records(standing, link, entry["leaf_index"])
|
|
fallback = [s for r in rungs(entry) for s in held_from_records(standing, r["group"], r["leaf_index"])]
|
|
return lines(entry, line_holders(link, entry, standing), primary, fallback)
|
|
|
|
|
|
def verdict(case):
|
|
"""§6: the verdict, or `refuse reason=R` for a group that fails."""
|
|
try:
|
|
groups = read_groups(case["groups"])
|
|
except JV.Refuse as r:
|
|
return "refuse reason=" + r.reason
|
|
labels, accepted = claims_step(case["claims"], groups, plan=False)
|
|
intents = intents_step(accepted, groups)
|
|
rlabels, kept = records_step(case["records"], groups)
|
|
standing = [kept[k] for k in sorted(kept) if rlabels[k] == "stands"]
|
|
out, found = [], {}
|
|
for link, i, entry in intents:
|
|
ls = [] if entry is None else intent_lines(link, entry, standing)
|
|
found[(link, i)] = (entry, ls)
|
|
out.append({"group": link, "leaf_index": i,
|
|
"intent": "stands" if entry is not None else "set-aside reason=timeliness_contradicted",
|
|
"end_ms": None if entry is None else end(entry), "lines": ls})
|
|
statement = None
|
|
s = case["statement"]
|
|
if s is not None:
|
|
entry, ls = found.get((s["group"], s["index"]), (None, []))
|
|
if entry is None and not drops("no_intent"):
|
|
statement = "reject reason=deadline_not_stated"
|
|
else:
|
|
line = next((x for x in ls if x["holder"] == s["holder"]), None)
|
|
statement = ({"outcome": "not_shown", "value_ppm": 0} if line is None
|
|
else {"outcome": line["outcome"], "value_ppm": line["value_ppm"]})
|
|
return {"objects": [g.verdicts for g in groups], "claims": labels, "records": rlabels, "intents": out,
|
|
"statement": statement}
|
|
|
|
|
|
# ------------------------------------------------------------------ §7: planning
|
|
|
|
def sources_valid(sources, loss, h):
|
|
"""§7.1: the sources, each exactly a bearer instance, a holder or the origin."""
|
|
if not isinstance(sources, list) or (not sources and not drops("sources_empty")):
|
|
return False
|
|
bearers, holders, origins = set(), set(), 0
|
|
for s in sources:
|
|
if not isinstance(s, dict):
|
|
return False
|
|
kind = s.get("kind")
|
|
fetch = s.get("fetch_ms")
|
|
if kind != "bearer" and not (drops("sources_fetch") or (CV.is_int(fetch) and fetch >= 1)):
|
|
return False
|
|
if kind == "bearer":
|
|
if set(s) != {"kind", "bearer_id"} or not (drops("sources_uri") or CV.is_uri(s["bearer_id"])):
|
|
return False
|
|
if s["bearer_id"] in bearers and not drops("sources_twice"):
|
|
return False
|
|
bearers.add(s["bearer_id"])
|
|
elif kind == "holder":
|
|
if set(s) != {"kind", "holder", "fetch_ms"} or not (drops("sources_key") or CV.is_hex(s["holder"], 32)):
|
|
return False
|
|
if s["holder"] in holders and not drops("sources_twice"):
|
|
return False
|
|
holders.add(s["holder"])
|
|
elif kind == "origin":
|
|
if set(s) != {"kind", "fetch_ms"}:
|
|
return False
|
|
origins += 1
|
|
elif not drops("sources_kind"):
|
|
return False
|
|
if origins > 1 and not drops("sources_twice"):
|
|
return False
|
|
return not (bearers and (loss is None or h is None)) or drops("sources_loss")
|
|
|
|
|
|
def plan_inputs(loss, h, t, sources):
|
|
"""§7.1's checks, in their order: the reason the plan is refused, or None."""
|
|
if loss is not None and not drops("loss_bound") and not (CV.is_int(loss) and loss <= 999_999):
|
|
return "loss_bound_invalid"
|
|
if h is not None and not drops("overhead") and not (CV.is_int(h) and h <= 2):
|
|
return "overhead_invalid"
|
|
if t != "from_ms" and not drops("instant") and not (CV.is_int(t) and t <= LAST_INSTANT):
|
|
return "instant_invalid"
|
|
if not sources_valid(sources, loss, h):
|
|
return "sources_invalid"
|
|
return None
|
|
|
|
|
|
def hash_and_position(v, kind):
|
|
"""§7.1: the `group` of a wanted object or a statement is 64 lowercase hexadecimal digits, and its
|
|
`index` an integer from 0 to 2^53 - 1."""
|
|
if not (drops(f"{kind}_group") or CV.is_hex(v.get("group"), 32)):
|
|
return False
|
|
index = v.get("index")
|
|
if drops(f"{kind}_index"):
|
|
return True
|
|
return CV.is_int(index) and not (drops(f"{kind}_greatest") and index == MAX_INT)
|
|
|
|
|
|
def wanted_formed(wanted):
|
|
"""§7.1: a list, each entry an object with a `group` and an `index`."""
|
|
if not isinstance(wanted, list):
|
|
return drops("wanted_list")
|
|
return all(hash_and_position(w, "wanted") if isinstance(w, dict) else drops("wanted_object") for w in wanted)
|
|
|
|
|
|
def wanted_list(wanted):
|
|
"""What a verifier without §7.1's rule for the list reads of one: nothing wanted."""
|
|
return wanted if isinstance(wanted, list) else []
|
|
|
|
|
|
def named(w):
|
|
"""What a wanted entry names. A verifier without §7.1's rule reads an entry that is no object as
|
|
naming none, so as an object with no intent."""
|
|
return (w.get("group"), w.get("index")) if isinstance(w, dict) else (None, None)
|
|
|
|
|
|
def statement_bearer(y, kind):
|
|
"""§7.1: a `bearer_id` that is a string and an absolute URI."""
|
|
bearer = y.get("bearer_id")
|
|
if not isinstance(bearer, str):
|
|
return drops(f"{kind}_bearer_string")
|
|
return drops(f"{kind}_uri") or CV.is_uri(bearer)
|
|
|
|
|
|
def symbols_formed(symbols):
|
|
"""§7.1: a list, each statement an object with a `bearer_id` that is an absolute URI, a `group`, an
|
|
`index` and a list of at least one number, each an integer from 0 to 2^53 - 1; and no two of one
|
|
object on one instance."""
|
|
if not isinstance(symbols, list):
|
|
return drops("symbols_list")
|
|
seen = set()
|
|
for y in symbols:
|
|
if not isinstance(y, dict):
|
|
if drops("symbols_statement"):
|
|
continue
|
|
return False
|
|
if not statement_bearer(y, "symbols") or not hash_and_position(y, "symbols"):
|
|
return False
|
|
if not statement_numbers(y.get("held"), "symbols"):
|
|
return False
|
|
key = (y.get("bearer_id"), y.get("group"), y.get("index"))
|
|
if key in seen and not drops("symbols_twice"):
|
|
return False
|
|
seen.add(key)
|
|
return True
|
|
|
|
|
|
def statement_numbers(held, kind):
|
|
"""§7.1: a list of at least one number, each an integer from 0 to 2^53 - 1."""
|
|
if not isinstance(held, list):
|
|
return drops(f"{kind}_held_list")
|
|
if not held and not drops(f"{kind}_empty"):
|
|
return False
|
|
if not drops(f"{kind}_integer") and any(type(n) is not int for n in held):
|
|
return False
|
|
return drops(f"{kind}_number") or all(CV.is_int(n) for n in held if type(n) is int)
|
|
|
|
|
|
def entry_on(schedules, bearer_id, group, index):
|
|
"""§7.2: the entry for an object in the schedule that stands for its group on an instance, or None."""
|
|
body = schedules.get((group, bearer_id))
|
|
return None if body is None else next((x for x in body["objects"] if x["leaf_index"] == index), None)
|
|
|
|
|
|
def holding_fits(e, held):
|
|
"""§7.2, by carousel §7's own check: one number for each source block, and no more of a `no-code`
|
|
block than it has. Whatever else the plan's loss bound is does not enter into it."""
|
|
p = KV.plan({"entry": e, "loss_ppm": 0, "overhead": 0, "at_ms": 0, "held": held})
|
|
return p.get("reason") != "holding_invalid"
|
|
|
|
|
|
def schedules_step(labels, accepted):
|
|
"""§7.2: the declarations (bearer-receipts §6.2 step 4) and the schedules (carousel §6.2 step
|
|
4), each of its group. Relabels the claims; returns the schedules that stand, by (group,
|
|
bearer_id)."""
|
|
def key(c):
|
|
return c["body"]["bearer_id"] if drops("per_group") else (c["subject"], c["body"]["bearer_id"])
|
|
|
|
def by_instance(cls):
|
|
out = {}
|
|
for i, c in enumerate(accepted):
|
|
if c is not None and c["class"] == cls:
|
|
out.setdefault(key(c), []).append(i)
|
|
return out
|
|
|
|
declared, contradicted = {}, set()
|
|
for k, idx in by_instance("transmission").items():
|
|
bodies = [accepted[i]["body"] for i in idx]
|
|
same = all(b == bodies[0] for b in bodies)
|
|
if same:
|
|
declared[k] = bodies[0]["bearer"]
|
|
else:
|
|
contradicted.add(k)
|
|
for i in idx:
|
|
labels[i] = "declared" if same else "set-aside reason=bearer_contradicted"
|
|
schedules = {}
|
|
for k, idx in by_instance("schedule").items():
|
|
bodies = [accepted[i]["body"] for i in idx]
|
|
if not KV.monotone(bodies) and not drops("schedule_contradicted"):
|
|
aside = "schedule_contradicted"
|
|
elif k in contradicted:
|
|
aside = "bearer_contradicted"
|
|
elif k not in declared and not drops("undeclared"):
|
|
aside = "bearer_not_declared"
|
|
elif declared.get(k) == "bidirectional" and not drops("bidirectional"):
|
|
aside = "schedule_on_bidirectional"
|
|
else:
|
|
aside = None
|
|
# The last of an instance's schedules is the one that stands (carousel §6.2 step 4).
|
|
last = bodies[0] if drops("schedule_last") else bodies[-1]
|
|
for i in idx:
|
|
if aside is not None:
|
|
labels[i] = "set-aside reason=" + aside
|
|
else:
|
|
labels[i] = "stands" if accepted[i]["body"] == last else "extended"
|
|
schedules[(accepted[i]["subject"], accepted[i]["body"]["bearer_id"])] = last
|
|
return schedules
|
|
|
|
|
|
def standing_at(kept, rlabels, s):
|
|
"""§7.2: the records that stand at s, by holdings §5.1 to §5.3 over those §6.2 step 4 did not
|
|
refuse, and not set aside there."""
|
|
live = [rec for k, rec in kept.items() if drops("record_at_s") or rec[1] <= s < rec[2]]
|
|
out = []
|
|
for holder in {rec[0] for rec in live}:
|
|
mine = [rec for rec in live if rec[0] == holder]
|
|
latest = max(rec[1] for rec in mine)
|
|
top = [rec for rec in mine if rec[1] == latest]
|
|
if len({HV.digest(rec) for rec in top}) == 1:
|
|
out.append(top[0])
|
|
aside = {HV.digest(kept[k]) for k in kept if rlabels[k] != "stands"}
|
|
return [rec for rec in out if HV.digest(rec) not in aside]
|
|
|
|
|
|
def arrival(src, group, index, s, ctx):
|
|
"""§7.2: a source's arrival of object `index` of `group` planned from s, or None."""
|
|
kind = src["kind"]
|
|
if kind == "origin":
|
|
return s + src["fetch_ms"]
|
|
if kind == "holder":
|
|
recs = standing_at(ctx["kept"], ctx["rlabels"], s)
|
|
held = any((drops("holder_record") or rec[0].hex() == src["holder"])
|
|
and HV.holds(rec, bytes.fromhex(group), index) is None for rec in recs)
|
|
return s + src["fetch_ms"] if held else None
|
|
if kind != "bearer":
|
|
return None
|
|
e = entry_on(ctx["schedules"], src["bearer_id"], group, index)
|
|
if e is None:
|
|
return None
|
|
blocks = sum(n for _, n in CV.schedule_blocks(e))
|
|
held = [0] * blocks
|
|
for y in ctx["symbols"]:
|
|
same_instance = y["bearer_id"] == src["bearer_id"] or drops("symbols_instance")
|
|
if same_instance and y["group"] == group and y["index"] == index and not drops("symbols_ignored"):
|
|
held = y["held"]
|
|
break
|
|
p = KV.plan({"entry": e, "loss_ppm": ctx["loss"], "overhead": ctx["h"], "at_ms": s, "held": held})
|
|
return p["complete_ms"]
|
|
|
|
|
|
def choose(entry, primary, ladder):
|
|
"""§7.3 for an object whose intent stands, from each source's arrivals of the primary and,
|
|
for each rung of the ladder in its order, of that rung."""
|
|
by = entry["by_ms"]
|
|
|
|
def choice(outcome, obj, r, i, arrived, v):
|
|
return {"outcome": outcome, "object": obj, "rung": r, "source": i, "arrival_ms": arrived, "value_ppm": v}
|
|
|
|
timely = [i for i, a in enumerate(primary) if a is not None and a <= by]
|
|
if timely:
|
|
i = min(timely, key=lambda j: (primary[j], j)) if drops("plan_order") else timely[0]
|
|
return choice("on_time", "primary", None, i, primary[i], FULL)
|
|
late = [(value(entry, a), i) for i, a in enumerate(primary) if a is not None]
|
|
best = max((v for v, _ in late), default=0)
|
|
# Rule 2: the first rung of at least the best late value that arrives by the deadline, from the
|
|
# first such source in the asker's order.
|
|
order = list(enumerate(rungs(entry)))
|
|
if drops("plan_rung_first"):
|
|
order = order[:1]
|
|
if drops("plan_rung_order"):
|
|
order.reverse()
|
|
for r, fb in order:
|
|
at_least = fb["value_ppm"] > best if drops("fallback_tie") else fb["value_ppm"] >= best
|
|
if not (at_least or drops("fallback_value")):
|
|
continue
|
|
arrivals = ladder[r]
|
|
in_time = [i for i, a in enumerate(arrivals)
|
|
if a is not None and (a < by if drops("plan_fallback_deadline") else a <= by)]
|
|
if in_time and drops("fallback_order"):
|
|
in_time.sort(key=lambda j: (arrivals[j], j))
|
|
if in_time:
|
|
return choice("replaced", "fallback", r, in_time[0], arrivals[in_time[0]], fb["value_ppm"])
|
|
if late and (best >= 1 or drops("late_zero")):
|
|
ties = [i for v, i in late if v == best]
|
|
i = ties[-1] if drops("late_tie") else ties[0]
|
|
return choice("after_deadline", "primary", None, i, primary[i], best)
|
|
return choice("no_source", None, None, None, None, 0)
|
|
|
|
|
|
def plan(case):
|
|
"""§7: the plan, or `refuse reason=R`."""
|
|
loss, h, t, sources = case["loss_ppm"], case["overhead"], case["at_ms"], case["sources"]
|
|
bad = plan_inputs(loss, h, t, sources)
|
|
if drops("wanted_before_sources") and not wanted_formed(case["wanted"]):
|
|
return "refuse reason=wanted_invalid"
|
|
if bad:
|
|
return "refuse reason=" + bad
|
|
if drops("wanted_before_symbols") and not wanted_formed(case["wanted"]):
|
|
return "refuse reason=wanted_invalid"
|
|
symbols = case.get("symbols", [])
|
|
if not symbols_formed(symbols):
|
|
return "refuse reason=symbols_invalid"
|
|
if not wanted_formed(case["wanted"]):
|
|
return "refuse reason=wanted_invalid"
|
|
# A verifier without those rules reads what it cannot count as nothing held; a formed list stays whole.
|
|
symbols = [y for y in symbols if isinstance(y, dict) and all(k in y for k in ("bearer_id", "group", "index"))
|
|
and isinstance(y.get("held"), list) and not any(isinstance(n, str) for n in y["held"])] \
|
|
if isinstance(symbols, list) else []
|
|
try:
|
|
groups = read_groups(case["groups"])
|
|
except JV.Refuse as r:
|
|
return "refuse reason=" + r.reason
|
|
labels, accepted = claims_step(case["claims"], groups, plan=True, chained=not case.get("unchained"))
|
|
intents = {(link, i): entry for link, i, entry in intents_step(accepted, groups)}
|
|
rlabels, kept = records_step(case["records"], groups)
|
|
schedules = schedules_step(labels, accepted)
|
|
# §7.2: a statement against the entry that stands for its object on its instance; one with no such
|
|
# entry is compared with nothing.
|
|
if not drops("symbols_holding"):
|
|
for y in symbols:
|
|
e = entry_on(schedules, y["bearer_id"], y["group"], y["index"])
|
|
if e is not None and not holding_fits(e, y["held"]):
|
|
return "refuse reason=holding_invalid"
|
|
ctx = {"kept": kept, "rlabels": rlabels, "schedules": schedules, "loss": loss, "h": h, "symbols": symbols}
|
|
choices = []
|
|
for w in wanted_list(case["wanted"]):
|
|
entry = intents.get(named(w))
|
|
if entry is None:
|
|
choices.append("reject reason=deadline_not_stated")
|
|
continue
|
|
s = entry["from_ms"] if t == "from_ms" else t
|
|
primary = [arrival(src, w["group"], w["index"], s, ctx) for src in sources]
|
|
ladder = [[arrival(src, r["group"], r["leaf_index"], s, ctx) for src in sources] for r in rungs(entry)]
|
|
choices.append(choose(entry, primary, ladder))
|
|
return {"claims": labels, "records": rlabels, "choices": choices}
|
|
|
|
|
|
# ------------------------------------------------------------------ §7.5: running a plan
|
|
|
|
def run_inputs(case):
|
|
"""§7.5.1's checks, in their order: the reason the run is refused, or None."""
|
|
plans = case["plans"]
|
|
if not plans and not drops("run_plans_none"):
|
|
return "plans_invalid"
|
|
if any(p["at_ms"] == "from_ms" for p in plans) and not drops("run_plans_from_ms"):
|
|
return "plans_invalid"
|
|
# §7.5.1 check 1: one that is not an integer reads as above every other, and §7.1 refuses it.
|
|
instants = [p["at_ms"] if type(p["at_ms"]) is int and p["at_ms"] >= 0 else float("inf") for p in plans]
|
|
if not drops("run_plans_order") and any(a >= b for a, b in zip(instants, instants[1:])):
|
|
return "plans_invalid"
|
|
if drops("run_wanted_before_twice") and not all(wanted_formed(p["wanted"]) for p in plans):
|
|
return "wanted_invalid"
|
|
if not drops("run_plans_twice"):
|
|
for p in plans:
|
|
wanted = wanted_list(p["wanted"])
|
|
# An entry §7.1 refuses names no object: that is check 2's, not this one's.
|
|
objects = [json.dumps(w, sort_keys=True) for w in wanted] if drops("run_wanted_malformed_twice") \
|
|
else [named(w) for w in wanted if wanted_formed([w])]
|
|
if len(set(objects)) != len(objects):
|
|
return "plans_invalid"
|
|
for p in plans:
|
|
if drops("run_wanted_before_sources") and not wanted_formed(p["wanted"]):
|
|
return "wanted_invalid"
|
|
bad = plan_inputs(case["loss_ppm"], case["overhead"], p["at_ms"], p["sources"])
|
|
if bad:
|
|
return bad
|
|
if not drops("run_wanted_late") and not wanted_formed(p["wanted"]):
|
|
return "wanted_invalid"
|
|
arrivals = case["arrivals"]
|
|
if not drops("run_arrivals_last") and any(not CV.is_int(a["at_ms"]) or a["at_ms"] > LAST_INSTANT for a in arrivals):
|
|
return "arrivals_invalid"
|
|
if not drops("run_arrivals_order") and any(a["at_ms"] > b["at_ms"] for a, b in zip(arrivals, arrivals[1:])):
|
|
return "arrivals_invalid"
|
|
if not drops("run_arrivals_twice") and len({(a["group"], a["index"]) for a in arrivals}) != len(arrivals):
|
|
return "arrivals_invalid"
|
|
def formed(kind, items, key, seconds):
|
|
if key not in case or (case[key] is None and drops(f"run_{kind}_null")):
|
|
return True
|
|
return dated_formed(kind, items, case[key], seconds)
|
|
|
|
checks = [("claims_invalid", formed("claims", case["claims"], "claims_at_ms", claim_seconds)),
|
|
("records_invalid", formed("records", case["records"], "records_at_ms", record_seconds)),
|
|
("groups_invalid", formed("groups", [json.dumps(g["group"], sort_keys=True) for g in case["groups"]],
|
|
"groups_at_ms", group_seconds)
|
|
and ("receipts_at_ms" not in case or receipts_formed(case["groups"], case["receipts_at_ms"]))),
|
|
("symbols_invalid", symbols_trace_formed(case.get("symbols", [])))]
|
|
# Checks 4 to 7, and each weakening that swaps two neighbours of them.
|
|
for k, name in enumerate(("run_dated_order", "run_order_records", "run_order_groups")):
|
|
if drops(name):
|
|
checks[k], checks[k + 1] = checks[k + 1], checks[k]
|
|
for reason, ok in checks:
|
|
if not ok:
|
|
return reason
|
|
return None
|
|
|
|
|
|
def group_seconds(key):
|
|
"""§7.5.1 check 6: the whole seconds jwp-receipts §2.4.3 reads from the `group_start` of a group that
|
|
passes jwp-receipts §3 steps 1 and 2, or None: one that fails them starts at no instant it can be
|
|
held to."""
|
|
item = json.loads(key)
|
|
try:
|
|
r = JV.read_group(item)
|
|
if not drops("run_groups_unverified"):
|
|
JV.verify_group(r)
|
|
return JV.seconds(r["group_start"])
|
|
except JV.Refuse:
|
|
return None
|
|
|
|
|
|
def receipts_formed(groups, at):
|
|
"""§7.5.1 check 6: a list for each group, an entry for each of its object receipts, none past the
|
|
last instant."""
|
|
if not isinstance(at, list):
|
|
return drops("run_receipts_not_list")
|
|
if len(at) != len(groups) and not drops("run_receipts_groups"):
|
|
return False
|
|
for g, a in zip(groups, at):
|
|
if not isinstance(a, list):
|
|
if drops("run_receipts_member_not_list"):
|
|
continue
|
|
return False
|
|
if len(a) != len(g["objects"]) and not drops("run_receipts_objects"):
|
|
return False
|
|
if not drops("run_receipts_last") and any(t is not None and (not CV.is_int(t) or t > LAST_INSTANT) for t in a):
|
|
return False
|
|
return True
|
|
|
|
|
|
def symbols_trace_formed(trace):
|
|
"""§7.5.1 check 7: each instant an integer to the last instant, none below the one before it; each
|
|
statement as §7.1 forms it; of one object on one instance, each statement at a later instant than
|
|
the one before it, of as many source blocks, and with no number below that one's."""
|
|
if not isinstance(trace, list):
|
|
return drops("run_symbols_list") or (trace is None and drops("run_symbols_null"))
|
|
last, previous = {}, 0
|
|
for s in trace:
|
|
if not isinstance(s, dict):
|
|
if drops("run_symbols_statement"):
|
|
continue
|
|
return False
|
|
at, held = s.get("at_ms"), s.get("held")
|
|
if not drops("run_symbols_last") and (not CV.is_int(at) or at > LAST_INSTANT):
|
|
return False
|
|
if at < previous and not drops("run_symbols_order"):
|
|
return False
|
|
previous = at
|
|
if not statement_bearer(s, "run_symbols") or not hash_and_position(s, "run_symbols"):
|
|
return False
|
|
if not statement_numbers(held, "run_symbols"):
|
|
return False
|
|
key = (s.get("bearer_id"), s.get("group"), s.get("index"))
|
|
if key in last:
|
|
was_at, was = last[key]
|
|
if was_at == at and not drops("run_symbols_twice"):
|
|
return False
|
|
if len(was) != len(held) and not drops("run_symbols_blocks"):
|
|
return False
|
|
if any(b < a for a, b in zip(was, held)) and not drops("run_symbols_fall"):
|
|
return False
|
|
last[key] = (at, held)
|
|
return True
|
|
|
|
|
|
def claim_seconds(wire):
|
|
"""§7.5.1 check 4: the whole seconds jwp-receipts §2.4.3 reads from the `issued_at` of a claim that
|
|
passes steps 1 to 4, or None: one that fails them is not known to be its signer's."""
|
|
try:
|
|
return JV.seconds(claim_steps(wire)["issued_at"])
|
|
except CV.Reject:
|
|
return JV.seconds(json.loads(wire)["issued_at"]) if drops("run_claims_unverified") else None
|
|
|
|
|
|
def record_seconds(hexed):
|
|
"""§7.5.1 check 5: the whole seconds of a record's `as_of_ms`, where holdings §4.4 reads and
|
|
verifies it, or None."""
|
|
try:
|
|
return read_record(bytes.fromhex(hexed))[1] // 1000
|
|
except HV.Refused:
|
|
return None
|
|
|
|
|
|
def dated_formed(kind, items, at, seconds):
|
|
"""§7.5.1 checks 4 to 6: one entry for each; none past the last instant; no statement given twice;
|
|
none held in a whole second before its own."""
|
|
if not isinstance(at, list):
|
|
return drops(f"run_{kind}_not_list")
|
|
if len(at) != len(items) and not drops(f"run_{kind}_length"):
|
|
return False
|
|
if not drops(f"run_{kind}_last") and any(a is not None and (not CV.is_int(a) or a > LAST_INSTANT) for a in at):
|
|
return False
|
|
if not drops(f"run_{kind}_twice") and len(set(items)) != len(items):
|
|
return False
|
|
if drops(f"run_{kind}_signed"):
|
|
return True
|
|
for item, a in zip(items, at):
|
|
since = seconds(item)
|
|
if a is None or since is None:
|
|
continue
|
|
# A statement's own second admits an instant anywhere in it.
|
|
if (a < since * 1000 + 999 if drops(f"run_{kind}_signed_ms") else a // 1000 < since):
|
|
return False
|
|
return True
|
|
|
|
|
|
def read_by(at, i, t, kind):
|
|
"""§7.5.1: what is held by t: its instant is at most t, or it has none."""
|
|
if not isinstance(at, list) or i >= len(at) or at[i] is None or drops(f"run_{kind}_undated"):
|
|
return True
|
|
return at[i] < t if drops(f"run_{kind}_strict") else at[i] <= t
|
|
|
|
|
|
def read_at(case, t):
|
|
"""§7.5.1: a run's inputs for its plan at t: the claims and records held by then, in the order given,
|
|
and their positions."""
|
|
claims = [i for i, _ in enumerate(case["claims"]) if read_by(case.get("claims_at_ms"), i, t, "claims")]
|
|
records = [i for i, _ in enumerate(case["records"]) if read_by(case.get("records_at_ms"), i, t, "records")]
|
|
return claims, records
|
|
|
|
|
|
def envelope_held(case, g, o, t):
|
|
"""§7.5.1: an envelope is held from its object's arrival; one whose object no arrival names, from
|
|
before the first plan. A group or receipt that does not read names no object."""
|
|
if drops("run_envelopes_undated"):
|
|
return True
|
|
try:
|
|
link, index = JV.link(JV.read_group(g["group"])).hex(), JV.read_object(o["receipt"])["object_id"]
|
|
except JV.Refuse:
|
|
return True
|
|
a = next((a for a in case["arrivals"] if a["group"] == link and a["index"] == index), None)
|
|
if a is None:
|
|
return not drops("run_envelopes_no_arrival")
|
|
return a["at_ms"] < t if drops("run_envelopes_strict") else a["at_ms"] <= t
|
|
|
|
|
|
def groups_read(case, t):
|
|
"""§7.5.1: the groups a plan at t reads. A run that dates none reads each whole, envelopes and all;
|
|
one that dates either reads what the sink held by t."""
|
|
at_groups, at_receipts = case.get("groups_at_ms"), case.get("receipts_at_ms")
|
|
if at_groups is None and at_receipts is None and not drops("run_envelopes_opt_in"):
|
|
return case["groups"]
|
|
out = []
|
|
for i, g in enumerate(case["groups"]):
|
|
if not read_by(at_groups, i, t, "groups"):
|
|
continue
|
|
at = at_receipts[i] if isinstance(at_receipts, list) and i < len(at_receipts) else None
|
|
objects = [dict(o, envelope=o["envelope"] if envelope_held(case, g, o, t) else None)
|
|
for k, o in enumerate(g["objects"]) if read_by(at, k, t, "receipts")]
|
|
out.append({"group": g["group"], "objects": objects})
|
|
return out
|
|
|
|
|
|
def symbols_read(case, t):
|
|
"""§7.5.1: what the sink holds at t: of each object on each instance, the last statement by t, as
|
|
§7.1 gives it."""
|
|
read = {}
|
|
trace = case.get("symbols", [])
|
|
for s in [s for s in trace if isinstance(s, dict)] if isinstance(trace, list) else []:
|
|
if not (drops("run_symbols_undated") or (s["at_ms"] < t if drops("run_symbols_strict") else s["at_ms"] <= t)):
|
|
continue
|
|
key = (s.get("bearer_id"), s.get("group"), s.get("index"))
|
|
if key not in read or not drops("run_symbols_latest"):
|
|
read[key] = {k: s[k] for k in ("bearer_id", "group", "index", "held") if k in s}
|
|
return list(read.values())
|
|
|
|
|
|
def not_held(n, read, labels):
|
|
"""§7.5.3: each of n statements' labels, `not-held` for one the plan did not read."""
|
|
out = ["not-held"] * n
|
|
for i, label in zip(read, labels):
|
|
out[i] = label
|
|
return out
|
|
|
|
|
|
def same_source(a, b):
|
|
"""§7.5.2: both name one bearer instance or one holder's key, or both are the origin, whatever
|
|
their `fetch_ms`."""
|
|
if a["kind"] != b["kind"]:
|
|
return False
|
|
if drops("run_same_fetch_ms") and a.get("fetch_ms") != b.get("fetch_ms"):
|
|
return False
|
|
return a.get("bearer_id") == b.get("bearer_id") and a.get("holder") == b.get("holder")
|
|
|
|
|
|
class Fetch:
|
|
"""§7.5.2: the object a fetch takes, its source, and the plan it serves."""
|
|
|
|
def __init__(self, obj, rung, takes, plan, index, source):
|
|
self.obj, self.rung, self.takes, self.plan, self.index, self.source = obj, rung, takes, plan, index, source
|
|
|
|
|
|
def takes(key, entry, choice):
|
|
"""The object a choice takes: the primary, or the rung it names."""
|
|
if choice["object"] == "primary":
|
|
return key
|
|
r = rungs(entry)[choice["rung"]]
|
|
return (r["group"], r["leaf_index"])
|
|
|
|
|
|
def run(case):
|
|
"""§7.5: the run of a sink's plans against its arrivals, or `refuse reason=R`."""
|
|
bad = run_inputs(case)
|
|
if bad:
|
|
return "refuse reason=" + bad
|
|
# Each plan derived by §7.2 and §7.3 from what the sink held by its instant, with its own
|
|
# sources and wanted objects; a group that fails refuses the run as it refuses a plan.
|
|
plans, read, intents_of = [], [], []
|
|
dated = ("claims_at_ms" in case or "records_at_ms" in case
|
|
or ("groups_at_ms" in case and not drops("run_groups_read"))
|
|
or ("receipts_at_ms" in case and not drops("run_receipts_read")))
|
|
unchained = dated and drops("run_claims_chain")
|
|
for p in case["plans"]:
|
|
cs, rs = read_at(case, p["at_ms"])
|
|
claims, records = [case["claims"][i] for i in cs], [case["records"][i] for i in rs]
|
|
held_groups = groups_read(case, p["at_ms"])
|
|
derived = plan(dict(case, at_ms=p["at_ms"], sources=p["sources"], wanted=p["wanted"], claims=claims,
|
|
records=records, groups=held_groups, symbols=symbols_read(case, p["at_ms"]),
|
|
unchained=unchained))
|
|
if isinstance(derived, str):
|
|
return derived
|
|
plans.append(derived["choices"])
|
|
read.append({"claims": not_held(len(case["claims"]), cs, derived["claims"]),
|
|
"records": not_held(len(case["records"]), rs, derived["records"])})
|
|
groups = read_groups(held_groups)
|
|
_, accepted = claims_step(claims, groups, plan=True, chained=not unchained)
|
|
intents_of.append({(link, i): entry for link, i, entry in intents_step(accepted, groups)})
|
|
|
|
def objects_run(k):
|
|
"""§7.5.2: what plan k wants whose choice is not refused, in its wanted order, each with its
|
|
choice and the entry that plan read."""
|
|
return [((w["group"], w["index"]), c, intents_of[k][(w["group"], w["index"])])
|
|
for w, c in zip(wanted_list(case["plans"][k]["wanted"]), plans[k]) if isinstance(c, dict)]
|
|
|
|
instants = {p["at_ms"] for p in case["plans"]} | {a["at_ms"] for a in case["arrivals"]}
|
|
for k in range(len(plans)):
|
|
for _, _, e in objects_run(k):
|
|
instants |= {e["by_ms"], end(e)}
|
|
|
|
held, settled, passed, fetches, force, out = {}, set(), set(), {}, None, []
|
|
|
|
def decide(n, k, action, key, obj=None, rung=None, source=None, v=None):
|
|
out.append({"at_ms": n, "plan": k, "action": action, "group": key[0], "index": key[1],
|
|
"object": obj, "rung": rung, "source": source, "value_ppm": v})
|
|
|
|
def abandon(n, key):
|
|
f = fetches.pop(key)
|
|
decide(n, f.plan, "abandon", key, f.obj, f.rung, f.index)
|
|
|
|
for n in sorted(instants):
|
|
# Step 1.
|
|
for a in case["arrivals"]:
|
|
if a["at_ms"] == n:
|
|
arrived = (a["group"], a["index"])
|
|
held[arrived] = n
|
|
if not drops("run_arrival_completes"):
|
|
for key in [key for key, f in fetches.items() if f.takes == arrived]:
|
|
del fetches[key]
|
|
# Step 2.
|
|
new = next((k for k, p in enumerate(case["plans"]) if p["at_ms"] == n), None)
|
|
if new is not None:
|
|
sources = case["plans"][new]["sources"]
|
|
choices = {key: (c, e) for key, c, e in objects_run(new)}
|
|
|
|
def continues(key, f):
|
|
c, e = choices.get(key, (None, None))
|
|
if c is None or c["object"] is None or drops("run_continue"):
|
|
return False
|
|
return takes(key, e, c) == f.takes and same_source(sources[c["source"]], f.source)
|
|
|
|
if force is not None:
|
|
for key, _, _ in objects_run(force):
|
|
if key not in settled and key in fetches and not continues(key, fetches[key]):
|
|
if drops("run_drop_abandon") and key not in choices:
|
|
continue
|
|
abandon(n, key)
|
|
force = new
|
|
for key, c, e in objects_run(new):
|
|
if (key in settled and not drops("run_settled_refetch")) or c["object"] is None:
|
|
continue
|
|
if drops("run_symbols_hold") and c["arrival_ms"] == n and sources[c["source"]]["kind"] == "bearer":
|
|
# §7.5.1: symbols are not an arrival, however many are stated.
|
|
held[takes(key, e, c)] = n
|
|
if takes(key, e, c) in held and not drops("run_held_fetch"):
|
|
continue
|
|
f = fetches.get(key)
|
|
if f is not None and continues(key, f):
|
|
if not drops("run_continued_plan"):
|
|
f.plan, f.index = new, c["source"]
|
|
continue
|
|
fetches[key] = Fetch(c["object"], c["rung"], takes(key, e, c), new, c["source"], sources[c["source"]])
|
|
decide(n, new, "fetch", key, c["object"], c["rung"], c["source"])
|
|
if force is None:
|
|
continue
|
|
current = objects_run(force)
|
|
if drops("run_passed_keeps_running"):
|
|
# §7.5.4: an object a later plan has no intent for is no longer run, passed or not.
|
|
run_now = {key for key, _, _ in current}
|
|
current = current + [x for k in range(force) for x in objects_run(k)
|
|
if x[0] in passed and x[0] not in run_now]
|
|
# Step 3, by what the sink holds by `by_ms`.
|
|
for key, _, e in current:
|
|
if key in settled or key in passed or e["by_ms"] > n:
|
|
continue
|
|
limit = n if drops("run_by_deadline") else e["by_ms"]
|
|
holds = [(r, f) for r, f in enumerate(rungs(e))
|
|
if held.get((f["group"], f["leaf_index"]), limit + 1) <= limit]
|
|
if held.get(key, limit + 1) <= limit:
|
|
settled.add(key)
|
|
decide(n, force, "present", key, "primary", v=FULL)
|
|
elif holds:
|
|
r, f = holds[-1] if drops("run_rung_order") else holds[0]
|
|
settled.add(key)
|
|
decide(n, force, "present", key, "fallback", r, v=f["value_ppm"])
|
|
else:
|
|
passed.add(key)
|
|
decide(n, force, "passed", key)
|
|
if key in fetches:
|
|
after_present = key in settled and not drops("run_present_abandon")
|
|
after_passed = key in passed and fetches[key].obj == "fallback" and not drops("run_passed_abandon")
|
|
if after_present or after_passed:
|
|
abandon(n, key)
|
|
# Step 4, at the primary's value at its arrival.
|
|
for key, _, e in current:
|
|
if key in passed and key not in settled and key in held:
|
|
v = value(e, n if drops("run_late_value") else held[key])
|
|
if v >= 1 or drops("run_late_zero"):
|
|
settled.add(key)
|
|
decide(n, force, "present", key, "primary", v=v)
|
|
# Step 5.
|
|
for key, _, e in current:
|
|
if key in passed and key not in settled and end(e) <= n:
|
|
if key in fetches and not drops("run_end_abandon"):
|
|
abandon(n, key)
|
|
settled.add(key)
|
|
if not drops("run_ended"):
|
|
decide(n, force, "ended", key)
|
|
return {"plans": plans, "read": read, "decisions": out} if dated else {"plans": plans, "decisions": out}
|
|
|
|
|
|
# ------------------------------------------------------------------ the corpus
|
|
|
|
def claim_outcome(c):
|
|
try:
|
|
claim_steps(c["claim"])
|
|
return "accept"
|
|
except CV.Reject as r:
|
|
return f"reject step={r.step} reason={r.reason}"
|
|
|
|
|
|
def value_outcome(c):
|
|
"""§5 for an entry read alone, as §3.1 steps 2 and 4 read an entry."""
|
|
e = c["entry"]
|
|
CV.timeliness_entry_body(e)
|
|
CV.timeliness_entry_rules(e, None)
|
|
return {"value_ppm": value(e, c["at_ms"]), "end_ms": end(e)}
|
|
|
|
|
|
RUNS = (("claims", claim_outcome), ("values", value_outcome), ("verdicts", verdict), ("plans", plan), ("runs", run))
|
|
KINDS = tuple(k for k, _ in RUNS)
|
|
|
|
|
|
def strings(v, out):
|
|
if isinstance(v, str):
|
|
out.append(v)
|
|
elif isinstance(v, list):
|
|
for x in v:
|
|
strings(x, out)
|
|
elif isinstance(v, dict):
|
|
for x in v.values():
|
|
strings(x, out)
|
|
|
|
|
|
def check(doc, quiet=False):
|
|
"""The failures of a corpus document: each case's outcome, every reason of §9, every outcome of
|
|
§6.3 and every action of §7.5.3."""
|
|
fails = []
|
|
for kind, run in RUNS:
|
|
for c in doc[kind]:
|
|
try:
|
|
got = run(c)
|
|
except CV.Reject as r:
|
|
got = {"error": f"reject step={r.step} reason={r.reason}"}
|
|
except (KeyError, TypeError, ValueError, IndexError, AttributeError, StopIteration,
|
|
ZeroDivisionError) as e:
|
|
got = {"error": repr(e)}
|
|
if got != c["outcome"]:
|
|
fails.append(c["name"])
|
|
if not quiet:
|
|
print(f"FAIL {c['name']}\n stated {json.dumps(c['outcome'])}\n got {json.dumps(got)}")
|
|
stated = []
|
|
for kind in KINDS:
|
|
for c in doc[kind]:
|
|
strings(c["outcome"], stated)
|
|
for code, _ in reasons_table():
|
|
if not any(s == code or s.endswith("reason=" + code) for s in stated):
|
|
fails.append("no case gives " + code)
|
|
for outcome in outcomes_table():
|
|
if outcome not in stated:
|
|
fails.append("no case gives the outcome " + outcome)
|
|
for action in ACTIONS:
|
|
if action not in stated:
|
|
fails.append("no case gives the action " + action)
|
|
return fails
|
|
|
|
|
|
def mutations(doc):
|
|
"""Copies of the corpus, one defect each, every one of which must fail."""
|
|
def case(d, name):
|
|
return next(c for k in KINDS for c in d[k] if c["name"] == name)
|
|
|
|
def m_instant(d):
|
|
# A millisecond before the end is 75; at the end, 0.
|
|
case(d, "example_last_ms")["at_ms"] += 1
|
|
|
|
def m_end(d):
|
|
case(d, "hard_one_ms")["outcome"]["end_ms"] -= 1
|
|
|
|
def m_floor(d):
|
|
case(d, "step_mid")["outcome"]["value_ppm"] += 1
|
|
|
|
def m_accept(d):
|
|
case(d, "decay_near_collinear")["outcome"] = "reject step=4 reason=timeliness_decay_invalid"
|
|
|
|
def m_line(d):
|
|
case(d, "holder_one_ms_after")["outcome"]["intents"][0]["lines"][0]["value_ppm"] = 999_801
|
|
|
|
def m_earliest(d):
|
|
c = case(d, "earliest_record_counts")
|
|
c["records"] = c["records"][1:]
|
|
|
|
def m_statement(d):
|
|
case(d, "statement_line")["statement"]["holder"] = d["keys"]["holder_c"]
|
|
|
|
def m_envelope(d):
|
|
c = case(d, "on_layer")
|
|
c["groups"][0]["objects"][3]["envelope"] = None
|
|
|
|
def m_order(d):
|
|
c = case(d, "order_over_earliest")
|
|
c["sources"] = list(reversed(c["sources"]))
|
|
|
|
def m_deadline(d):
|
|
case(d, "on_time_at_deadline")["at_ms"] += 1
|
|
|
|
def m_fetch(d):
|
|
# Both late sources at 300,000: the first in order, the origin.
|
|
case(d, "late_choice_by_value")["sources"][1]["fetch_ms"] = 66_000
|
|
|
|
def m_loss(d):
|
|
# 70%: 3 symbols a pass of the 12 needed, a fourth pass.
|
|
case(d, "every_pass_from_first")["loss_ppm"] = 700_000
|
|
|
|
def m_from_ms(d):
|
|
case(d, "publisher_check_fails")["at_ms"] = d["plans"][0]["at_ms"]
|
|
|
|
def m_object(d):
|
|
case(d, "fallback_from_carousel")["outcome"]["choices"][0]["object"] = "primary"
|
|
|
|
def m_reason_gone(d):
|
|
d["verdicts"].remove(case(d, "record_before_group"))
|
|
|
|
def m_extended(d):
|
|
case(d, "plan_bearer_extended")["outcome"]["claims"][1] = "stands"
|
|
|
|
def m_withdrawn(d):
|
|
# The withdrawn pass's schedule given first and last alike: one body, which stands.
|
|
c = case(d, "plan_bearer_withdrawn")
|
|
c["claims"][2] = c["claims"][1]
|
|
|
|
def m_rung_extended(d):
|
|
# The rung above the one the extended schedule reaches.
|
|
case(d, "ladder_rung_on_extended_schedule")["outcome"]["choices"][0]["rung"] = 0
|
|
|
|
def m_rung_value(d):
|
|
# The lower rung presented at the higher's value.
|
|
case(d, "ladder_lower_rung_alone")["outcome"]["intents"][0]["lines"][0]["value_ppm"] = 700_000
|
|
|
|
def m_rung_instant(d):
|
|
# The lower rung a millisecond late: nothing held by the deadline.
|
|
r = case(d, "ladder_lower_rung_alone")
|
|
r["outcome"]["intents"][0]["lines"][0]["fallback_ms"][1] += 1
|
|
|
|
def m_rung_form(d):
|
|
# A ladder's line reports its list.
|
|
r = case(d, "ladder_first_rung_replaces")
|
|
r["outcome"]["intents"][0]["lines"][0]["fallback_ms"] = r["outcome"]["intents"][0]["lines"][0]["fallback_ms"][0]
|
|
|
|
def m_plan_rung(d):
|
|
case(d, "ladder_lower_rung")["outcome"]["choices"][0]["rung"] = 0
|
|
|
|
def m_arrival_late(d):
|
|
# The trace a millisecond later is a late object at another value.
|
|
case(d, "run_late_object")["arrivals"][0]["at_ms"] += 1
|
|
|
|
def m_arrival_gone(d):
|
|
case(d, "run_on_time")["arrivals"] = []
|
|
|
|
def m_decision_value(d):
|
|
case(d, "run_late_object")["outcome"]["decisions"][-1]["value_ppm"] += 1
|
|
|
|
def m_decision_action(d):
|
|
case(d, "run_every_rung_passed_no_primary")["outcome"]["decisions"][1]["action"] = "ended"
|
|
|
|
def m_plan_dropped(d):
|
|
c = case(d, "run_replaced_changes_source")
|
|
c["plans"] = c["plans"][:1]
|
|
|
|
def m_plan_instant(d):
|
|
case(d, "run_replaced_at_deadline")["plans"][1]["at_ms"] += 1
|
|
|
|
def m_decision_order(d):
|
|
c = case(d, "run_in_wanted_order")
|
|
c["outcome"]["decisions"][0], c["outcome"]["decisions"][1] = c["outcome"]["decisions"][1], c["outcome"]["decisions"][0]
|
|
|
|
def m_dates_dropped(d):
|
|
# The schedules given undated: every plan reads the extension, and the fetches differ.
|
|
c = case(d, "run_extension_reaches_mid_run")
|
|
del c["claims_at_ms"]
|
|
|
|
def m_date_later(d):
|
|
# Past the plan that read it.
|
|
case(d, "run_extension_reaches_mid_run")["claims_at_ms"][3] = T0_PLUS_LATER
|
|
|
|
def m_read_label(d):
|
|
case(d, "run_extension_reaches_mid_run")["outcome"]["read"][1]["claims"][3] = "not-held"
|
|
|
|
def m_dated_fetch(d):
|
|
# The rung fetched at T0, as the undated run does.
|
|
first = case(d, "run_extension_reaches_mid_run")["outcome"]["decisions"][0]
|
|
first["object"], first["rung"], first["source"] = "fallback", 1, 1
|
|
|
|
def m_withdrawn_read(d):
|
|
# The withdrawal never read: every schedule stands in the second plan.
|
|
c = case(d, "run_contradiction_mid_run")
|
|
c["claims_at_ms"][3] = T0_PLUS_LATER
|
|
|
|
def m_parent_first(d):
|
|
# The parent held with its child: nothing is refused.
|
|
c = case(d, "run_claim_parent_not_held_yet")
|
|
c["claims_at_ms"][0] = c["claims_at_ms"][1]
|
|
|
|
def m_before_signed(d):
|
|
# Within the claim's own second: no refusal.
|
|
case(d, "claims_held_before_signed")["claims_at_ms"][0] += 1
|
|
|
|
def m_record_date(d):
|
|
del case(d, "run_record_reaches_mid_run")["records_at_ms"]
|
|
|
|
def m_claims_reason_gone(d):
|
|
d["runs"] = [c for c in d["runs"] if c["outcome"] != "refuse reason=claims_invalid"]
|
|
|
|
def m_records_reason_gone(d):
|
|
d["runs"] = [c for c in d["runs"] if c["outcome"] != "refuse reason=records_invalid"]
|
|
|
|
def m_reason_gone_run(d):
|
|
d["runs"] = [c for c in d["runs"] if c["outcome"] != "refuse reason=arrivals_invalid"]
|
|
|
|
def m_symbols_gone(d):
|
|
# Holding nothing, no rung is in reach: the rung the case states is not chosen.
|
|
del case(d, "run_symbols_reach_a_rung")["symbols"]
|
|
|
|
def m_symbols_later(d):
|
|
# Past the only plan.
|
|
case(d, "run_symbols_reach_a_rung")["symbols"][0]["at_ms"] = T0_PLUS_LATER
|
|
|
|
def m_symbols_fewer(d):
|
|
# 6 of 12 takes two passes, so the rung comes a pass later.
|
|
case(d, "run_symbols_reach_a_rung")["symbols"][0]["held"] = [6]
|
|
|
|
def m_symbols_instance(d):
|
|
case(d, "run_symbols_reach_a_rung")["symbols"][0]["bearer_id"] = "urn:example:bearer:b3"
|
|
|
|
def m_symbols_arrival_stated(d):
|
|
case(d, "run_symbols_reach_a_rung")["outcome"]["plans"][0][0]["arrival_ms"] += 1
|
|
|
|
def m_symbols_fetch_stated(d):
|
|
d_ = case(d, "run_symbols_reach_a_rung")["outcome"]["decisions"]
|
|
d_[:] = d_[1:]
|
|
|
|
def m_plan_symbols_gone(d):
|
|
del case(d, "plan_symbols_reach_a_rung")["symbols"]
|
|
|
|
def m_symbols_reason_gone(d):
|
|
for k in ("plans", "runs"):
|
|
d[k] = [c for c in d[k] if c["outcome"] != "refuse reason=symbols_invalid"]
|
|
|
|
def m_group_date_dropped(d):
|
|
del case(d, "run_group_reaches_mid_run")["groups_at_ms"]
|
|
|
|
def m_group_date_later(d):
|
|
case(d, "run_group_reaches_mid_run")["groups_at_ms"][0] = T0_PLUS_LATER
|
|
|
|
def m_envelope_read_early(d):
|
|
# With no list the envelope is read throughout, so the schedule is refused from T0.
|
|
del case(d, "run_envelope_read_from_its_arrival")["groups_at_ms"]
|
|
|
|
def m_envelope_arrival_gone(d):
|
|
# An envelope whose object no arrival names was held from before the first plan.
|
|
case(d, "run_envelope_read_from_its_arrival")["arrivals"] = []
|
|
|
|
def m_receipt_date_dropped(d):
|
|
del case(d, "run_object_receipt_reaches_mid_run")["receipts_at_ms"]
|
|
|
|
def m_groups_reason_gone(d):
|
|
d["runs"] = [c for c in d["runs"] if c["outcome"] != "refuse reason=groups_invalid"]
|
|
|
|
def m_passed_then_presented(d):
|
|
# A primary arriving after an entry is set aside is not presented.
|
|
case(d, "run_entry_contradicted_after_passed")["outcome"]["decisions"].append(
|
|
case(d, "run_late_object")["outcome"]["decisions"][-1])
|
|
|
|
def m_contradiction_early(d):
|
|
# Read from before the first plan, the entry is set aside before anything is fetched.
|
|
case(d, "run_entry_contradicted_after_passed")["claims_at_ms"][1] = None
|
|
|
|
def m_enough_then_arrives(d):
|
|
# With an arrival the sink holds the object, and presents it at the deadline.
|
|
case(d, "run_symbols_enough_no_arrival")["arrivals"] = [{"group": d["groups"]["F"], "index": 0, "at_ms": 1_793_664_021_000}]
|
|
|
|
def restored(name, pointer, value):
|
|
"""A copy in which one case written over holds its well-formed member: its refusal goes."""
|
|
def m(d):
|
|
target = case(d, name)
|
|
for key in pointer[:-1]:
|
|
target = target[key]
|
|
target[pointer[-1]] = value
|
|
m.__name__ = "m_restored_" + name
|
|
return m
|
|
|
|
bearer_b2 = "urn:example:bearer:b2"
|
|
|
|
def statement(d):
|
|
return [{"bearer_id": bearer_b2, "group": d["groups"]["F"], "index": 0, "at_ms": 1_793_664_020_000, "held": [10]}]
|
|
|
|
def symbols_restored(name):
|
|
def m(d):
|
|
case(d, name)["symbols"] = statement(d)
|
|
m.__name__ = "m_restored_" + name
|
|
return m
|
|
|
|
def plan_symbols_restored(name):
|
|
def m(d):
|
|
case(d, name)["symbols"] = [{k: v for k, v in statement(d)[0].items() if k != "at_ms"}]
|
|
m.__name__ = "m_restored_" + name
|
|
return m
|
|
|
|
def wanted_restored(name, pointer, as_list=False):
|
|
"""A copy in which one case written over names a wanted object again."""
|
|
def m(d):
|
|
target = case(d, name)
|
|
for key in pointer[:-1]:
|
|
target = target[key]
|
|
formed = {"group": d["groups"]["G"], "index": 0}
|
|
target[pointer[-1]] = [formed] if as_list else formed
|
|
m.__name__ = "m_restored_" + name
|
|
return m
|
|
|
|
def statement_restored(name, instant):
|
|
"""A copy in which one case written over holds a formed statement again."""
|
|
def m(d):
|
|
formed = {"bearer_id": "urn:example:bearer:b3", "group": d["groups"]["F"], "index": 0, "held": [10]}
|
|
case(d, name)["symbols"][0] = dict(formed, at_ms=1_793_664_020_000) if instant else formed
|
|
m.__name__ = "m_restored_" + name
|
|
return m
|
|
|
|
def m_wanted_reason_gone(d):
|
|
for k in ("plans", "runs"):
|
|
d[k] = [c for c in d[k] if c["outcome"] != "refuse reason=wanted_invalid"]
|
|
|
|
def m_wanted_twice_reason(d):
|
|
# One object wanted twice stays the plans' reason.
|
|
case(d, "run_wanted_twice_and_malformed")["outcome"] = "refuse reason=wanted_invalid"
|
|
|
|
def m_not_an_instant_restored(d):
|
|
for name, key in (("claims_at_not_an_instant", "claims_at_ms"), ("records_at_not_an_instant", "records_at_ms"),
|
|
("groups_at_not_an_instant", "groups_at_ms")):
|
|
case(d, name)[key][0] = 1_793_664_002_000
|
|
|
|
return [m_instant, m_end, m_floor, m_accept, m_line, m_earliest, m_statement, m_envelope, m_order, m_deadline,
|
|
m_fetch, m_loss, m_from_ms, m_object, m_reason_gone, m_extended, m_withdrawn, m_rung_extended, m_rung_value,
|
|
m_rung_instant, m_rung_form, m_plan_rung,
|
|
m_arrival_late, m_arrival_gone, m_decision_value, m_decision_action, m_plan_dropped, m_plan_instant,
|
|
m_decision_order, m_reason_gone_run, m_dates_dropped, m_date_later, m_read_label, m_dated_fetch,
|
|
m_withdrawn_read, m_parent_first, m_before_signed, m_record_date, m_claims_reason_gone,
|
|
m_records_reason_gone, m_symbols_gone, m_symbols_later, m_symbols_fewer, m_symbols_instance,
|
|
m_symbols_arrival_stated, m_symbols_fetch_stated, m_plan_symbols_gone, m_symbols_reason_gone,
|
|
m_group_date_dropped, m_group_date_later, m_envelope_read_early, m_envelope_arrival_gone,
|
|
m_receipt_date_dropped, m_groups_reason_gone, m_passed_then_presented, m_contradiction_early,
|
|
m_not_an_instant_restored, m_enough_then_arrives,
|
|
restored("claims_at_not_a_list", ["claims_at_ms"], [None]),
|
|
restored("claims_at_null", ["claims_at_ms"], [None]),
|
|
restored("records_at_not_a_list", ["records_at_ms"], [None]),
|
|
restored("groups_at_not_a_list", ["groups_at_ms"], [None]),
|
|
restored("receipts_at_not_a_list", ["receipts_at_ms"], [[None] * 4]),
|
|
restored("receipts_at_one_not_a_list", ["receipts_at_ms", 0], [None] * 4),
|
|
restored("symbols_held_a_string", ["symbols", 0, "held"], [10]),
|
|
restored("symbols_held_a_fraction", ["symbols", 0, "held"], [10]),
|
|
restored("symbols_held_not_a_list", ["symbols", 0, "held"], [10]),
|
|
symbols_restored("symbols_not_a_list"),
|
|
symbols_restored("symbols_null"),
|
|
restored("plan_symbols_held_a_string", ["symbols", 0, "held"], [10]),
|
|
restored("plan_symbols_held_a_fraction", ["symbols", 0, "held"], [10]),
|
|
restored("plan_symbols_held_not_a_list", ["symbols", 0, "held"], [10]),
|
|
plan_symbols_restored("plan_symbols_not_a_list"),
|
|
statement_restored("plan_symbols_statement_not_an_object", False),
|
|
statement_restored("plan_symbols_bearer_id_not_a_string", False),
|
|
statement_restored("plan_symbols_group_uppercase", False),
|
|
statement_restored("plan_symbols_index_a_string", False),
|
|
statement_restored("symbols_statement_not_an_object", True),
|
|
statement_restored("symbols_bearer_id_not_a_string", True),
|
|
statement_restored("symbols_group_uppercase", True),
|
|
statement_restored("symbols_index_a_string", True),
|
|
wanted_restored("plan_wanted_not_an_object", ["wanted", 0]),
|
|
wanted_restored("plan_wanted_group_uppercase", ["wanted", 0]),
|
|
wanted_restored("plan_wanted_index_a_string", ["wanted", 0]),
|
|
wanted_restored("plan_wanted_not_a_list", ["wanted"], as_list=True),
|
|
wanted_restored("wanted_not_an_object", ["plans", 0, "wanted", 0]),
|
|
wanted_restored("wanted_group_uppercase", ["plans", 0, "wanted", 0]),
|
|
wanted_restored("wanted_index_a_string", ["plans", 0, "wanted", 0]),
|
|
wanted_restored("wanted_not_a_list", ["plans", 0, "wanted"], as_list=True),
|
|
m_wanted_reason_gone, m_wanted_twice_reason]
|
|
|
|
|
|
WEAKENINGS = ["subject", "signer", "range", "before", "same_second", "content", "layer", "fallback_position", "derived",
|
|
"context", "intent_contradiction", "leaf_count", "held_before", "record_contradiction", "line_holders",
|
|
"least", "least_fallback", "own_statements", "key_order", "deadline_inclusive", "fallback_deadline",
|
|
"sink_rule", "floor", "no_intent", "loss_bound",
|
|
"overhead", "instant", "sources_empty", "sources_kind", "sources_uri", "sources_key", "sources_fetch",
|
|
"sources_twice", "sources_loss", "per_group", "schedule_contradicted", "schedule_last", "undeclared",
|
|
"bidirectional", "record_at_s",
|
|
"holder_record", "plan_order", "fallback_value", "fallback_tie", "fallback_order",
|
|
"plan_fallback_deadline", "late_zero", "late_tie",
|
|
"rung_layer", "rung_position", "rung_derived", "rung_holders", "rung_order",
|
|
"plan_rung_first", "plan_rung_order",
|
|
"run_plans_none", "run_plans_from_ms", "run_plans_order", "run_plans_twice", "run_arrivals_last",
|
|
"run_arrivals_order", "run_arrivals_twice", "run_same_fetch_ms", "run_arrival_completes", "run_continue",
|
|
"run_drop_abandon", "run_settled_refetch", "run_held_fetch", "run_continued_plan", "run_by_deadline",
|
|
"run_rung_order", "run_present_abandon", "run_passed_abandon", "run_late_value", "run_late_zero",
|
|
"run_end_abandon", "run_ended",
|
|
"run_claims_undated", "run_claims_strict", "run_claims_length", "run_claims_last", "run_claims_twice",
|
|
"run_claims_signed", "run_claims_signed_ms", "run_claims_unverified", "run_claims_chain",
|
|
"run_records_undated", "run_records_strict", "run_records_length", "run_records_last",
|
|
"run_records_twice", "run_records_signed", "run_records_signed_ms", "run_dated_order",
|
|
"symbols_uri", "symbols_empty", "symbols_number", "symbols_twice", "symbols_holding", "symbols_ignored",
|
|
"symbols_instance", "run_symbols_last", "run_symbols_order", "run_symbols_uri", "run_symbols_empty",
|
|
"run_symbols_number", "run_symbols_twice", "run_symbols_blocks", "run_symbols_fall",
|
|
"run_symbols_undated", "run_symbols_strict", "run_symbols_latest",
|
|
"run_groups_undated", "run_groups_strict", "run_groups_length", "run_groups_last", "run_groups_twice",
|
|
"run_groups_signed", "run_groups_signed_ms", "run_groups_unverified", "run_groups_read",
|
|
"run_receipts_undated", "run_receipts_strict", "run_receipts_groups", "run_receipts_objects",
|
|
"run_receipts_last", "run_receipts_read", "run_envelopes_undated", "run_envelopes_strict",
|
|
"symbols_statement", "symbols_bearer_string", "symbols_group", "symbols_index", "symbols_greatest",
|
|
"run_symbols_statement", "run_symbols_bearer_string", "run_symbols_group", "run_symbols_index",
|
|
"run_symbols_greatest", "wanted_list", "wanted_object", "wanted_group", "wanted_index", "wanted_greatest",
|
|
"wanted_before_sources", "wanted_before_symbols", "run_wanted_before_twice", "run_wanted_malformed_twice",
|
|
"run_wanted_before_sources", "run_wanted_late",
|
|
"symbols_list", "symbols_held_list", "symbols_integer", "run_symbols_list", "run_symbols_null",
|
|
"run_symbols_held_list", "run_symbols_integer", "run_symbols_hold", "run_claims_not_list", "run_claims_null",
|
|
"run_records_not_list", "run_groups_not_list", "run_receipts_not_list", "run_receipts_member_not_list",
|
|
"run_envelopes_opt_in", "run_envelopes_no_arrival", "run_order_records", "run_order_groups", "run_passed_keeps_running"]
|
|
|
|
|
|
def direct_checks():
|
|
"""What no vector carries, and what this text states of the texts beside it."""
|
|
fails = []
|
|
# §7.1: the greatest fetch estimate is admitted; the corpus carries only the one past it.
|
|
if not sources_valid([{"kind": "origin", "fetch_ms": MAX_INT}], None, None):
|
|
fails.append("a fetch_ms of 2^53 - 1 is refused")
|
|
# §4.2 rule 3.2 where the products pass 2^64: three points on one line are refused, and a
|
|
# part per million off it is not. A float of 64 bits cannot tell them apart.
|
|
half = (LAST_INSTANT - 1) // 2
|
|
for v1, ok in ((500_000, False), (500_001, True)):
|
|
e = {"leaf_index": 0, "content_hash": "00" * 32, "from_ms": 0, "by_ms": 0, "fallback": None,
|
|
"decay": [{"at_ms": half, "value_ppm": v1}, {"at_ms": 2 * half, "value_ppm": 0}]}
|
|
try:
|
|
CV.timeliness_entry_rules(e, None)
|
|
got = True
|
|
except CV.Reject:
|
|
got = False
|
|
if got != ok:
|
|
fails.append(f"a point of {v1} at {half} is {'refused' if ok else 'accepted'}")
|
|
# §7.5.3 states each action a run decides.
|
|
decisions = text_of(EXTENSION)
|
|
decisions = decisions[decisions.index("\n#### 7.5.3 Decisions"):decisions.index("\n#### 7.5.4 ")]
|
|
for action in ACTIONS:
|
|
if f"`{action}`" not in decisions:
|
|
fails.append(f"§7.5.3 does not state {action}")
|
|
# jwp-receipts §3.1 lists §4.2's codes at step 4 and §4.3's at step 6; delivery-telemetry §5.1
|
|
# lists §8.6's switch reason.
|
|
jwp = text_of(JWP_RECEIPTS)
|
|
for code, where in reasons_table():
|
|
step = {"§4.2": 4, "§4.3": 6}.get(where.split(" ")[0])
|
|
if step is not None and f"| {step} | `{code}` |" not in jwp:
|
|
fails.append(f"jwp-receipts §3.1 does not list {code} at step {step}")
|
|
if "- `not-held-by-deadline`:" not in text_of(TELEMETRY):
|
|
fails.append("delivery-telemetry §5.1 does not list not-held-by-deadline")
|
|
if REFINEMENT_ONLY != {"wai.image.int_refine", "wai.video.int_refine", "wai.audio.int_refine",
|
|
"wai.splat.int_refine"}:
|
|
fails.append(f"SPEC §5's refinement capabilities read as {sorted(REFINEMENT_ONLY)}")
|
|
return fails
|
|
|
|
|
|
def main(argv):
|
|
global DROPPED
|
|
if len(argv) != 2:
|
|
print(__doc__)
|
|
return 2
|
|
doc = json.load(open(os.path.join(argv[1], "vectors.json"), encoding="utf-8"))
|
|
extra = sorted(set(os.listdir(argv[1])) - {"vectors.json", "README.md"})
|
|
fails = check(doc)
|
|
if extra:
|
|
fails.append(f"files the corpus does not hold: {extra}")
|
|
fails += direct_checks()
|
|
print(f"{sum(len(doc[k]) for k in KINDS)} cases ({', '.join(f'{len(doc[k])} {k}' for k in KINDS)}): "
|
|
f"{len(fails)} failure(s)")
|
|
caught = 0
|
|
for m in mutations(doc):
|
|
d = copy.deepcopy(doc)
|
|
m(d)
|
|
if check(d, quiet=True):
|
|
caught += 1
|
|
else:
|
|
fails.append(f"mutation {m.__name__} was not caught")
|
|
print(f"{caught} of {len(mutations(doc))} mutated copies fail")
|
|
weak = 0
|
|
for w in WEAKENINGS:
|
|
DROPPED = w
|
|
if check(doc, quiet=True):
|
|
weak += 1
|
|
else:
|
|
fails.append(f"a verifier without its {w} check passes the corpus")
|
|
DROPPED = None
|
|
print(f"{weak} of {len(WEAKENINGS)} weakened verifiers are caught")
|
|
for f in fails:
|
|
print("FAIL", f)
|
|
return 1 if fails else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv))
|