chore: release - merge dev into main#1452
Merged
Merged
Conversation
…d queue client (#1438) Co-authored-by: Cascade Bot <bot@cascade.dev>
…1 plan 1) Introduce the multi-org membership data model (spec 021, plan 1 of 4) so one account can belong to many orgs: - org_memberships table: user <-> org link with a per-org role; at most one membership per (user, org) - sessions.active_org_id column: nullable, ON DELETE SET NULL so deleting an org never logs a user out - forward-only migration 0053 that backfills exactly one membership per existing user from their home org (users.org_id) + role, mapping the global superadmin role to an admin membership Ships dormant: nothing reads the new table/column yet (plan 2 wires resolution). users.org_id / users.role are retained as the home org + global role. Satisfies spec AC #6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…memberships-schema
…r-org roles (spec 021 plan 2)
…(spec 021 plan 3) The additive admin side of multi-org membership (spec 021, plan 3 of 4): an admin of an org (or a superadmin) can grant an existing email a membership in their org with a per-org role, creating a user with an already-registered email returns a clear CONFLICT instead of a 500, and listing an org returns its true membership — including accounts whose home org is elsewhere. - addExistingUserToOrg grant mutation: org-admin/superadmin only (refined by the per-org actor role), NOT_FOUND when no account owns the email, idempotent re-grant (upsert updates the per-org role). - Graceful duplicate-email create: catch the Postgres unique violation (23505, unwrapped from drizzle's DrizzleQueryError cause) and map it to a typed CONFLICT whose message distinguishes "already a member here" from "account exists — add to this org". - createUserWithMembership: create mirrors a membership in the same transaction so new accounts appear in the membership-based listing; a duplicate email rolls back without an orphan membership. - listOrgMembers: membership-based listing joining org_memberships -> users with the per-org role; excludeGlobalRole keeps regular admins from seeing global superadmins. - CLI parity: new `cascade users add-to-org`, membership-based `list`, and clean conflict messaging on `create`. Satisfies spec ACs #1 (add existing account) and #2 (no more 500); #5 partial (membership listing API; UI render is plan 4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (PR #1441 review) Address review concerns on spec 021 plan 3: - BLOCKING: `users.list` returned the per-org membership role while the live Settings → Users editor still reads/writes the global `users.role`, so the editor could silently revert a user's global role and the superadmin "Manage via CLI" guard stopped matching. `listOrgMembers` now returns BOTH `role` (per-org) and `globalRole`; the table badge + CLI guard + editor target the global role, eliminating the drift until the plan-4 UI reconciliation. - SHOULD_FIX: the membership-based listing inner-joins `org_memberships`, so accounts created via the old `createUser` (and bootstrap superadmins) had no membership row and vanished from their own org. Re-run the idempotent home-org backfill as migration 0054, and mirror a membership in `tools/create-admin-user.ts`. - Remove the now-dead `listOrgUsers` / `OrgUser` (only its own test referenced it) per the review question. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The membership-based list (listOrgMembers) returns a per-org role that can only ever be member|admin, so the CLI table was mislabeling superadmins as admin. Render globalRole instead, matching the web UI's interim choice and restoring correct superadmin visibility (PR #1441 review). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… review)
SHOULD_FIX from review: home-org permissions are read from
org_memberships.role (resolveActorRoleInOrg), not users.role. The `update`
mutation only wrote users.role, so after this spec's universal home-org
membership backfill, member↔admin edits via Settings → Users (and
`cascade users update --role`) were silent no-ops — the global role flipped but
the membership the home-org permission check reads stayed put, so a "promoted"
admin still hit FORBIDDEN.
updateUser now accepts an optional syncHomeOrgMembership; when the role is part
of the update it upserts the target's home-org membership role in the SAME
transaction as the users.role write (no drift). Membership roles are per-org
('member' | 'admin'), so a global 'superadmin' maps to an 'admin' membership,
mirroring createUserWithMembership. The mutation passes the target's home org
(targetUser.orgId); access control already restricts non-superadmins to
same-org targets, so this is the target's home org in every allowed case.
Tests: 4 real-DB integration cases (sync on role change, superadmin→admin
membership, upsert when legacy account has no row, no-op when role unchanged);
updated the router unit assertions to the new call shape + a dedicated
member→admin promotion test. typecheck, lint, unit (58) and integration (11)
green.
Note: the related addExistingUserToOrg observation (granting an admin
*membership* in a non-home org is inert because adminProcedure gates the global
role) is a separate, deeper model reconciliation that lands with the plan-4
dashboard work (MNG-1674) — out of scope here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RyBTx5JozjbpUko5SRyz4X
…eview)
Review flagged a footgun: now that the Users list is membership-based, a guest
(home org elsewhere, granted via add-to-org) appears in the org's table, and the
Delete button deletes the ENTIRE account — org_memberships cascades, dropping
the account's memberships in every org, not just the one being viewed.
Adds a proper "remove from this org" action that drops only the membership:
- removeOrgMembership(userId, orgId) repo helper — deletes the single
org_memberships row, returns { removed } (false when none existed). Account
and all other memberships are untouched.
- users.removeFromOrg mutation — org admins (and superadmins) can remove guests
from THEIR org. Unlike delete/update it intentionally does not hide
cross-home-org targets as NOT_FOUND (managing your own org's guest list is the
point). Guards: no self-removal; only superadmins can act on a superadmin;
refuses to remove a user from their HOME org (delete the account instead);
NOT_FOUND when the target has no membership here.
- listOrgMembers now returns homeOrgId + isGuest (computed against the listed
org) so the UI can branch. Additive — existing consumers unaffected.
- Web Users table: guests show a "Remove from org" action (UserMinus) with a
dedicated dialog ("removes access to this org only; account and other orgs
unaffected"); home-org members keep "Delete account" (dialog now says it
deletes across every org).
- CLI: `cascade users remove-from-org <id>` (mirrors users delete: positional
id + -y confirm).
Tests: 2 real-DB integration cases for removeOrgMembership + homeOrgId/isGuest
on listOrgMembers; 7 router unit cases (guest removal, self-removal, NOT_FOUND
account/membership, home-org refusal, superadmin-target guard, member caller);
4 CLI unit cases. typecheck, lint, web build, unit-api (1737) and CLI (651)
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RyBTx5JozjbpUko5SRyz4X
…i-org-management feat(users): grant membership, graceful create, member listing + CLI (spec 021 plan 3)
…ember list Spec 021 plan 4 (MNG-1674): the user-facing layer over the curl-testable backend from plans 2-3 (web/ only). - Active-org switcher (sidebar) for non-superadmin multi-org members, backed by auth.listMyOrgs + auth.setActiveOrg; invalidates every query on switch so the dashboard refetches against the new active org. Single-org users get an inert org-name banner (spec AC #9). Superadmin cross-org switching via x-org-context is unchanged (spec AC #7). - Add-existing-account dialog on Settings → Users calling users.addExistingUserToOrg, surfacing the NOT_FOUND envelope inline; the complementary CONFLICT from users.create is already shown inline by the create dialog (spec AC #1). - Member list now renders per-org role alongside the account role plus a Guest badge for cross-home members (spec AC #5). Pure helpers (shouldShowOrgSwitcher, resolveActiveOrgName, formatAddToOrgSuccess, describeMemberRow) and SSR-safe presentational components (OrgSwitcherView, AddToOrgForm) are unit-tested; hook-wired containers are thin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ter review request The review work-item fallback resolves the JIRA key from the webhook PR snapshot captured at review_requested time. The GitHub worker re-dispatches with that same stored payload, so a key added to the PR description *after* review is requested is never seen — the linked issue gets no progress comment and no image pre-fetch, even though the agent later finds the key in the diff on its own. Re-resolve from live PR state at the shared execution chokepoint (createAgentExecutionContext), before budget / persistence / progress-comment / image pre-fetch consume the work item. New best-effort helper reresolveReviewWorkItemFromFreshPR fetches the PR fresh and reuses the existing resolveWorkItemIdWithFallback (branch / title / last body line + provider verify). Scoped tightly: review agent only, JIRA only, only when dispatch resolved nothing and the PR/repo are known. Any failure (missing GitHub/PM scope, GitHub error) degrades to prior behavior. One insertion covers all three review dispatch routes (review-requested, pr-opened, check-suite-success). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011tKnnXDiRkMJMmESXywmqm
…i-org-ui feat(web): multi-org membership UI — org switcher, add-to-org form, member list
Introduce src/config/updateChannel.ts as the single source of truth for
the update-channel concept: a const catalog (none/scm-only/pm-only/both),
the UpdateChannel type, DEFAULT_UPDATE_CHANNEL ('both'), a Zod enum schema,
the resolveUpdateChannel(project, agentType) resolver, the PM/SCM posting
matrix helpers (isPmPostingEnabled / isScmPostingEnabled), the
communication-only gadget name lists (PM_POSTING_GADGETS / SCM_POSTING_GADGETS),
and filterPostingGadgetNames().
Pure, dependency-free foundation (Zod only) — no DB/UI/gating wiring yet,
so nothing else consumes it and the codebase stays green. Covered by
tests/unit/config/updateChannel.test.ts.
Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nnels (#1445) Adds the nullable `update_channel` column to `agent_configs` (migration 0055) and maps it into `ProjectConfig.agentUpdateChannels` so the resolved channel is available at runtime. NULL means inherit the default (`both`) — no backfill; existing projects are untouched. Read path only (no API writes or gating yet). - Migration 0055_agent_config_update_channel.sql + journal entry (idx 55) - Drizzle agentConfigs schema gains updateChannel: text('update_channel') - AgentConfigRow + buildAgentMaps accumulate updateChannels validated against UPDATE_CHANNELS (unknown values ignored) - ProjectConfigRaw/mapProjectRow set agentUpdateChannels via orUndefined; ProjectConfigSchema adds agentUpdateChannels record - Unit + integration round-trip tests (NULL resolves to both) MNG-1682 Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…m-fresh-pr-reresolve fix(review): re-resolve work item from live PR when JIRA key added after review request
…engine families (#1446) Drop the communication-only PM/SCM posting tools an agent is allowed to call based on the resolved per-agent update channel, so an agent literally cannot post on a disabled channel — across both engine families. - buildExecutionPlan (native-tool family: claude-code/codex/opencode): after profile.filterTools(...), apply filterPostingGadgetNames over the resolved channel to drop posting tool manifests from availableTools (the system-prompt tool list). - getLlmistGadgets result (llmist family): apply the same filter to the gadget instances passed to createConfiguredBuilder. Layers on top of the existing integration-availability filtering: an enabled channel against an absent tool simply has nothing to drop. MNG-1685 Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#1447) * feat(posting): gate system-driven PM/SCM posts by agent update channel Layer the resolved update channel onto every system-driven (non-agent) communication surface so acks, progress comments, lifecycle comments, and summaries respect the per-agent channel. Communication only — status moves, label add/remove, linkPR, and PR creation stay untouched. - buildProgressMonitorConfig: omit `trello` (PM) when PM posting is disabled and `github` (SCM) when SCM posting is disabled (ProgressMonitor already no-ops an absent poster block). - PMLifecycleManager: new `pmPostingEnabled = true` constructor flag that short-circuits the comment helpers (success-fallback, failure, budget exceeded/warning, error); constructed with the resolved flag in createAgentExecutionContext. - postAgentSummaryToPM: early-returns when PM posting is disabled (project threaded in to resolve the channel). - Router adapters: each postAck skips the PM ack when PM posting is disabled; the GitHub adapter additionally skips the PR ack when SCM posting is disabled and gates the PM-focused-agent ack branch on PM posting (postAck refactored into postPMFocusedAgentAck + postRegularPRAck helpers). Tests cover progress-config omission, lifecycle comment suppression vs move/label/linkPR retention, summary early-return, and per-channel adapter postAck gating. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(posting): gate worker-side last-resort error comment by update channel The operational-fault catch path in processPMWebhook constructed PMLifecycleManager with the default pmPostingEnabled=true, so an agent whose update channel disables PM posting (none / scm-only) would still post a `❌ Error:` comment to the PM card on an unhandled exception escaping executeAgent. Resolve the flag from the agent's update channel (mirroring createAgentExecutionContext) so the "disabled PM channel simply no-ops" invariant also holds on the error path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ndary (#1448) Document the per-agent `updateChannel` setting (values none/scm-only/pm-only/both, default both, the PM/SCM posting matrix, and the communication-only vs workflow boundary) implemented in MNG-1684/MNG-1685. - CLAUDE.md (AGENTS.md follows via symlink): new "Agent update channel" section. - docs/architecture/04-agent-system.md: gated vs not-gated posting surface map. - docs/architecture/08-config-credentials.md: per-agent config field + resolution. - CHANGELOG.md: Unreleased/Added entry. Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adds the operator write path for the per-agent updateChannel override: - agentConfigsRepository create/update accept and persist updateChannel?: string | null (mirrors the maxConcurrency shape) - tRPC agentConfigs.create/.update inputs add updateChannel: UpdateChannelSchema.nullish(), forwarded with the same spread used for maxConcurrency - CLI agents create/update add --update-channel (none/scm-only/pm-only/both), forwarded into the mutation Value persists and round-trips through config (NULL preserved); behavior is still effectively `both` until the gating stories land. Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
) Adds an "Update channel" Select (Both / SCM only / PM only / None) to the project agent config form's Engine tab, defaulting to `both`. Mirrors the existing maxConcurrency field 1:1: local state, config-sync useEffect, and save-handler mapping into both the create and update tRPC mutation inputs. - AgentConfig gains `updateChannel: UpdateChannel | null`; SaveConfigValues gains `updateChannel: string`. - Engine-tab Select wired to local state with a config-sync useEffect. - Save handler maps values.updateChannel into create + update inputs. - Adds a source-read regression test pinning the wiring. The backend tRPC write path (MNG-1683) already accepts updateChannel. Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Introduce web/src/lib/run-pending.ts, a node-testable decision helper that both run pages will consume to share consistent 'starting vs not-found' logic, plus its full unit-test suite. Foundational, pure logic only — no UI, API, or DB changes; no runtime consumers yet. Co-authored-by: Cascade Bot <bot@cascade.dev> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated release PR created by the release workflow.
Commits (23):