Skip to content

feat: add chat ACL database foundation#25080

Merged
DanielleMaywood merged 5 commits into
mainfrom
dm/chat-acl-database-foundation
May 14, 2026
Merged

feat: add chat ACL database foundation#25080
DanielleMaywood merged 5 commits into
mainfrom
dm/chat-acl-database-foundation

Conversation

@DanielleMaywood

@DanielleMaywood DanielleMaywood commented May 8, 2026

Copy link
Copy Markdown
Contributor

Note

🤖 This PR was written by Coder Agent on behalf of Danielle Maywood

Adds a new view to add ownership information for chats

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

Docs preview

📖 View docs preview for docs/admin/security/audit-logs.md

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clean decomposition of a database foundation for chat ACL sharing. The schema design is sound: JSONB ACL columns on the chats table, CHECK constraints to enforce the root-only ACL invariant, and a view to resolve inheritance for child chats. The selective application of the view CTE (only to queries that can return child chats) shows good understanding of the inheritance model. Pattern reuse from WorkspaceACL is appropriate given the existing RBAC infrastructure.

1 P2, 5 P3, 3 nits, 2 P4, 1 note. The P2 is test coverage for the view's COALESCE inheritance logic, which is the core deliverable of this PR. The P3s are a mix of design awareness (ForUpdate ACL mismatch, missing view CTE on GetActiveChatsByAgentID), latent type safety (Value() nil marshaling), and untested constraints.

Several reviewers noted that the explicit 30-column enumeration in the view creates a maintenance coupling: every future column addition to chats requires a DROP/recreate migration for the view. This is inherent to PostgreSQL views with COALESCE overrides and will surface as a sqlc codegen failure, so the breakage is compile-time.

"A split-personality Chat struct where the data depends on the query that produced it, with no signal to the consumer." (Hisoka)


coderd/database/queries/chats.sql:1463

P3 [DEREM-6] GetActiveChatsByAgentID reads from the raw chats table without the chats_with_effective_acl view CTE. The query has no parent_chat_id IS NULL filter, so it can return child chats with empty ACLs instead of inherited ones.

Three other read queries (GetChatByID, GetChildChatsByParentIDs, GetChatsByWorkspaceIDs) were upgraded in this PR to use the view. This one was missed. The dbauthz wrapper uses fetchWithPostFilter, which will run RBAC checks against the returned Chat objects. When ACL enforcement arrives, child chats from this query will appear to have zero grants.

Fix: add the same WITH chats AS (SELECT * FROM chats_with_effective_acl) CTE. (Meruem)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/migrations/000489_chat_acl_sharing.up.sql Outdated
Comment thread coderd/database/queries/chats.sql Outdated
Comment thread coderd/database/types.go Outdated
Comment thread coderd/database/migrations/000491_chat_acl_sharing.up.sql Outdated
Comment thread coderd/database/dbpurge/dbpurge.go Outdated
Comment thread coderd/database/sqlc.yaml Outdated
Comment thread coderd/database/types.go Outdated
Comment thread coderd/database/types.go Outdated
Comment thread coderd/database/migrations/000491_chat_acl_sharing.up.sql Outdated
Comment thread coderd/database/types.go Outdated
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch 2 times, most recently from 545192e to 84c4226 Compare May 11, 2026 10:37

Copy link
Copy Markdown
Contributor Author

🤖 This comment was written by Coder Agent on behalf of Danielle Maywood 🤖

DEREM-6: Fixed in ab44019f0f. GetActiveChatsByAgentID now reads through chats_expanded, preserving child rows while returning inherited ACLs consistently. I also added active-agent chat coverage for inherited root and child ACLs.

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good progress. All 7 R1 fixes verified by the panel: view inheritance tests, CHECK constraint rejection test, Value() nil guard, GetActiveChatsByAgentID CTE, Scan receiver pattern, roundtrip tests, and the view rename. The new tests are genuine (assert inherited values, not just "no error"; constraint test names the specific constraint; roundtrip tests exercise all Scan branches).

The panel unanimously closed all 4 contested findings (DEREM-4, DEREM-8, DEREM-10, DEREM-13). For DEREM-4 (ForUpdate mismatch): Chat.RBACObject() does not consume ACL fields, the upper stack is concrete (PRs 2-4 named in the description), and FOR UPDATE on a view would broaden lock scope. For DEREM-8 (dbpurge untested): audit diff is unaffected because before/after carry identical ACLs, and CHECK constraints guarantee valid JSONB. For DEREM-10 (no doc comment): the type signature is self-documenting and the project guidance supports omitting what-comments. For DEREM-13 (Scan nil): sibling types follow the identical pattern and coercing NULL would mask a broken invariant.

1 new P2 from the R2 changes: the GetChats query now materializes the full chats_expanded view on every call because the chats CTE is referenced twice (main query + cursor subquery). Before this PR, the cursor lookup was an indexed point read on the raw table. 1 nit.

