mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 05:24:49 +09:00
Plan 76 milestone C, drift/bug row 3 — the ratchet only. 23 sites choose an
HTTP status by searching an error message:
status = 403 if 'denied' in result.get('error', '').lower() else 400
That is what `{'success': False, 'error': '<prose>'}` forces on a caller: the
service knew the failure was a denial, discarded it, and the route rebuilds it
from the sentence. The status is then coupled to the wording — rephrasing
"Access denied: path not in allowed directories" to "You do not have access to
this path" turns a 403 into a 400 and nothing fails, not a test, not a lint
rule.
The migration is NOT done here on purpose. Converging these means services
raising typed errors instead of returning result dicts, and file_service alone
(12 of the 23 sites) has eight callers. Converting it to raise while its other
callers still expect a dict leaves two contracts, which is the disease rather
than the cure — that is milestone C's envelope decision, sequenced after A and
B by the plan's own execution order.
So the ratchet lands first, per second-wave rule 1, to stop the pattern
spreading in the meantime. Ceiling 23. Counts a conditional choosing between
two HTTP status codes whose test inspects string content (`in`, `.lower()`,
`.startswith()`); a status chosen from a typed exception, an enum, or a boolean
is not counted, since that is the thing being asked for. Verified non-vacuous:
an injected site fails it and it passes again on revert.
Gate: tests/test_status_sniffing_ratchet.py, 8 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>