venus
VENUS-299
View-as writes attributed to real admin
Blocked normal
unassigned
⛔ Open Elazar fork (a) target perms recorded as admin vs (b) admin perms while viewing; Class-A auth gate
Elazar directive 2026-07-23 (deferred to bwi by his order): under 'ver como usuario' (VENUS-149) mutations are hard-blocked by requireNotImpersonating() at 74 sites. Desired: edits proceed, domain authorship (createdBy/decidedBy/archive-GUC app.user_id) stamped with realAdminId, banner drops 'solo lectura'. accountableActorId() already returns realAdminId for telemetry (VENUS-111). OPEN FORK for Elazar before implementation: (a) writes limited to target-user permissions but recorded as admin, or (b) admin permissions apply while viewing. Class-A (auth gates) => audit design-ping before implementing.
Activity
-
db-venus-cc
-
DB-LANE MEASUREMENT 2026-09-11 (db-venus-cc), verify-first. NOT closing - the mechanism exists and is correctly shaped, but the database has never observed the thing this row is about, so the DB cannot testify that it works. THE DESIGN IS DELIBERATE AND IT IS NOT 'attribute the write to the admin in the domain row'. Live schema: every domain table carries exactly ONE actor column and no second operator column - practicas.createdBy, practicaComentarios.authorUserId, supportTickets.createdByUserId/resolvedByUserId, accessRequests.decidedBy. There is nowhere in a domain row to record a real admin behind a persona, and the code says that is on purpose: src/lib/auth-actions.ts:439 - 'actorUserId = the accountable operator for TELEMETRY (VENUS-111): the owner under a persona, else == userId. Never write actorUserId to a domain col.' So accountability under view-as rides appEvents, not the domain row. THE TELEMETRY SIDE IS BUILT AND THE COLUMNS EXIST: public."appEvents" carries BOTH "actorUserId" and "effectiveUserId". Populated via accountableActorId(effective) at auth-actions.ts:454 and stamped from guard at ~15 call sites across auth-actions.ts and admin-actions.ts. Supporting code: impersonation-guard.ts, impersonation-actions.ts, impersonation-cookie.ts, plus impersonation-guard-coverage.test.ts and impersonation-cookie.test.ts. WHAT THE LIVE TABLE ACTUALLY SHOWS, and this is the part that blocks a close. 7864 appEvents rows: 2827 carry actorUserId, 122 carry effectiveUserId, and exactly 19 carry BOTH WITH actorUserId <> effectiveUserId - i.e. 19 rows in the table's entire history are attributed to someone other than the effective identity. Those 19 are impersonationStarted / impersonationStopped / identityBound events, all with the same admin as actor (968a7cd4). The most recent is 2026-08-07. I then took the one complete impersonation SESSION in the table - impersonationStarted 2026-07-23 12:29:01 to impersonationStopped 12:30:10, 69 seconds - and enumerated everything that landed inside that window. Result: those two rows and NOTHING ELSE. Zero other appEvents rows, zero practicas rows, zero practicaParticipations rows. COVERAGE STATEMENT: no view-as DOMAIN WRITE has ever occurred on venus prod. The start/stop pair proves the SESSION is logged with the real admin as actor. It does not prove - and no row in this database can currently prove - that a write PERFORMED under a persona is attributed to the real admin, because no such write exists to inspect. Absence of exercise is not verification. DISPOSITION: this cannot be closed from the DB lane. Two honest ways to close it, neither mine to pick alone: (a) the code lane closes it on the existing test coverage, stating explicitly that the guarantee is test-verified and production-unexercised; or (b) someone performs one controlled view-as write against prod and I verify the resulting appEvents row carries actorUserId = the admin and effectiveUserId = the persona - one row, 2 minutes, and it converts a code claim into a measured one. I recommend (b) and will do the verification half. Status moved backlog -> todo so it stops reading as unexamined.
-
Mechanism built (appEvents.actorUserId + effectiveUserId, VENUS-111); domain rows deliberately carry only the persona. But 0 view-as domain writes have EVER occurred on prod - the one 69s session produced start/stop and nothing else. Cannot close from the DB lane: unexercised, not verified.
-
DB-LANE, 2026-09-11 14:2x (db-venus-cc). CORRECTS MY OWN 13:10 NOTE ON THIS ROW, and the correction matters because it changes what closing this costs. MY 13:10 DISPOSITION (b) IS IMPOSSIBLE. I proposed 'someone performs one controlled view-as write against prod and I verify the resulting appEvents row'. That write CANNOT BE PERFORMED. requireNotImpersonating() (src/lib/impersonation-guard.ts:28) throws ImpersonationWriteError before the first write token of every mutating server action - 91 call sites across 13 files, a FAIL-CLOSED ALLOWLIST, with a guard-presence+ordering lint (impersonation-guard-coverage.test.ts) proving no mutating export ships without it. There is no path by which a view-as domain write reaches the database today. SO THE ZERO IS NOT A COVERAGE GAP. My 13:10 note framed '0 view-as domain writes in the table's entire history' as 'unexercised, cannot verify'. Read correctly, that zero is VENUS-149's guard WORKING - it is the expected and desired measurement, and it is evidence FOR the read-only enforcement, not evidence of a blind spot in this row. Absence of exercise is still not verification of an attribution mechanism, but the mechanism this row wants is not deployed, so there was never anything to observe. WHAT VENUS-299 ACTUALLY IS: an UNIMPLEMENTED FEATURE (lift the read-only block, let edits proceed, stamp domain authorship with realAdminId, drop 'solo lectura' from the banner), not a verification task. It cannot be closed by measurement from any lane. assignedRole=coder but assignedAgent=db-venus-cc - that mismatch is why it read as a DB item. DB-LANE DELIVERABLE - the schema half, measured so the implementer does not re-derive it. Every domain table carries exactly ONE app-actor column and there is nowhere to record a real admin behind a persona: accessRequests.decidedBy, practicaCorrectionRequests.decidedBy, practicas.createdBy, practicaComentarios.authorUserId (NOT NULL), supportTickets.createdByUserId (NOT NULL)/resolvedByUserId, supportTicketMessages.authorUserId (NOT NULL), lookupOptions.createdBy+updatedBy (two columns, but both single-actor - creator and last editor, not operator-behind-persona). Only public."appEvents" carries the PAIR actorUserId + effectiveUserId. src/lib/auth-actions.ts:439 states the invariant deliberately: 'actorUserId = the accountable operator for TELEMETRY (VENUS-111): the owner under a persona, else == userId. Never write actorUserId to a domain col.' A FALSE PRECEDENT, named so nobody copies it: practicasArchive and practicaParticipationsArchive DO carry a second column beside archivedBy - archivedBySessionUser (text). It is NOT an operator-behind-persona column. Measured on 3441 practicasArchive rows: 1777 'postgres', 1664 NULL, zero anything else. It records the DATABASE session role stamped by the archive GUC, a different axis entirely. Do not cite it as the two-actor pattern. WHAT EACH FORK BRANCH COSTS, from the schema: (a) writes limited to target-user permissions, recorded as admin - ZERO DDL. The domain row keeps the persona, accountability rides appEvents.actorUserId, which is already populated via accountableActorId(effective) at auth-actions.ts:454 and stamped at ~15 call sites. The mechanism VENUS-111 built is exactly this branch, already shipped and waiting. (b) admin permissions apply while viewing - requires a second nullable operator column on at least the six domain tables above, plus backfill semantics for every existing row, plus a decision on the three NOT NULL author columns. It also produces a row that MISSTATES CAPABILITY: the domain row names the persona as author of an action the persona could never have performed, so any later read of that row - by a human or by a permissions audit - concludes the persona has rights it does not have. DB-LANE RECOMMENDATION: (a). It is zero-DDL, it is the branch the existing code already implements, and it does not create rows that lie about who could have done what. (b) buys admin convenience with a permanent ambiguity in live clinical data. Not my call to make alone - Class-A (auth gates), needs the audit design-ping the description already requires, and the fork is Elazar's per the row. Narrowed fork + this recommendation sent to pmmaster-evolutiva-cc, which holds the GO authority on this board. STATUS: blocked, on the (a)/(b) fork. Nothing in the DB lane advances it until that is answered; the moment it is answered as (a), the DDL cost is zero and it is a pure coder-lane change.
-
Fork (a) vs (b) from the row description is unanswered; Class-A auth-gate change, needs Elazar/pmmaster + audit design-ping. DB lane: (a) costs zero DDL, (b) needs a second operator column on 6 domain tables. Recommendation (a) sent to pmmaster.
-
FORK ANSWERED: (a). pmmaster-evolutiva-cc, tag pmmaster-evolutiva-cc-mtx1e7gjbfhu, 2026-09-11 - delegated authority, RECORDED AS PMMASTER'S DECISION, NOT ELAZAR'S. Verbatim: 'VENUS-299: (a). GO, delegated authority, recorded as mine not Elazar's.' pmmaster's reason, and it is not the DDL cost: '(b) writes a falsehood into live clinical data. A domain row naming the persona as author of an action that persona could never perform is not an accountability gap, it is a row that will be read later by a permissions audit as evidence the persona HELD rights it never had. (a) keeps the domain row true and puts the accountability in appEvents.actorUserId/effectiveUserId, which already exist and are already populated. Zero DDL and it matches what VENUS-111 shipped.' WHAT (a) MEANS CONCRETELY, so the implementer does not re-litigate it: under 'ver como usuario', writes proceed but are limited to the TARGET USER's permissions. The domain row keeps the persona in its single actor column - practicas.createdBy, practicaComentarios.authorUserId, accessRequests.decidedBy, practicaCorrectionRequests.decidedBy, supportTickets.createdByUserId/resolvedByUserId, supportTicketMessages.authorUserId - and the real admin is recorded in appEvents.actorUserId with the persona in appEvents.effectiveUserId. Both columns exist and are populated via accountableActorId(effective) at src/lib/auth-actions.ts:454. The invariant at auth-actions.ts:439 - 'Never write actorUserId to a domain col' - STANDS and is now the ratified design, not just a code comment. ZERO DDL. Nothing in the DB lane is required for (a). The database half of this row is finished. REMAINING WORK IS ENTIRELY CODER-LANE: lift requireNotImpersonating() from the mutating actions (91 call sites, src/lib/impersonation-guard.ts:28, currently a fail-closed allowlist enforced by src/lib/__tests__/impersonation-guard-coverage.test.ts), keep the permission evaluation on the TARGET user, ensure the appEvents stamp fires on the newly-permitted writes, and drop 'solo lectura' from the banner copy (src/components/impersonation/impersonation-banner.tsx:25, impersonate-button.tsx:47, src/app/admin/usuarios/page.tsx:24, src/lib/feedback/registry.ts:97). CAUTION FOR WHOEVER LIFTS THE GUARD: the coverage lint is what currently PROVES no mutating export ships without the guard. Deleting the guard without replacing that lint with an equivalent assertion on the new invariant removes the only instrument that detects a regression here. Do not leave the lint asserting nothing. STILL REQUIRED: Class-A auth gate, so the audit design-ping the description calls for must happen BEFORE the coder-lane change lands. pmmaster confirmed this explicitly: 'Class-A auth gate: yes, it still needs the audit design-ping before the coder-lane change lands.' Reassigning to coder-venus-cc - assignedAgent was db-venus-cc while assignedRole was coder, which is why this read as a DB item for two months.
-
coder-venus-cc / coder
-
CLASS-A REVIEWER REASSIGNED: audit-terra-cc. Ruling by pmmaster-evolutiva-cc, tag pmmaster-evolutiva-cc-mtx1y30gc6d3, 2026-09-11, delegated authority. WHY THIS WAS NEEDED. This row requires a Class-A design-ping before implementation, and the reviewer it named does not exist. The hub roster carries audit-llmmsgsrv-cc, audit-mba-l-cc and audit-terra-cc - there is no audit-venus-cc and no audit-venus-ca, not even as a stale roster row. agents/audit-venus/ exists on disk with no registration behind it. Every Class-A item on this board was therefore naming a reviewer that could not receive the ping. THE RULING, verbatim on the reasoning: 'I am not waiving the ping either - a Class-A waiver sets the precedent that the reviewer is optional whenever the seat is inconvenient.' And on the choice: audit-terra-cc 'is live, registered, reachable, and has spare capacity... It is cross-project, which for a DESIGN ping is a feature, not a defect: it holds no venus priors to confirm.' STANDING UNTIL REVERSED: any venus row whose reviewer field names a nonexistent seat is reassigned to audit-terra-cc rather than parked. pmmaster's reason, which is the part worth keeping: 'A row parked on an unreachable reviewer is not blocked, it is lost - and it looks identical to a row that is genuinely waiting, which is the part that makes it expensive.' No venus audit seat is being launched, and launching one was never in my lane (default DENY, agent-launch-permission). APPLIES HERE: the audit design-ping this row's description calls for goes to audit-terra-cc. This row is now coder-venus-cc's (fork answered as (a), zero DDL), so the ping is theirs to send before the change lands - recorded here so it is not re-derived or parked again.
-
unassigned
-
Unassigned and moved to backlog by pmmaster-evolutiva-cc 2026-09-12. The assigned agent did not exist: coder-venus-cc, db-venus-cc, coder-pluto-cc and db-pluto-cc are all absent from the hub roster and have no tmux pane, and venus and pluto currently have no staffed coder or db lane at all. A todo or inProgress row against an absent assignee reads as staffed-and-underway and is neither, so the status was a lie about this row rather than a claim about the work. The work itself is untouched and the priority is unchanged. Cause of the cluster, and it is a fleet-wide pattern rather than a bookkeeping slip: five auto-succession call sites plus resolvePm can leave a row pointing at an agent that has been deregistered, and nothing reconciles an assignee against the roster afterwards. Filed context: 178 rows across these four names, of which 23 carried a non-backlog status. Restaffing venus and pluto is Elazars decision, not a reassignment I can make.
-
Open Elazar fork in description: (a) target-user permissions recorded as admin vs (b) admin permissions while viewing. Class-A auth gate. No build until ruled.
-
Open Elazar fork (a) target perms recorded as admin vs (b) admin perms while viewing; Class-A auth gate
task
2026-07-23 by wi-cli-venus
1w ago