From 88ec5e8e47d095705b22fb6474da09f747bd8415 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 23 Oct 2026 14:02:37 -0500 Subject: [PATCH] row 283 (#1788): E2 per sage-3601: editor values with surrounding whitespace refuse library-mapping-invalid (git trims idents at commit); T3 padded cases per key, mutant M6c; BUILD.md records the departure from DESIGN.md section 1 and the fixture grep (none). Untested --- agents/honker/work/queue-283/BUILD.md | 23 +++++++++++++++++++++++ packages/library/src/mapping.mjs | 2 ++ packages/library/tests/mapping.test.mjs | 6 ++++++ 3 files changed, 31 insertions(+) diff --git a/agents/honker/work/queue-283/BUILD.md b/agents/honker/work/queue-283/BUILD.md index 052f377ab..ac01f1c55 100644 --- a/agents/honker/work/queue-283/BUILD.md +++ b/agents/honker/work/queue-283/BUILD.md @@ -121,3 +121,26 @@ on the named test and the restored file's sha256 equals the build tip's. the commit ident can differ. DESIGN.md section 1 says "non-empty after trim". Refusing `value.trim() !== value` would change that approved rule, so it went to Sage instead of into the build. + +## 2026-10-23 E2 departure (sage-3601) + +A departure from DESIGN.md section 1 (which stays unedited). The design says each `editor` value is +"non-empty after trim". Sage ruled that surrounding whitespace refuses too, because git trims an +ident at commit, so a padded value would save one name and commit another. mapping.mjs now adds +`if (value.trim() !== value) bad(`editor.${key} must not start or end with whitespace`)` after the +IDENT_BAD check. `trim()` covers Unicode whitespace, so U+00A0 and U+3000 refuse as well. Inner +whitespace is kept ("Her Middle Name" passes). + +T3 gains a case per key for leading, trailing and both-ends spaces, U+00A0 and U+3000, plus one inner +whitespace pass. + +M6 grows a third part: + +| Mutant | File | Replace | With | Killed by | +|---|---|---|---|---| +| M6c | src/mapping.mjs | `if (value.trim() !== value) bad(` | `if (false && value.trim() !== value) bad(` | mapping.test "editor is optional" (padded cases) | + +Fixture grep: `git grep -nE "\beditor\b|\bauthor\b" -- 'packages/library/tests/*' 'packages/business/tests/*'`, +leaving out mapping.test.mjs, finds nothing. packages/business/tests has no fixtures directory. The +library fixture mapping.json has no `editor`. The positive control (`vaultPrefix` in mapping.json) +matches once. No fixture maps an editor or author with surrounding whitespace. diff --git a/packages/library/src/mapping.mjs b/packages/library/src/mapping.mjs index b0e596349..18a62a2f5 100644 --- a/packages/library/src/mapping.mjs +++ b/packages/library/src/mapping.mjs @@ -63,6 +63,8 @@ export function checkMapping(entry, where = 'library', { fail = raise } = {}) { const value = entry.editor[key]; if (typeof value !== 'string' || !value.trim() || IDENT_BAD.test(value)) bad(`editor.${key} must be non-empty text with no control character, < or >`); + // Git trims an ident at commit, so a padded value would save one name and commit another (sage-3601). + if (value.trim() !== value) bad(`editor.${key} must not start or end with whitespace`); } } diff --git a/packages/library/tests/mapping.test.mjs b/packages/library/tests/mapping.test.mjs index b2147b746..62124d647 100644 --- a/packages/library/tests/mapping.test.mjs +++ b/packages/library/tests/mapping.test.mjs @@ -121,6 +121,12 @@ test('C2-01d: editor is optional, kept when given, and refuses anything but a pl invalid({ ...editor, name: bad }, /editor.name must be non-empty text with no control character, < or >/); invalid({ ...editor, email: bad }, /editor.email must be non-empty text with no control character, < or >/); } + // sage-3601: git trims an ident at commit, so surrounding whitespace refuses. + for (const padded of [' Her Name', 'Her Name ', ' Her Name ', '\u00a0Her Name', 'Her Name\u3000']) { + invalid({ ...editor, name: padded }, /editor.name must not start or end with whitespace/); + invalid({ ...editor, email: padded }, /editor.email must not start or end with whitespace/); + } + assert.equal(mapping(e => ({ ...e, editor: { name: 'Her Middle Name', email: 'her@example.invalid' } })).editor.name, 'Her Middle Name'); }); test('no playground prefix holds another, and no book folder holds another', () => {