600 lines
24 KiB
Python
600 lines
24 KiB
Python
#!/usr/bin/env python3
|
|
"""Grade Q116's grep half: no career-lane/ cell quotes OpenTrain's
|
|
`validThrough` as an expiry or as a remaining-days count.
|
|
|
|
Q116 (filed 2026-10-28) asked for two queue notes to be corrected in place.
|
|
Its 2026-10-28 addendum struck that half -- this archive appends, it does not
|
|
rewrite -- and restated the done-means as a reading: `career-lane/` must carry
|
|
no `validThrough` quoted as an expiry or a remaining-days count, while the
|
|
superseded lines "stand as dated record with this addendum as their
|
|
correction". Those two clauses can only both hold if the reading can tell a
|
|
*live cell* from a *dated record*, which is what this file does.
|
|
|
|
Three verdicts per line, and nothing else:
|
|
|
|
LIVE a line naming OpenTrain or `validThrough` that quotes a
|
|
remaining-days count, or pairs `validThrough` with "expir*", and
|
|
carries no supersession after it. This is the Q116 fault.
|
|
RECORD the same quote with a supersession marker later in the same file,
|
|
or in a later-dated file that names this file's date and the
|
|
quoted field. Dated record, exempt by the addendum.
|
|
(clean) no quote at all.
|
|
|
|
A marker is only a supersession when it comes *after* the claim it corrects:
|
|
a correction printed above a claim does not reach it, and cell 5 of
|
|
`--selftest` fails if this ordering is loosened. Exemptions are always
|
|
printed with the record that grants them, so a marker that exempts too much
|
|
is a reading a human can overturn -- the failure mode Q117 named.
|
|
|
|
Legs: `quotes` (the scan above), `rule` (the replacement rule is written in a
|
|
career tick file dated 2026-10-29 or later), `probe` (opt-in, `--probe URL`:
|
|
measures the host's own render-time arithmetic).
|
|
|
|
Exit codes follow the repo's tools/ contract: 0 clean, 1 graded a fault,
|
|
2 could not grade.
|
|
|
|
Usage:
|
|
python3 opentrain_deadline_audit.py # scan + rule leg
|
|
python3 opentrain_deadline_audit.py --selftest # cells, no repo needed
|
|
python3 opentrain_deadline_audit.py --probe URL
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import datetime as _dt
|
|
import json
|
|
import re
|
|
import sys
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
LANE = REPO / "career-lane"
|
|
|
|
# A cell about *this* field or *this* host.
|
|
SUBJECT_RE = re.compile(r"validThrough|OpenTrain", re.I)
|
|
# "<n> days remaining" / "<n> day remaining", any spacing.
|
|
DAYS_LEFT_RE = re.compile(r"\b\d+\s+days?\s+remaining\b", re.I)
|
|
# "expiry" / "expires" / "expiring" / "expiration".
|
|
EXPIRY_RE = re.compile(r"expir", re.I)
|
|
# Supersession markers. Any one, case-insensitively, means "this line is a
|
|
# correction or a pointer to one", never "this line is a live cell".
|
|
MARKERS = (
|
|
"withdrawn",
|
|
"superseded",
|
|
"render-synthesized",
|
|
"build+30d",
|
|
"never a deadline",
|
|
"never an expiry",
|
|
"no expiry line",
|
|
"not a deadline",
|
|
"dated record",
|
|
)
|
|
# A supersession record has to say what it is superseding, or it is a blanket.
|
|
SUPERSEDES_RE = re.compile(r"superseded|withdrawn", re.I)
|
|
DATE_IN_NAME_RE = re.compile(r"(\d{4})-(\d{2})-(\d{2})")
|
|
|
|
|
|
class Ungradable(Exception):
|
|
"""Raised when the input cannot be graded at all (exit 2)."""
|
|
|
|
|
|
def mmdd(name: str) -> str | None:
|
|
"""'daily-2026-10-28.md' -> '10-28'; None when the name has no date."""
|
|
m = DATE_IN_NAME_RE.search(name)
|
|
return "%s-%s" % (m.group(2), m.group(3)) if m else None
|
|
|
|
|
|
def file_date(name: str) -> str | None:
|
|
m = DATE_IN_NAME_RE.search(name)
|
|
return m.group(0) if m else None
|
|
|
|
|
|
def has_marker(line: str) -> bool:
|
|
low = line.lower()
|
|
return any(m in low for m in MARKERS)
|
|
|
|
|
|
def marker_names_subject(text: str) -> bool:
|
|
"""A supersession record grants nothing unless it names the field it corrects.
|
|
|
|
Without this, `apply-queue-2026-10-28.md`'s item-27 "WITHDRAWN" heading
|
|
exempted item 25's deadline quote 300 lines away -- the blanket-marker
|
|
failure Q117 named, caught by this file's own `--show-record` audit.
|
|
"""
|
|
return has_marker(text) and bool(SUBJECT_RE.search(text))
|
|
|
|
|
|
# A bullet, heading or bold lead starts a record block; a continuation line
|
|
# (the wrapped remainder of a bullet) belongs to the block above it. Records in
|
|
# this archive wrap, so quoting is judged over a block, never over a line.
|
|
BULLET_RE = re.compile(r"^\s*(?:#+|[-*+]\s|\d+[.)]\s|\*\*)")
|
|
|
|
|
|
def blocks(text: str) -> list[tuple[int, list[str]]]:
|
|
"""Split into (0-based start index, lines) record blocks."""
|
|
out: list[tuple[int, list[str]]] = []
|
|
cur: list | None = None
|
|
for i, line in enumerate(text.splitlines()):
|
|
if not line.strip():
|
|
cur = None
|
|
continue
|
|
if cur is None or BULLET_RE.match(line):
|
|
cur = [i, [line]]
|
|
out.append((cur[0], cur[1]))
|
|
else:
|
|
cur[1].append(line)
|
|
return out
|
|
|
|
|
|
def block_quote(lines: list[str]) -> str | None:
|
|
"""Return the joined block when it quotes the field as a deadline."""
|
|
joined = " ".join(ln.strip() for ln in lines)
|
|
if not SUBJECT_RE.search(joined):
|
|
return None
|
|
if DAYS_LEFT_RE.search(joined):
|
|
return joined
|
|
if re.search(r"validThrough", joined, re.I) and EXPIRY_RE.search(joined):
|
|
return joined
|
|
return None
|
|
|
|
|
|
def scan(files: dict[str, str], queue_text: str = "") -> dict:
|
|
"""Classify every career-lane record block.
|
|
|
|
`files` maps file name -> full text. Pure: no I/O, so `--selftest` drives
|
|
exactly the code the repo scan runs. `queue_text` is WORK-QUEUE.md, where
|
|
Q116's addendum rules that the superseded career cells stand as dated
|
|
record "with this addendum as their correction" -- an explicit grant,
|
|
bounded to cells dated on or before the addendum's own date.
|
|
"""
|
|
live, record = [], []
|
|
cutoff = addendum_cutoff(queue_text)
|
|
for name in sorted(files):
|
|
blist = blocks(files[name])
|
|
texts = [" ".join(ln.strip() for ln in lines) for _, lines in blist]
|
|
marked = [k for k, t in enumerate(texts) if marker_names_subject(t)]
|
|
for k, (start, lines) in enumerate(blist):
|
|
quoted = block_quote(lines)
|
|
if not quoted:
|
|
continue
|
|
hit = {"file": name, "line": start + 1, "text": quoted}
|
|
# (a) the block corrects itself, (b) a correction follows it here.
|
|
if marker_names_subject(texts[k]):
|
|
record.append(dict(hit, by="self (line %d)" % (start + 1)))
|
|
continue
|
|
after = [m for m in marked if m > k]
|
|
if after:
|
|
record.append(dict(hit, by="%s:%d"
|
|
% (name, blist[after[0]][0] + 1)))
|
|
continue
|
|
|
|
# (c) a later-dated file names this file's date and the field.
|
|
token = mmdd(name)
|
|
this_date = file_date(name) or ""
|
|
by = None
|
|
for other in sorted(files):
|
|
if other == name or (file_date(other) or "") <= this_date:
|
|
continue
|
|
if not token:
|
|
continue
|
|
for j, oline in enumerate(files[other].splitlines()):
|
|
if not SUPERSEDES_RE.search(oline):
|
|
continue
|
|
if token not in oline:
|
|
continue
|
|
if not SUBJECT_RE.search(oline) and "remaining" not in oline.lower():
|
|
continue
|
|
by = "%s:%d" % (other, j + 1)
|
|
break
|
|
if by:
|
|
break
|
|
if by:
|
|
record.append(dict(hit, by=by))
|
|
elif cutoff and (file_date(name) or "") <= cutoff:
|
|
record.append(dict(hit, by="WORK-QUEUE.md Q116 addendum "
|
|
"(grants cells dated <= %s)" % cutoff))
|
|
else:
|
|
live.append(hit)
|
|
return {"live": live, "record": record, "addendum_cutoff": cutoff}
|
|
|
|
|
|
ADDENDUM_RE = re.compile(r"Q116 addendum", re.I)
|
|
|
|
|
|
def addendum_cutoff(queue_text: str) -> str | None:
|
|
"""Date of Q116's append-only addendum, if the queue carries it.
|
|
|
|
The addendum rules that superseded career cells stand as dated record
|
|
"with this addendum as their correction", so it is the correction of
|
|
record for cells dated on or before the date it names. Anything later
|
|
must carry its own marker: the grant cannot reach a future cell.
|
|
"""
|
|
if not queue_text:
|
|
return None
|
|
for start, lines in blocks(queue_text):
|
|
joined = " ".join(ln.strip() for ln in lines)
|
|
if ADDENDUM_RE.search(joined) and marker_names_subject(joined):
|
|
d = DATE_IN_NAME_RE.search(joined)
|
|
if d:
|
|
return d.group(0)
|
|
# "<name>, 2026-10-28)" forms: fall back to the block's own date.
|
|
m = re.search(r"\b(20\d{2}-\d{2}-\d{2})\b", joined)
|
|
return m.group(1) if m else None
|
|
return None
|
|
|
|
|
|
def rule_present(files: dict[str, str]) -> list[str]:
|
|
"""Files dated 2026-10-29 or later that carry the replacement rule."""
|
|
found = []
|
|
for name in sorted(files):
|
|
d = file_date(name)
|
|
if not d or d < "2026-10-29":
|
|
continue
|
|
low = files[name].lower()
|
|
if "render-synthesized" in low and "build+30d" in low:
|
|
found.append(name)
|
|
return found
|
|
|
|
|
|
def load_lane(lane: Path = LANE) -> dict[str, str]:
|
|
if not lane.is_dir():
|
|
raise Ungradable("no career-lane/ at %s" % lane)
|
|
files = {}
|
|
for p in sorted(lane.glob("*.md")):
|
|
try:
|
|
files[p.name] = p.read_text(encoding="utf-8")
|
|
except OSError as exc:
|
|
raise Ungradable("cannot read %s: %s" % (p, exc))
|
|
if not files:
|
|
raise Ungradable("career-lane/ holds no .md files at %s" % lane)
|
|
return files
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Probe leg: the host's own render-time arithmetic, measured in seconds.
|
|
# --------------------------------------------------------------------------
|
|
|
|
EPOCH = _dt.datetime(1970, 1, 1, tzinfo=_dt.timezone.utc)
|
|
|
|
|
|
def _http_date(value: str) -> float:
|
|
return _dt.datetime.strptime(value, "%a, %d %b %Y %H:%M:%S GMT").replace(
|
|
tzinfo=_dt.timezone.utc).timestamp()
|
|
|
|
|
|
def presence_reading(headers: dict, body: str = "") -> dict:
|
|
"""Classify x-opentrain-job-presence-expires-at against the same response.
|
|
|
|
Q116's mechanism claim is that this host emits absolute timestamps
|
|
computed at render time. If `presence - Date` equals the response's own
|
|
remaining `s-maxage`, the header is render-instant + TTL, i.e. the same
|
|
class as `validThrough` -- provable in seconds instead of over 30 days.
|
|
"""
|
|
h = {k.lower(): v for k, v in headers.items()}
|
|
out = {"fields": {}, "verdict": None}
|
|
if "date" not in h or "x-opentrain-job-presence-expires-at" not in h:
|
|
out["verdict"] = "NO-FIELDS"
|
|
return out
|
|
try:
|
|
date_epoch = _http_date(h["date"])
|
|
presence = int(h["x-opentrain-job-presence-expires-at"]) / 1000.0
|
|
except (ValueError, TypeError):
|
|
out["verdict"] = "UNPARSEABLE"
|
|
return out
|
|
delta = presence - date_epoch
|
|
out["date_epoch"] = date_epoch
|
|
out["presence_epoch"] = presence
|
|
out["presence_delta_s"] = round(delta, 1)
|
|
ttl = re.search(r"s-maxage=(\d+)", h.get("cache-control", "")
|
|
+ h.get("cdn-cache-control", ""))
|
|
if ttl:
|
|
out["s_maxage_s"] = int(ttl.group(1))
|
|
out["verdict"] = ("RENDER-COMPUTED"
|
|
if abs(delta - out["s_maxage_s"]) <= 2 else "OTHER")
|
|
else:
|
|
out["verdict"] = "NO-TTL"
|
|
for key in ("dateposted", "validthrough"):
|
|
m = re.search(r'"%s":"([^"]+)"' % key, body, re.I)
|
|
if m:
|
|
out["fields"][key] = m.group(1)
|
|
if "dateposted" in out["fields"] and "validthrough" in out["fields"]:
|
|
try:
|
|
dp = _dt.datetime.strptime(out["fields"]["dateposted"],
|
|
"%Y-%m-%dT%H:%M:%S.%fZ").replace(
|
|
tzinfo=_dt.timezone.utc)
|
|
vt = _dt.datetime.strptime(out["fields"]["validthrough"],
|
|
"%Y-%m-%dT%H:%M:%S.%fZ").replace(
|
|
tzinfo=_dt.timezone.utc)
|
|
out["span_days"] = round((vt - dp).total_seconds() / 86400.0, 2)
|
|
out["build_implied"] = (vt - _dt.timedelta(days=30)).strftime(
|
|
"%Y-%m-%dT%H:%M:%SZ")
|
|
# Measured, not asserted: how old the implied build is *relative to
|
|
# the response's own Date header*. A value that always reads "30
|
|
# days ahead" is only a deadline if this age is stable across
|
|
# readings; the field is per-build, so it is the build's age here.
|
|
if "date_epoch" in out:
|
|
out["build_age_hours"] = round(
|
|
(out["date_epoch"] - (vt - _dt.timedelta(days=30)).timestamp())
|
|
/ 3600.0, 2)
|
|
except ValueError:
|
|
pass
|
|
return out
|
|
|
|
|
|
def probe(url: str, timeout: float = 25.0) -> dict:
|
|
req = urllib.request.Request(url, method="GET",
|
|
headers={"User-Agent": "volt-q116-audit/1"})
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
body = resp.read().decode("utf-8", "replace")
|
|
headers = dict(resp.headers.items())
|
|
status = resp.status
|
|
except Exception as exc: # noqa: BLE001 - reported, not swallowed
|
|
raise Ungradable("fetch failed for %s: %s" % (url, exc))
|
|
reading = presence_reading(headers, body)
|
|
reading.update({"url": url, "status": status, "bytes": len(body)})
|
|
return reading
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Cells
|
|
# --------------------------------------------------------------------------
|
|
|
|
CLEAN = ("career-lane/daily-2026-10-29.md", """
|
|
- **Reply 145 section 0 -- the item-25 expiry correction is WITHDRAWN.** OpenTrain's
|
|
`validThrough` is render-synthesized at build + 30 days; it is never a deadline.
|
|
""")
|
|
|
|
LIVE = ("career-lane/daily-2026-10-28.md", """
|
|
1. **OpenTrain item 25 -- LIVE, validThrough 2026-11-26 (page-computed),
|
|
29 days remaining as of today, datePosted 2026-10-07.**
|
|
""")
|
|
|
|
ORDERED = ("career-lane/apply-queue-2026-10-28.md", """
|
|
- **2026-10-28 note:** `validThrough` 2026-11-26, 29 days remaining as of 2026-10-28.
|
|
- **2026-10-29:** item 25 correction WITHDRAWN: OpenTrain's `validThrough` is
|
|
render-synthesized at build+30d, never a deadline.
|
|
""")
|
|
|
|
# A correction that never names the field it corrects grants nothing.
|
|
UNNAMED = ("career-lane/apply-queue-2026-10-28.md", """
|
|
- **2026-10-28 note:** `validThrough` 2026-11-26, 29 days remaining as of 2026-10-28.
|
|
- **2026-10-29:** item 25 WITHDRAWN: render-synthesized, build+30d.
|
|
""")
|
|
|
|
REVERSED = ("career-lane/apply-queue-2026-10-28.md", """
|
|
- **2026-10-29:** item 25 correction WITHDRAWN: OpenTrain's `validThrough` is
|
|
render-synthesized at build+30d.
|
|
- **2026-10-28 note:** `validThrough` 2026-11-26, 29 days remaining as of 2026-10-28.
|
|
""")
|
|
|
|
CROSS = ("career-lane/apply-queue-2026-10-28.md", """
|
|
- **2026-10-28 note:** `validThrough` 2026-11-26, 29 days remaining as of 2026-10-28.
|
|
"""), ("career-lane/apply-queue-2026-10-30.md", """
|
|
- **2026-10-30:** the 10-28 `validThrough` "29 days remaining" cell is Superseded:
|
|
the field is render-synthesized, build+30d.
|
|
""")
|
|
|
|
OTHER_VENDOR = ("career-lane/daily-2026-10-28.md", """
|
|
- **Alignerr item 29:** $20-100/hr, header badge span, 12 days remaining on the listing.
|
|
""")
|
|
|
|
# Q116's addendum, as WORK-QUEUE.md carries it: the grant for pre-addendum
|
|
# cells, and the shape the cutoff is read from.
|
|
ADDENDUM_FIXTURE = (
|
|
"**Q116 addendum (pipe, 2026-10-28) -- the \"corrected in place\" half of "
|
|
"the done-means is struck as my error, and append-only wins.** The "
|
|
"superseded lines stand as dated record with this addendum as their "
|
|
"correction. The grep half is unchanged: no `validThrough` quoted as an "
|
|
"expiry or a remaining-days count.\n")
|
|
|
|
|
|
def _one(files: dict[str, str]) -> dict:
|
|
return scan(files)
|
|
|
|
|
|
def selftest() -> int:
|
|
cells = []
|
|
|
|
def cell(name, fn):
|
|
try:
|
|
fn()
|
|
cells.append((name, True, ""))
|
|
except AssertionError as exc:
|
|
cells.append((name, False, str(exc) or "assertion failed"))
|
|
|
|
def control_clean():
|
|
r = _one(dict([CLEAN]))
|
|
assert not r["live"], "clean control reported a live quote: %r" % r["live"]
|
|
|
|
def mutant_live_unmarked():
|
|
r = _one(dict([LIVE]))
|
|
assert len(r["live"]) == 1, "unmarked remaining-days quote not flagged"
|
|
assert r["live"][0]["line"] == 2, r["live"][0]
|
|
|
|
def record_marker_after():
|
|
r = _one(dict([ORDERED]))
|
|
assert not r["live"], "marker after the claim did not exempt it"
|
|
assert len(r["record"]) == 1, "exemption not reported as a record"
|
|
|
|
def mutant_marker_before():
|
|
r = _one(dict([REVERSED]))
|
|
assert len(r["live"]) == 1, (
|
|
"a correction printed ABOVE the claim exempted it -- ordering lost")
|
|
|
|
def record_cross_file():
|
|
r = _one(dict(CROSS))
|
|
assert not r["live"], "later-dated supersession record did not exempt"
|
|
assert r["record"], "cross-file exemption not reported"
|
|
|
|
def mutant_other_vendor():
|
|
r = _one(dict([OTHER_VENDOR]))
|
|
assert not r["live"], "flagged a non-OpenTrain vendor line"
|
|
|
|
def mutant_blanket_marker():
|
|
# A marker about a different item grants nothing, however near it sits.
|
|
files = dict([LIVE])
|
|
files["career-lane/daily-2026-10-29.md"] = (
|
|
"- **2026-10-27:** item 27 Gramian WITHDRAWN at source.\n")
|
|
r = _one(files)
|
|
assert len(r["live"]) == 1, "an unrelated WITHDRAWN exempted a cell"
|
|
|
|
def addendum_grants_past_cells():
|
|
r = scan(dict([LIVE]), ADDENDUM_FIXTURE)
|
|
assert not r["live"], "the Q116 addendum did not exempt a pre-addendum cell"
|
|
assert r["addendum_cutoff"] == "2026-10-28", r["addendum_cutoff"]
|
|
|
|
def mutant_addendum_cannot_reach_future():
|
|
future = ("career-lane/daily-2026-10-31.md",
|
|
"- **10-31:** OpenTrain item 25 validThrough 2026-11-30, "
|
|
"29 days remaining as of today.\n")
|
|
r = scan(dict([future]), ADDENDUM_FIXTURE)
|
|
assert len(r["live"]) == 1, (
|
|
"the addendum grant reached a cell dated after it -- grant is unbounded")
|
|
|
|
def mutant_marker_unnamed_subject():
|
|
r = _one(dict([UNNAMED]))
|
|
assert len(r["live"]) == 1, (
|
|
"a correction that never names validThrough/OpenTrain exempted a cell")
|
|
|
|
def probe_observed():
|
|
# The three readings taken 2026-10-30T13:14:34-39Z, verbatim.
|
|
for delta, smax in ((206, 206), (203, 203), (201, 201)):
|
|
hdrs = {"date": "Fri, 30 Oct 2026 13:14:34 GMT",
|
|
"x-opentrain-job-presence-expires-at":
|
|
str(int(1793366074 * 1000 + delta * 1000)),
|
|
"cache-control": "public, max-age=0, s-maxage=%d, must-revalidate" % smax}
|
|
rd = presence_reading(hdrs)
|
|
assert rd["verdict"] == "RENDER-COMPUTED", (delta, smax, rd)
|
|
|
|
def probe_mutant_drifted():
|
|
hdrs = {"date": "Fri, 30 Oct 2026 13:14:34 GMT",
|
|
"x-opentrain-job-presence-expires-at": "1793367000000",
|
|
"cache-control": "s-maxage=206"}
|
|
assert presence_reading(hdrs)["verdict"] == "OTHER", "drift not detected"
|
|
|
|
def probe_fields_observed():
|
|
# The item-32 page as read 2026-10-30T13:18:55Z, verbatim.
|
|
hdrs = {"date": "Fri, 30 Oct 2026 13:18:55 GMT",
|
|
"x-opentrain-job-presence-expires-at": "1793366572773",
|
|
"cache-control": "public, max-age=0, s-maxage=237, must-revalidate"}
|
|
body = ('{"@type":"JobPosting","datePosted":"2026-10-09T10:42:55.303Z",'
|
|
'"validThrough":"2026-11-29T11:09:35.329Z"}')
|
|
rd = presence_reading(hdrs, body)
|
|
assert rd["fields"].get("validthrough") == "2026-11-29T11:09:35.329Z", rd
|
|
assert rd["span_days"] == 51.02, rd.get("span_days")
|
|
assert rd["build_implied"] == "2026-10-30T11:09:35Z", rd["build_implied"]
|
|
# The measured half: the implied build is hours old, not "30 days ahead".
|
|
assert 0 <= rd["build_age_hours"] <= 6, rd.get("build_age_hours")
|
|
|
|
def mutant_probe_fields_absent():
|
|
hdrs = {"date": "Fri, 30 Oct 2026 13:18:55 GMT",
|
|
"x-opentrain-job-presence-expires-at": "1793366572773",
|
|
"cache-control": "s-maxage=237"}
|
|
rd = presence_reading(hdrs, "<html>no JSON-LD here</html>")
|
|
assert not rd["fields"], "fields invented from a page that has none"
|
|
|
|
cell("control-clean", control_clean)
|
|
cell("mutant-live-unmarked", mutant_live_unmarked)
|
|
cell("record-marker-after", record_marker_after)
|
|
cell("mutant-marker-before", mutant_marker_before)
|
|
cell("record-cross-file", record_cross_file)
|
|
cell("mutant-other-vendor", mutant_other_vendor)
|
|
cell("mutant-blanket-marker", mutant_blanket_marker)
|
|
cell("mutant-unnamed-subject", mutant_marker_unnamed_subject)
|
|
cell("addendum-grants-past", addendum_grants_past_cells)
|
|
cell("mutant-addendum-future", mutant_addendum_cannot_reach_future)
|
|
cell("probe-observed", probe_observed)
|
|
cell("probe-mutant-drifted", probe_mutant_drifted)
|
|
cell("probe-fields-observed", probe_fields_observed)
|
|
cell("mutant-fields-absent", mutant_probe_fields_absent)
|
|
|
|
failed = 0
|
|
for name, ok, msg in cells:
|
|
print(" %s %-24s %s" % ("PASS" if ok else "FAIL", name, msg))
|
|
if not ok:
|
|
failed += 1
|
|
print("selftest: %d cells, %d passed, %d failed" % (len(cells), len(cells) - failed, failed))
|
|
return 1 if failed else 0
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
ap.add_argument("--selftest", action="store_true", help="run the cells only")
|
|
ap.add_argument("--probe", metavar="URL", help="measure one posting's fields")
|
|
ap.add_argument("--json", action="store_true", help="machine reading")
|
|
ap.add_argument("--show-record", action="store_true",
|
|
help="print every exemption with the record that grants it")
|
|
ap.add_argument("--lane", metavar="DIR",
|
|
help="grade another career-lane/ (default: this tree's)")
|
|
ap.add_argument("--queue", metavar="FILE",
|
|
help="the queue carrying the addendum (default: this tree's)")
|
|
args = ap.parse_args(argv)
|
|
|
|
if args.selftest:
|
|
return selftest()
|
|
|
|
if args.probe:
|
|
try:
|
|
rd = probe(args.probe)
|
|
except Ungradable as exc:
|
|
print("could not grade: %s" % exc, file=sys.stderr)
|
|
return 2
|
|
print(json.dumps(rd, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
try:
|
|
files = load_lane(Path(args.lane) if args.lane else LANE)
|
|
except Ungradable as exc:
|
|
print("could not grade: %s" % exc, file=sys.stderr)
|
|
return 2
|
|
|
|
queue_path = Path(args.queue) if args.queue else REPO / "WORK-QUEUE.md"
|
|
try:
|
|
queue_text = queue_path.read_text(encoding="utf-8")
|
|
except OSError as exc:
|
|
print("could not grade: cannot read %s: %s" % (queue_path, exc),
|
|
file=sys.stderr)
|
|
return 2
|
|
|
|
result = scan(files, queue_text)
|
|
rule_files = rule_present(files)
|
|
faults = 0
|
|
|
|
live = result["live"]
|
|
if live:
|
|
faults += 1
|
|
print("quotes: FAULT -- %d live cell(s) quote OpenTrain validThrough "
|
|
"as an expiry or a remaining-days count" % len(live))
|
|
for hit in live:
|
|
print(" %s:%d %s" % (hit["file"], hit["line"], hit["text"][:150]))
|
|
else:
|
|
print("quotes: PASS -- %d deadline-quote block(s) in %d file(s), all "
|
|
"exempt as dated record, 0 live" % (len(result["record"]),
|
|
len({r["file"] for r in result["record"]})))
|
|
print(" addendum grant: cells dated <= %s"
|
|
% (result["addendum_cutoff"] or "none (addendum absent)"))
|
|
|
|
if args.show_record:
|
|
for hit in result["record"]:
|
|
print(" record %s:%d by %s" % (hit["file"], hit["line"], hit["by"]))
|
|
|
|
if rule_files:
|
|
print("rule: PASS -- replacement rule present in %s" % ", ".join(rule_files))
|
|
else:
|
|
faults += 1
|
|
print("rule: FAULT -- no career-lane file dated 2026-10-29 or later "
|
|
"states the render-synthesized rule")
|
|
|
|
print("q116: %d leg(s), %d fault(s) -- %s"
|
|
% (2, faults, "OK" if not faults else "FAULT"))
|
|
return 0 if not faults else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|