Skip to content

chore: release - merge dev into main#1452

Merged
zbigniewsobiecki merged 23 commits into
mainfrom
dev
Jun 25, 2026
Merged

chore: release - merge dev into main#1452
zbigniewsobiecki merged 23 commits into
mainfrom
dev

Conversation

@zbigniewsobiecki

Copy link
Copy Markdown
Member

Automated release PR created by the release workflow.

Commits (23):

7e57870c feat(web): add pure run-pending decision helper (#1451)
c86f8306 feat(dashboard): add update-channel selector to agent config form (#1450)
88deacb7 feat(agents): set agent update channel via tRPC API and CLI (#1449)
7397bb6d docs(agents): document per-agent updateChannel setting and gating boundary (#1448)
e5f510ea feat(posting): gate system-driven PM/SCM posts by agent update channel (#1447)
bf6100d5 feat(backends): gate agent posting tools per update channel for both engine families (#1446)
379877d8 Merge pull request #1443 from mongrel-intelligence/fix/review-workitem-fresh-pr-reresolve
fca7f579 feat(config): persist agent update_channel and surface agentUpdateChannels (#1445)
75e71e12 feat(config): add shared UpdateChannel type and gating helpers (#1444)
ecee78a2 Merge pull request #1442 from mongrel-intelligence/feat/mng-1674-multi-org-ui
d28d377e fix(review): re-resolve work item from live PR when JIRA key added after review request
699002c9 feat(web): multi-org membership UI — org switcher, add-to-org form, member list
3804d494 Merge pull request #1441 from mongrel-intelligence/feat/mng-1673-multi-org-management
291f6100 feat(users): add "remove from this org" for guest members (PR #1441 review)
774b6aec fix(users): sync home-org membership role on global-role edit (PR #1441 review)
db782129 fix(cli): users list renders global role, not per-org membership role
4f7a8945 fix(users): reconcile member listing roles + backfill membership gaps (PR #1441 review)
58718b56 feat(users): grant membership, graceful create, member listing + CLI (spec 021 plan 3)
1d9b8f46 Merge pull request #1440 from mongrel-intelligence/feat/mng-1672-multi-org-access
98d68d99 feat(auth): membership-aware org resolution, active-org switching, per-org roles (spec 021 plan 2)
7135f7f2 Merge pull request #1439 from mongrel-intelligence/feat/mng-1671-org-memberships-schema
8304127d feat(db): add org_memberships table + sessions.active_org_id (spec 021 plan 1)
692827ed feat(queue): add durable debug-analysis job-state helpers to dashboard queue client (#1438)

aaight and others added 23 commits June 24, 2026 16:23
…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>
…(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>
@zbigniewsobiecki zbigniewsobiecki merged commit fccd6d4 into main Jun 25, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants