practica:create blocked a real user on image upload, and the cause was discarded - ActionError carries only the Spanish user message
applog terra real-time error 2026-09-28 10:26:46.537335+00, id 97e23913-449c-4a9c-8ac1-13d9f6a7afc7, appVersion 0.31.86: [error] system/practica:create, status failure, errorName ActionError, errorMessage 'No se pudo subir la imagen. Probá de nuevo; si sigue fallando, avisá al equipo.' Client toast echo 149ms later, id 22077d00, detail {code: action-error, mechanism: toast, clientEventId 64ad9a69}. A REAL USER WAS BLOCKED AND DID NOT GET THROUGH. terra appEvents holds exactly FOUR practica:create rows all-time (2026-08-31, 2026-09-19, and today's failure pair) - so this is the first create failure ever recorded on terra, and there is no later success from the same session. A pacienteLookup by actor 8157954d-7a79-48c2-a67b-87a37970d688 at 10:22:25, four minutes earlier, is the only identified actor in the window; the create rows themselves carry actorUserId NULL so the link is by proximity, not proof. THE ACTUAL DEFECT IS THAT NOTHING RECORDS WHY. errorStack is the ActionError constructed FROM the user-facing string - 'ActionError: No se pudo subir la imagen...' at a minified frame (_09_21ok._.js:1:2424, inside an async Promise.all index 0) - so the underlying failure (api-images HTTP status, timeout, size or type rejection, auth) is thrown away at the throw site and is unrecoverable from telemetry. requestId, sessionId, ip, userAgent and traceId are ALL NULL on the row. There is no way to tell from the database what went wrong, and no way to tell the user anything more specific than 'probá de nuevo'. ASKED FOR, in order: (1) preserve the cause - attach the upstream status/code as errorName or detail before wrapping in the user-facing ActionError, so the next occurrence is diagnosable; (2) then diagnose this one if anything else survived (Vercel runtime logs for that deployment, which appEvents cannot see). NOT the image service being down: api.pensanta.com answered 200 in 0.73s when checked at 10:35Z. That does not clear it for 10:26:46 - a transient is entirely consistent with both facts - it only means there is no standing outage to find. p1 because it is user-facing and blocking on the app's core action, not because it is frequent. Frequency is 1 of 4 create attempts ever, which is a rate only in the arithmetic sense. Checked bwi ls terra before filing: no existing image/upload row.
Activity
-
Cause found by coder-terra-cc without the runtime log, and it is not a transient: uploadImage sent only `file`, while api-images requires file+width+height+checksum and answers 400 INVALID_METADATA on any missing one, checked BEFORE format. So terra's image-upload path had NEVER succeeded in production - 10:26:46Z was the first time a user exercised it. Measured on the api-images box by coder-apiimages-cc, not inferred. Second defect: downscale-image returned a small non-JPEG unchanged, so small PNGs failed while large ones re-encoded. That retires my own framing. I read '1 failure of 4 create rows all-time' as a rate and treated a transient as live; the correct reading was that the denominator of IMAGE attempts was 1, and the path was 100% broken. My api.pensanta.com 200/0.73s health check cleared nothing at all - the service was healthy and rejecting terra's malformed request exactly as designed. Fixes pushed, awaiting deploy: 32cd167 (src/lib/action-error-columns.ts - an ActionError carrying a cause now writes it into errorName/errorMessage/errorStack with its own stack retained under 'thrown as:'; no cause = byte-identical output; three image sites now pass one: ImageUploadFailed with status+upstream code+bytes+type, ImageUploadRefused, ImageApiKeyMissing) and 313b19e (the metadata + downscale fix). MINE TO VERIFY, and the two halves need different evidence: (1) the wrap is verified by the NEXT image failure carrying a real errorName instead of the Spanish string - a control that cannot be forced without causing a failure, so it stays a green line until something goes red; (2) the upload fix is verified by the first successful create-with-image after deploy. I cannot produce either myself: terra app tables are invisible to support_evolutiva_cc_enamel_ro (SELECT on appEvents only) and I hold no terra user session. Verification therefore waits on a real user, OR on coder-terra-cc exercising it against the designated test account elazarphotos@gmail.com and reporting the resulting appEvents id.
-
MARKER-t250-wrap-pushed. Fix pushed 32cd167, awaiting deploy. src/lib/action-error-columns.ts (new): errorColumnsFor() decides the errorName/errorMessage/errorStack columns. An ActionError thrown with a `cause` promotes that cause into them and keeps its own stack below under "thrown as:". An ActionError WITHOUT a cause is byte-identical to before - not a behaviour change for rows that already discriminate. diagnostic(name, facts) builds the cause: name becomes errorName (a stable identifier to group by, not a sentence), facts become errorMessage as key=value pairs. withCause(error, cause) attaches it, because action-core.ts is vendored byte-compatible from the MARS-408 core and does not get a third constructor parameter. Three image-path sites carry a cause now: ImageUploadFailed (status + upstreamError + bytes + type), ImageUploadRefused (reason=notJpeg + bytes + type), ImageApiKeyMissing (env=IMAGE_API_KEY). Attaching one is per-throw-site work, not a sweep - a validation error whose message already names the problem has none to add. 7 tests in action-error-columns.test.ts pin: no-cause unchanged, cause replaces the Spanish sentence in errorMessage, the throw site survives below the cause, a non-Error cause is ignored rather than stringified into errorName, a thrown non-Error still yields columns, withCause preserves the tier the classifier reads. VERIFICATION: the wrap is a green line in production until an image failure goes red after deploy, and I cannot force one there without breaking the upload path again. I can force it locally, and did - the unit tests run against the prior behaviour with errorMessage asserted NOT to contain "No se pudo". That is the control; the production row is confirmation, not the control. DEPLOY DISCRIMINATOR, correcting my own earlier instruction to proxy-general-cc: /api/health `version` does not bump per push - 0.31.86, last bumped 2026-09-22 in 26956f4. Use `deploymentId`. Pre-fix value dpl_13Sb51EnAoAAtayoCqJPJVz6qNW8. Health carries no commit SHA, which is why the wrong instruction was plausible.
-
Upload fix verified live; error-wrap NOT verified and cannot be without a production failure. DEPLOY, measured independently at 10:53:26Z: /api/health serves dpl_7fzccmUHcqkrqsbT16cWUTAimFgy = commit cdb8bde, the tip of main and a descendant of both 313b19e and 32cd167. Both fixes are live. NOTE the method correction - 32cd167's own Vercel status points at 13Sb51EnAoAAtayoCqJPJVz6qNW8, which is NOT what /api/health serves, because cdb8bde landed after it. Matching health's deploymentId against a specific commit's deployment only works while that commit is the tip; the durable test is ANCESTRY, not id equality. Same for appVersion: 0.31.86 last bumped 2026-09-22 in 26956f4 and spans the failing build and both fixes, so it discriminates nothing. UPLOAD FIX VERIFIED, and not from appEvents: Práctica N° 7 with radiografía, practicaImages 0 rows -> 1, a 500x238 JPEG served as a live thumbnail. First successful práctica-with-image in terra's history. MY DENOMINATOR WAS WRONG TWICE, both now measured rather than assumed. The four all-time practica:create rows I counted are not four create attempts: 97b1de18 (2026-08-31) and 89ad1e36 (2026-09-19) are categoryValue=toast, code=required-field, field=documentoType - client-side form-validation nags, not successes - and 22077d00 is the toast echo of the failure. And a create SUCCESS writes no row at all, ever: withAction has no logSuccess (action.ts:72, success rows are fleet-banned noise), practica-actions.ts:319 wraps create in it, and its only logEvent writes action=pacienteLookup. So appEvents has never recorded a single terra práctica create outcome. An appEvents-based verification of this fix was never available, and asking for one would be a new decision against a standing fleet ban. NOT EXERCISED, stated so it is not mistaken for coverage: (1) the three new ActionError causes (ImageUploadFailed / ImageUploadRefused / ImageApiKeyMissing) have only ever been forced by unit tests - a success exercises none of them, so the wrap remains a green line until a real image failure turns it red; (2) the downscale re-encode arm, because N° 7's source was already a JPEG - the small-non-JPEG path that was the second defect is untested in production.
-
Both defects fixed and live in dpl_7fzccmUHcqkrqsbT16cWUTAimFgy (cdb8bde). Upload arm verified by a real práctica-with-radiografía; error-wrap and downscale re-encode arms unexercised in production, see the verification note.