D028 - The param.sfo reader comes from SELFish; the in-place writer stays

decided · 2026-09-10 · principle 6

pros-core::sfo carried its own \0PSF parser - a header read at offset 20, sixteen-byte index entries, a Value enum, read. param.sfo is a platform format, and principle 6 says Prosperous reads the platform's formats from SELFish and invents none of its own. A second copy of a layout table is the exact thing that principle exists to prevent, so the parser is the one piece here that should not have been here.

It was filed as a cross-project request (Prosperous's inbox REQ-...f7ad) and SELFish delivered selfish_title::sfo::Sfo with a bytes(key) accessor - the one thing missing for reading ACCOUNT_ID as its raw eight bytes rather than as text it happens to resemble. So the reader is gone from here, and the three consumers - origin and saves reading an account, graft reading a save's parameters - go through Sfo now. What Prosperous adds stays: account_in and account_id render the id as hex (the shape it is compared in, never a number), over SELFish's read.

The writer does not move with the reader. sfo::set edits one parameter in place - nothing is rebuilt, a value is written over the old one within the room the file already left, and everything it does not understand is untouched by construction. That is a deliberate opposite of Sfo::to_bytes, which re-serialises the whole file; graft::set_account depends on the in-place behaviour, because a save this project reassembled rather than edited would still parse and be refused only later, by a target, with nothing pointing at the wrong byte. So the writer is Prosperous's own concern layered over SELFish's reader, and the module note says so.

The cost, stated: pros-core now depends on the selfish sibling repository by path, the way it already depends on the oops-* crates - so a standalone clone of Prosperous alone no longer builds, the collection is checked out as a set. No new heavy dependency: selfish-title brings serde/serde_json, which pros-core already carries for the manifest.

Not a bug fix. SELFish's resolution surfaced two defects in its parser - a whole-file failure on a non-UTF-8 ACCOUNT_ID, and trailing zeroes trimmed off the unterminated format. Prosperous's own parser was immune to both, because it bucketed anything that was not 0x0204 text or 0x0404 number as raw bytes, and read ACCOUNT_ID through a single accessor. So this removed a correct duplicate, not a wrong one; the value is one home for the format, not a repair.