Collapses the project-context resolution that had been copied across the
issue, chat and quick-create claim paths (and would have been copied a
fourth time) into one workspace-scoped, fail-closed resolver, and hydrates
run_only autopilot project context on top of it.
- Project and resource reads are both workspace-scoped, so a dangling or
cross-tenant project reference can no longer lift another workspace's
repository URL, local directory path, title or description into a claim.
- A failed read is no longer treated as "there is no project". Transient
failures reject the claim and preserve the task for the stale-dispatched
reclaim rather than silently falling back to workspace repos.
- Source rows (issue, chat session, autopilot run, autopilot) fail closed
the same way. Previously a read error skipped the branch entirely and,
because taskToResponse seeds the runtime workspace, sailed through the
backstop isolation check — dispatching an agent with no issue context or,
for chat, no input at all, bypassing the empty-input guard.
- Workspace isolation is resolved authoritatively per branch and checked
before any context is hydrated. For run_only autopilots that check had
been a tautology, comparing the runtime workspace against itself.
- run_only autopilots now receive their configured project's repositories
and local_directory, the same contract issue-bound and quick-create tasks
already had.
Absorbs #7396. Its inline implementation is replaced by the shared
resolver; the author's regression test is carried over unchanged.
Co-authored-by: RAVN <[email protected]>
* feat(issue-status): per-workspace custom issue statuses over 7 canonical categories (MUL-6243)
Co-authored-by: multica-agent <[email protected]>
* fix(issue-status): address review — normalize all status consumers, close archive race, gate rollout, split PK index
Review round 1 (MUL-6243 / PR #7065):
1. Behavior normalization was incomplete. Adds issue_effective_status() as a
SQL mirror of issuestatus.Effective so the six SQL consumers the Go resolver
cannot reach (duplicate guard, open-issue list, sub-issue progress, project
stats, completed-inbox archive, open_only) resolve categories too. Wires the
resolver into the task-failure sweeper and the stage-barrier helpers.
2. Archive TOCTOU. Archive now runs its in-use census and the archive itself in
one transaction under an exclusive catalog lock; writes that target a custom
status take the shared side. Built-in writes stay lock-free — a built-in can
never be archived.
3. Rollout safety. Creating a custom status is gated behind the default-off
CustomIssueStatuses flag, so a value old pods cannot interpret cannot exist
until the fleet is updated. Readers ship ungated and are safe.
4. Primary key index. Follows the repo convention (327-329): table without a
PK, unique index built CONCURRENTLY in its own migration, attached via
PRIMARY KEY USING INDEX, and registered for invalid-index cleanup.
5. Status normalization vs stored value. Write paths now persist the canonical
key from Resolve, so 'HUMAN_REVIEW' no longer validates and then fails the
column format constraint as a 500.
Also fixes the two CLI tests failing in CI: they asserted the pre-MUL-6243
contract that an unknown-but-well-formed status is rejected locally.
Co-authored-by: multica-agent <[email protected]>
* fix(issue-status): close the archive race in both orderings; resolve categories in batch barrier and CLI progress
Review round 2 (MUL-6243 / PR #7065):
1. The archive race was only half closed. Handlers resolved the status BEFORE
opening a transaction, so an archive could commit in that window and the
write would still land on an archived key. assertIssueStatusStillActive now
takes the shared catalog lock and RE-RESOLVES inside the caller's
transaction, which is what actually makes the status provably active at
write time. Applied to create, update, batch, and both description-merge
branches, which previously bypassed the guard entirely. A losing writer gets
409 rather than a silent partial update.
2. The batch stage-barrier prefilter still compared literals, so a batch that
moved the last child onto a custom done status left childDoneCompleted empty
and skipped the parent notification. It now resolves categories first.
3. multica issue children --output json counted only literal done/cancelled.
ListChildIssues now emits status_category and the CLI reads it, falling back
to status for older backends.
TestArchiveCommitsInsideTheWriteRaceWindow interleaves the two operations so the
writer validates against an active status and the archive commits before the
write. Verified it fails without the re-resolve (issue stranded on the archived
status) and passes with it.
Co-authored-by: multica-agent <[email protected]>
* refactor(issue-status): address review nits — honest race-test naming, one catalog read per list
Review round 3 nits (MUL-6243 / PR #7065):
1. TestArchiveRaceBothOrderings claimed more than it proved. Its 'archive wins'
cases archived first and only then sent the request, which the pre-flight
check alone rejects. Split into TestWritesRejectAnArchivedStatus (the
sequential case, named for what it is) and TestArchiveRefusesAfterACommitted
Write, and extended TestArchiveCommitsInsideTheWriteRaceWindow — the only
test that truly interleaves archive between pre-flight and write — from
create alone to all five write endpoints. Re-verified by deleting the
in-transaction re-resolve: 4 of 5 fail with issues stranded on the archived
status (create is guarded in the service layer and checked separately).
2. ListChildIssues / ListChildrenByParents issued one category lookup per
custom-status child. issuestatus.Resolver now amortizes that to a single
catalog read per request, lazily on the first custom key, so built-in
statuses still cost zero queries however long the list is.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(chat): add project context
Co-authored-by: multica-agent <[email protected]>
* fix(chat): resolve MUL-5150 review blockers
- Renumber project-context migrations to unique prefixes after current main:
206_chat_session_project -> 212 (column), 207_chat_session_project_index ->
213 (concurrent index). 206/207 collided with 206_agent_disabled_runtime_skills
and main's 207-211 client_usage_daily set.
- Add the 4 missing chat input.project_context keys to ja/ko locales so the
locale parity test passes (en/zh-Hans already had them).
- Lock the project-context control while a send is in flight (isSubmitting),
not just while the agent is running. A brand-new chat creates its session
lazily during send bound to the project at click time; switching project
mid-send would create the session against the stale project and clear the
editor as if the send landed on the new selection. Add a regression test.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(chat): complete project context handling
* fix(chat): pin fresh chat to open session's agent on project switch
Switching an existing session to a different project opens a fresh chat but
only cleared the active session, dropping selection back to the stored
`selectedAgentId`. When that preference was stale (open session belongs to
agent B while the persisted pick is still agent A), the lazily-created session
and its first send bound to the wrong agent (agent A).
Extract the project-switch decision into a shared `planProjectContextChange`
pure helper in use-chat-controller.ts and route both chat surfaces (the chat
tab controller and the floating ChatWindow) through it, so the fresh chat is
pinned to the open session's agent and the rule cannot drift between the two
copies. Add a dual-entry regression test (pure-fn guard + controller
integration) covering the stale selectedAgentId case.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* chore(ci): re-trigger required checks on latest head
The prior push updated the branch ref but GitHub did not emit a pull_request
synchronize for it (PR head-sync lag), so CI/Mobile Verify never ran on the
commit carrying the stale-agent project-switch fix. Empty commit to force a
fresh synchronize on a head that includes it.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(chat): renumber project migrations to 213/214 after main added 212
Current main added 212_agent_service_tier; the PR's 212/213 chat migrations
collided with it on the merge ref, failing TestMigrationNumericPrefixesStay
UniqueAfterLegacySet. Merge current main and move the chat column migration to
213 and the concurrent index migration to 214 (column before index preserved).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(chat): lock ProjectPicker clear control during send (keyboard path)
The send-pending lock only put pointer-events-none on the wrapper, which
blocks the mouse but leaves ProjectPicker's inline clear button in the tab
order — a keyboard user could Tab to "Remove from project" and press Enter
mid-send, detaching the project after the lazily-created session already went
out with the old one (reopens the mid-send retarget path via keyboard).
Add an explicit `disabled` capability to the shared ProjectPicker that locks
the trigger, the menu (forced closed), and the inline clear button (disabled +
out of the tab order). Defaults to false, so issue/create/autopilot callers
keep their hover/keyboard clear. ChatInput passes disabled while the project
selection is locked.
Tests: real-ProjectPicker regression (keyboard activation of the clear control
is inert when disabled; still works when enabled) + ChatInput wiring assertion
that the picker is disabled mid-send.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Lambda <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Walt <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Naiyuan Qing <[email protected]>
Co-authored-by: NevilleQingNY <[email protected]>
Projects become schedulable planning objects alongside their issues: add
optional start_date / due_date, mirroring issue.start_date / issue.due_date.
This is only the first slice of #5227 — labels, metadata, and the editable
metadata UI are still out of scope.
- migration 166: two nullable DATE columns on `project` (calendar days, no
FK/index — matches the issue end-state after migration 112)
- sqlc CreateProject / UpdateProject carry the dates; UpdateProject uses
narg so an explicit null clears
- handler: parse YYYY-MM-DD (400 on bad format), rawFields-presence clear on
update, and the hand-scanned SearchProjects query returns the columns
- CLI: `project create/update --start-date/--due-date` (empty clears on update)
- frontend + mobile types/zod schemas: the two new schema fields are
nullable().default(null) so a project from an older backend (frontend
deploys before backend) parses to null instead of degrading the batch to
the empty fallback; added a search schema drift test
- projects skill / CLI docs
Part of #5227
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(security): scope DELETE/UpdateIssueStatus by workspace_id
Add workspace_id to the WHERE clause of DeleteIssue, DeleteComment,
DeleteProject, DeleteSkill, DeleteChatSession, and UpdateIssueStatus
as SQL-layer defense-in-depth.
Handler loaders (loadIssueForUser / loadSkillForUser / etc.) already
enforce workspace membership today, so this is not patching a known
live vuln. But the tenant invariant is currently a handler-layer
guarantee — a future loader bypass or a new caller skipping the
loader would be silently catastrophic. Making workspace_id part of
the SQL identity collapses the trust surface to the schema itself:
forging a sibling-workspace UUID becomes ErrNoRows instead of a
cross-tenant write.
Reference: incident #1661 (util.ParseUUID silent zero UUID returning
204 on a DELETE that matched zero rows) — same class of failure,
prevented at a different layer.
Scope:
- 5 DELETE queries: issue, comment, project, skill, chat_session
- 1 simple UPDATE: UpdateIssueStatus (2 narg, no SET ordering risk)
- All callers updated (handlers, service, runtime sweeper fallback)
Multi-narg UPDATE queries (UpdateIssue, UpdateProject, UpdateSkill,
UpdateComment, UpdateChatSession*) are deferred to a follow-up to
keep this change reviewable: each needs its narg pinning shifted
and per-caller verification.
sqlc was regenerated by hand (no local sqlc toolchain); CI's
backend job is the authoritative compile check.
* test(security): add workspace_scope_guard regression test
Locks in the SQL-layer tenant guard added in this PR. For each of the 6
scoped queries (DeleteIssue, DeleteComment, DeleteProject, DeleteSkill,
DeleteChatSession, UpdateIssueStatus), creates the resource in workspace
A, invokes the query with a foreign workspace UUID, and asserts the row
is untouched (0 rows affected with no error for :exec; pgx.ErrNoRows for
:one). A future refactor that drops the workspace_id arg from any of
these queries will now fail loudly instead of silently regressing.
Includes a sanity sub-test that the in-workspace path still mutates, so
a buggy guard that returns no-op for every call would not pass.
Co-Authored-By: Claude Opus 4 <[email protected]>
---------
Co-authored-by: Tom Qiao <[email protected]>
Co-authored-by: Claude Opus 4 <[email protected]>
- Add migration 106: CREATE INDEX CONCURRENTLY on member(user_id, workspace_id)
- Rewrite ListWorkspaces to drive from member table with explicit fields
- Regenerate all sqlc code with v1.31.1 (intentional version upgrade)
Co-authored-by: multica-agent <[email protected]>
* feat(projects): show completion progress (done/total issues) in project list
Add a progress column to the projects list page that displays a mini progress
bar and done/total issue count for each project. Backend batch-fetches issue
stats per project using a single query for efficiency.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* feat(projects): show progress on project overview page
Add a progress bar with done/total (percentage) to the project detail
overview tab, computed from the already-loaded project issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Add priority field (urgent/high/medium/low/none) to projects, matching
the existing issue priority system. Includes database migration, API
support for create/update/list filtering, and UI for the create dialog,
project list table, and project detail page.