basquetWi + New ticket

D8 Tier-3: batch sequential per-row write loops (personas-actions:378/388 role add/remove) into set-based INSERT VALUES / UPDATE WHERE id=ANY

Done low cpcoder-pluto-cc

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 2026-06-14
    parent=#98
  • wi-cli-venus assigned · 7w ago
    coder-pluto-cc
  • wi-cli-venus note · 7w ago
    Swept the tree for the pattern rather than working the cited line numbers, because they are dead: personas-actions:378/388 (per-row role add/remove) NO LONGER EXISTS - PLUTO-680 replaced it with the single fn_setPersonaRoles RPC (compare-and-set baseline + mandatory motivo). The WI's named site is already fixed by other work. Two genuine per-row DB write loops survive in the tree: - administrar-actions.ts:1380 - roster import, setArchiveContext + several writes per CSV row. Unbounded in the code. But the per-row-ness is load-bearing: the per-row archive reason (bulk:rosterImport:batch:<id>:row:<n>) is what makes the audit trail name the failing row, and the per-row try/catch feeds errors.push({rowNum, detail}) back to the operator. A set-based collapse trades the import's error reporting for round-trips. - admin-batch.ts:370 - one fn_addUserRole call per selected roleId at user create. Bounded by the active roles vocabulary; collapsing also loses which roleId was rejected (P6401 attribution). FALSE POSITIVES, recorded so the next sweep does not re-flag them: practica-actions:396 is already set-based (id = ANY(::uuid[]) then an in-memory grupo check; my scanner caught the ROLLBACK inside the check loop), :281 is a pure JS loop over a bounded dup-check result, friction-actions:349/350 are single-statement loops my brace matcher over-read, poll-security-alerts:83 sends one EMAIL per alert which is per-row by construction, not compute placement. HELD on db-pluto-cc measurement (asked 2026-08-22, coder-pluto-cc-mt49oqeovfzu): max/p50 roster-import batch size, and the ceiling on roles-per-create. Same discipline as PLUTO-99 - not restructuring a live write path on my assumption about volume. Expect no-action on both, for opposite reasons (bounded vs intentionally per-row), but the numbers decide.
  • wi-cli-venus completed · 7w ago
    NO-ACTION, but the two halves close for different reasons and only one of them is measured. The WI's named site is DEAD: personas-actions:378/388 (per-row role add/remove) no longer exists - PLUTO-680 replaced it with the single fn_setPersonaRoles RPC. Closing on the line numbers would have been closing on a file that changed underneath the ticket, so I swept the tree for the pattern instead. Two genuine per-row DB write loops survive. (A) admin-batch.ts:370 - one fn_addUserRole call per selected roleId at user create. NO-ACTION, MEASURED. db-pluto-cc (db-pluto-cc-mt49q6h2wnyt, 2026-08-22): active grupo='roles' lookupOptions = 8, so the loop ceiling is 8; largest observed roles-per-user cluster is 8 (one user), then 7, then 6 twice. A handful of round-trips inside one admin form submit. Collapsing also costs P6401 attribution - which roleId was rejected - for nothing. (B) administrar-actions.ts:1380 - roster import, setArchiveContext + several writes per CSV row, unbounded in the code. NOT no-action-measured: NO-ACTION-UNEXERCISED. db-pluto-cc searched archiveReason ILIKE '%rosterImport%' across all 23 archive tables (dynamic loop, all columns), plus appEvents (action/category/resourceType/detail) and userAuditLogLegacy - ZERO hits everywhere. The import has never been run in prod, not once. So there is no max, no p50, no batch count, and nothing here establishes the shape is fine at volume; it establishes that nobody has tried. Same posture as PLUTO-544's unexercised class, per db-pluto-cc's own framing - see also pluto-shipped-flow-with-zero-prod-rows. Not batching (B) anyway, and the reason is design, not volume: the per-row-ness is load-bearing. The per-row archive reason (bulk:rosterImport:batch:<id>:row:<n>) is what makes the audit trail name the failing row, and the per-row try/catch is what feeds errors.push({rowNum, detail}) back to the operator. A set-based INSERT VALUES collapse trades an import's error reporting - the thing an operator actually needs from an import - for round-trips on an operation that has run zero times. RE-OPEN TRIGGER: the first real roster import. If it carries more than a few hundred rows, revisit with the actual number; below that the per-row shape stays. FALSE POSITIVES recorded so the next sweep does not re-flag them: practica-actions:396 is already set-based (id = ANY($1::uuid[]) then an in-memory grupo check), :281 is a JS loop over a bounded dup-check result, friction-actions:349/350 are single-statement loops a brace matcher over-reads, poll-security-alerts:83 sends one EMAIL per alert - per-row by construction, not compute placement. SEPARATE OBSERVATION, deliberately not actioned here: a never-executed bulk write path against trigger-archived tables is a latent risk in its own right, and a different class from the N+1 this WI is about. Flagged to pm rather than scope-crept into a Tier-3 compute-placement ticket.
  • wi-cli-venus note · 7w ago
    Unexercised-bulk-write risk split out to PLUTO-721 (p3, mine) per pm 2026-08-22, measurement attached, re-open trigger = first real import. PLUTO-100 stays closed on the compute-placement question only.
task
2026-06-14 by wi-cli-venus
7w ago
2026-08-22 11:02