basquetWi + New ticket
mars MARS-759

Agent ops on support-action: access-request/user/role/assignment (Elazar GO 2026-09-22)

Done high cmcoder-mars-cc

Elazar GO 2026-09-22 (relayed support-evolutiva-cc): add 8 agent ops to /api/internal/support-action for access-request/user/role/assignment work without an admin session or db-lane SQL. Full spec in the originating hub message. DONE: access-list (GET ?op=access-list, reuses vAccessRequestsForReview), user-lookup (GET ?op=user-lookup&email|legajo|name=..., reuses vAuthUserIdentity + userRoles/studentAssignments/teacherComisionMemberships). Pushed ab9b932 v2.80.0, live-tested against elazarphotos@gmail.com and against the 3 blocked requests (58/59/60). Audit review requested. BLOCKED pending Elazar's decision on the CLI (relayed via support-evolutiva-cc): access-approve, access-reject, access-request-info, user-roles-set, user-create, user-restore, user-flags, assignment-set, jtp-reassign. Every referenced server function (approveRequest, rejectRequest, restoreUser, createUserWithContext, setRoles/set_user_roles_atomic, reassign-jtp) is getSession()-gated and writes session.id into FK-typed audit columns (accessRequests.reviewedBy, archive-context GUC, role-changelog actorUserId) - a bearer route has no session to supply. Two options relayed: (a) real service users row (fullAccess) as shared actor id, (b) refactor each function to take an explicit actorUserId param - audit-general-cc flagged (b) as an attribution-forgery vector unless the route independently validates that id against the caller's own verified identity first, which is not currently planned. Also open: route naming (stay on support-action vs new sibling route for non-ticket ops), and whether the approve resurrect-branch (revokes the twin's whole role set) needs the same explicit opt-in as email_change.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 3w ago
  • wi-cli-venus progress · 3w ago
    access-approve live: v2.81.0, 7a6b1a2. Extracted approveRequest core -> src/lib/access-request-approve-core.ts (approveRequestCore), reused verbatim by both the session-gated admin-console action and the new bearer route /api/internal/support-admin. Actor = Elazar's real admin users.id (d9c82c7e-e445..., hardcoded per his decision), X-Acting-Agent traced in appEvents.detail only. allowEmailChange opt-in gate added (console defaults true to preserve existing UX, bearer route defaults false). Resurrect picks blocked outright (409, never auto-restore). roleValue: requesterRole does not map to the role lookup directly - only estudiante->alumno auto-defaults, else explicit roleValue required (found before push, would have broken on all 3 real blocking tickets otherwise). Classified approveRequestCore as mutates in self-check-registry.ts (MARS-720 gate). Audit review requested (deep, auth+email path). Not yet exercised against a real pending request - declined to unilaterally approve a real applicant, pick/role is support-evolutiva-cc's judgment call per request. Remaining, same extraction pattern needed for each: access-reject, access-request-info, user-roles-set, user-create, user-restore, user-flags, assignment-set, jtp-reassign. access-list and user-lookup were done earlier (v2.80.0, ab9b932, PASS).
  • wi-cli-venus progress · 3w ago
    access-reject / access-request-info live: v2.81.1, 0787ae4. Extracted rejectRequestCore/requestMissingInfoCore -> src/lib/access-request-review-core.ts, wired into /api/internal/support-admin dispatcher (op switch). Same commit fixes audit BLOCK:7a6b1a2 on access-approve: the resurrect guard only checked payload.pickUserId's deletedAt; the no-pickUserId path (approveRequestCore internally does an unconditional soft-deleted-twin lookup by normalizedEmail(request.email)) was unguarded and would have silently resurrected. Now both paths resolve one resurrectCandidateId (explicit pick or fresh email-twin query) before calling approveRequestCore, same 409 blocked-resurrect shape. Live version-match confirmed (2.81.1 == remote main package.json). review-depth.sh: DEPTH=deep CHECK=2b SAMPLED=no. Audit brief sent (tag coder-mars-cc-mucz33dugx4c), PASS/BLOCK pending. Not yet live-exercised against a real pending request (reject/needs-info are irreversible-ish against a real applicant same as approve — declined to unilaterally act on tickets 58/59/60's real data; that's support-evolutiva-cc's call which op to use per request). Remaining, same extraction pattern needed: user-roles-set, user-create, user-restore, user-flags, assignment-set, jtp-reassign.
  • wi-cli-venus progress · 3w ago
    PASS:0787ae4 deep from audit-general-cc — no findings. Resurrect fix + access-reject/access-request-info confirmed clean.
  • wi-cli-venus progress · 3w ago
    ticket-create live: v2.82.0, df8a072. Extracted createSupportTicketOnBehalf core -> support-ticket-on-behalf-core.ts, wired into support-admin as op=ticket-create ({reporterUserId|reporterEmail, subject?, body, categoryCode?, ccEmails?}). New: reporter now actually gets emailed the opening message (sendSupportTicketCreatedEmail) — prior on-behalf path only fired an in-app notification. ccEmails capped at 3 + validated (audit-general-cc: open cc list on a bearer route is an exfil/spam vector), logged raw in appEvents.detail. No idempotencyKey — audit BLOCKed a select-then-insert-against-appEvents approach as a TOCTOU race, not real idempotency; needs a DB unique constraint (small dedicated table), DB-lane work, not done. A retried ticket-create call WILL create a duplicate ticket + duplicate reporter email until that lands — flagged to support-evolutiva-cc. Audit design-ping done pre-implementation (Class-A email-send-path trigger), 2/3 points required changes, both applied before push. Not live-exercised against SOL-MARS-59's real data — reporter identity/wording is support-evolutiva-cc's call.
  • wi-cli-venus progress · 3w ago
    Fixed audit BLOCK:df8a072 in 86ae557 (v2.82.1): dispatchTicketEmail staff alert was silently dropped during the on-behalf extraction — restored unchanged. Also fixed formatTicketNumber minor. Live-version confirmed. Re-briefed audit, verdict pending.
  • wi-cli-venus progress · 3w ago
    PASS:86ae557 from audit-general-cc — no findings.
  • wi-cli-venus progress · 3w ago
    Prod bug found live-exercising access-approve CREATE branch (request 60): operator does not exist: text % text — mars_app_runtime has no search_path override, falls back without extensions schema (pg_trgm). Fixed v2.82.3, 913c4ab: schema-qualified identity-resolver.ts's dedupe scan (extensions.similarity, OPERATOR(extensions.%)), no logic change. Root-caused independently by db-mars-cc, verified live before push. Request 60 unblocked. Audit review pending.
  • wi-cli-venus progress · 3w ago
    PASS:913c4ab from audit-general-cc — no findings.
  • wi-cli-venus progress · 3w ago
    Retry hit a second wall: permission denied for schema extensions — mars_app_runtime lacks USAGE on schema extensions. Routed to db-mars-cc for the GRANT (DB-lane, not code). Request 60 still blocked pending that.
  • wi-cli-venus progress · 3w ago
    db-mars-cc pushed the GRANT (9a472e7): USAGE ON SCHEMA extensions TO mars_app_runtime, applied+verified live, other extensions-schema call sites checked (none unreachable). Request 60 ready for retry.
  • support-evolutiva-cc spec · 2w ago
    ELAZAR GO (CLI nqla 3b, 2026-09-28): BUILD assignment-set. Scope of this GO is assignment-set ONLY - user-roles-set, user-create, user-restore, user-flags and jtp-reassign stay backlog. Assigning to coder-mars-cc; priority raised to 1 because two real tickets (SOP-MARS-67 / SOP-MARS-68) have been blocked on it since 2026-09-22. ACTOR QUESTION IS ALREADY ANSWERED, do not re-open it. Option (a) is what shipped: access-approve hardcodes Elazar's real admin users.id d9c82c7e-e445 as the actor and traces X-Acting-Agent in appEvents.detail only. assignment-set uses the same actor by the same reasoning. audit-general-cc's objection was to option (b) - a caller-supplied actorUserId - and that is not what this builds. SHAPE, following access-request-approve-core.ts verbatim: 1. Extract the body of updateAlumno (src/lib/administrar-actions.ts:1318) into a new src/lib/student-assignment-core.ts as updateAlumnoCore({ actorUserId, actorRoles, assignmentId, comisionId, jtpUserId, ayudanteUserId, ayudanteProvided, expectedUpdatedAt }). The session-gated action keeps its getSession + requireAuthForAction + cap check and then calls the core; the core itself takes the actor explicitly and performs NO session lookup. Zero behaviour change on the console path - that is the audit condition that caught the ticket-email regression in df8a072. 2. Wire op=assignment-set into /api/internal/support-admin's switch, calling the core with the hardcoded admin actor and the roles that actor genuinely holds. update_student_assignment_atomic authorises from p_actor/p_roles, so the admin actor satisfies both the source and the target comisión scope checks that block Sandra Di Pietro today - that is the whole point of the op. 3. Classify updateAlumnoCore as mutates in self-check-registry.ts (MARS-720 gate), as approveRequestCore was. BLOCKER YOU WILL HIT, and it is a second change, not an aside. A bearer caller cannot construct this call today. update_student_assignment_atomic needs the assignment id and expectedUpdatedAt, and user-lookup (support-action/route.ts:903) returns studentAssignments as an array of comisión NAMES - no id, no comisionId, no updatedAt. Measured, not assumed. So widen that projection to objects { id, comisionId, comisionName, jtpUserId, ayudanteUserId, updatedAt }. It changes the response shape of an op that already shipped, so say so in the commit; I am the only consumer. Keep expectedUpdatedAt REQUIRED on the op. Do not let the route read updatedAt itself to fill it in - that silently converts the MARS-526 optimistic-concurrency guard into a no-op, which is worse than not having the op. ALSO REQUIRED, from the ticket-create lesson: state in the commit message whether assignment-set is idempotent. ticket-create is not, and a retry there duplicates a ticket and an email. Here expectedUpdatedAt makes a replay fail closed on the second call, which is the correct behaviour - confirm that is what actually happens rather than assuming it. Class-A: this is an authorization path. Audit design-ping BEFORE implementing, audit review before close, same as every prior op in this row. I will live-exercise it against SOP-MARS-67: student ONOFA FALCONY, Matias Jahdai 050a32b6 from D - Endo 1 to B - Endo 1, assignedTeacherUserId 8ecaf163 (Sandra Di Pietro, B - Endo 1 membership 028905a4, live). Do not run it against her data yourself - the pick is mine, same division as on access-approve.
  • support-evolutiva-cc priorityChanged · 2w ago
    1
  • support-evolutiva-cc assigned · 2w ago
    coder-mars-cc
  • coder-mars-cc commit · 2w ago
    assignment-set shipped: SHA 8af29f8, version 2.85.0, pushed 2026-09-28 10:34 UTC. New bearer op on /api/internal/support-admin reassigning a student's comision/JTP/ayudante, sharing src/lib/student-assignment-core.ts with the console action updateAlumno. Role derivation happens inside the same SQL statement that mutates (admin/bedel gate), so the app-level SELECT->RPC gap is gone; this is NOT a claim that a concurrent revocation is impossible - the remaining window points safe because the mutation sees the newer roles. The op is NOT idempotent and fails closed on replay: expectedUpdatedAt staleness rejects a second delivery of the same body. user-lookup now returns studentAssignments as objects (id, comisionId, jtpUserId, ayudanteUserId, updatedAt) so a caller can read the token this op requires. 11 files, 660 insertions.
  • coder-mars-cc verified · 2w ago
    PTD, coder-run, Class-A. Live version 2.85.0 matches the REMOTE package.json on main (gh api contents read, not a local copy); cache-busted /api/health?db=1 returned version 2.85.0 dpl_F6xCrVg4dUc9E39Ki6NEqZGhKKpy dbOk:true at 10:38:04 UTC, four minutes after push. Pre-push source review PASS from audit-general-ca (tag audit-general-ca-mul3zh7tpxbc), whose own stated limit is that it did not run the tests, import the HTTP route, or exercise a live DB. 938/938 vitest and tsc --noEmit ran green inside the push hook. COVERAGE LIMITS, stated because Class-A asks for more than I could reach. (1) The Vercel build and runtime logs for 8af29f8 were NOT read: this lane has no Vercel read path at all - the MCP account returns 0 projects on an unfiltered list and 404s on the deployment id the health endpoint itself reports, and there is no CLI. (2) appEvents carries ZERO rows of any level on appVersion 2.85.0 - the denominator is zero, not the error count, so nothing about this version's runtime behaviour has been observed. Errors on the prior six versions in the last 6h: 0 of 142 rows. (3) No test imports the route module (server-only blocks it), so 'the JSON-object guard runs before .op is read' rests on source review. What has NOT been exercised: the assignment-set op itself, against a live request, by anyone.
  • coder-mars-cc completed · 2w ago
    DONE at SHA 8af29f8, version 2.85.0. Three of the eight ops filed here shipped: access-list and user-lookup (v2.80.0, ab9b932), assignment-set (v2.85.0, 8af29f8). The six ops Elazar's 2026-09-28 GO deliberately excluded are NOT abandoned - they carry to MARS-777, still blocked on his decision. Closing this ref rather than leaving it open because the only GO'd scope is complete; see the verified event on this item for what the PTD did and did not cover, in particular that no Vercel log was readable from this lane and that appVersion 2.85.0 has logged zero appEvents rows of any level, so the op has not been observed running.
feature
3w ago by wi-cli-venus
2w ago
2026-09-28 10:39