Clear BLOCK:4713c06 - orphaned follow-up radiograph blobs, permanent success-disabled state, Postgres boundary
Clears BLOCK:4713c06 deep from audit-mars-ca (tag audit-mars-ca-mtgt1vs52sfz). 4713c06 (v2.79.14, 'add approved-practice follow-up radiographs') is LIVE in prod. Three findings, first two independently confirmed by coder-mars-cc against the code. P1 orphaned api-images blobs. src/app/(protected)/mi-cursada/[id]/followup-radiograph-action.ts:171-187 uploads the image, then on any transaction failure logs action=followup-radiograph-orphaned with detail.orphanedImageId and returns the user error - it never deletes the blob. The cleanup helper already exists (src/lib/practica-orphan-cleanup.ts:23, deleteOrphanedApiImages) and already has a caller precedent at src/app/(protected)/practicas/nueva/actions.ts:987. Import and invoke it before returning, preserving its per-image success/failure telemetry. Every state race and DB failure to date has stranded a blob. P2 success is a permanent disabled state. followup-radiograph-control.tsx: the submit button is disabled={pending || state.status === 'success' || !file || !toothId} and the success effect calls router.refresh(), which preserves client component state - so useActionState stays 'success' and the button never re-enables. A student cannot add a second follow-up radiograph without a full page reload. Reset/remount the action form and file input after success instead of using success as a terminal disabled state. Add a regression test. P2 Postgres boundary. followup-radiograph-action.ts:73-84,120-169 does the ownership/approved-state gate, lookup resolution, row locking, sortOrder computation, archive context and insert as raw Node SQL. That is relational transactional business logic and is the class mars-commons PostgreSQL Boundary assigns to Postgres. Needs db-mars-cc to supply one typed atomic function; Node keeps auth, api-images I/O and Spanish copy. This also collapses the duplicated preflight/commit gates. Coordinate with db-mars-cc - do not hand-roll the function in the coder lane. Author owns the fix per evolutiva-commons Bug Ownership. P1+P2a are shippable without the DB lane; P2b should not block them.
Activity
-
coder-mars-cc / coder
-
Reassigned from coder-mars-ca to coder-mars-cc: coder-mars-ca is not in the roster (send refused recipient_not_registered 2026-08-31), so the author-owns-the-fix default cannot be honoured. Note both facts hold at once - it is unreachable now AND it authored the six 08-25..27 commits; an absent roster row is not evidence of an empty tenure.
-
ce0e232 v2.79.16 pushed. P1 (orphaned blob) and P2 (permanent success-disabled state) FIXED; P2b Postgres boundary deliberately NOT in this commit - it needs db-mars-cc to supply the atomic function and was not allowed to block the two user-visible fixes. P1: deleteOrphanedApiImages now called before the summary log, cleanup-then-tier per the MARS-69 caller precedent, so the row is error only when a byte is actually stranded and warn when the image was reclaimed; cleanupFailures added to detail. P2: the pick is now DERIVED as belonging to the action result it was made under, so a success retires the image it stored while a later pick survives. Two intermediate versions were rejected by the push lane and are worth recording: clearing the pick inside the effect trips react-hooks/set-state-in-effect, clearing it during render trips react-hooks/refs. The effect-based version was additionally WRONG - addToast is not a stable reference, so the effect re-fired on every render and wiped each newly picked file, reproducing the wedged form with a different cause; the new regression test caught it before the push. 3 new tests (re-enable after success, native input cleared, pick retained on error), vitest 743/743, tsc clean, eslint clean. PTD: live 2.79.16 == REMOTE main 2.79.16 (dpl_Exjb16nwza59NX3xZ7ACLm1LQ9V9), read via gh api. Sent to audit-mars-ca for review, tag coder-mars-cc-mtgtgv8gn6i7.
-
Review of ce0e232 deferred by audit-mars-ca (tag audit-mars-ca-mtgtsb0daw7g): it cannot start until the host/session controller completes an instructed Terra/medium relaunch. Condition is external to the Mars lane - neither coder nor audit can clear it. Per evolutiva-coder-commons Push-to-Done (Vercel-read ownership / do not block FINISHED on an absent audit) the fix is NOT held: ce0e232 v2.79.16 is live, PTD-confirmed against remote main, 743/743 tests green. Standing state: the two user-visible defects from BLOCK:4713c06 are fixed in prod and UNREVIEWED. audit-mars-ca has undertaken to review independently once the relaunch lands; MARS-683 stays open until PASS:ce0e232 or a superseding verdict. Escalated to Elazar as the party who controls the relaunch.
-
Audit blocker now VERIFIED with provenance, upgrading the earlier on-their-word record. audit-mars-ca read mars/agents/audit-mars/.claude/settings.json (pins model sonnet, no effort key) and llmlist.sh (live session gpt-5.6-terra high). Required state is Terra/medium; effort differs and a running session's effort cannot be changed without the controller. Outcome 3 of the three-outcome check, established rather than assumed. Caveat on the instrument, mine not theirs: .claude/settings.json is a Claude Code pin and audit-mars-ca is a Codex agent, so that file does not govern its live session - llmlist.sh is the instrument that answers the question here. Standing state unchanged: ce0e232 v2.79.16 is live and PTD-confirmed, the two BLOCK:4713c06 defects are fixed in prod and UNREVIEWED, MARS-683 stays open until PASS:ce0e232. Escalated to Elazar as controller. Coder outbound to audit is now at the 3-round cap; no further DMs on this thread.
-
Blocker CLEARED and the Elazar escalation is WITHDRAWN - no relaunch is required. audit-mars-ca re-read llmlist.sh and it now reports gpt-5.6-terra / medium, satisfying the precondition; it has started the ce0e232 review and has retracted its own BLOCK-on-runtime-mismatch as incorrect. Note the sequence, which is the reusable part: the blocker was (1) asserted, (2) upgraded to VERIFIED with provenance when a read returned terra/high, then (3) found FALSE when the same instrument was read again minutes later. A sourced blocker is a SNAPSHOT of a live value, not a standing fact - provenance answers 'was this read', never 'is this still true'. Three reads, three different standings, same instrument. Standing state otherwise unchanged: ce0e232 v2.79.16 live and PTD-confirmed, both BLOCK:4713c06 defects fixed in prod, MARS-683 open until PASS:ce0e232.
-
439749a v2.79.17 pushed, clears BLOCK:ce0e232. Audit's P1 was correct and the mechanism was confirmed at src/lib/db.ts:83 before fixing: withTransaction runs COMMIT inside its try, so the catch cannot distinguish a rollback from a lost commit ack and the trailing ROLLBACK is a no-op if the commit landed. My ce0e232 cleanup therefore could delete an image that a committed practicaImages row still referenced - strictly worse than the orphan it replaced. Behaviour is now three-way: probe finds the row (commit landed, ack lost) -> do NOT delete, log followup-radiograph-commit-ambiguous, return SUCCESS since the image IS on the practica and an error would invite a duplicate upload; probe confirms absence -> delete as before; probe throws -> RETAIN and report stranded with detail.persistenceVerified=null, because unverifiable is not absent. The asymmetry is the argument: a retained orphan is invisible, cheap and repairable forever; a deleted-but-referenced image is permanent and user-facing. 3 new tests pin all three branches including the COMMIT-ambiguity case audit asked for. Vitest 746/746, tsc clean, eslint clean. PTD: live 2.79.17 == REMOTE main 2.79.17 (dpl_JAZxR9fCvUDg7j1ezSXahgc3FsHr) via gh api. Sent to audit for review, tag coder-mars-cc-mtguncerc2oi. Open caveat stated to audit: the probe reads through the same pool that just failed, so a partitioned connection yields the unverifiable branch rather than a false negative.
-
CLOSED. PASS:439749a deep from audit-mars-ca (tag audit-mars-ca-mtguo4f5tbzb): the post-error probe correctly distinguishes committed, confirmed-absent and unverifiable; returning success on a found row is correct as it prevents a duplicate upload and permits normal revalidation; no credible false-absence path under the image-id invariant. Audit's stated scope limit: source/test review only, no live commit-ack loss exercised. Shipped across two commits, both live and PTD-confirmed against REMOTE main via gh api: ce0e232 v2.79.16 (P1 orphaned blob now freed via deleteOrphanedApiImages with MARS-69 cleanup-then-tier ordering; P2 success no longer a terminal disabled state, pick derived from the action result it was made under) and 439749a v2.79.17 (blob freed ONLY on confirmed non-persistence, after audit showed the first fix could delete an image a committed row still referenced because withTransaction commits inside its try at src/lib/db.ts:83). 6 regression tests total across the two files; suite 746/746, tsc and eslint clean. Two of my own errors are recorded on this WI rather than tidied away: the first P2 fix re-fired on every render because addToast is not a stable reference and wiped each newly picked file, caught by my own new test; and ce0e232's unconditional delete was a severity REGRESSION - a wasted blob is invisible and repairable, a deleted-but-referenced image is permanent and user-facing - caught by audit. Audit finding 3 (Postgres boundary) is NOT closed here: split to MARS-684 for db-mars-cc, with an explicit warning that the persistence probe must survive that refactor. Impact context: appEvents shows the feature has been exercised exactly ONCE since it shipped 08-27, a success by an alumno, zero orphan rows. Nothing was stranded on api-images, so no reclamation is needed; coder-apiimages-cc informed.