Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 77 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions coderd/database/db2sdk/db2sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,17 @@ func UserAIBudgetOverride(o database.UserAIBudgetOverride) codersdk.UserAIBudget
}
}

func OrganizationGroupAISpend(row database.GetOrganizationGroupsAISpendRow) codersdk.OrganizationGroupAISpend {
group := codersdk.OrganizationGroupAISpend{
GroupID: row.GroupID,
CurrentSpendMicros: row.CurrentSpendMicros,
}
if row.SpendLimitMicros.Valid {
group.SpendLimitMicros = &row.SpendLimitMicros.Int64
}
return group
}

func InvalidatedPresets(invalidatedPresets []database.UpdatePresetsLastInvalidatedAtRow) []codersdk.InvalidatedPreset {
var presets []codersdk.InvalidatedPreset
for _, p := range invalidatedPresets {
Expand Down
4 changes: 4 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -4217,6 +4217,10 @@ func (q *querier) GetOrganizationByName(ctx context.Context, name database.GetOr
return fetch(q.log, q.auth, q.db.GetOrganizationByName)(ctx, name)
}

func (q *querier) GetOrganizationGroupsAISpend(ctx context.Context, arg database.GetOrganizationGroupsAISpendParams) ([]database.GetOrganizationGroupsAISpendRow, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetOrganizationGroupsAISpend)(ctx, arg)
}

func (q *querier) GetOrganizationIDsByMemberIDs(ctx context.Context, ids []uuid.UUID) ([]database.GetOrganizationIDsByMemberIDsRow, error) {
// TODO: This should be rewritten to return a list of database.OrganizationMember for consistent RBAC objects.
// Currently this row returns a list of org ids per user, which is challenging to check against the RBAC system.
Expand Down
16 changes: 16 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6938,6 +6938,22 @@ func (s *MethodTestSuite) TestAIBridge() {
check.Args(database.GetAIModelPriceByProviderModelParams{}).Asserts(rbac.ResourceAiModelPrice, policy.ActionRead)
}))

s.Run("GetOrganizationGroupsAISpend", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
org := testutil.Fake(s.T(), faker, database.Organization{})
row1 := testutil.Fake(s.T(), faker, database.GetOrganizationGroupsAISpendRow{OrganizationID: org.ID})
row2 := testutil.Fake(s.T(), faker, database.GetOrganizationGroupsAISpendRow{OrganizationID: org.ID})
arg := database.GetOrganizationGroupsAISpendParams{
OrganizationID: org.ID,
GroupIds: []uuid.UUID{row1.GroupID, row2.GroupID},
PeriodStart: time.Now().UTC().Truncate(24 * time.Hour),
}
dbm.EXPECT().GetOrganizationGroupsAISpend(gomock.Any(), arg).
Return([]database.GetOrganizationGroupsAISpendRow{row1, row2}, nil).AnyTimes()
check.Args(arg).
Asserts(row1, policy.ActionRead, row2, policy.ActionRead).
Returns([]database.GetOrganizationGroupsAISpendRow{row1, row2})
}))

s.Run("GetGroupAIBudget", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
g := testutil.Fake(s.T(), faker, database.Group{})
b := testutil.Fake(s.T(), faker, database.GroupAIBudget{GroupID: g.ID})
Expand Down
8 changes: 8 additions & 0 deletions coderd/database/dbmetrics/querymetrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions coderd/database/dbmock/dbmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions coderd/database/modelmethods.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,10 @@ func (g GetGroupsRow) RBACObject() rbac.Object {
return g.Group.RBACObject()
}

func (g GetOrganizationGroupsAISpendRow) RBACObject() rbac.Object {

@ssncferreira ssncferreira Jul 14, 2026

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.

AFAIU, for full parity with /api/v2/organizations/{org}/groups, we need this for regular org members. These members see all groups in GET /organizations/{org}/groups, but without this they would get empty rows from this new endpoint.

Note that regular members already see the group's configured spend limit via the existing group budget endpoint (/api/v2/groups/{group}/ai/budget), so the only new property here is the aggregate current spend.

As a result, this comes down to: should regular members have visibility into their groups' current AI spend? Let me know what you think.

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.

P2 [CRF-5] Answering your question: as written, group AI spend inherits group-read authz, so on a default deployment every org member can read every group's aggregate spend, and a human needs to decide whether that is intended (Pariston P2, Kurapika/Knov P3)

GetOrganizationGroupsAISpendRow.RBACObject delegates to Group.RBACObject, so visibility equals GET /organizations/{org}/groups. A plain org member gets org-wide ResourceGroup read only when org.ShareableWorkspaceOwners == everyone (roles.go:1168), which is the column default; the passing Member case (a user not in the group) confirms it. Meruem's nuance: the budget limit is not new exposure (GetGroupAIBudget already authorizes member read), so the only incremental disclosure here is each group's aggregate current_spend_micros, summed across members the caller may not be able to enumerate individually. This is deliberate parity and not a code defect, but cost-control spend is a different sensitivity class from group identity. Per our review policy an agent can't accept this gap as permanent: either confirm members should see group spend (and keep group-read authz), or gate spend behind an admin-level action/resource. Flagging so it's decided rather than defaulted.

🤖

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.

I think this is fine as-is.

Comment thread
ssncferreira marked this conversation as resolved.
return Group{ID: g.GroupID, OrganizationID: g.OrganizationID}.RBACObject()
}

func (gm GroupMember) RBACObject() rbac.Object {
return rbac.ResourceGroupMember.WithID(gm.UserID).InOrg(gm.OrganizationID).WithOwner(gm.UserID.String())
}
Expand Down
5 changes: 5 additions & 0 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading