basquetWi + New ticket

Borrador de alta se borra en cada navegación SPA

Done normal cmcoder04-mba-l-ca

MOVILBA-334 audit 2026-10-08: ErrorContext clears the previous pathname draft on every SPA navigation. A draft therefore survives only same-route reload; the 24-hour expiry and logout cleanup are rarely reached. Define and implement the intended navigation lifecycle without weakening the deploy-reload preservation. Add a browser-level regression for restore, navigation, and clear behavior. Follow-up only; 334 audit PASS and shipping are unaffected.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • coder04-mba-l-ca created · 1w ago
    parent=#3482
  • researcher-mba-l-cc note · 1w ago
    2026-10-08T13:04:38-03:00 researcher-mba-l-cc measurement. Only one page currently uses the draft API (src/app/vehiculos/agregar/colectivo/page.tsx) - 'six paths' are the six distinct clear/expiry CODE PATHS, not six pages. Enumerated by grepping every caller of save/read/clear*FormDraft fleet-wide: 1. ErrorContext.tsx:70-72 - SPA navigation to a different pathname. Unconditional, immediate, fires on every route change regardless of elapsed time. This is the one audit flagged: it fires before the 24h check or logout path ever gets a chance to matter. 2. form-drafts.ts:19 (readFormDraft's MAX_AGE_MS check) - only reachable on a same-route full reload, since path 1 already wiped the draft on any SPA navigation away. This is the ONLY path that exercises the 24h expiry at all. 3. SessionProvider.tsx:19 - session status flips to 'unauthenticated' (token naturally expired, signed out elsewhere, etc). Wipes ALL drafts immediately, ignores 24h. 4. SessionProvider.tsx:28 - RefreshTokenExpired forced sign-out. Wipes ALL drafts immediately, then calls signOut() - which itself triggers path 3 a second time once status updates. Redundant with 3 in practice, not a bug, just double-coverage. 5. federated-logout.ts:12 - explicit user-initiated logout. Wipes ALL drafts immediately. 6. vehiculos/agregar/colectivo/page.tsx:378 - explicit clear on successful form submit (one single draft, not clearAllFormDrafts). Immediate, intentional - the draft's job is done. Intended lifetime per path, as a recommendation (no code changed, this is definition per coder03's ask): - Paths 3/4/5 (auth boundary) and 6 (submit success) are correct as-is: an ended session or a saved form has no reason to keep a draft, 24h or not. - Path 1 (SPA nav) is the one that defeats the stated 24h design: today it is indistinguishable from 'user is done with this draft.' Intended behavior should distinguish accidental navigation (user clicked a link, lost unsaved work, would want it back within 24h) from intentional abandonment - there is no signal in the codebase today that tells the two apart. Cheapest fix that keeps the deploy-reload win from 334: drop path 1 entirely and rely only on path 2's MAX_AGE_MS check plus a size/count cap in sessionStorage, so a draft survives ANY return to the same route (nav away and back, not just reload) within 24h, and ages out the same way reload already does. - Path 2 (24h expiry) becomes the sole time-based mechanism once path 1 is removed - it needs no change, it already does what the ticket wants once it's reachable. Browser-level regression test (restore/navigate/clear) is implementation scope, not researcher - flagging for coder04 to add alongside the fix, covering: draft survives SPA nav-and-back within 24h, draft gone after 24h on same route, draft gone after any of paths 3-6.
  • coder03-mba-l-cc assigned · 1w ago
    coder04-mba-l-ca / coder
  • coder03-mba-l-cc note · 1w ago
    coder03 2026-10-08 13:10 ART: fix is researcher's recommendation - DROP the SPA-nav clear in ErrorContext and rely solely on the 24h check, so a draft survives navigating away and back. The auth, logout and submit-success paths are correct as they stand. 'Six paths' means six clear/expiry code paths, one page.
  • coder04-mba-l-ca progress · 1w ago
    Removed SPA-navigation draft clear from ErrorContext; navegación still calls limpiarEdicion, while 24h expiry/logout/submit-success clear remain unchanged. One-file diff sent to audit.
  • audit-mba-l-cc audited · 1w ago
    audit pass: 1 path: src/contexts/ErrorContext.tsx, removal of the SPA-navigation draft clear and the previousPathname ref. Gates ran tree-wide; typecheck is RED but only from 646, never from this file. The other working-tree paths (646's five, the 442 SQL, ~20 src files) are NOT in this scope.
    payload
    {"scope":"1 path: src/contexts/ErrorContext.tsx, removal of the SPA-navigation draft clear and the previousPathname ref. Gates ran tree-wide; typecheck is RED but only from 646, never from this file. The other working-tree paths (646's five, the 442 SQL, ~20 src files) are NOT in this scope.","outcome":"pass","evidence":"eslint clean on the path, test:source 65/65, test:tools 113/113. typecheck has exactly 11 errors and all 11 are the 646 TS2345 in src/actions/choferes.ts - censused, none in ErrorContext.tsx or anything this diff touches - so this PASS is on the diff and does NOT certify a green tree: nobody pushes it before 646 compiles. The surviving clears are the whole set and I enumerated them rather than taking the claim: submit success at colectivo/page.tsx:378, logout via SessionProvider.tsx:19 and :28 and federated-logout.ts:12, the 24h expiry plus the fail-closed self-clear inside readFormDraft. Only the navigation clear is gone, limpiarEdicion still runs on pathname change, and colectivo is the only route with a draft, so the blast radius is one screen. This is the fix for my 334 note 3 and it has a real benefit worth recording: the 24h expiry and the logout clears were nearly unreachable while every navigation wiped the draft, and they become live controls now. Consequence to accept deliberately: the draft, which holds dominio and expediente, now lingers in sessionStorage for up to 24h per tab after the user leaves the screen, bounded by tab close and logout.","reportRef":null}
  • coder04-mba-l-ca completed · 1w ago
    Shipped in patch 192: pathname change in ErrorContext now resets only limpiarEdicion and leaves the alta draft in sessionStorage. Audit PASS 2026-10-08T16:13:34Z covered the one-file diff; 24-hour expiry, logout and successful submit still clear the draft. Browser navigation/restore was not exercised in that verdict and remains an explicit coverage limit, not an unimplemented fix.
  • coder04-mba-l-ca note · 1w ago
    Closeout coverage, 2026-10-08: browser inventory in this session has zero apps and zero browser tabs, so no authenticated current-build browser navigation was exercised. Static code and audit establish that SPA pathname changes no longer call clearFormDraft; this is not a browser runtime control. Separate latent silent-loss path: readFormDraft validates every key of current empty form as a string, and its catch deletes the stored draft on a missing/invalid field; a schema addition or corrupted/truncated storage can therefore discard older typed values. Assess compatibility/migration for valid older drafts and invalid-payload handling before claiming the broader no-loss invariant. Shipped navigation fix remains closed.
  • coder04-mba-l-ca statusChanged · 1w ago
    Patch-192 SPA-navigation fix remains shipped and code-confirmed; browser leg UNEXERCISED. PM added a same-item compatibility fix: valid older-shaped drafts must restore present string fields and retain defaults for missing new keys; only unreadable/invalid drafts clear. Implement and hand three branch controls to audit after MOVILBA-400 census.
  • coder04-mba-l-ca spec · 1w ago
    PM decision 2026-10-08: a parseable, age-valid draft whose fields object omits keys added by a newer form build is older-shaped. readFormDraft restores its present string fields and leaves missing keys at the current empty defaults, without deleting storage. Clear only JSON parse failures; non-object payload; absent/non-numeric/future/expired savedAt; non-object fields; or a present field with non-string value. Controls required: older-shaped restores and is retained; corrupt clears; present non-string clears. Audit must demonstrate both branches fail against the current implementation before accepting the fix. Patch-192 pathname fix is already shipped; authenticated browser navigation remains UNEXERCISED.
  • coder04-mba-l-ca progress · 1w ago
    Draft compatibility candidate: src/lib/form-drafts.ts SHA256 f6c17f01f0da7b9b02fa114635e304222a55f50d8f35f634b472980d3bd957b8; src/lib/form-drafts.test.ts SHA256 3642928671bfc6742d823406bf72f2a0dfcad6bd3e6acc1a8ffb81c89ff34513. Parseable, age-valid older-shaped draft restores present strings and keeps missing keys at current defaults without clearing storage. Truly malformed or expired drafts and any present non-string field clear. Audit control direction: older-shaped assertion must fail against HEAD. Corrupt and present-nonstring cases already clear in HEAD, so they cannot honestly fail against HEAD; verify they fail against a clear-disabled mutant instead. No browser navigation run. Coder did not run lint/typecheck/tests per audit workflow; git diff --check clean.
  • coder04-mba-l-ca progress · 1w ago
    Discriminating D2 test fixture repinned: src/lib/form-drafts.test.ts SHA256 ba36018c22a950dc4569d90711cafba054e01db2871ad50026476219c6c230d3; implementation src/lib/form-drafts.ts unchanged at f6c17f01f0da7b9b02fa114635e304222a55f50d8f35f634b472980d3bd957b8. Extra non-string case now includes every known key as a string (dominio and expediente) plus oldField:123, so the HEAD body cannot clear it merely because expediente is missing. Audit owes two carved-out failing-before controls: older-shaped restore against HEAD, and extra non-string against the :26-28 validation-deleted mutant; then current bytes and gates. Browser navigation remains UNEXERCISED. Coder did not run gates per lane workflow; git diff --check clean.
  • coder04-mba-l-ca verified · 1w ago
    Isolated failing-before controls executed outside the shared checkout, using byte-for-byte HEAD src/lib/form-drafts.ts, current f6c17f01 implementation, and a current-body mutant with only the Object.values(savedFields) non-string validation deleted. Each assertion block ran in its own temporary test file (temporary files removed after run). Older-shaped: HEAD exit 1, actual null vs expected {dominio:'AA123BB',expediente:''}; current exit 0. Extra non-string with EVERY known key present as a string plus oldField:123: HEAD exit 1, actual restored object vs expected null; validation-deleted mutant exit 1 with same actual; current exit 0. This is control evidence, not the audit verdict or browser coverage. Test pin ba36018c22a950dc4569d90711cafba054e01db2871ad50026476219c6c230d3; implementation pin f6c17f01f0da7b9b02fa114635e304222a55f50d8f35f634b472980d3bd957b8. Audit owns gates/verdict.
  • audit-mba-l-cc audited · 1w ago
    audit pass: CODE ONLY, the draft-compatibility pair: impl f6c17f01 and test ba36018c, re-hashed from the restored tree at 00:19:11.564Z. NOT 733's SPA-navigation half, verdicted 16:13:34.527Z on ErrorContext.tsx. Gates and 7 control runs were MINE under lock gates-checkout-movilba-web. NO browser.
    payload
    {"scope":"CODE ONLY, the draft-compatibility pair: impl f6c17f01 and test ba36018c, re-hashed from the restored tree at 00:19:11.564Z. NOT 733's SPA-navigation half, verdicted 16:13:34.527Z on ErrorContext.tsx. Gates and 7 control runs were MINE under lock gates-checkout-movilba-web. NO browser.","outcome":"pass","evidence":"audit-mba-l-cc, 2026-10-08. PASS on the pair, code only. Every figure here is MINE.\n\nGATES, unpiped rc, under the hub lock: typecheck 0, test:source 0 at 68/68, test:tools 0 at 118/118, eslint 0 on both files with --no-warn-ignored. The test file is inside test:source, so it gates both ship paths.\n\nCONTROL SEEN TO FAIL. 7 runs in separate scratch dirs, rc unpiped: c0 cand/cand 0; c1 cand test vs HEAD cb4e0d2b rc=1 at :27, null vs {dominio:'AA123BB',expediente:''}, the defect itself; m1 Object.values non-string validation deleted rc=1; m5 Object.hasOwn guard dropped rc=1; m2 Array.isArray(fields) removed rc=0 NOT CAUGHT; m3 !Number.isFinite(savedAt) removed rc=0; m4 Array.isArray(parsed) removed rc=0. coder04's same two conclusions ran in deleted temp files; not re-checkable, mine is on disk.\n\n5 findings in note, none blocks. The one for a later edit: m2 is LOAD-BEARING and untested - fields:[] returns {\"a\":\"\",\"b\":\"\"} on m2 where the candidate returns null, so dropping it makes the form report an all-blank draft as recovered. m3 and m4 are REDUNDANT, measured (note §3.3, §3.4).\n\nTHE TRADE, measured HEAD vs candidate: consumed key non-string null/null, unchanged; extra non-string key only RESTORED by HEAD and null now, STRICTER; extra string key restored by both; a missing consumed key null by HEAD and {\"a\":\"x\",\"b\":\"\"} now, THE FIX. HEAD already rejected a non-string on a consumed key, so the Object.values sweep buys nothing there and only costs the extras row - the MOVILBA-754 shape from the other direction. Intended: :38-40 asserts it, coder04 built the fixture for it, and the spec's \"a present field with non-string value\" admits both readings (note §3.1).\n\nSIN EJERCITAR: no browser, no real sessionStorage, no navigation, no render, no DB, no HTTP. 733's own SPA subject is untouched here. node aborts at the first failure, so each rc=1 is evidence about ONE assertion and the three values.has clearing asserts were never seen to fail.","reportRef":null}
  • audit-mba-l-cc audit-finding · 1w ago
    # MOVILBA-733 — audit verdict: PASS on the draft-compatibility pair, CODE ONLY. audit-mba-l-cc, 2026-10-08 src/lib/form-drafts.ts f6c17f01f0da7b9b02fa114635e304222a55f50d8f35f634b472980d3bd957b8 src/lib/form-drafts.test.ts ba36018c22a950dc4569d90711cafba054e01db2871ad50026476219c6c230d3 Both re-hashed from the restored working tree at 2026-10-09T00:19:11.564Z and exact against coder04's pins. All gates and all seven control runs were mine, under the hub lock `gates-checkout-movilba-web`. **This is not a verdict on 733's original subject.** The SPA-navigation half — `ErrorContext.tsx` no longer clearing the previous pathname's draft — was verdicted separately at 2026-10-08T16:13:34.527Z and shipped in patch 192. These two files are the compatibility fix the PM added at 22:12:17.047Z. The browser leg of both halves remains unexercised. ## 1. Gates typecheck rc=0 test:source rc=0 68/68 test:tools rc=0 118/118 eslint, both files, --no-warn-ignored rc=0 Every rc read unpiped. `src/lib/form-drafts.test.ts` is inside `test:source`, so it gates both ship paths (`mba-deploy-dev.sh:175`, `mba-deploy-prod.sh:59`). ## 2. The control was seen to fail, and two more mutants confirm what it covers Seven runs, each in its own scratch directory with the implementation as `./form-drafts.ts`, every rc read unpiped: c0 candidate test / candidate impl rc=0 c1 candidate test / HEAD impl (cb4e0d2b) rc=1 :27 'older-shaped draft' m1 Object.values(savedFields) non-string validation deleted rc=1 m2 `|| Array.isArray(fields)` removed rc=0 NOT CAUGHT m3 `|| !Number.isFinite(savedAt)` removed rc=0 NOT CAUGHT m4 `|| Array.isArray(parsed)` removed rc=0 NOT CAUGHT m5 the `Object.hasOwn(savedFields, field)` guard dropped rc=1 c1 failed with `actual: null, expected: { dominio: 'AA123BB', expediente: '' }` — the defect itself, so the assertion is a control and not a green line. m1 and m5 confirm the two halves of the new behaviour. m2, m3 and m4 are each a guard the candidate adds that no assertion covers; §3.2 to §3.4 say which of those matter. coder04's own controls (23:43:27.187Z) reached the same two conclusions. They ran in temporary files the run then deleted, so that evidence cannot be re-checked by anyone; mine are on disk in the session scratchpad. The agreement is independent, not inherited. ## 3. Findings — none blocks the PASS ### 3.1 Moving the type check from the consumed keys to every stored value buys nothing for the consumed keys, and costs one recovery case. It is intended, and the spec sentence admits both readings. Measured, HEAD against the candidate, same four payloads, `empty = { a: '', b: '' }`: payload shape HEAD candidate consumed key non-string null null unchanged extra non-string key only {"a":"x","b":"y"} null STRICTER extra string key only {"a":"x","b":"y"} {"a":"x","b":"y"} one consumed key missing null {"a":"x","b":""} THE FIX HEAD already rejected a non-string value on a key the form reads, through its per-field check. So the only thing the `Object.values(savedFields)` sweep changes is the second row: a draft whose sole invalid value sits on a key `readFormDraft` never reads is now discarded along with the user's typing on the keys that are valid. That is the shape coder03 named on MOVILBA-754 — validate the shape you consume, not every key present — reached here from the other direction. It is **not** a defect and **not** an accident. coder04's 23:42:03.032Z note says the extras fixture was built so HEAD "cannot clear it merely because expediente is missing", and `:38-40` of the test asserts the rejection. The PM spec says "or a present field with non-string value", which reads either as *present among the consumed keys* or *present anywhere in the stored object*; the implementation picked the wider one without the spec saying so. That is a wording point for the PM, not a change to these bytes. ### 3.2 `Array.isArray(fields)` is load-bearing and untested m2 rc=0, so no assertion covers it, and removing it is **not** harmless. Measured on m2 with `fields: []`: `readFormDraft` returns `{"a":"","b":""}` where the candidate returns `null`. Every value of an empty array passes the string sweep and `Object.hasOwn([], 'a')` is false, so the function reports a successfully recovered draft that is entirely blank. The candidate is correct here; the gap is that the test seeds no array and so this is the one new guard whose removal changes behaviour without reddening anything. ### 3.3 `!Number.isFinite(savedAt)` is redundant, not merely untested m3 rc=0. Measured: `NaN`, `Infinity` and `-Infinity` are not JSON literals and `JSON.parse` throws on all three (`Unexpected token 'N'`, `Unexpected token 'I'`, `No number after minus sign`), so the only route to a non-finite `savedAt` is an overflowing literal. `1e400` does parse to `Infinity` — and `savedAt > now` already rejects it, while `-1e400` is rejected by `now - savedAt > MAX_AGE_MS`. Candidate and m3 return `null` on both, identically. The clause is reachable and changes no outcome. ### 3.4 `Array.isArray(parsed)` is redundant m4 rc=0. An array carries no `savedAt` through `JSON.parse`, so `typeof savedAt !== 'number'` already rejects it. Measured: candidate and m4 both return `null` for `[{savedAt: <now>, fields: {a:'x'}}]`. ### 3.5 Node aborts at the first failure, so one run demonstrates one assertion All of `src/lib/form-drafts.test.ts` is a single top-level block, so the c1 run died at `:27` and lines `:31` to `:56` never executed in it. Each rc=1 above is evidence about exactly one assertion, and the three `values.has(key)` clearing assertions at `:32`, `:36` and `:40` have been seen to PASS but never to fail. ## 4. NOT EXERCISED - **No browser, no React, no real `sessionStorage`.** Every run used the test's own `Map`-backed `Storage` double. 733's original subject is SPA navigation and nothing here navigates; the authenticated browser leg the PM asked for at 22:12:17.047Z is still owed and is not covered by this PASS. - No form, no page and no submit path was rendered or called. `saveFormDraft`, `clearFormDraft` and `clearAllFormDrafts` were exercised only as the test calls them. - The six recovery paths MOVILBA-334 shipped were not enumerated against this change; I verdicted `readFormDraft`'s contract, not every caller's use of it. - No database, no SQL, no HTTP. - `next build` runs on no ship path, so nothing here establishes the production bundle compiles these bytes.
  • coder03-mba-l-cc completed · 1w ago
  • coder03-mba-l-cc verified · 1w ago
    Shipped and LIVE on both surfaces. dev ec6a1b94/patch 222, prod main merged and app.movilba.com/api/health answers serviceId 31325 = patch 222, probed 2026-10-08 23:33 ART. Root cause of the earlier 221 reading: the post-receive hook on /srv/movilba-web.git runs synchronously inside the push and my push was killed by a 2-minute timeout at 23:15:55, exactly when pnpm build finished writing BUILD_ID - so the hook never reached its restart stage (deploy.json has install and build entries and NO restart entry) and next-server kept serving the 221 build from 22:28. Fixed by completing the hook step the kill skipped: systemctl restart movilba-web.service. SIN EJERCITAR: only the health route was probed; no screen, PDF, XLSX or pod. POD leg still owed.
  • coder03-mba-l-cc note · 1w ago
    CORRECCION que supersede mi evento de 02:21:46Z: el SHA de dev esta mal escrito ahi. dev = ec4b68642158f9bba09edbeb5562c34a8ecbd790 (ec4b6864), NO ec6a1b94, que no es un objeto de este repo. prod main = 3dd4b3ec971026c2c5dbb6ebb298aa734938bc05. Todo lo demas de ese evento se sostiene sin cambios: prod responde serviceId 31325 = patch 222 y la causa del 221 fue la etapa restart del hook que el kill de los 2 minutos salteo.
  • coder03-mba-l-cc verified · 1w ago
    FLEET PARITY alcanzada: las TRES superficies sirven patch 222. prod app.movilba.com serviceId 31325 (23:33 ART), dev01 ya estaba en 222 por el deploy, y el pod permisos-movilba-dev.gcba.gob.ar devolvio 31325 a las 23:31 ART despues de que Tekton terminara - mba-deploy-pod.sh espejo el arbol de dev ec4b6864 a repositorio-asi, force-push fe6d299 -> b5feafd en develop y EventListener HTTP 202. Las dos primeras sondas al pod a las 23:27 y 23:28 devolvieron 31188 (patch 221) y eso era el pipeline todavia corriendo, no una falla: no hay sesion de oc para mirar los builds (oc whoami da Unauthorized, el token sale de la consola OCP y eso es Elazar en el navegador), asi que el pipeline no se observo, solo su resultado. SIN EJERCITAR en las tres superficies: ninguna pantalla, PDF, XLSX ni consulta - la unica ruta probada es /api/health.
bug
1w ago by coder04-mba-l-ca
1w ago
2026-10-09 02:21