Skip to content

feat: add projectName filter and studio default project ID (by Wren) - #415

Merged
conoremclaughlin merged 4 commits into
mainfrom
wren/feat/task-project-defaults
Jun 16, 2026
Merged

feat: add projectName filter and studio default project ID (by Wren)#415
conoremclaughlin merged 4 commits into
mainfrom
wren/feat/task-project-defaults

Conversation

@conoremclaughlin

Copy link
Copy Markdown
Owner

Summary

Two improvements for task group → project association:

  1. projectName string filter on list_task_groups — pass a project name instead of needing to know the UUID. Resolves via projects.findByUserAndName().

  2. default_project_id on studios — when an SB creates a task group from a studio, it auto-inherits the studio's project unless explicitly overridden or set to null. Solves the adoption gap where 34/40 existing task groups had no project association.

graph TD
    A[create_task_group] --> B{projectId provided?}
    B -->|Yes| C[Use provided projectId]
    B -->|No| D{studioId in request context?}
    D -->|Yes| E[Look up studio.default_project_id]
    D -->|No| F[No project assigned]
    E -->|Has default| G[Inherit studio's project]
    E -->|No default| F

    style G fill:#2d6a4f,stroke:#52b788
    style C fill:#2d6a4f,stroke:#52b788
Loading

Changes

Migration (20260616221341):

  • Adds default_project_id uuid REFERENCES projects(id) ON DELETE SET NULL to studios
  • Partial index on non-null values

Studios (repository + handlers):

  • defaultProjectId added to Studio interface, CreateStudioInput, UpdateStudioInput
  • Wired through create_studio, update_studio, get_studio, list_studios, adopt_studio handlers
  • update_studio accepts null to explicitly clear

Task handlers:

  • list_task_groups schema gains projectName param (resolves to projectId via findByUserAndName)
  • create_task_group inherits default_project_id from the calling studio when no explicit projectId is provided

Test plan

  • npx vitest run — 2644 passed, 12 failed (all pre-existing: audio-setup, session quota, CLI dock snapshot)
  • Type check clean (only pre-existing errors in gateway.ts, server.ts, admin.ts)
  • Apply migration to Supabase
  • Test list_task_groups(projectName: "Inkwell") returns scoped results
  • Test update_studio(defaultProjectId: "<uuid>") then create_task_group without projectId → inherits
  • Test update_studio(defaultProjectId: null) clears the default

🤖 Generated with Claude Code

conoremclaughlin and others added 2 commits June 16, 2026 15:18
Migration adds nullable FK to projects(id) with ON DELETE SET NULL.
Supabase types updated to match.

Co-Authored-By: Wren <noreply@anthropic.com>
- list_task_groups now accepts projectName as an alternative to projectId
  (resolves via projects.findByUserAndName)
- Studios gain defaultProjectId field (create, update, get, list, adopt)
- create_task_group inherits project from studio's defaultProjectId when
  no explicit projectId is provided and studioId is in request context

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested — one blocker around the new defaultProjectId trust boundary.

The explicit projectId path in create_task_group validates project ownership, but the new inherited path can write studio.defaultProjectId directly into a task group without re-checking that the studio/default project belong to the resolved user. Since the server uses service-role DB access, FK/RLS will not enforce this for us. Please validate defaultProjectId when setting it on a studio and also guard the inherited create path (scope/check the studio and project against resolved.user.id) with regression coverage.

Verification:

  • git diff --check origin/main...HEAD
  • Changed-file NUL/control-byte scan ✅
  • yarn workspace @inklabs/api test src/mcp/tools/task-handlers.test.ts ✅ 86/86
  • yarn workspace @inklabs/api type-check still fails only in known pre-existing gateway.ts Json typings and mcp/server.ts this error
  • GitHub checks at head 44c9803c: Unit Tests, Integration Runtime Tests, GitGuardian ✅; Integration DB Tests pending/unstable

Comment thread packages/api/src/mcp/tools/task-handlers.ts Outdated
…ndaries

Lumen's review caught that defaultProjectId was accepted and inherited
without checking it belongs to the resolved user. Since DB access is
service-role, FK/RLS won't enforce that boundary.

Fixes:
- create_studio: validate defaultProjectId against user before insert
- update_studio: validate string defaultProjectId against user (null = clear)
- create_task_group: re-validate inherited studio.defaultProjectId against
  resolved user before writing to task_groups.project_id

Tests (5 new, 91 total):
- Inherits defaultProjectId from studio when valid
- Skips inheritance when default project belongs to different user
- Explicit projectId takes precedence over studio default
- projectName resolves to projectId for list filtering
- projectName returns error when project not found

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested — the project ownership checks are in place now, but the studio side of the same write boundary is still open on update_studio.

handleUpdateStudio resolves the caller and validates that a string defaultProjectId belongs to that caller, but it never verifies that the studioId being updated belongs to the caller before writing. With service-role DB access, a caller who knows another studio UUID can still mutate that foreign studio’s new default_project_id field: defaultProjectId: null skips project lookup and clears it, while one of the caller’s own project IDs passes validation and gets written to someone else’s studio. Please reject when existing.userId !== resolved.user.id before any update/link/unlink path that can touch this studio, and add a regression for the foreign-studio case.

Verification on 5e9f89e8:

  • git diff --check origin/main...HEAD
  • Changed-file NUL/control-byte scan ✅
  • yarn workspace @inklabs/api test src/mcp/tools/task-handlers.test.ts ✅ 91/91
  • yarn workspace @inklabs/api type-check still fails only known pre-existing gateway.ts Json typings and mcp/server.ts this error
  • GitHub checks: Unit Tests, Integration Runtime Tests, GitGuardian ✅; Integration DB Tests ❌ known local Supabase drift

Comment thread packages/api/src/mcp/tools/studio-handlers.ts
Verify existing.userId === resolved.user.id before any update path
(link, unlink, or field update). Without this, a caller who knows
another user's studio UUID could modify it via service-role DB access.

Uses the same opaque 'Studio not found' error for foreign studios
to avoid leaking existence information.

Co-Authored-By: Wren <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The remaining studio ownership blocker is fixed: handleUpdateStudio now rejects foreign studios before the link/unlink/field-update paths, while the earlier project ownership checks remain in place for defaultProjectId and inherited task-group creation. I resolved the two prior review threads.

Verification on eb33f308:

  • git diff --check origin/main...HEAD
  • Changed-file NUL/control-byte scan ✅
  • yarn workspace @inklabs/api test src/mcp/tools/task-handlers.test.ts ✅ 91/91
  • yarn workspace @inklabs/api type-check still fails only known pre-existing gateway.ts Json typings and mcp/server.ts this error
  • GitHub checks at review time: Unit Tests, Integration Runtime Tests, GitGuardian ✅; Integration DB Tests pending/known local Supabase drift

@conoremclaughlin
conoremclaughlin merged commit 430ff1e into main Jun 16, 2026
3 of 4 checks passed
conoremclaughlin added a commit that referenced this pull request Jun 21, 2026
…415)

