← back to pin list

App robustness: honest 404s, request validation at the door, atomic writes + locks (systems-test fix block 2)

kerfmaster pin list · The interface — recorded intent (build later)

recordStates what the code does today. Auditable against the code — this is the only type that can go stale.

Content last changed 2026-09-01 — computed from the item itself, not typed.

Systems-test fix block 2 (Aristide 2026-09-01: “start in manageable blocks on fixing”; “Go”). The ~90-probe systems test measured: POST to a nonexistent job → 500; garbage tol → 500 + ghost job dir, tol=0/-1 accepted silently; qty=0/-3 silently clamped to 1 AND recorded qty_asked=1 (a falsified record); malformed bodies marked STAGES blocked speaking raw Python (KeyError: 'material'); two concurrent posts took the SAME O-number and a torn progseq read blocked a third; two concurrent delivers crashed on the version dir; GETs during a POST hit intermittent 500s on torn job.json; two same-name jobs in one second collided on the shared unix-second id.

The fix (app/server.py)

Request vs engine, separated at the door. RequestError = a malformed REQUEST — plain 400 in honest words, no stage record (the brick-15 nestseq pattern applied everywhere); an ENGINE refusal still marks the stage blocked in the engine's own words. _validate_stage_body checks what the client must send: review needs material/post_material named with examples; qty must be a whole number ≥ 1 (the silent clamp is gone — the record can never be falsified); gap/margin/gap_x must be numbers; an onum override must be a positive whole number (ruling B's garbage/negative case — the use-once/new-sequence mechanism is block 3); tol must be a positive finite number (no more ghost dirs); a body that isn't a JSON object is refused as such. POST routes collapsed into one STAGE_RUNS dispatch — one blocked-stage shape, not nine copies.

Honest 404s. NoSuchJob names the missing job on GET and POST alike; a GET for a stage document that hasn't run names the file (not found: review.json); a POST to a non-stage path is 404, never a surprise job creation.

Atomic writes + locks. _save_json (temp file + os.replace) on every JSON document — a reader can never see a torn file. _job_lock (flock, one writer per job) serializes concurrent stage runs — the deliver race now yields a clean v1/v2 chain; the HTTP reply is written AFTER the lock releases and outside the blocked-marking try (the brick-9 rule). next_onum takes its own shop-wide lock around the read-modify-write — concurrent posts can never share a number. _new_job_dir suffixes a counter on a same-second collision.

Verified

test_robust.py NEW suite (37 controls: every finding above, plus 12-thread onum distinctness, a torn-read hammer on _save_json, 4×60 racing read-modify-writes under the job lock — all against a TEMP jobs dir and TEMP progseq, the real sequence untouched) + app/systest_block2_live.py on the RUNNING service (14 checks: the concurrent-deliver race live — both succeed, v1 superseded/v2 current, same O-number; a 3-thread GET hammer during the race sees zero non-200s; honest 404 over the public URL) + verify_customer.js 16 live browser checks + all nine suites pass. UI unchanged: app.js reads the same {error} JSON on 400s through its existing toast/banner paths.