componentsMerge origin/staging into fix/dm-container-kind
22 conflicted files. Most were unions — two sides adding different imports,
exports, wiki entries or allowed-import patterns. Four were competing designs,
resolved on evidence rather than preference:
* New chat (LeftSidebarContent) — staging. `fbca03d0d` (Aug 30) makes a new chat
the DRAFT chat, with no thread id to mint; it retires this branch's Aug 26
`goHomeToNewChat` + `findOpenEmptyChat` reuse by name, solving the same
pile-up more fundamentally.
* `permissions:` -> allowedSkills (playbook-execution-triggers) — HEAD. Staging
threads `subagent.permissions` into `allowedSkills`; its OWN comments say that
value is never enforced ("nothing sets this key today", automation-agent.ts:
"a scoping feature that has never been enforced"). `77ecfb397` audited the
data before deleting it: of 768 subagent docs, 21 declare the key, 19 are
no-ops, and the 2 with real values name four skills that do not exist — wiring
it on would have broken both agents and changed nothing for the rest. Staging's
parsedByRefId restructure is kept; only the dead feed is dropped.
* Before-meeting preps (calendar-events) — composed. This branch's
`selectBeforeMeetingConfigs` (shared with the prep-status read model so the
"wrong stage" chip cannot contradict the code that skipped it) plus staging's
meeting-moved re-arm. Staging's inline stage filter is dropped: identical
semantics, and the loop below already reads `schedulableConfigs`.
* Settings/general — composed. This branch's form-kit row layout plus staging's
2FA card, ported onto SettingsSection. Staging's Cache card is the pre-kit copy
of a section this page already carries.
Agent tool scoping was two independently-built narrowings of one permission
surface (`narrowSkillToolsToAgentScope` here, `withoutTools(deniedToolIds)` on
staging); both are restrictions, so both are applied. MCP connections was the
same shape: staging's extracted `createMcpConnection` owns the write, extended
to carry this branch's `requiresAgentGrant` and setup-time tool policy.
Fallout the tests caught, all cross-cutting rather than in any conflict:
`optionColor` moved to components/ui/option-picker (board still imported the old
path); `encryptCredential` dropped from an import line still used by
updateMcpConnection; the drafter rename left a stale slug in EXPECTED_FOLDER;
and staging's new remind test matched two options once this branch's "at 8am"
suggestion existed.
pnpm run autofix:local green: oxlint, deps:check, 3741 Jest, 9671 Vitest.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 1, 2026, 1:56 AMhooksfeat(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 AMthemefeat(agents,meetings): collapse the connection list into a ring, and split the meetings tab further
WIP on the agent config surfaces and the meetings tab, committed so the
branch can take origin/staging.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 3:43 PM