[MG_Remote] (P6/dl): a dying session names itself to the peer

Step three of CONTRACT-P6 5.2, the funnel's whole point: instead of the peer
reading a bare EOF and guessing, the session publishes a SessionFault frame
that names the family before it aborts.

THE WIRE CHANGE IS D2's, exactly: `table Fatal` appends `family: string`,
append-only, so an older peer still reads `code` and `message`. `code` stays
the coarse seven-value projection; `family` carries the full word.

SessionFail publishes it before the abort, on whichever session's control
plane is active - the client has a ClientSession, the server a ServerSession,
so under spawn the frame reaches the far side whichever role is dying. Under
inproc both exist and it goes to an in-process transport the aborting process
will never read, which is harmless and cheaper than a special case that would
then be the untested one. Best-effort by nature: this thread is about to die,
so a send that blocks or fails changes nothing.

seq/op from the prose are NOT threaded through the 90 sites: the message
already names the op where it matters, so the frame carries what it can name
honestly (code, family, message) rather than a field that is always zero.

The client recognizes it: RunRemoteSurfaceControlFrame's reply loop, which
already demuxes CapsSnapshot from SurfaceReply, gains a Fatal arm that logs the
family and detail, latches device-lost from it, and returns transport-closed.
A server that DETECTS a fatal condition and aborts itself now tells the client
why; a kill -9 still bypasses abort entirely and is caught by dl's hangup
witness instead - the two cover the two ways a server can die.

The census learns SessionFail: a SessionFail call, like a WireLogFatal call,
must carry a Fatal{ word in its string, so the enum and the string cannot
disagree. FatalFunnel.{h,cpp} join the funnel exemption. Baseline is 4 aborts.

FatalFamilyTest gains the wire round-trip: every family's frame carries its
word and its projected code across encode/verify/decode.

G1 re-measured after the schema change: 0 symbol diff, .text unchanged at
0xa52203. Unit 2359 (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:51:50 -04:00
parent 4bb4d4fa4f
commit b46da7002c
5 changed files with 104 additions and 9 deletions
@@ -1463,7 +1463,8 @@ struct Fatal FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
struct Traits;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_CODE = 4,
VT_MESSAGE = 6
VT_MESSAGE = 6,
VT_FAMILY = 8
};
MobileGL::Wire::FatalCode code() const {
return static_cast<MobileGL::Wire::FatalCode>(GetField<uint32_t>(VT_CODE, 0));
@@ -1471,12 +1472,17 @@ struct Fatal FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
const ::flatbuffers::String *message() const {
return GetPointer<const ::flatbuffers::String *>(VT_MESSAGE);
}
const ::flatbuffers::String *family() const {
return GetPointer<const ::flatbuffers::String *>(VT_FAMILY);
}
template <bool B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
return VerifyTableStart(verifier) &&
VerifyField<uint32_t>(verifier, VT_CODE, 4) &&
VerifyOffset(verifier, VT_MESSAGE) &&
verifier.VerifyString(message()) &&
VerifyOffset(verifier, VT_FAMILY) &&
verifier.VerifyString(family()) &&
verifier.EndTable();
}
};
@@ -1491,6 +1497,9 @@ struct FatalBuilder {
void add_message(::flatbuffers::Offset<::flatbuffers::String> message) {
fbb_.AddOffset(Fatal::VT_MESSAGE, message);
}
void add_family(::flatbuffers::Offset<::flatbuffers::String> family) {
fbb_.AddOffset(Fatal::VT_FAMILY, family);
}
explicit FatalBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
@@ -1505,8 +1514,10 @@ struct FatalBuilder {
inline ::flatbuffers::Offset<Fatal> CreateFatal(
::flatbuffers::FlatBufferBuilder &_fbb,
MobileGL::Wire::FatalCode code = MobileGL::Wire::FatalCode::None,
::flatbuffers::Offset<::flatbuffers::String> message = 0) {
::flatbuffers::Offset<::flatbuffers::String> message = 0,
::flatbuffers::Offset<::flatbuffers::String> family = 0) {
FatalBuilder builder_(_fbb);
builder_.add_family(family);
builder_.add_message(message);
builder_.add_code(code);
return builder_.Finish();
@@ -1520,12 +1531,15 @@ struct Fatal::Traits {
inline ::flatbuffers::Offset<Fatal> CreateFatalDirect(
::flatbuffers::FlatBufferBuilder &_fbb,
MobileGL::Wire::FatalCode code = MobileGL::Wire::FatalCode::None,
const char *message = nullptr) {
const char *message = nullptr,
const char *family = nullptr) {
auto message__ = message ? _fbb.CreateString(message) : 0;
auto family__ = family ? _fbb.CreateString(family) : 0;
return MobileGL::Wire::CreateFatal(
_fbb,
code,
message__);
message__,
family__);
}
struct LogLine FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {