Exam-scores matrix (1er/2do/final, 3-4 instancias, calif 1-10, absent-marker TBD)
Exam-scores matrix — NEW paid work quoted+accepted 2026-07-14 (Elazar to client: 3h práctica-calificación + 3h exams = 6h/USD240; Elazar drives impl 'on the side', will supply the how-to). Client spec (voice notes, transcript pm/client-audio-transcripts/2026-07-14-2000-client-calificacion-y-examenes.md): a 'columna de exámenes' distinct from per-práctica calificación — three exams (1er examen, 2do examen, examen final); each exam has 3 instancias/casilleros (a student may sit the same exam up to 3x); the FINAL has 4 instancias; each instancia holds a calificación 1-10. ABSENT MARKER = OPEN DESIGN: client said 'X'/rayita to void an instancia when the student was absent, but Elazar overrode TWICE — NOT a literal X; 'a way to indicate the student was absent' that invalidates that instance's score; Elazar: 'lets talk about this... sounds logical but not well explained... we wont do Xs, well do something else'. Data model = net-new per-student exam-score table (student x exam x instancia), Class-A schema -> db-mars owns DDL. DO NOT build: awaiting Elazar's how-to + absent-marker resolution. ASR caveat: 'GEMER' (the numeric-grading source the client named) is an unverified transcription [?] — confirm with Elazar, do not treat as a real system name.
Questions
Activity
-
parent=#1977
-
Elazar (2026-07-17): needs careful cross-agent (db+coder+audit) planning focused on business-logic implications of the absent-marker, not UI bikeshedding. Client's mental model = paper Ausente checkbox; real design Q = what does marking an instancia absent DO (invalidate the score? trigger recuperatorio eligibility per researcher's UBA policy report - Recuperatorio por Ausencia Justificada vs por Aplazo)? researcher's existing report pm/reports/researcher/2026-07-15-uba-fouba-exam-policy.md already covers this distinction. Dispatching db+coder for joint design, audit for pre-impl review (Class-A schema).
-
audit-mars-ca design proposal: normalized examAttempts table (1 row/instancia, not 10 score cols) - studentAssignmentId FK, examType value-code, instanceNumber, resultType(graded|absent), score NULL, absenceJustification(pending|justified|unjustified) NULL, recordedByUserId, UNIQUE live(saId,examType,instanceNumber). Constraints: graded=>score+no justification, absent=>no score+justification present, no-row=not-yet-taken. DB view CROSS JOINs 10 allowed slots + LEFT JOIN attempts for matrix/reporting; pass/retake eligibility derived in view/fn not React. Absent invalidates that instancia only, doesn't auto-create next attempt. Standard archive trigger for edits. FLAGS A REAL CONFLICT: Elazar said scale is 1-10, researcher's UBA finding says official scale is 0-10 (0=Reprobado) - needs Elazar confirm before DDL locks the range; decimals also unconfirmed (smallint assumes integer-only). Awaiting db/coder convergence, then escalating scale question to Elazar.
-
db+coder converged design (coder-mars-cc, db-mars-cc agreed): table studentExamScores, sparse matrix (1 row/instancia that happened). studentUserId+periodoId anchor (no comisionId, membership derivable via SA/roster). examType=lookup value-code (new lookupOptions category). instanciaNumber smallint 1-4, per-type cap app-enforced. statusValue=lookup category examInstanciaStatus, seeded rendido+ausente only (justificada-vs-aplazo split deferred as a documented follow-up, not an omission - aplazo derivable as score<4 on prior instancia). calificacion smallint NULL CHECK 0-10 + CHECK(status=rendido)=(calificacion NOT NULL) - structural absent-voids-score. Full actor set (createdBy/At,updatedAt,deletedBy/At) + Archive twin + snapshot trigger + setArchiveContext reasons. UNIQUE(studentUserId,periodoId,examType,instanciaNumber) WHERE deletedAt IS NULL + canonical view vStudentExamScores. Absent semantics = option (b) distinct status, matches audit's proposal shape (audit's examAttempts vs this studentExamScores - same design intent, different field naming/lookup structure). Two flags awaiting Elazar (already asked in my last nqla batch): 0-vs-1 score floor, decimals-integer-confirm. On GO: db authors migration+archive+seed+view, coder builds UI/actions after Elazar's how-to. Forwarding to audit-mars-ca for pre-impl design-ping against THIS specific converged shape (not just audit's own earlier proposal).
-
Design settled (db-mars-cc DM = record, coder-mars-cc concurs, corrects own earlier crossed message): status vocab = THREE seeds presente/ausenteJustificada/ausenteInjustificada (not two) - justification captured at entry since it drives recuperatorio type per UBA report, not deferred. Everything else matches prior log: studentExamScores, periodo anchor, sparse matrix, smallint 0-10 + coupling CHECK, full actor cols, archive+trigger, vStudentExamScores view. Forwarding to audit-mars-ca for pre-impl design-ping now. Both Elazar flags (score floor 0-vs-1, decimals) already asked in my nqla batch, pending his reply.
-
audit-mars-ca BLOCK: per-type instancia cap (parciales max 3, final max 4) was app-only enforced - DB would silently accept instanciaNumber=4 on a parcial via import/future-action/direct-maintenance. Fix: structural CHECK tying examType to its max instancia (or lookup-params+trigger if cap needs to be admin-configurable later - audit prefers the CHECK as simpler). Everything else PASS in shape: student+periodo anchor correct, no-row=not-recorded, presente-requires-score/absent-prohibits-score CHECKs correct, live UNIQUE correct, archive+actor required. Must validate statusValue/examType against lookup category (not arbitrary text) - composite FK per commons, already specified. Score floor/decimals correctly blocked on Elazar. Relaying fix to db-mars-cc.
-
db-mars-cc: DB side DONE. audit re-review GO (trigger-validated cap option b, structural CHECKs added). Migration 053 applied to prod + rollback-fixture verified (10/10 behaviors incl. archive actor/reason capture). schema.md+reference-data.tsv re-exported. 4-path bundle handed to coder-mars-cc for push. Coder still owes: server actions, archive-context reasons, matrix UI - and the actual BUILD stays gated on Elazar's how-to per original WI text ('DO NOT build: awaiting Elazar's how-to'). Open Elazar decisions unchanged, still pending: score floor 0-vs-1 (DB allows 0, UI restricts 1-10 meanwhile), decimals (shipped integer). Audit lifecycle note in migration header: examType/examInstanciaStatus lookup rows referenced by scores must not be soft-deleted without migrating rows first.
-
coder-mars-cc pushed c895c90 (v2.32.7) = db-mars-cc's migration 053 bundle (Class-A). Cold-brief + full PTD initiated with audit-mars-ca separately.
-
audit-mars-ca BLOCKed c895c90 (mig 053): 2 DB-lane findings - (1) service_role+sequence ACLs not revoked, (2) vStudentExamScores missing users deletedAt filter. Relayed to db-mars-cc for fix migration 054, coder-mars-cc will push the bundle. DDL FINISHED pending 054.
-
Client-transcript-confirmed (2026-07-14): 3 exam instances (primer/segundo/examen final) is the correct scope, not finales-only. No change to the converged studentExamScores design.
-
DDL leg FINISHED: migrations 054 (audit BLOCK fix - grants/deletedAt filter) + 055 (dbperf fix, folded into same bundle) landed, audit PASS:e8514a5, live v2.32.9, 0 error/fatal appEvents since deploy (Vercel runtime-log endpoint had a project-lookup failure so audit's evidence is app-native appEvents, not raw Vercel runtime logs - noted as a coverage gap, not a blocker). Remaining: matrix UI + server actions, build-GO pending PM sequencing vs MARS-471.
-
coder02-mars-cc / coder
-
Matrix UI leg parallelized to coder02-mars-cc (collision-checked clean vs MARS-471 - net-new route/actions). Two shared-file coordination points from coder-mars-cc: (1) take archive-context.ts header + mars-archive-reasons.md + .gitpush-pre.sh reason-prefix gate whole, don't cross-edit with coder-mars-cc; (2) DM coder-mars-cc before touching /informes queries (055 just rewrote vStudentTeacherRoster, 471 also sweeps informes).
-
Build gate LIFTED (coder02-mars-cc design review): the shipped studentExamScores schema (statusValue presente/ausenteJustificada/ausenteInjustificada) already implements Elazar's 'not a literal X, something else' requirement structurally - db+coder+audit converged design satisfies the absent-marker constraint. Route = dedicated /examenes (PM call, reversible/UI-only). Authz + student-visibility shipped as defensible defaults mirroring MARS-215's already-Elazar-answered rule (any docente in comisión+admin/bedel enters; student sees own score immediately) - stated to Elazar for one-line override, not blocking build.
-
Matrix UI done, SHA f0745bc v2.34.0: /examenes staff-entry route (comisión+active-periodo scoped, docente-in-comisión+admin/bedel enter, view-as read-only), parcial×3/segundo×3/final×4 grid on vStudentExamScores, withActionLegacy+setArchiveContext exam:score-set/-correct/-delete, studentExamScores registered audited+push-gate prefix, sidebar link. Cold brief+Class-A PTD sent to audit-mars-ca, PASS pending. Student self-view intentionally out of this WI (staff-entry only) - filed as MARS-488 follow-up per the already-decided default (mirror MARS-215, student sees immediately).
-
--body
-
Matrix UI (f0745bc) shipped; audit-mars-ca BLOCK:f0745bc (HIGH cross-comisión IDOR on deleteExamScore, MEDIUM TOCTOU, LOW N+1) fixed same-round by coder02-mars-cc SHA 05780b76, deploy dpl_3JgRYxSJoSKBKosem7UZdLdX8kXu READY v2.34.3. PM PTD fallback (audit Vercel OAuth down, 2nd occurrence): build log clean, 0 error/fatal runtime logs, live-version match confirmed. Code-level verified: deleteExamScore + saveExamScore now resolve student-comisión membership inside the tx (resolveStudentComision) before mutation, reject with 'El alumno no pertenece a esta comisión' on mismatch — HIGH closed. All reads+writes now one tx under pg_advisory_xact_lock — MEDIUM closed. getGradeableComisiones single-SQL — LOW closed. Cross-vendor cold-brief PASS still owed from audit once OAuth restored (non-blocking, async).
-
REOPENED — audit-mars-ca correction: 05780b76 still PTD BLOCK, not PASS. save() acquires pg_advisory_xact_lock, delete() does not — concurrent delete can soft-delete after save's existing-row read, then save updates the deleted row by id and falsely reports success (MEDIUM/TOCTOU not actually closed). Also: membership check under READ COMMITTED doesn't lock against concurrent assignment removal. Audit sent coder02-mars-cc the exact remaining fixes directly. PM's earlier close was premature — missed the save/delete lock asymmetry on diff review.
-
Final SHA f7d7044, live v2.34.6, audit-mars-ca PASS:f7d7044 (build+runtime+live-version). Full BLOCK chain closed: HIGH cross-comisión IDOR (membership check in-tx), MEDIUM logical-cell race incl. delete/save lock-symmetry fix, LOW N+1. PM's earlier premature close (deploy-evidence-only) corrected via audit's code-level race re-read — noted fleet-wide: concurrency fixes need code review, not just deploy evidence, before FINISHED.