"PostgreSQL materializes CTEs referenced more than once. This means every GetChats call evaluates the full chats_expanded view, stores the result in a temp table, then filters." (Hisoka)

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/queries/chats.sql
Comment thread coderd/database/types_test.go Outdated
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch from f6cbfda to cfa03fc Compare May 12, 2026 12:28
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

R2 fixes verified: the CTE materialization regression is fixed (cursor lookup now reads the raw table via a separate cursor_chat CTE), and tc := tc is removed. The structural restructuring to chats_expanded with owner fields is clean: lock semantics on GetChatByIDForUpdate are preserved, the ChatTable/Chat rename follows the established TemplateTable/Template pattern, and the new owner-field tests assert against real user data.

2 P3s this round.

coderd/database/gentest/models_test.go

P3 [DEREM-16] Missing TestViewSubsetChat for the new ChatTable/Chat type pair. Every other table/view pair has a gentest here (TestViewSubsetTemplate, TestViewSubsetTemplateVersion, TestViewSubsetWorkspaceBuild, TestViewSubsetWorkspace). This test prevents a future column addition to chats from silently diverging from chats_expanded. (Netero)

Meruem noted that the 16 inline CTEs duplicating the column list create a CTE-vs-view drift risk (distinct from the view-vs-table drift the gentest catches). The workspaces_expanded pattern avoids this by returning the raw type from write queries. Worth considering for a follow-up, but scope-extending for this PR.


coderd/database/querier_test.go:11509

P3 [DEREM-17] The 14 mutation return paths (ArchiveChatByID, UnarchiveChatByID, UpdateChatByID, etc.) that now return through the CTE+JOIN expansion lack assertions on OwnerUsername/OwnerName. Only 4 read paths are tested for owner field correctness.

All 16 JOINs are structurally identical by inspection, and sqlc catches column-count mismatches at generation. The real risk is low. But adding owner field assertions to the existing UpdateChatByID call here (2 lines) would cover the mutation CTE pattern with zero new fixtures:

require.Equal(t, owner.Username, chat.OwnerUsername)
require.Equal(t, owner.Name, chat.OwnerName)

(Bisky)

🤖

🤖 This review was automatically generated with Coder Agents.

Copy link
Copy Markdown
Contributor Author

🤖 This comment was written by Coder Agent on behalf of Danielle Maywood 🤖

Partially agreed. The gap is real: this PR adds a ChatTable/Chat table-view split without the same subset guard used for several existing expanded models, so a future chats column could drift from chats_expanded. The "every other pair" rationale is broader than the current test coverage, but TestViewSubsetChat is the right focused guard here. Fixed in ad113f72fbc8.

Copy link
Copy Markdown
Contributor Author

🤖 This comment was written by Coder Agent on behalf of Danielle Maywood 🤖

Partially agreed. There is already mutation-return coverage for owner fields on InsertChat, so the gap is not limited to read paths. But UpdateChatByID uses a separate inline owner join and the existing test does not assert OwnerUsername or OwnerName. I added those assertions to cover the representative mutation CTE path. Fixed in ad113f72fbc8.

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All 17 findings from rounds 1-3 are resolved: 11 author-fixed, 4 panel-closed (contested), 2 author-accepted. R3 fixes verified: TestViewSubsetChat gentest guards against view/table column drift, owner-field assertions cover the representative mutation CTE path. Netero R4 and the panel (Bisky, Mafuuu, Meruem) found no new issues. CI green (18 passed).

This PR is a solid database foundation for chat ACL sharing. The schema design (JSONB ACL columns, CHECK constraints, view with COALESCE inheritance), the query architecture (selective view CTE application, cursor separation, inline CTE expansion for mutations), the type system (ChatTable/Chat split), and the test coverage (view inheritance, constraint rejection, ACL roundtrip, gentest subset guard, owner-field assertions) are all in good shape.

🤖 This review was automatically generated with Coder Agents.

WHERE id = @id::uuid OR root_chat_id = @id::uuid
RETURNING *
),
chats_expanded AS (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this naming so that sqlc creates the right struct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Assuming I understand the question, I believe so.

Comment thread coderd/database/querier_test.go Outdated
@DanielleMaywood DanielleMaywood marked this pull request as ready for review May 14, 2026 15:23
@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch from eee5f5f to d443ca2 Compare May 14, 2026 15:25

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Change looks OK to me once the migrations are fixed

@DanielleMaywood DanielleMaywood force-pushed the dm/chat-acl-database-foundation branch from d443ca2 to 499b122 Compare May 14, 2026 16:01
@DanielleMaywood DanielleMaywood merged commit 9ddfafe into main May 14, 2026
24 checks passed
@DanielleMaywood DanielleMaywood deleted the dm/chat-acl-database-foundation branch May 14, 2026 16:18
@github-actions github-actions Bot locked and limited conversation to collaborators May 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants