__mocks__test: aadded crm testsOct 16, 2025, 2:03 PMclient-providers.tsxfeat(permissions): one authority for every access decision
Consolidates every "may this caller act on this data" decision onto a single
function, and closes eleven instances of one bug class found along the way.
## The authority
services/auth/authorize.ts returns a VERDICT, never a role. There is
deliberately no isOrgAdmin on the return type, so no call site can branch on
one, and changing the permission model is a change to the DEFAULT_POLICY table
rather than a sweep through the codebase. Policy is a frozen constant keyed
(resource, scope); a missing key fails closed. resolvePolicy(orgId) is the seam
for per-org policy later, so this ships with ZERO schema changes.
Replaces canActOnTarget, resolveOrgMembership, userIsOrgAdmin, an inline
same-org check in listTasksTool, and eleven `if (!isOrgAdmin)` sites that each
re-derived the rule. One of them computed it on the wrong user.
## Holes closed
All the same shape: a caller-supplied identifier resolving to another user's
data below the gate.
applyConfigChangeTool userId input, write
readConnectionSettings userId input, read
writeDocumentTool org-linked playbook path redirect
listDocumentsTool same, ungated, plus displayDocumentTool
configureStrategistTool bare aopId, no check at all
getSentEmailsTool userId input, verbatim sent/drafted bodies
aopAgents.listForAop subagent docs to any authenticated caller
agent-action-queue 5 routes, incl. delete and replay (destructive)
custom-field-management ownerId resolved from the verdict, then ignored
overview-instance org-scope write with no tenant comparison
admin router ~45 procedures + 14 sub-routers, cross-tenant
Two were destructive, three were cross-tenant writes, one was introduced by an
earlier fix in this same change set and caught by a later sweep.
## Feature
Org admins can administer a teammate's playbook and configuration. targetUserId
is uniform across the tool layer, the tRPC layer gained the org-admin path, and
the frontend gained a scoped provider and member picker that never renders one
person's data labelled as another's.
## Enforcement
Five guards keyed on the LIVE REGISTRY, not hand-written lists, each with an
anti-vacuity floor and each proven by planting the violation:
no bare userId inputs on any registered tool
every targetUserId file reaches the authority
every mutating action is administrable or documented self-only
role reads confined to the authority (depcruise rule + source scan)
redirect sites consult the authority about the RESOLVED target
## Notes
- agentExecutionEnabled is Cedar-staff-only, enforced in the single writer.
Stronger than SELF, so deliberately not a policy row.
- playbook:org and document:org are ANY_MEMBER, matching today's behaviour.
57 of 73 orgs have no admin; tightening is one cell once they do.
- Fixes two org-role gates that read undefined on every request and so refused
everyone, including statistics.getOrgOverview.
- config-write split: apply-change moved to connection-write. 17,615 -> 10,142
bytes against the connector's silent-drop ceiling.
Verified: server 799 files / 9,310 tests; mail 316 suites / 2,959 tests; types
and deps:check clean. End-to-end against the production Cedar org with a
temporary non-staff account, 42 assertions across member/admin/viewer, since
staff accounts cannot exercise the org-grant branch. Account deleted and org
state verified identical to its pre-test backup.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 8:01 AMquery-provider.tsxfix(threads): a thread marked done stays gone, whatever writes the list
Round 1 treated the optimistic removal as an edit and repaired the one
window it could name -- a listThreads response issued inside the archive
round-trip -- by re-applying the removal once, the instant the mutation
settled. That assumed a 227-380ms round-trip. Staging measures
mail.markDone at up to 7381ms, and all three resurrections prod ever
recorded landed 15.7-18.2s after the click, every one a fetch-response:
seconds after the one-shot repair had already run, with nothing left to
oppose them.
So the removal becomes an invariant instead of an edit. While a thread is
guarded, any write to a mail.listThreads query that reintroduces it is
repaired immediately -- late fetch response, IndexedDB restore, side-inbox
prefetch and fetchNextPage alike -- so we no longer have to name the
guilty write. The guard is time-boxed at 60s; markActive, undo and
mutation rollback all release first, so a legitimate return still lands.
Suppressions now report to the server, not PostHog. The previous round's
diagnostic was PostHog-only and the browser reporting the bug sends
PostHog nothing -- 66 archives on staging produced zero client events --
so the fix could never be verified. mail.reportListSuppression writes a
structured log readable in CloudWatch.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 24, 2026, 3:51 PMserver-providers.tsxchore: take Autumn billing out until we actually bill
Nothing in the product charges anyone, but the Autumn provider still wrapped
the entire app — and its error boundary sat above every route, so any render
error anywhere below it got swallowed and reported as "billing unavailable"
while the whole tree silently remounted. Real bugs were hiding behind a
billing message.
Removes the provider, the /autumn route, the customer-delete side effects in
account deletion, and AUTUMN_SECRET_KEY from the runtime contract. useBilling
stays as a no-op stub so the billing-aware UI keeps compiling and renders its
"no subscription" branches; restore the Autumn-backed implementation to turn
billing back on.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 13, 2026, 11:04 PMsession-provider.tsxfix: add session retry escape hatch and tighten auth cookie detection
Transient session errors show a login link and redirect after 30s; escapeXml
quotes apostrophes; privateProcedure logs session_token cookie presence.
Co-authored-by: Cursor <<email>>Jun 1, 2026, 11:36 PM