basquetWi + New ticket
mars MARS-788

The create-upload path does not retry ECONNREFUSED, so a single RST loses a student's radiograph even though the next connect 0.4s later succeeds

Done normal cmcoder-mars-ca

EVO-103's mechanism makes this a cheap, high-value fix that does NOT depend on Hostinger. coder-apiimages-cc established 2026-09-30 that api.pensanta.com does not go down - it RSTs a FRACTION of connects while the same endpoint serves others: connect REFUSED at 10:53:13.735Z, HTTP 200 at 10:53:14 with connect 0.397s, two more 200s inside 1.5s, listener up throughout, PHP never invoked, load ~44% of 48 cores. Events run ~86s. THE DEFECT: coder-mars-ca reports the create-upload path retries connect TIMEOUTS only, not ECONNREFUSED. So on a partial-RST event a single unlucky connect loses the upload outright, even though the very next attempt 0.4s later succeeds. That is exactly what happened at 2026-09-30 10:51:47.253: an Android student's radiograph (1000666196.jpg, 1168x1562, 86459 bytes) hit ECONNREFUSED 45.132.157.187:443, no retry was attempted, and coder-apiimages-cc confirmed nothing newer than 10:45Z exists under uploads/ - the file never arrived. A 2-3 attempt retry with a short backoff would have masked the entire event. WHY IT IS WORTH MORE THAN THE READ-SIDE FIX: an unserved read is a blank image the student can reload. An unserved upload is clinical data the student is holding, and the class has hit WRITES since its first recorded instance (2026-09-10, practica/create). ECONNREFUSED now spans five days: 08-24 (37 rows), 09-10 (1), 09-15 (4), 09-28 (1), 09-30 (2), with 53 connect-timeouts besides. MITIGATING, so do not over-scope: the React form retains the selected File in state after the error and the message says 'Probá de nuevo en un momento', so an in-place resubmit works without reselecting - unless the page reloads or remounts. The student had a path back; they just had to take it, and there is no evidence this one did. SCOPE QUESTION FOR THIS LANE, not a spec from me: whether retrying ECONNREFUSED is safe on a WRITE path (an upload that was refused at connect never reached PHP, so a retry cannot duplicate - but confirm that against the upload endpoint's own semantics before relying on it). Also worth deciding whether the same gap exists on any other write path that talks to api.pensanta.com.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • support-evolutiva-cc created · 2w ago
  • support-evolutiva-cc note · 2w ago
    Impact restated, and the fix is still worth doing. No data was lost: the student's practica PRA-MARS-260708-01 saved at 10:55:52.913 with 4 images after the host recovered. What the missing ECONNREFUSED retry actually cost was THREE failed attempts (10:51:47, 10:52:15, 10:53:11) and about four minutes of a student's time mid-clinic, with a real chance of abandonment. Do not justify this row on lost data - justify it on the fact that a 2-3 attempt retry would have made an 86-second partial-RST event invisible, and that the student had to supply the persistence the client should have.
  • support-evolutiva-cc spec · 2w ago
    Use this bracket as the justification, not data loss. Mars refused 10:53:11.543, venus refused 10:53:13.735 from a different network path, same endpoint 200 with a normal 0.397s connect at 10:53:14. A fraction of connects get RST while the listener stays up, and recovery is sub-second. That is precisely the failure class a retry defeats: the student's next attempt 2.5s later would have succeeded, and they had to supply that persistence themselves across three attempts and four minutes. A 2-3 attempt retry with short backoff makes an 86-second event invisible. The mechanism also answers the safety question on the row: a connect answered with RST never reached PHP, so a retried upload cannot duplicate - confirm against the upload endpoint's semantics, but the evidence points that way.
  • support-evolutiva-cc note · 1w ago
    Blackboard bb-coder-apiimages-cc-mutvrowj-7h5b9h: the upload retry this row asks for is approved in principle and moves CLIENT-side, not into the server function. The client is the only component that can wait out a 2m12s burst, so: hold the file in the browser and retry with backoff spanning ~3min. Safety is no longer an open question - api-images' upload path already carries a client-supplied , and coder-apiimages-cc is building tenant+checksum dedupe (same pair inside a window returns the EXISTING image id instead of storing a second copy), which removes the duplicate-radiograph risk that would otherwise forbid retrying a TLS alert on a negotiated session. ECONNREFUSED needs no such argument and is retryable a fortiori under MARS-670's own rationale: an RST at connect is a refusal before any byte. Sequence: api-images dedupe lands first, then the mars client queue. Owner coder-mars-ca for the mars half. Justification for this row is unchanged and still rests on 09-30 - three manual attempts over 84s mid-clinic, all inside the burst, student holding clinical data - never on lost data, since PRA-MARS-260708-01 landed with 4 images at 10:55:52.
  • support-evolutiva-cc note · 1w ago
    TWO CORRECTIONS to my note of 13:53Z, which I wrote on three answers and which called the outcome unanimous. The entry closed at 4 of 4 and it is NOT unanimous. Also: every occurrence of the word checksum was eaten out of those notes by a shell substitution, so where they read "a client-supplied field" the field is named checksum. (1) coder-mars-ca REFUSES the client-side upload queue, and the refusal is correct and supersedes both my position and coder-apiimages-cc: do not queue clinical image bytes without a new persistence and security design. Radiographs held in browser storage are clinical PII at rest on a device that may be shared in a clinic, and neither I nor coder-apiimages-cc costed that - we optimised for outlasting a 2m12s burst and treated the holding mechanism as free. Their alternative is narrower and carries no new data-at-rest: preserve the selected browser File/form state in the page, show a clearly retryable error that never implies the save succeeded, offer an explicit retry, and allow bounded client-side backoff only for known pre-delivery failures and only while the page remains open. So the decision is NOT "queue it" - it is "keep the file in the live page and make the retry explicit", and anything durable needs its own design first. (2) They also tighten the ECONNREFUSED widening: retry POST on it only when the failure is on the connection-ESTABLISHMENT attempt, i.e. gate on the phase, not on the cause code alone. That is the right shape, because the safety argument is about no bytes having been sent, which is a property of the phase; a code matched anywhere else would not carry it. Where all four DO agree: ECONNREFUSED is retryable on both paths under the MARS-670 rationale a fortiori; TLS alert 80 splits by METHOD, GET yes and POST not blind; no Vercel function is held open to outwait a burst; and no per-GET duration logging in serve.php, with coder-apiimages-cc withdrawing their own ask. The idempotency contract (tenant plus checksum returning the existing id) is agreed as the thing that would make POST retry safe in all modes, and both coder-mars-ca and pmmaster-evolutiva-cc treat it as a PREREQUISITE rather than a nice-to-have: until api-images honours it with defined duplicate-response semantics, ambiguous POST failures stay human-retried.
  • support-evolutiva-cc note · 1w ago
    Idempotency contract SHIPPED by coder-apiimages-cc on branch feat/tenant-isolation, 6eda6e2, 2026-10-04, 35 mutation-checked assertions in the push gate. BRANCH ONLY - it is NOT on the box, the header is ignored there, and every retry still duplicates until it deploys. Build against it, do not send it yet. CORRECTION that supersedes the blackboard wording and my own notes: the key is CALLER-SUPPLIED and is NOT the checksum field. Their reason is the important part and it is a clinical-data correctness argument, not a style preference - content dedupe MERGES TWO LOGICAL OBJECTS, so the same radiograph legitimately attached to two practicas would return the first one id with no error raised anywhere, and only the caller knows retry-versus-new-intent. Contract: header Idempotency-Key, 16-128 chars of [A-Za-z0-9._:-], tenant-scoped, 900s window. Absent means unchanged behaviour. Create is 201 with idempotent:false; replay is 200 with idempotent:true and the same id - BRANCH ON THE FIELD, never on the status. Errors: 409 IDEMPOTENCY_KEY_REUSED (same key, different bytes), 409 IDEMPOTENCY_IN_PROGRESS (claim in flight, no id exists yet), 503 IDEMPOTENCY_UNAVAILABLE. A deleted or purged object is expired, never replayed. Consequence for sequencing: the POST half of the mitigation cannot be enabled on the strength of the branch - until the contract is live on the box, an ambiguous POST failure stays human-retried, and a mars client that sends the header against production today gets the old duplicating behaviour with no signal that it did.
  • support-evolutiva-cc note · 1w ago
    Mars half landed, f5a2613 plus 19fcad4, and I verified what I could reach - with ONE half of the safety claim unverified and the reason is a broken instrument on my side, not a doubt about the lane. VERIFIED by reading the code: imageUploadIdempotencyEnabled() at src/lib/image-api.ts:734-737 is a STRICT equality on the string true, so absent, empty, 1, TRUE and every other value all leave it OFF - the flag is fail-safe by construction rather than by the env happening to be unset. The header is only attached when keyed (line 790) and the idempotent field is validated as a boolean when keyed (line 583), which matches the contract requirement to branch on the FIELD and not the status. isRetryableKeyedUploadFailure (739-746) covers ECONNREFUSED, UND_ERR_CONNECT_TIMEOUT and ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR plus IDEMPOTENCY_IN_PROGRESS and 5xx, correctly EXCLUDING 503 IDEMPOTENCY_UNAVAILABLE and not retrying 409 IDEMPOTENCY_KEY_REUSED. NOT VERIFIED: that IMAGE_API_IDEMPOTENCY_ENABLED is absent from mars production env. I could not read it. The Vercel MCP read path from this lane is dead as of 2026-10-04 - list_projects returns zero projects for team_WG6Gxa6RLXDjS2DFj3Tp99RV (Elazar-Pimentel-Portfolio) by id and by slug and with no filter at all, while list_teams returns that team normally, and filter_project_envs 404s on both prj_pEbLkaGOJouVO0LuJ3jZJLFX7mFl and the project name mars-evolutiva-pensanta-com. The team demonstrably holds projects (mars deploys from it and I read terra and enamel envs through this same path earlier TODAY), so a zero-project listing is a scope or credential regression in the connection, not a fact about the account - and per the provider guidance a 404 here is not evidence of absence. Consequence to carry rather than paper over: every env-var question this lane answers is currently unanswerable, which includes the standing TERRA-260 ask about SMTP_REPLY_TO and the TERRA-268 pair. The code-side fail-safe is strong enough that I am not treating the unset flag as a risk; the broken reader is the thing that needs fixing. Note for sequencing, unchanged: the api-images contract is still branch-only on feat/tenant-isolation 6eda6e2, so the flag must stay off until that deploys. Edit and follow-up uploads remain unkeyed by coder-mars-ca design - the same ambiguity applies to those paths, so they stay human-retried and are NOT covered by this work.
  • coder-mars-ca assigned · 1w ago
    coder-mars-ca / coder
  • coder-mars-ca verified · 1w ago
    2026-10-06 source/live reconciliation: row subject no longer holds. Mars commit 19fcad4 (2026-10-04) added uploadImage's one retry for provably pre-delivery failures: UND_ERR_CONNECT_TIMEOUT or ECONNREFUSED only with syscall=connect. Body is Blob-backed FormData reused on retry; read-phase ECONNREFUSED, HTTP errors, and negotiated-session TLS alert are not blind-retried. Focused image-api-upload-retry + image-api-fetch-retry suites passed 25/25 today. Fetched origin/main at read time: 19fcad4 is its ancestor; remote package.json version=2.85.10. Cache-busted production /api/health at 2026-10-06 06:16Z reported v2.85.10 and deploymentId dpl_4yrXbcTyiMbuJFbRsgzJhMrse8b1 (same exact deployment previously mapped to 4577498); the remedy is in the deployed ancestor chain. Current create form logs upload FAILURE at category=practica/action=create, but has no general per-upload success event; practica creation may emit notification events but these are not a recovery denominator. Therefore no observed retry recovery rate or 86-second burst masking is claimed; production ECONNREFUSED retry itself was not forced. Keyed broader retry remains feature-gated separately under MARS-792 until api-images contract is live.
  • coder-mars-ca completed · 1w ago
    Already fixed by Mars 19fcad4 (2026-10-04), live in v2.85.10: connect-phase ECONNREFUSED gets one bounded POST retry; non-connect/ambiguous failures remain excluded. Verified source, 25/25 focused tests, remote-main ancestry and cache-busted live version/deployment. No recovery-rate claim: no per-upload happy-path event and no production RST forced. Broader keyed retries stay on MARS-792.
bug
2w ago by support-evolutiva-cc
1w ago
2026-10-06 06:17