pluto
PLUTO-575
lastPractica/inactiveStudents: persisted-attribution scope-gate
Done high
dpdb-pluto-cc
PLUTO-508 follow-up (audit-pluto-ca HIGH, filed as immediate pair-scope campaign, not low-priority cleanup). fn_getInformesBundle's lastPractica (feeds inactiveStudents) is global-by-student with NO rosterScope join at all — different scope-input shape from the 5 surfaces fixed in migration 070/scopedFacts. Needs its own persisted-attribution (or equivalent scope) treatment so it doesn't leak/misattribute across comision reassignment the same way the pre-070 studentFacts card did. Same authz predicate pattern as scopedFacts (070) should generalize.
Activity
-
Exact location: db/migrations/applied/070-pluto-508-informes-studentfacts-pair-gate.sql line 403, CTE "lastPractica" (SELECT f."studentId", MAX(f."fecha") AS "lastPracticaAt" ...), consumed at line 759-760 in the inactiveStudents jsonb_build_object block. Currently sources from vPracticaParticipantFacts with no rosterScope/scopedFacts join at all (global-by-student, unlike the 5 surfaces fixed in 070). Ref PLUTO-508/migration 070.
-
Migration 118 applied to prod and pushed (211a839, 2026-09-11). The lastPractica CTE fed inactiveStudents an UNSCOPED date: it read every row of vPracticaParticipantFacts for the periodo with no roster predicate at all, so a student's lastPracticaAt could come from a practica the caller cannot see, while the counts beside it on the same card were scope-filtered - the card mixed two different populations per row and read as if it were one. Fix byte-mirrors rankingBase's WHERE clause (the already-reviewed 070/scopedFacts predicate shape) with a p.->f. alias swap and the source swapped vPracticasCompletas->vPracticaParticipantFacts, so the two halves of the card now answer from the same population rather than the new predicate being independently invented. Pre-apply review: audit-terra-cc PASS - predicate byte-match confirmed against rankingBase, LEFT JOIN confirmed safe (a student scoped-out of lastPractica keeps a NULL date rather than dropping off the card). CONSEQUENCE RECORDED, not a defect: a student whose only practicas fall outside the caller's scope now reads as never-having-practiced for that caller instead of borrowing a foreign date, so some students move INTO the inactive list - that is the reclassification the fix necessarily causes, documented in the migration header so the first person to notice the list grew does not read it as a regression. Post-apply verified directly in pg_proc.prosrc: new predicate present, length 53025->53842 (+817, matching the pre-apply prediction). schema.md regenerated.
bug
2026-07-17 by wi-cli-venus
5w ago
2026-09-11 13:20