Gosalyn's6d43618a3(gosalyn/c2-20b), Webby round 1 APPROVE webby-0076 (rev 2852), manifest 6d3f279f692b, 4 paths. Webby's row 221 notes N1, N2 and N4. Slot runs sage-1969 (red base, the builder's comma regression) and sage-1971 (14/0, M1-M4 killed). M1/M2 sit at :248 in this commit, not :237 as BUILD.md says (gosalyn-0111). JSON.stringify leaves DEL, C1, U+2028/9 and bidi raw, a known limit by sage-1964. Sage gate on5c6f73ed7plus these 4 paths (12:10-12:21Z): the test file alone 14/0, cli 680/0, every scripts/test-*.sh 429/0.f8276c5ebadds only docs/SESSIONS and docs/case2 sheets no test reads.
5.1 KiB
Row 247 build notes, C2-20b persona import follow-up (#1749)
Brief: docs/plans/2026-10-20_case2-rows.md "C2-20b persona import follow-up: printed keys, the dry run
fence check and the mkdir race", landed in 335bb3e30. These are Webby's row 221 notes N1, N2 and N4
(webby-0073, REVIEW-r1 a0a00630e on webby/reviews). N3 is in DEFERRED (sage-1941).
Base
Branch gosalyn/c2-20b from 335bb3e30, which is row 221's landing 300766450 plus the brief.
What changed
- N1,
scripts/case2-persona-import.mjs: the per-channel prompt line prints the platform key, with its fixedplatforms.orgateway.platforms.prefix, and the channel key throughJSON.stringify. A key holding a newline prints as\ninside one quoted line. The profile name on that line is not config text:pairingalready holds it toPROFILE_ID. - N2: the dry run calls
personaTextFaulton each persona after its count line. A fault prints asprofile P: persona <reason>, so C2-08's fence would refuse it, where the reason is the fixed stringpersonaTextFaultreturns, never persona text or a line number. With any fault the run refuses, exit 1, and writes nothing, as stage would. The header comment no longer says a fault is counted, not refused, and now says a SOUL.md over four times the fence in bytes refuses before any count, which is whatsoul()already did (Webby's second point under N2). - N4:
privateDircatches an EEXIST from its mkdir and refuses with<path> was made by something else between the check and mkdir. Any other mkdir error is thrown as before, since the brief names only EEXIST. - Tests,
packages/cli/tests/case2-persona-import.test.mjs:- The per-channel prompt test expects the quoted keys.
- New: a chet config.yaml with a newline in the platform key and in the channel key prints one quoted line, and no output line starts with the forged text.
- The dry run heading test now expects the fence fault line, exit 1 and the refusal, still with nothing written. Its persona has two heading lines, which the fence refuses.
- New: a persona of 8001 two-byte characters, 16002 bytes and under the 32000 byte cap, makes the dry run print the over 8000 characters fault, write nothing and exit 1. At exactly 8000 it exits 0.
- New: the mkdir race refuses by name at stage, for the staging directory, and at install, for
personas/<business>. Each refused directory is left empty.
- New fixture
packages/cli/tests/fixtures/case2-persona-import/mkdir-race.mjs, an--importpreload liketrace.mjs. It makes the directory named by$PERSONA_MKDIR_RACEjust before the script's ownmkdirSyncof that path, so the script meets a real EEXIST from the kernel, not a thrown stub.
Known limit of N1
JSON.stringify escapes C0 controls, " and \, but leaves DEL, C1, U+2028, U+2029 and the bidi
controls raw. None of those ends a line on a terminal, so the forged line N1 names can't happen. A bidi
control could still reorder how the rest of that one line displays. The brief asks for JSON.stringify,
and Sage kept it in sage-1964 because the keys come from Jason's own Hermes config. Note for Webby: row
231's quote() in packages/library covers the wider set, and this row doesn't import it. If you want it,
ask for it in review.
Mutants
One per fix, plus a second for N1 so each of its two keys has its own. Each replaces one exact string in the script, runs the test file, then restores the script from a copy and checks its sha256.
| Mutant | Fix | Change |
|---|---|---|
| M1 | N1 | the channel key prints raw |
| M2 | N1 | the platform key prints raw |
| M3 | N2 | the dry run's refusal after a fault is skipped |
| M4 | N4 | privateDir's EEXIST branch never matches, so EEXIST is thrown raw |
Outside readers
git grep over packages//tests/ and scripts/test-*.sh finds case2-persona-import only in its own
test file and in two files of its fixture tree, README.md and trace.mjs, which name the test file in a
comment. No test pins a hash of the script.
Tests
Two slot runs, busy.sh && run.sh in /mnt/storage/scratch/gosalyn-r247, on the test file alone.
Run 1, grant sage-1969, 11:47:45Z to 11:48:00Z, was red: 14 tests, 12 pass, 2 fail. The fault was in the
script. Adding JSON.stringify to the per-channel line dropped the space after the comma, so it printed
"-1001",29 bytes. The two per-channel tests caught it. run.sh also went on to the mutants with a red
base, so their counts meant nothing. Sage approved both fixes in sage-1971: the space goes back, and run.sh
now exits 3 before the mutants unless the base is rc=0 with fail 0.
Run 2, grant sage-1971, 11:49:04Z to 11:49:22Z, was green: 14 tests, 14 pass, 0 fail, rc=0.
| Mutant | Line | rc | Failed | Killed by |
|---|---|---|---|---|
| M1 | 237 | 1 | 2 | the per-channel prompt test and the newline key test |
| M2 | 237 | 1 | 2 | the same two |
| M3 | 238 | 1 | 2 | the dry run fence test and the dry run heading test |
| M4 | 192 | 1 | 1 | the mkdir race test |
After the run the script's sha256 was 7031d2c0dc9bd22bc748b364a35b461fc01cb98b795032a2e0ec436f3188d4e4, the same as the copy taken before it. The /proc scan found no process in the worktree or the scratch dir.