[MG_Remote] (P6/dl): every session death goes through one funnel

Step two of CONTRACT-P6 5.2. The ~90 scattered `MGLOG_F("...Fatal{...}"); std::abort();`
pairs under MG_Remote/ become one call each to SessionFail(MGFatalFamily, fmt, ...),
which logs, echoes to stderr, counts, and aborts in one place. a6's census
found no single chokepoint existed; now there is one, and step three hangs the
SessionFault-to-peer publish off it.

THE MESSAGE STRING IS PASSED VERBATIM. The contract requires the log lines, the
CI greps and every red-once byte-identical, so SessionFail does not compose the
line from the family - it takes the SAME string the site handed MGLOG_F and
forwards it unchanged. The family enum rides alongside, redundantly, so the
funnel has the coarse wire FatalCode without parsing the string. The census now
checks that redundancy the way it checks WireLogFatal: every SessionFail call's
string must carry a Fatal{ word, so the enum and the string cannot silently
disagree.

The conversion was mechanical - 89 sites by a transform that injects the family
as MGLOG_F's new first argument and drops the trailing abort - with three sites
fixed by hand that the transform would have broken: a stub macro, and two
if/else-then-shared-abort blocks (PersistentMapTracker's adopt-tier and
SurfaceOpCodec's three surface-error branches) where a naive rewrite stole the
abort from the earlier branches and would have let them fall through. The
death tests are the safety net for byte-identity, and every one stayed green.

Four std::abort() remain under MG_Remote/, all inside the two funnels
(WireLog.cpp, FatalFunnel.cpp); the census exempts both files because their
callers carry the family word, and its baseline drops from 93 aborts to 4.
SessionFaultCount() is the one telemetry point S8 will assert zero over a good
run.

G1 re-measured: 0 symbol diff, .text unchanged at 0xa52203 (FatalFunnel and
FatalFamily live under MG_Remote/, compiled only in the disaggregated build).
Unit 2358 (one pre-existing environmental failure), integration-split 186/186,
integration-spawn 102/102, retrace 6/6, census 0 unmarked.
This commit is contained in:
2026-09-21 21:44:17 -04:00
parent 3a12a48add
commit 4bb4d4fa4f
15 changed files with 246 additions and 195 deletions
+20 -8
View File
@@ -1,13 +1,16 @@
#!/usr/bin/env python3
"""The Fatal census gate (CONTRACT-P6 5.2, exit gate S7).
WHAT IT REFUSES: a `std::abort()` under MG_Remote/ that carries no family word.
WHAT IT REFUSES: a `std::abort()` under MG_Remote/ that carries no family word, and a
SessionFail / WireLogFatal call whose string forgot its family.
S7 says "a bare abort() added outside Session::Fail -> the census gate goes red". Session::Fail
does not exist yet - it is the 94-site funnel 5.2 describes, and this file is its precondition
rather than its replacement. What CAN be checked today is the property that funnel exists to
guarantee and that every consumer already depends on: every death names a FAMILY, in a
`Fatal{Word...}` marker, on a line a grep can find.
S7: "a bare abort() added outside Session::Fail -> the census gate goes red". Session::Fail now
EXISTS - it is SessionFail() in FatalFunnel.cpp, and the ~90 scattered aborts route through it,
so a bare std::abort() under MG_Remote/ is now the exception this gate refuses rather than the
rule. Four aborts remain, all inside the two funnels (WireLog.cpp, FatalFunnel.cpp), and both
files are exempt because their callers carry the family word. The property this enforces is the
one every downstream consumer depends on: every death names a FAMILY, in a `Fatal{Word...}`
marker, on a line a grep can find.
That is not as weak as it sounds. a6's census found the vocabulary had drifted to 30 family
words against a 7-value wire FatalCode, and found two aborts carrying no marker at all - so "the
@@ -44,6 +47,11 @@ BASELINE = Path(__file__).with_name("fatal_census_baseline.json")
FUNNEL_FILES = {
"MobileGL/MG_Remote/Transport/WireLog.cpp",
"MobileGL/MG_Remote/Transport/WireLog.h",
# P6 dl: Session::Fail's funnel. Its abort is the sanctioned one every SessionFail reaches,
# exactly as WireLog.cpp's is for WireLogFatal; its own file also defines the SessionFail
# call, so it is exempt from both rules for the same reason WireLog is.
"MobileGL/MG_Remote/FatalFunnel.cpp",
"MobileGL/MG_Remote/FatalFunnel.h",
}
# Twelve lines: long enough for a wrapped MGLOG_F argument list (the longest in the tree runs to
@@ -54,6 +62,10 @@ kMarkerWindow = 12
MARKER = re.compile(r"Fatal\{([A-Za-z][A-Za-z0-9]*)")
ABORT = re.compile(r"\bstd::abort\(\)")
WIRE_LOG_FATAL_CALL = re.compile(r"WireLogFatal\s*\(")
# P6 dl: a SessionFail call must carry a Fatal{ word in its string, the same rule
# WireLogFatal has - the enum and the string both name the family, and a call whose string
# forgot it would let the two disagree. Checked everywhere SessionFail is called.
SESSION_FAIL_CALL = re.compile(r"SessionFail\s*\(")
def strip_comments(text):
@@ -136,10 +148,10 @@ def census():
families.add(word)
if rel not in FUNNEL_FILES:
# Rule 2: every WireLogFatal call carries a family word. The call may wrap, so the
# Rule 2: every WireLogFatal / SessionFail call carries a family word in its string.
# window covers its continuation lines.
for index, line in enumerate(lines):
if not WIRE_LOG_FATAL_CALL.search(line):
if not (WIRE_LOG_FATAL_CALL.search(line) or SESSION_FAIL_CALL.search(line)):
continue
window = "\n".join(lines[index:index + kMarkerWindow])
if not MARKER.search(window):
+1 -1
View File
@@ -1,5 +1,5 @@
{
"abort_sites": 93,
"abort_sites": 4,
"families": [
"AbiMismatch",
"ApplyThreadJoinTimeout",