D104 - The report is written to a file as well, not instead. And the backend is chosen by which file is compiled
Status: derived - the sink is exercised by make host and
writes a complete report.
A second destination, not a fifth channel
runtime.c picks one of four text channels and sticks
with it; the rest are never tried again. The file is not among them. It
receives every record in addition to whichever channel
was chosen, because the two answer opposite questions: a text channel
answers "can this system talk to a terminal I am watching", and a file
answers "can this system leave something behind". On a console the first
is often no and the second is what matters once the run is over.
Written before the channel loop rather than after, so a text channel that hangs or ends the process cannot cost the record on disk. Same reasoning as announce-before-attempting: the durable write goes ahead of the risky one.
Never buffered. A record reaches the file at the moment it is produced. Buffering would be faster and would destroy the property the whole program rests on - a report that stops mid-record names the call that ended the run. A crash discards a buffer, and the crash is the finding.
The path is discovered and the discovery is reported
Four candidates, tried in order. Which directory an unsigned module
may write to differs by platform and by how it was launched, so the list
is a set of guesses - and the sink record naming the winner
turns guessing into measurement. On the host it falls through three
absent paths to a bare filename and says so.
A null path still emits sink|none rather than no record.
A missing record is indistinguishable from an older build that never had
a sink; a word is a fact about this run.
Source lists, not preprocessor branches
sink.c holds the path list, the write loop and the
close-on-failure rule. The three platform calls live in
sink_target.c and sink_host.c, and the
Makefile compiles exactly one.
The alternative was #if defined(OBSCENE_HOST_BUILD)
inside sink.c. That puts two targets' code in one file
where only one is ever compiled, so the untaken branch is never parsed
and rots unnoticed. A list in the Makefile says which backend a target
gets, in one place, visibly - and adding a target becomes a file plus a
line rather than another condition threaded through code that already
works.
It also resolved a genuine conflict rather than compromising on it.
The host stubs report sceKernelOpen as not implemented,
which is the correct answer for the
040-file checks that measure it and the wrong one for a
sink that has to actually write. Two backends let the checks keep seeing
a stub while the sink gets tested, instead of bending one to suit the
other.
This is the structure the socket layer will use for the same reason.