pluto
PLUTO-544
Feature gap: no way to edit participants on an already-created practica
Done normal
cpcoder-pluto-cc
Surfaced via PLUTO-543 ticket triage: no role (student/docente/admin/bedelia) has any app path to correct operadorId/asistenteId/sinAsistente on an already-created practica -- updatePractica is deliberately clinical-only, participants stay fixed post-creation. Genuine product gap, not a regression. Needs design: self-service correction window vs docente-gated approval, archive-context requirements (practicas is trigger-archived), whether a time limit applies. Scope jointly with db-pluto-cc; flag to PM if the design forks into anything security/authz-shaped before building.
Questions
No questions.
Activity
-
3-tier approval design proposed to Elazar, pending confirmation. Tier1 (slot null + not calificada): creator fills directly, no approval - scoped to asistenteId only since operadorId is schema-NOT-NULL (creator can be either operador or asistente - practica-form.tsx 'Tu rol' picker confirmed asistente-can-create). Tier2 (not calificada, swapping existing person): needs OK from person being removed; no response in 7 calendar days -> teacher/administrador/jtp-comisión can approve instead. Tier3 (calificada): needs OK from BOTH removed+added person, PLUS jtp-en-práctica or jtp-comisión sign-off. New table practicaParticipantChangeRequests proposed (practicaId/role/from/to/requestedBy/tier/status/consent fields/approvedBy/expiresAt). Class-A (new table+approval authz+notification) - full team + pre-impl design-ping once confirmed. 3 open questions sent to Elazar: (1) is there a distinct 'creator' field or do we infer from operador/asistente role-pick at creation; (2) does an explicit REJECTION from the removed person hard-block regardless of escalation, or can teacher/admin/jtp override a rejection too (not just silence); (3) fallback if no JTP exists for that comisión/práctica. Do not dispatch build until Elazar confirms.
-
Elazar final decisions (nqla): 1a - overriding an EXPLICIT rejection requires a typed reason + loud appEvents authz/user-tier log (overriding silence stays reason-optional). 2b - Tier2 (not-yet-graded swap) is notify-only for the added person, same as Tier1 - added-person consent NOT required until Tier3 (calificada), where both-party consent already applies. All 3 original open questions + these 2 follow-up knobs are now resolved. Dispatched to build: db-pluto-cc (DDL: practicaParticipantChangeRequests consent-ledger, not trigger-archived, soft-delete+fn_forbidHardDelete, UNIQUE(practicaId,role) WHERE status='pending', role=value-code text no CHECK; + in-txn practicas operador/asistente swap with archive context) + coder-pluto-cc (request/consent/approval server actions, UI, emailOutbox fan-out, persisted-attribution scope-gate on overrides per PLUTO-508/575/576 pattern). Class-A: audit-pluto-ca design-ping + pre-push diff review required before ship.
-
Design fork surfaced during build (audit-pluto-ca, blocked): Tier2->Tier3 transition (práctica gets calificada while a Tier2 request is still pending) - coder proposed cancel+resubmit, audit recommended in-place monotonic tier promotion (2->3), preserving already-collected removed-person consent/rejection, requiring the now-missing added-consent + persisted-scope JTP sign-off, enqueuing only newly-required notifications, never downgrading. PM decision: adopt monotonic promotion - preserves valid prior consent state, avoids inventing an unplanned cancellation lifecycle (cancelledAt/cancelledBy/cancelReason not in the locked schema), less state churn, and the existing addedConsentAt/approver/tier columns already support it without a schema change. Cancel+resubmit rejected as the default path.
-
DB lane done (db-pluto-cc). Migration 072: practicaParticipantChangeRequests table + fn_applyParticipantChange + fn_promoteParticipantRequestOnGrading (Tier2->Tier3 monotonic promotion), 3-tier consent gate. 4 audit rounds w/ audit-pluto-ca (v1-v3 BLOCK, v4 PASS) fixed: lock-order proof, notification-payload deliverability, dedupeKey collision, JTP-fallback deliverability, completion-notification invariant (empty/unrelated emailPayloads rejected - requires deterministic pluto544:apply:<requestId>:added row bound to canonical email). Applied to prod, schema.md regenerated, pushed 4fe319b/v2.17.0. Coder lane (request/consent/approval actions + UI + outbox) still in progress - interface contract relayed to coder-pluto-cc directly.
-
UI decisions locked (coder's 4 Qs): (1) initiator set = createdBy/creator of the práctica (whichever role, operador or asistente - corrected from coder's 'operador only' default to match Tier1's creator-framing) + docente with grade-scope (jtp/adjunto/titular en comisión) + admin/bedelia; non-creator alumno-asistente cannot initiate. (2) consent via existing notification system + práctica detail page panel, no dedicated inbox - confirmed. (3) override via same panel, Aprobar de todos modos on expired-silence or immediate on rejection with mandatory reason+log - confirmed. (4) Tier3 sign-off = persisted assignmentJtpId OR any active JTP of assignmentComisionId, mirrors canGradePractica scope - confirmed. Coder building UI now.
-
Migration 073 (tier3-at-birth notification trigger + sibling-supersede outbox neutralization) applied to prod, 3 audit rounds (2 BLOCK: stale-mail/telemetry-rollback/caller-trust guard gaps, then NULL-semantics on same guard -> PASS), full harness A-M re-verified each round in rolled-back txn. DB-side work done. Push of schema.md+073 migration currently held by shared-tree friction-code lint (coder's untracked participant-change-panel.tsx has 8 unregistered codes) - db flagged coder directly, self-resolving once coder registers the codes, no PM action needed.
-
DB lane fully clear: migration 073 pushed 7c390e2 (v2.17.1), schema.md+applied migration only. Coder-pluto-cc's app-side push unblocked on DB dependency; friction-code lint was the only remaining app-side gate.
-
Coder DONE+LIVE: SHA d993009 v2.17.3, deploy READY, live-version verified. Audit PASS on diff after 4 rounds (roster-membership mutation-boundary enforcement, least-privilege Tier3 consent surface, pending-only initiation + sibling-aware override, coded transport-failure toasts, pure-adjunto roster union). Transparency note: intermediate push ba0dbf0/v2.17.2 errored Vercel type-check (dirty-but-unpushed types/db.ts omitted from --only set) - live-verify caught it, prod stayed frozen at 2.17.1 until fixed by d993009. Class-A runtime-log PTD dispatched to audit-pluto-ca before FINISHED.
-
Participant-change (operador/asistente) 3-tier consent/approval feature, live SHA d993009 v2.17.3. Audit Class-A PTD PASS: build clean, zero runtime errors, health/version/auth-contract all verified. DB migrations 072-074 (074 is PLUTO-581, separate WI) applied prod.
-
Elazar direct feedback (post-ship): the participant-change banner/panel is 'plain horrible', doesn't follow app UI/UX philosophy. Dispatched coder-pluto-cc (shadcn skill + shared primitives check) + audit-pluto-ca for review/fix, Class-S self-verify+push.
-
UI polish follow-up on the participant-change panel (Elazar 'plain horrible' feedback): SHA 96f4739 v2.20.2 (theme-token/Badge/SectionTitle rework, presentation-only) + SHA 12e9c0e v2.20.4 (space-y-3 -> flex flex-col gap-3 shadcn-strict residual). audit-pluto-ca FINAL PASS both SHAs, zero logic drift. No live pending participant-change request existed to screenshot (table was empty) - visual confirm pending Elazar or next real occurrence.
feature
5w ago by wi-cli-venus
4w ago
2026-07-17 10:53