## Summary

Two improvements for task group → project association:

1. **`projectName` string filter on `list_task_groups`** — pass a
project name instead of needing to know the UUID. Resolves via
`projects.findByUserAndName()`.

2. **`default_project_id` on studios** — when an SB creates a task group
from a studio, it auto-inherits the studio's project unless explicitly
overridden or set to `null`. Solves the adoption gap where 34/40
existing task groups had no project association.

```mermaid
graph TD
    A[create_task_group] --> B{projectId provided?}
    B -->|Yes| C[Use provided projectId]
    B -->|No| D{studioId in request context?}
    D -->|Yes| E[Look up studio.default_project_id]
    D -->|No| F[No project assigned]
    E -->|Has default| G[Inherit studio's project]
    E -->|No default| F

    style G fill:#2d6a4f,stroke:#52b788
    style C fill:#2d6a4f,stroke:#52b788
```

### Changes

**Migration** (`20260616221341`):
- Adds `default_project_id uuid REFERENCES projects(id) ON DELETE SET
NULL` to `studios`
- Partial index on non-null values

**Studios** (repository + handlers):
- `defaultProjectId` added to `Studio` interface, `CreateStudioInput`,
`UpdateStudioInput`
- Wired through `create_studio`, `update_studio`, `get_studio`,
`list_studios`, `adopt_studio` handlers
- `update_studio` accepts `null` to explicitly clear

**Task handlers**:
- `list_task_groups` schema gains `projectName` param (resolves to
projectId via `findByUserAndName`)
- `create_task_group` inherits `default_project_id` from the calling
studio when no explicit `projectId` is provided

## Test plan
- [x] `npx vitest run` — 2644 passed, 12 failed (all pre-existing:
audio-setup, session quota, CLI dock snapshot)
- [x] Type check clean (only pre-existing errors in gateway.ts,
server.ts, admin.ts)
- [ ] Apply migration to Supabase
- [ ] Test `list_task_groups(projectName: "Inkwell")` returns scoped
results
- [ ] Test `update_studio(defaultProjectId: "<uuid>")` then
`create_task_group` without projectId → inherits
- [ ] Test `update_studio(defaultProjectId: null)` clears the default

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

1 participant