Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56ff5dfded |
Generated
+12
@@ -13445,6 +13445,11 @@ const docTemplate = `{
|
||||
"chat:delete",
|
||||
"chat:read",
|
||||
"chat:update",
|
||||
"chat_automation:*",
|
||||
"chat_automation:create",
|
||||
"chat_automation:delete",
|
||||
"chat_automation:read",
|
||||
"chat_automation:update",
|
||||
"coder:all",
|
||||
"coder:apikeys.manage_self",
|
||||
"coder:application_connect",
|
||||
@@ -13654,6 +13659,11 @@ const docTemplate = `{
|
||||
"APIKeyScopeChatDelete",
|
||||
"APIKeyScopeChatRead",
|
||||
"APIKeyScopeChatUpdate",
|
||||
"APIKeyScopeChatAutomationAll",
|
||||
"APIKeyScopeChatAutomationCreate",
|
||||
"APIKeyScopeChatAutomationDelete",
|
||||
"APIKeyScopeChatAutomationRead",
|
||||
"APIKeyScopeChatAutomationUpdate",
|
||||
"APIKeyScopeCoderAll",
|
||||
"APIKeyScopeCoderApikeysManageSelf",
|
||||
"APIKeyScopeCoderApplicationConnect",
|
||||
@@ -19038,6 +19048,7 @@ const docTemplate = `{
|
||||
"audit_log",
|
||||
"boundary_usage",
|
||||
"chat",
|
||||
"chat_automation",
|
||||
"connection_log",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
@@ -19084,6 +19095,7 @@ const docTemplate = `{
|
||||
"ResourceAuditLog",
|
||||
"ResourceBoundaryUsage",
|
||||
"ResourceChat",
|
||||
"ResourceChatAutomation",
|
||||
"ResourceConnectionLog",
|
||||
"ResourceCryptoKey",
|
||||
"ResourceDebugInfo",
|
||||
|
||||
Generated
+12
@@ -12015,6 +12015,11 @@
|
||||
"chat:delete",
|
||||
"chat:read",
|
||||
"chat:update",
|
||||
"chat_automation:*",
|
||||
"chat_automation:create",
|
||||
"chat_automation:delete",
|
||||
"chat_automation:read",
|
||||
"chat_automation:update",
|
||||
"coder:all",
|
||||
"coder:apikeys.manage_self",
|
||||
"coder:application_connect",
|
||||
@@ -12224,6 +12229,11 @@
|
||||
"APIKeyScopeChatDelete",
|
||||
"APIKeyScopeChatRead",
|
||||
"APIKeyScopeChatUpdate",
|
||||
"APIKeyScopeChatAutomationAll",
|
||||
"APIKeyScopeChatAutomationCreate",
|
||||
"APIKeyScopeChatAutomationDelete",
|
||||
"APIKeyScopeChatAutomationRead",
|
||||
"APIKeyScopeChatAutomationUpdate",
|
||||
"APIKeyScopeCoderAll",
|
||||
"APIKeyScopeCoderApikeysManageSelf",
|
||||
"APIKeyScopeCoderApplicationConnect",
|
||||
@@ -17410,6 +17420,7 @@
|
||||
"audit_log",
|
||||
"boundary_usage",
|
||||
"chat",
|
||||
"chat_automation",
|
||||
"connection_log",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
@@ -17456,6 +17467,7 @@
|
||||
"ResourceAuditLog",
|
||||
"ResourceBoundaryUsage",
|
||||
"ResourceChat",
|
||||
"ResourceChatAutomation",
|
||||
"ResourceConnectionLog",
|
||||
"ResourceCryptoKey",
|
||||
"ResourceDebugInfo",
|
||||
|
||||
@@ -7,6 +7,11 @@ type CheckConstraint string
|
||||
// CheckConstraint enums.
|
||||
const (
|
||||
CheckAPIKeysAllowListNotEmpty CheckConstraint = "api_keys_allow_list_not_empty" // api_keys
|
||||
CheckChatAutomationEventsChatExclusivity CheckConstraint = "chat_automation_events_chat_exclusivity" // chat_automation_events
|
||||
CheckChatAutomationTriggersCronFields CheckConstraint = "chat_automation_triggers_cron_fields" // chat_automation_triggers
|
||||
CheckChatAutomationTriggersWebhookFields CheckConstraint = "chat_automation_triggers_webhook_fields" // chat_automation_triggers
|
||||
CheckChatAutomationsMaxChatCreatesPerHourCheck CheckConstraint = "chat_automations_max_chat_creates_per_hour_check" // chat_automations
|
||||
CheckChatAutomationsMaxMessagesPerHourCheck CheckConstraint = "chat_automations_max_messages_per_hour_check" // chat_automations
|
||||
CheckChatModelConfigsCompressionThresholdCheck CheckConstraint = "chat_model_configs_compression_threshold_check" // chat_model_configs
|
||||
CheckChatModelConfigsContextLimitCheck CheckConstraint = "chat_model_configs_context_limit_check" // chat_model_configs
|
||||
CheckChatProvidersProviderCheck CheckConstraint = "chat_providers_provider_check" // chat_providers
|
||||
|
||||
@@ -1694,6 +1694,13 @@ func (q *querier) CleanTailnetTunnels(ctx context.Context) error {
|
||||
return q.db.CleanTailnetTunnels(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) CleanupDeletedMCPServerIDsFromChatAutomations(ctx context.Context) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceChatAutomation); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.CleanupDeletedMCPServerIDsFromChatAutomations(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) CleanupDeletedMCPServerIDsFromChats(ctx context.Context) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceChat); err != nil {
|
||||
return err
|
||||
@@ -1731,6 +1738,28 @@ func (q *querier) CountAuditLogs(ctx context.Context, arg database.CountAuditLog
|
||||
return q.db.CountAuthorizedAuditLogs(ctx, arg, prep)
|
||||
}
|
||||
|
||||
func (q *querier) CountChatAutomationChatCreatesInWindow(ctx context.Context, arg database.CountChatAutomationChatCreatesInWindowParams) (int64, error) {
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, arg.AutomationID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, automation); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.CountChatAutomationChatCreatesInWindow(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) CountChatAutomationMessagesInWindow(ctx context.Context, arg database.CountChatAutomationMessagesInWindowParams) (int64, error) {
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, arg.AutomationID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, automation); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.CountChatAutomationMessagesInWindow(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) CountConnectionLogs(ctx context.Context, arg database.CountConnectionLogsParams) (int64, error) {
|
||||
// Just like the actual query, shortcut if the user is an owner.
|
||||
err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceConnectionLog)
|
||||
@@ -1842,6 +1871,28 @@ func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, u
|
||||
return q.db.DeleteApplicationConnectAPIKeysByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteChatAutomationByID(ctx context.Context, id uuid.UUID) error {
|
||||
return deleteQ(q.log, q.auth, q.db.GetChatAutomationByID, q.db.DeleteChatAutomationByID)(ctx, id)
|
||||
}
|
||||
|
||||
// Triggers are sub-resources of an automation. Deleting a trigger
|
||||
// is a configuration change, so we authorize ActionUpdate on the
|
||||
// parent rather than ActionDelete.
|
||||
func (q *querier) DeleteChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) error {
|
||||
trigger, err := q.db.GetChatAutomationTriggerByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, trigger.AutomationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, automation); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteChatAutomationTriggerByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return err
|
||||
@@ -2386,6 +2437,16 @@ func (q *querier) GetActiveAISeatCount(ctx context.Context) (int64, error) {
|
||||
return q.db.GetActiveAISeatCount(ctx)
|
||||
}
|
||||
|
||||
// GetActiveChatAutomationCronTriggers is a system-level query used by
|
||||
// the cron scheduler. It requires read permission on all automations
|
||||
// (admin gate) because it fetches triggers across all orgs and owners.
|
||||
func (q *querier) GetActiveChatAutomationCronTriggers(ctx context.Context) ([]database.GetActiveChatAutomationCronTriggersRow, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChatAutomation.All()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetActiveChatAutomationCronTriggers(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceTemplate.All()); err != nil {
|
||||
return nil, err
|
||||
@@ -2477,6 +2538,64 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
|
||||
return q.db.GetAuthorizationUserRoles(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatAutomationByID(ctx context.Context, id uuid.UUID) (database.ChatAutomation, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetChatAutomationByID)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatAutomationEventsByAutomationID(ctx context.Context, arg database.GetChatAutomationEventsByAutomationIDParams) ([]database.ChatAutomationEvent, error) {
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, arg.AutomationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, automation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetChatAutomationEventsByAutomationID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) (database.ChatAutomationTrigger, error) {
|
||||
trigger, err := q.db.GetChatAutomationTriggerByID(ctx, id)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, trigger.AutomationID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, automation); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return trigger, nil
|
||||
}
|
||||
|
||||
func (q *querier) GetChatAutomationTriggersByAutomationID(ctx context.Context, automationID uuid.UUID) ([]database.ChatAutomationTrigger, error) {
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, automationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, automation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetChatAutomationTriggersByAutomationID(ctx, automationID)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatAutomations(ctx context.Context, arg database.GetChatAutomationsParams) ([]database.ChatAutomation, error) {
|
||||
// Shortcut if the caller has broad read access (e.g. site admins
|
||||
// / owners). The SQL filter is noticeable, so skip it when we
|
||||
// can.
|
||||
err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChatAutomation.All())
|
||||
if err == nil {
|
||||
return q.db.GetChatAutomations(ctx, arg)
|
||||
}
|
||||
|
||||
// Fall back to SQL-level row filtering for normal users.
|
||||
prep, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceChatAutomation.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("prepare chat automation SQL filter: %w", err)
|
||||
}
|
||||
return q.db.GetAuthorizedChatAutomations(ctx, arg, prep)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetChatByID)(ctx, id)
|
||||
}
|
||||
@@ -4772,6 +4891,36 @@ func (q *querier) InsertChat(ctx context.Context, arg database.InsertChatParams)
|
||||
return insert(q.log, q.auth, rbac.ResourceChat.WithOwner(arg.OwnerID.String()), q.db.InsertChat)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertChatAutomation(ctx context.Context, arg database.InsertChatAutomationParams) (database.ChatAutomation, error) {
|
||||
return insert(q.log, q.auth, rbac.ResourceChatAutomation.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID), q.db.InsertChatAutomation)(ctx, arg)
|
||||
}
|
||||
|
||||
// Events are append-only records produced by the system when
|
||||
// triggers fire. We authorize ActionUpdate on the parent
|
||||
// automation because inserting an event is a side-effect of
|
||||
// processing the automation, not an independent create action.
|
||||
func (q *querier) InsertChatAutomationEvent(ctx context.Context, arg database.InsertChatAutomationEventParams) (database.ChatAutomationEvent, error) {
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, arg.AutomationID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationEvent{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, automation); err != nil {
|
||||
return database.ChatAutomationEvent{}, err
|
||||
}
|
||||
return q.db.InsertChatAutomationEvent(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertChatAutomationTrigger(ctx context.Context, arg database.InsertChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, arg.AutomationID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, automation); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return q.db.InsertChatAutomationTrigger(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertChatFile(ctx context.Context, arg database.InsertChatFileParams) (database.InsertChatFileRow, error) {
|
||||
// Authorize create on chat resource scoped to the owner and org.
|
||||
return insert(q.log, q.auth, rbac.ResourceChat.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID), q.db.InsertChatFile)(ctx, arg)
|
||||
@@ -5569,6 +5718,13 @@ func (q *querier) PopNextQueuedMessage(ctx context.Context, chatID uuid.UUID) (d
|
||||
return q.db.PopNextQueuedMessage(ctx, chatID)
|
||||
}
|
||||
|
||||
func (q *querier) PurgeOldChatAutomationEvents(ctx context.Context, arg database.PurgeOldChatAutomationEventsParams) (int64, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceChatAutomation.All()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return q.db.PurgeOldChatAutomationEvents(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
|
||||
template, err := q.db.GetTemplateByID(ctx, templateID)
|
||||
if err != nil {
|
||||
@@ -5715,6 +5871,58 @@ func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKe
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateAPIKeyByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatAutomation(ctx context.Context, arg database.UpdateChatAutomationParams) (database.ChatAutomation, error) {
|
||||
fetchFunc := func(ctx context.Context, arg database.UpdateChatAutomationParams) (database.ChatAutomation, error) {
|
||||
return q.db.GetChatAutomationByID(ctx, arg.ID)
|
||||
}
|
||||
return updateWithReturn(q.log, q.auth, fetchFunc, q.db.UpdateChatAutomation)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatAutomationTrigger(ctx context.Context, arg database.UpdateChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
trigger, err := q.db.GetChatAutomationTriggerByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, trigger.AutomationID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, automation); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return q.db.UpdateChatAutomationTrigger(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatAutomationTriggerLastTriggeredAt(ctx context.Context, arg database.UpdateChatAutomationTriggerLastTriggeredAtParams) error {
|
||||
trigger, err := q.db.GetChatAutomationTriggerByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, trigger.AutomationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, automation); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpdateChatAutomationTriggerLastTriggeredAt(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatAutomationTriggerWebhookSecret(ctx context.Context, arg database.UpdateChatAutomationTriggerWebhookSecretParams) (database.ChatAutomationTrigger, error) {
|
||||
trigger, err := q.db.GetChatAutomationTriggerByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
automation, err := q.db.GetChatAutomationByID(ctx, trigger.AutomationID)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, automation); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return q.db.UpdateChatAutomationTriggerWebhookSecret(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatBuildAgentBinding(ctx context.Context, arg database.UpdateChatBuildAgentBindingParams) (database.Chat, error) {
|
||||
chat, err := q.db.GetChatByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
@@ -7352,3 +7560,7 @@ func (q *querier) ListAuthorizedAIBridgeSessionThreads(ctx context.Context, arg
|
||||
func (q *querier) GetAuthorizedChats(ctx context.Context, arg database.GetChatsParams, _ rbac.PreparedAuthorized) ([]database.GetChatsRow, error) {
|
||||
return q.GetChats(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetAuthorizedChatAutomations(ctx context.Context, arg database.GetChatAutomationsParams, _ rbac.PreparedAuthorized) ([]database.ChatAutomation, error) {
|
||||
return q.GetChatAutomations(ctx, arg)
|
||||
}
|
||||
|
||||
@@ -1121,6 +1121,10 @@ func (s *MethodTestSuite) TestChats() {
|
||||
dbm.EXPECT().CleanupDeletedMCPServerIDsFromChats(gomock.Any()).Return(nil).AnyTimes()
|
||||
check.Args().Asserts(rbac.ResourceChat, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("CleanupDeletedMCPServerIDsFromChatAutomations", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
dbm.EXPECT().CleanupDeletedMCPServerIDsFromChatAutomations(gomock.Any()).Return(nil).AnyTimes()
|
||||
check.Args().Asserts(rbac.ResourceChatAutomation, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("DeleteMCPServerConfigByID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
id := uuid.New()
|
||||
dbm.EXPECT().DeleteMCPServerConfigByID(gomock.Any(), id).Return(nil).AnyTimes()
|
||||
@@ -1250,6 +1254,226 @@ func (s *MethodTestSuite) TestChats() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestChatAutomations() {
|
||||
s.Run("CountChatAutomationChatCreatesInWindow", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
arg := database.CountChatAutomationChatCreatesInWindowParams{
|
||||
AutomationID: automation.ID,
|
||||
WindowStart: dbtime.Now().Add(-time.Hour),
|
||||
}
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().CountChatAutomationChatCreatesInWindow(gomock.Any(), arg).Return(int64(3), nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionRead).Returns(int64(3))
|
||||
}))
|
||||
s.Run("CountChatAutomationMessagesInWindow", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
arg := database.CountChatAutomationMessagesInWindowParams{
|
||||
AutomationID: automation.ID,
|
||||
WindowStart: dbtime.Now().Add(-time.Hour),
|
||||
}
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().CountChatAutomationMessagesInWindow(gomock.Any(), arg).Return(int64(5), nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionRead).Returns(int64(5))
|
||||
}))
|
||||
s.Run("DeleteChatAutomationByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().DeleteChatAutomationByID(gomock.Any(), automation.ID).Return(nil).AnyTimes()
|
||||
check.Args(automation.ID).Asserts(automation, policy.ActionDelete).Returns()
|
||||
}))
|
||||
s.Run("DeleteChatAutomationTriggerByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
trigger := testutil.Fake(s.T(), faker, database.ChatAutomationTrigger{
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeWebhook,
|
||||
})
|
||||
dbm.EXPECT().GetChatAutomationTriggerByID(gomock.Any(), trigger.ID).Return(trigger, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().DeleteChatAutomationTriggerByID(gomock.Any(), trigger.ID).Return(nil).AnyTimes()
|
||||
check.Args(trigger.ID).Asserts(automation, policy.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("GetActiveChatAutomationCronTriggers", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
rows := []database.GetActiveChatAutomationCronTriggersRow{}
|
||||
dbm.EXPECT().GetActiveChatAutomationCronTriggers(gomock.Any()).Return(rows, nil).AnyTimes()
|
||||
check.Args().Asserts(rbac.ResourceChatAutomation.All(), policy.ActionRead).Returns(rows)
|
||||
}))
|
||||
s.Run("GetChatAutomationByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
check.Args(automation.ID).Asserts(automation, policy.ActionRead).Returns(automation)
|
||||
}))
|
||||
s.Run("GetChatAutomationEventsByAutomationID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
arg := database.GetChatAutomationEventsByAutomationIDParams{
|
||||
AutomationID: automation.ID,
|
||||
}
|
||||
events := []database.ChatAutomationEvent{}
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationEventsByAutomationID(gomock.Any(), arg).Return(events, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionRead).Returns(events)
|
||||
}))
|
||||
s.Run("GetChatAutomationTriggerByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
trigger := testutil.Fake(s.T(), faker, database.ChatAutomationTrigger{
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeWebhook,
|
||||
})
|
||||
dbm.EXPECT().GetChatAutomationTriggerByID(gomock.Any(), trigger.ID).Return(trigger, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
check.Args(trigger.ID).Asserts(automation, policy.ActionRead).Returns(trigger)
|
||||
}))
|
||||
s.Run("GetChatAutomationTriggersByAutomationID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
triggers := []database.ChatAutomationTrigger{}
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationTriggersByAutomationID(gomock.Any(), automation.ID).Return(triggers, nil).AnyTimes()
|
||||
check.Args(automation.ID).Asserts(automation, policy.ActionRead).Returns(triggers)
|
||||
}))
|
||||
s.Run("GetChatAutomations", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
params := database.GetChatAutomationsParams{}
|
||||
dbm.EXPECT().GetChatAutomations(gomock.Any(), params).Return([]database.ChatAutomation{}, nil).AnyTimes()
|
||||
dbm.EXPECT().GetAuthorizedChatAutomations(gomock.Any(), params, gomock.Any()).Return([]database.ChatAutomation{}, nil).AnyTimes()
|
||||
check.Args(params).Asserts(rbac.ResourceChatAutomation.All(), policy.ActionRead).WithNotAuthorized("nil")
|
||||
}))
|
||||
s.Run("GetAuthorizedChatAutomations", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
params := database.GetChatAutomationsParams{}
|
||||
dbm.EXPECT().GetAuthorizedChatAutomations(gomock.Any(), params, gomock.Any()).Return([]database.ChatAutomation{}, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomations(gomock.Any(), params).Return([]database.ChatAutomation{}, nil).AnyTimes()
|
||||
check.Args(params, emptyPreparedAuthorized{}).Asserts(rbac.ResourceChatAutomation.All(), policy.ActionRead)
|
||||
}))
|
||||
s.Run("InsertChatAutomation", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
arg := database.InsertChatAutomationParams{
|
||||
ID: uuid.New(),
|
||||
OwnerID: uuid.New(),
|
||||
OrganizationID: uuid.New(),
|
||||
Name: "test-automation",
|
||||
Description: "test description",
|
||||
Instructions: "test instructions",
|
||||
Status: database.ChatAutomationStatusActive,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{
|
||||
ID: arg.ID,
|
||||
OwnerID: arg.OwnerID,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
Status: arg.Status,
|
||||
})
|
||||
dbm.EXPECT().InsertChatAutomation(gomock.Any(), arg).Return(automation, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceChatAutomation.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID), policy.ActionCreate).Returns(automation)
|
||||
}))
|
||||
s.Run("InsertChatAutomationEvent", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
arg := database.InsertChatAutomationEventParams{
|
||||
ID: uuid.New(),
|
||||
AutomationID: automation.ID,
|
||||
ReceivedAt: dbtime.Now(),
|
||||
Payload: json.RawMessage(`{}`),
|
||||
Status: database.ChatAutomationEventStatusFiltered,
|
||||
}
|
||||
event := testutil.Fake(s.T(), faker, database.ChatAutomationEvent{
|
||||
ID: arg.ID,
|
||||
AutomationID: automation.ID,
|
||||
Status: arg.Status,
|
||||
})
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().InsertChatAutomationEvent(gomock.Any(), arg).Return(event, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionUpdate).Returns(event)
|
||||
}))
|
||||
s.Run("InsertChatAutomationTrigger", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
arg := database.InsertChatAutomationTriggerParams{
|
||||
ID: uuid.New(),
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeWebhook,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
trigger := testutil.Fake(s.T(), faker, database.ChatAutomationTrigger{
|
||||
ID: arg.ID,
|
||||
AutomationID: automation.ID,
|
||||
Type: arg.Type,
|
||||
})
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().InsertChatAutomationTrigger(gomock.Any(), arg).Return(trigger, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionUpdate).Returns(trigger)
|
||||
}))
|
||||
s.Run("PurgeOldChatAutomationEvents", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
arg := database.PurgeOldChatAutomationEventsParams{
|
||||
Before: dbtime.Now().Add(-7 * 24 * time.Hour),
|
||||
LimitCount: 1000,
|
||||
}
|
||||
dbm.EXPECT().PurgeOldChatAutomationEvents(gomock.Any(), arg).Return(int64(5), nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceChatAutomation.All(), policy.ActionDelete).Returns(int64(5))
|
||||
}))
|
||||
s.Run("UpdateChatAutomation", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
arg := database.UpdateChatAutomationParams{
|
||||
ID: automation.ID,
|
||||
Name: "updated-name",
|
||||
Description: "updated description",
|
||||
Status: database.ChatAutomationStatusActive,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
updated := automation
|
||||
updated.Name = arg.Name
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().UpdateChatAutomation(gomock.Any(), arg).Return(updated, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionUpdate).Returns(updated)
|
||||
}))
|
||||
s.Run("UpdateChatAutomationTrigger", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
trigger := testutil.Fake(s.T(), faker, database.ChatAutomationTrigger{
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeCron,
|
||||
})
|
||||
arg := database.UpdateChatAutomationTriggerParams{
|
||||
ID: trigger.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
updated := trigger
|
||||
dbm.EXPECT().GetChatAutomationTriggerByID(gomock.Any(), trigger.ID).Return(trigger, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().UpdateChatAutomationTrigger(gomock.Any(), arg).Return(updated, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionUpdate).Returns(updated)
|
||||
}))
|
||||
s.Run("UpdateChatAutomationTriggerLastTriggeredAt", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
trigger := testutil.Fake(s.T(), faker, database.ChatAutomationTrigger{
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeCron,
|
||||
})
|
||||
arg := database.UpdateChatAutomationTriggerLastTriggeredAtParams{
|
||||
ID: trigger.ID,
|
||||
LastTriggeredAt: dbtime.Now(),
|
||||
}
|
||||
dbm.EXPECT().GetChatAutomationTriggerByID(gomock.Any(), trigger.ID).Return(trigger, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().UpdateChatAutomationTriggerLastTriggeredAt(gomock.Any(), arg).Return(nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("UpdateChatAutomationTriggerWebhookSecret", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
automation := testutil.Fake(s.T(), faker, database.ChatAutomation{Status: database.ChatAutomationStatusActive})
|
||||
trigger := testutil.Fake(s.T(), faker, database.ChatAutomationTrigger{
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeWebhook,
|
||||
})
|
||||
arg := database.UpdateChatAutomationTriggerWebhookSecretParams{
|
||||
ID: trigger.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
WebhookSecret: sql.NullString{
|
||||
String: "new-secret",
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
updated := trigger
|
||||
dbm.EXPECT().GetChatAutomationTriggerByID(gomock.Any(), trigger.ID).Return(trigger, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatAutomationByID(gomock.Any(), automation.ID).Return(automation, nil).AnyTimes()
|
||||
dbm.EXPECT().UpdateChatAutomationTriggerWebhookSecret(gomock.Any(), arg).Return(updated, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(automation, policy.ActionUpdate).Returns(updated)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestFile() {
|
||||
s.Run("GetFileByHashAndCreator", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
f := testutil.Fake(s.T(), faker, database.File{})
|
||||
|
||||
@@ -264,6 +264,14 @@ func (m queryMetricsStore) CleanTailnetTunnels(ctx context.Context) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) CleanupDeletedMCPServerIDsFromChatAutomations(ctx context.Context) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.CleanupDeletedMCPServerIDsFromChatAutomations(ctx)
|
||||
m.queryLatencies.WithLabelValues("CleanupDeletedMCPServerIDsFromChatAutomations").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "CleanupDeletedMCPServerIDsFromChatAutomations").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) CleanupDeletedMCPServerIDsFromChats(ctx context.Context) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.CleanupDeletedMCPServerIDsFromChats(ctx)
|
||||
@@ -296,6 +304,22 @@ func (m queryMetricsStore) CountAuditLogs(ctx context.Context, arg database.Coun
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) CountChatAutomationChatCreatesInWindow(ctx context.Context, arg database.CountChatAutomationChatCreatesInWindowParams) (int64, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.CountChatAutomationChatCreatesInWindow(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("CountChatAutomationChatCreatesInWindow").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "CountChatAutomationChatCreatesInWindow").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) CountChatAutomationMessagesInWindow(ctx context.Context, arg database.CountChatAutomationMessagesInWindowParams) (int64, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.CountChatAutomationMessagesInWindow(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("CountChatAutomationMessagesInWindow").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "CountChatAutomationMessagesInWindow").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) CountConnectionLogs(ctx context.Context, arg database.CountConnectionLogsParams) (int64, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.CountConnectionLogs(ctx, arg)
|
||||
@@ -400,6 +424,22 @@ func (m queryMetricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.C
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteChatAutomationByID(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteChatAutomationByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("DeleteChatAutomationByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteChatAutomationByID").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteChatAutomationTriggerByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("DeleteChatAutomationTriggerByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteChatAutomationTriggerByID").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteChatModelConfigByID(ctx, id)
|
||||
@@ -936,6 +976,14 @@ func (m queryMetricsStore) GetActiveAISeatCount(ctx context.Context) (int64, err
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetActiveChatAutomationCronTriggers(ctx context.Context) ([]database.GetActiveChatAutomationCronTriggersRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetActiveChatAutomationCronTriggers(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetActiveChatAutomationCronTriggers").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetActiveChatAutomationCronTriggers").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetActivePresetPrebuildSchedules(ctx)
|
||||
@@ -1032,6 +1080,46 @@ func (m queryMetricsStore) GetAuthorizationUserRoles(ctx context.Context, userID
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatAutomationByID(ctx context.Context, id uuid.UUID) (database.ChatAutomation, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatAutomationByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("GetChatAutomationByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatAutomationByID").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatAutomationEventsByAutomationID(ctx context.Context, arg database.GetChatAutomationEventsByAutomationIDParams) ([]database.ChatAutomationEvent, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatAutomationEventsByAutomationID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetChatAutomationEventsByAutomationID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatAutomationEventsByAutomationID").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) (database.ChatAutomationTrigger, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatAutomationTriggerByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("GetChatAutomationTriggerByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatAutomationTriggerByID").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatAutomationTriggersByAutomationID(ctx context.Context, automationID uuid.UUID) ([]database.ChatAutomationTrigger, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatAutomationTriggersByAutomationID(ctx, automationID)
|
||||
m.queryLatencies.WithLabelValues("GetChatAutomationTriggersByAutomationID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatAutomationTriggersByAutomationID").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatAutomations(ctx context.Context, arg database.GetChatAutomationsParams) ([]database.ChatAutomation, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatAutomations(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetChatAutomations").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatAutomations").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatByID(ctx, id)
|
||||
@@ -3224,6 +3312,30 @@ func (m queryMetricsStore) InsertChat(ctx context.Context, arg database.InsertCh
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertChatAutomation(ctx context.Context, arg database.InsertChatAutomationParams) (database.ChatAutomation, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertChatAutomation(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertChatAutomation").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertChatAutomation").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertChatAutomationEvent(ctx context.Context, arg database.InsertChatAutomationEventParams) (database.ChatAutomationEvent, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertChatAutomationEvent(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertChatAutomationEvent").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertChatAutomationEvent").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertChatAutomationTrigger(ctx context.Context, arg database.InsertChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertChatAutomationTrigger(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertChatAutomationTrigger").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertChatAutomationTrigger").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertChatFile(ctx context.Context, arg database.InsertChatFileParams) (database.InsertChatFileRow, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertChatFile(ctx, arg)
|
||||
@@ -3952,6 +4064,14 @@ func (m queryMetricsStore) PopNextQueuedMessage(ctx context.Context, chatID uuid
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) PurgeOldChatAutomationEvents(ctx context.Context, arg database.PurgeOldChatAutomationEventsParams) (int64, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.PurgeOldChatAutomationEvents(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("PurgeOldChatAutomationEvents").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "PurgeOldChatAutomationEvents").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx, templateID)
|
||||
@@ -4080,6 +4200,38 @@ func (m queryMetricsStore) UpdateAPIKeyByID(ctx context.Context, arg database.Up
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatAutomation(ctx context.Context, arg database.UpdateChatAutomationParams) (database.ChatAutomation, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateChatAutomation(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateChatAutomation").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatAutomation").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatAutomationTrigger(ctx context.Context, arg database.UpdateChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateChatAutomationTrigger(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateChatAutomationTrigger").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatAutomationTrigger").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatAutomationTriggerLastTriggeredAt(ctx context.Context, arg database.UpdateChatAutomationTriggerLastTriggeredAtParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateChatAutomationTriggerLastTriggeredAt(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateChatAutomationTriggerLastTriggeredAt").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatAutomationTriggerLastTriggeredAt").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatAutomationTriggerWebhookSecret(ctx context.Context, arg database.UpdateChatAutomationTriggerWebhookSecretParams) (database.ChatAutomationTrigger, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateChatAutomationTriggerWebhookSecret(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateChatAutomationTriggerWebhookSecret").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatAutomationTriggerWebhookSecret").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatBuildAgentBinding(ctx context.Context, arg database.UpdateChatBuildAgentBindingParams) (database.Chat, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateChatBuildAgentBinding(ctx, arg)
|
||||
@@ -5351,3 +5503,11 @@ func (m queryMetricsStore) GetAuthorizedChats(ctx context.Context, arg database.
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetAuthorizedChats").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetAuthorizedChatAutomations(ctx context.Context, arg database.GetChatAutomationsParams, prepared rbac.PreparedAuthorized) ([]database.ChatAutomation, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetAuthorizedChatAutomations(ctx, arg, prepared)
|
||||
m.queryLatencies.WithLabelValues("GetAuthorizedChatAutomations").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetAuthorizedChatAutomations").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
@@ -335,6 +335,20 @@ func (mr *MockStoreMockRecorder) CleanTailnetTunnels(ctx any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanTailnetTunnels", reflect.TypeOf((*MockStore)(nil).CleanTailnetTunnels), ctx)
|
||||
}
|
||||
|
||||
// CleanupDeletedMCPServerIDsFromChatAutomations mocks base method.
|
||||
func (m *MockStore) CleanupDeletedMCPServerIDsFromChatAutomations(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CleanupDeletedMCPServerIDsFromChatAutomations", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CleanupDeletedMCPServerIDsFromChatAutomations indicates an expected call of CleanupDeletedMCPServerIDsFromChatAutomations.
|
||||
func (mr *MockStoreMockRecorder) CleanupDeletedMCPServerIDsFromChatAutomations(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupDeletedMCPServerIDsFromChatAutomations", reflect.TypeOf((*MockStore)(nil).CleanupDeletedMCPServerIDsFromChatAutomations), ctx)
|
||||
}
|
||||
|
||||
// CleanupDeletedMCPServerIDsFromChats mocks base method.
|
||||
func (m *MockStore) CleanupDeletedMCPServerIDsFromChats(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -454,6 +468,36 @@ func (mr *MockStoreMockRecorder) CountAuthorizedConnectionLogs(ctx, arg, prepare
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountAuthorizedConnectionLogs", reflect.TypeOf((*MockStore)(nil).CountAuthorizedConnectionLogs), ctx, arg, prepared)
|
||||
}
|
||||
|
||||
// CountChatAutomationChatCreatesInWindow mocks base method.
|
||||
func (m *MockStore) CountChatAutomationChatCreatesInWindow(ctx context.Context, arg database.CountChatAutomationChatCreatesInWindowParams) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CountChatAutomationChatCreatesInWindow", ctx, arg)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CountChatAutomationChatCreatesInWindow indicates an expected call of CountChatAutomationChatCreatesInWindow.
|
||||
func (mr *MockStoreMockRecorder) CountChatAutomationChatCreatesInWindow(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountChatAutomationChatCreatesInWindow", reflect.TypeOf((*MockStore)(nil).CountChatAutomationChatCreatesInWindow), ctx, arg)
|
||||
}
|
||||
|
||||
// CountChatAutomationMessagesInWindow mocks base method.
|
||||
func (m *MockStore) CountChatAutomationMessagesInWindow(ctx context.Context, arg database.CountChatAutomationMessagesInWindowParams) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CountChatAutomationMessagesInWindow", ctx, arg)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CountChatAutomationMessagesInWindow indicates an expected call of CountChatAutomationMessagesInWindow.
|
||||
func (mr *MockStoreMockRecorder) CountChatAutomationMessagesInWindow(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountChatAutomationMessagesInWindow", reflect.TypeOf((*MockStore)(nil).CountChatAutomationMessagesInWindow), ctx, arg)
|
||||
}
|
||||
|
||||
// CountConnectionLogs mocks base method.
|
||||
func (m *MockStore) CountConnectionLogs(ctx context.Context, arg database.CountConnectionLogsParams) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -643,6 +687,34 @@ func (mr *MockStoreMockRecorder) DeleteApplicationConnectAPIKeysByUserID(ctx, us
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationConnectAPIKeysByUserID", reflect.TypeOf((*MockStore)(nil).DeleteApplicationConnectAPIKeysByUserID), ctx, userID)
|
||||
}
|
||||
|
||||
// DeleteChatAutomationByID mocks base method.
|
||||
func (m *MockStore) DeleteChatAutomationByID(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteChatAutomationByID", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteChatAutomationByID indicates an expected call of DeleteChatAutomationByID.
|
||||
func (mr *MockStoreMockRecorder) DeleteChatAutomationByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatAutomationByID", reflect.TypeOf((*MockStore)(nil).DeleteChatAutomationByID), ctx, id)
|
||||
}
|
||||
|
||||
// DeleteChatAutomationTriggerByID mocks base method.
|
||||
func (m *MockStore) DeleteChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteChatAutomationTriggerByID", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteChatAutomationTriggerByID indicates an expected call of DeleteChatAutomationTriggerByID.
|
||||
func (mr *MockStoreMockRecorder) DeleteChatAutomationTriggerByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatAutomationTriggerByID", reflect.TypeOf((*MockStore)(nil).DeleteChatAutomationTriggerByID), ctx, id)
|
||||
}
|
||||
|
||||
// DeleteChatModelConfigByID mocks base method.
|
||||
func (m *MockStore) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1609,6 +1681,21 @@ func (mr *MockStoreMockRecorder) GetActiveAISeatCount(ctx any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveAISeatCount", reflect.TypeOf((*MockStore)(nil).GetActiveAISeatCount), ctx)
|
||||
}
|
||||
|
||||
// GetActiveChatAutomationCronTriggers mocks base method.
|
||||
func (m *MockStore) GetActiveChatAutomationCronTriggers(ctx context.Context) ([]database.GetActiveChatAutomationCronTriggersRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetActiveChatAutomationCronTriggers", ctx)
|
||||
ret0, _ := ret[0].([]database.GetActiveChatAutomationCronTriggersRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetActiveChatAutomationCronTriggers indicates an expected call of GetActiveChatAutomationCronTriggers.
|
||||
func (mr *MockStoreMockRecorder) GetActiveChatAutomationCronTriggers(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActiveChatAutomationCronTriggers", reflect.TypeOf((*MockStore)(nil).GetActiveChatAutomationCronTriggers), ctx)
|
||||
}
|
||||
|
||||
// GetActivePresetPrebuildSchedules mocks base method.
|
||||
func (m *MockStore) GetActivePresetPrebuildSchedules(ctx context.Context) ([]database.TemplateVersionPresetPrebuildSchedule, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1804,6 +1891,21 @@ func (mr *MockStoreMockRecorder) GetAuthorizedAuditLogsOffset(ctx, arg, prepared
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedAuditLogsOffset", reflect.TypeOf((*MockStore)(nil).GetAuthorizedAuditLogsOffset), ctx, arg, prepared)
|
||||
}
|
||||
|
||||
// GetAuthorizedChatAutomations mocks base method.
|
||||
func (m *MockStore) GetAuthorizedChatAutomations(ctx context.Context, arg database.GetChatAutomationsParams, prepared rbac.PreparedAuthorized) ([]database.ChatAutomation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAuthorizedChatAutomations", ctx, arg, prepared)
|
||||
ret0, _ := ret[0].([]database.ChatAutomation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAuthorizedChatAutomations indicates an expected call of GetAuthorizedChatAutomations.
|
||||
func (mr *MockStoreMockRecorder) GetAuthorizedChatAutomations(ctx, arg, prepared any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedChatAutomations", reflect.TypeOf((*MockStore)(nil).GetAuthorizedChatAutomations), ctx, arg, prepared)
|
||||
}
|
||||
|
||||
// GetAuthorizedChats mocks base method.
|
||||
func (m *MockStore) GetAuthorizedChats(ctx context.Context, arg database.GetChatsParams, prepared rbac.PreparedAuthorized) ([]database.GetChatsRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1894,6 +1996,81 @@ func (mr *MockStoreMockRecorder) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx,
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedWorkspacesAndAgentsByOwnerID", reflect.TypeOf((*MockStore)(nil).GetAuthorizedWorkspacesAndAgentsByOwnerID), ctx, ownerID, prepared)
|
||||
}
|
||||
|
||||
// GetChatAutomationByID mocks base method.
|
||||
func (m *MockStore) GetChatAutomationByID(ctx context.Context, id uuid.UUID) (database.ChatAutomation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatAutomationByID", ctx, id)
|
||||
ret0, _ := ret[0].(database.ChatAutomation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatAutomationByID indicates an expected call of GetChatAutomationByID.
|
||||
func (mr *MockStoreMockRecorder) GetChatAutomationByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatAutomationByID", reflect.TypeOf((*MockStore)(nil).GetChatAutomationByID), ctx, id)
|
||||
}
|
||||
|
||||
// GetChatAutomationEventsByAutomationID mocks base method.
|
||||
func (m *MockStore) GetChatAutomationEventsByAutomationID(ctx context.Context, arg database.GetChatAutomationEventsByAutomationIDParams) ([]database.ChatAutomationEvent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatAutomationEventsByAutomationID", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.ChatAutomationEvent)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatAutomationEventsByAutomationID indicates an expected call of GetChatAutomationEventsByAutomationID.
|
||||
func (mr *MockStoreMockRecorder) GetChatAutomationEventsByAutomationID(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatAutomationEventsByAutomationID", reflect.TypeOf((*MockStore)(nil).GetChatAutomationEventsByAutomationID), ctx, arg)
|
||||
}
|
||||
|
||||
// GetChatAutomationTriggerByID mocks base method.
|
||||
func (m *MockStore) GetChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) (database.ChatAutomationTrigger, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatAutomationTriggerByID", ctx, id)
|
||||
ret0, _ := ret[0].(database.ChatAutomationTrigger)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatAutomationTriggerByID indicates an expected call of GetChatAutomationTriggerByID.
|
||||
func (mr *MockStoreMockRecorder) GetChatAutomationTriggerByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatAutomationTriggerByID", reflect.TypeOf((*MockStore)(nil).GetChatAutomationTriggerByID), ctx, id)
|
||||
}
|
||||
|
||||
// GetChatAutomationTriggersByAutomationID mocks base method.
|
||||
func (m *MockStore) GetChatAutomationTriggersByAutomationID(ctx context.Context, automationID uuid.UUID) ([]database.ChatAutomationTrigger, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatAutomationTriggersByAutomationID", ctx, automationID)
|
||||
ret0, _ := ret[0].([]database.ChatAutomationTrigger)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatAutomationTriggersByAutomationID indicates an expected call of GetChatAutomationTriggersByAutomationID.
|
||||
func (mr *MockStoreMockRecorder) GetChatAutomationTriggersByAutomationID(ctx, automationID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatAutomationTriggersByAutomationID", reflect.TypeOf((*MockStore)(nil).GetChatAutomationTriggersByAutomationID), ctx, automationID)
|
||||
}
|
||||
|
||||
// GetChatAutomations mocks base method.
|
||||
func (m *MockStore) GetChatAutomations(ctx context.Context, arg database.GetChatAutomationsParams) ([]database.ChatAutomation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatAutomations", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.ChatAutomation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatAutomations indicates an expected call of GetChatAutomations.
|
||||
func (mr *MockStoreMockRecorder) GetChatAutomations(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatAutomations", reflect.TypeOf((*MockStore)(nil).GetChatAutomations), ctx, arg)
|
||||
}
|
||||
|
||||
// GetChatByID mocks base method.
|
||||
func (m *MockStore) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -6048,6 +6225,51 @@ func (mr *MockStoreMockRecorder) InsertChat(ctx, arg any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChat", reflect.TypeOf((*MockStore)(nil).InsertChat), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertChatAutomation mocks base method.
|
||||
func (m *MockStore) InsertChatAutomation(ctx context.Context, arg database.InsertChatAutomationParams) (database.ChatAutomation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertChatAutomation", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatAutomation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertChatAutomation indicates an expected call of InsertChatAutomation.
|
||||
func (mr *MockStoreMockRecorder) InsertChatAutomation(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChatAutomation", reflect.TypeOf((*MockStore)(nil).InsertChatAutomation), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertChatAutomationEvent mocks base method.
|
||||
func (m *MockStore) InsertChatAutomationEvent(ctx context.Context, arg database.InsertChatAutomationEventParams) (database.ChatAutomationEvent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertChatAutomationEvent", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatAutomationEvent)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertChatAutomationEvent indicates an expected call of InsertChatAutomationEvent.
|
||||
func (mr *MockStoreMockRecorder) InsertChatAutomationEvent(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChatAutomationEvent", reflect.TypeOf((*MockStore)(nil).InsertChatAutomationEvent), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertChatAutomationTrigger mocks base method.
|
||||
func (m *MockStore) InsertChatAutomationTrigger(ctx context.Context, arg database.InsertChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertChatAutomationTrigger", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatAutomationTrigger)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertChatAutomationTrigger indicates an expected call of InsertChatAutomationTrigger.
|
||||
func (mr *MockStoreMockRecorder) InsertChatAutomationTrigger(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChatAutomationTrigger", reflect.TypeOf((*MockStore)(nil).InsertChatAutomationTrigger), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertChatFile mocks base method.
|
||||
func (m *MockStore) InsertChatFile(ctx context.Context, arg database.InsertChatFileParams) (database.InsertChatFileRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -7501,6 +7723,21 @@ func (mr *MockStoreMockRecorder) PopNextQueuedMessage(ctx, chatID any) *gomock.C
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PopNextQueuedMessage", reflect.TypeOf((*MockStore)(nil).PopNextQueuedMessage), ctx, chatID)
|
||||
}
|
||||
|
||||
// PurgeOldChatAutomationEvents mocks base method.
|
||||
func (m *MockStore) PurgeOldChatAutomationEvents(ctx context.Context, arg database.PurgeOldChatAutomationEventsParams) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PurgeOldChatAutomationEvents", ctx, arg)
|
||||
ret0, _ := ret[0].(int64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PurgeOldChatAutomationEvents indicates an expected call of PurgeOldChatAutomationEvents.
|
||||
func (mr *MockStoreMockRecorder) PurgeOldChatAutomationEvents(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PurgeOldChatAutomationEvents", reflect.TypeOf((*MockStore)(nil).PurgeOldChatAutomationEvents), ctx, arg)
|
||||
}
|
||||
|
||||
// ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate mocks base method.
|
||||
func (m *MockStore) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -7732,6 +7969,65 @@ func (mr *MockStoreMockRecorder) UpdateAPIKeyByID(ctx, arg any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAPIKeyByID", reflect.TypeOf((*MockStore)(nil).UpdateAPIKeyByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatAutomation mocks base method.
|
||||
func (m *MockStore) UpdateChatAutomation(ctx context.Context, arg database.UpdateChatAutomationParams) (database.ChatAutomation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateChatAutomation", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatAutomation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateChatAutomation indicates an expected call of UpdateChatAutomation.
|
||||
func (mr *MockStoreMockRecorder) UpdateChatAutomation(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatAutomation", reflect.TypeOf((*MockStore)(nil).UpdateChatAutomation), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatAutomationTrigger mocks base method.
|
||||
func (m *MockStore) UpdateChatAutomationTrigger(ctx context.Context, arg database.UpdateChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateChatAutomationTrigger", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatAutomationTrigger)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateChatAutomationTrigger indicates an expected call of UpdateChatAutomationTrigger.
|
||||
func (mr *MockStoreMockRecorder) UpdateChatAutomationTrigger(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatAutomationTrigger", reflect.TypeOf((*MockStore)(nil).UpdateChatAutomationTrigger), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatAutomationTriggerLastTriggeredAt mocks base method.
|
||||
func (m *MockStore) UpdateChatAutomationTriggerLastTriggeredAt(ctx context.Context, arg database.UpdateChatAutomationTriggerLastTriggeredAtParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateChatAutomationTriggerLastTriggeredAt", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateChatAutomationTriggerLastTriggeredAt indicates an expected call of UpdateChatAutomationTriggerLastTriggeredAt.
|
||||
func (mr *MockStoreMockRecorder) UpdateChatAutomationTriggerLastTriggeredAt(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatAutomationTriggerLastTriggeredAt", reflect.TypeOf((*MockStore)(nil).UpdateChatAutomationTriggerLastTriggeredAt), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatAutomationTriggerWebhookSecret mocks base method.
|
||||
func (m *MockStore) UpdateChatAutomationTriggerWebhookSecret(ctx context.Context, arg database.UpdateChatAutomationTriggerWebhookSecretParams) (database.ChatAutomationTrigger, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateChatAutomationTriggerWebhookSecret", ctx, arg)
|
||||
ret0, _ := ret[0].(database.ChatAutomationTrigger)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateChatAutomationTriggerWebhookSecret indicates an expected call of UpdateChatAutomationTriggerWebhookSecret.
|
||||
func (mr *MockStoreMockRecorder) UpdateChatAutomationTriggerWebhookSecret(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatAutomationTriggerWebhookSecret", reflect.TypeOf((*MockStore)(nil).UpdateChatAutomationTriggerWebhookSecret), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatBuildAgentBinding mocks base method.
|
||||
func (m *MockStore) UpdateChatBuildAgentBinding(ctx context.Context, arg database.UpdateChatBuildAgentBindingParams) (database.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+187
-2
@@ -220,7 +220,12 @@ CREATE TYPE api_key_scope AS ENUM (
|
||||
'chat:read',
|
||||
'chat:update',
|
||||
'chat:delete',
|
||||
'chat:*'
|
||||
'chat:*',
|
||||
'chat_automation:create',
|
||||
'chat_automation:read',
|
||||
'chat_automation:update',
|
||||
'chat_automation:delete',
|
||||
'chat_automation:*'
|
||||
);
|
||||
|
||||
CREATE TYPE app_sharing_level AS ENUM (
|
||||
@@ -270,6 +275,32 @@ CREATE TYPE build_reason AS ENUM (
|
||||
'task_resume'
|
||||
);
|
||||
|
||||
CREATE TYPE chat_automation_event_status AS ENUM (
|
||||
'filtered',
|
||||
'preview',
|
||||
'created',
|
||||
'continued',
|
||||
'rate_limited',
|
||||
'error'
|
||||
);
|
||||
|
||||
COMMENT ON TYPE chat_automation_event_status IS 'Outcome of a chat automation event: filtered, preview, created, continued, rate_limited, or error.';
|
||||
|
||||
CREATE TYPE chat_automation_status AS ENUM (
|
||||
'disabled',
|
||||
'preview',
|
||||
'active'
|
||||
);
|
||||
|
||||
COMMENT ON TYPE chat_automation_status IS 'Lifecycle state of a chat automation: disabled, preview, or active.';
|
||||
|
||||
CREATE TYPE chat_automation_trigger_type AS ENUM (
|
||||
'webhook',
|
||||
'cron'
|
||||
);
|
||||
|
||||
COMMENT ON TYPE chat_automation_trigger_type IS 'Discriminator for chat automation triggers: webhook or cron.';
|
||||
|
||||
CREATE TYPE chat_message_role AS ENUM (
|
||||
'system',
|
||||
'user',
|
||||
@@ -1238,6 +1269,104 @@ COMMENT ON COLUMN boundary_usage_stats.window_start IS 'Start of the time window
|
||||
|
||||
COMMENT ON COLUMN boundary_usage_stats.updated_at IS 'Timestamp of the last update to this row.';
|
||||
|
||||
CREATE TABLE chat_automation_events (
|
||||
id uuid NOT NULL,
|
||||
automation_id uuid NOT NULL,
|
||||
trigger_id uuid,
|
||||
received_at timestamp with time zone NOT NULL,
|
||||
payload jsonb NOT NULL,
|
||||
filter_matched boolean NOT NULL,
|
||||
resolved_labels jsonb,
|
||||
matched_chat_id uuid,
|
||||
created_chat_id uuid,
|
||||
status chat_automation_event_status NOT NULL,
|
||||
error text,
|
||||
CONSTRAINT chat_automation_events_chat_exclusivity CHECK (((matched_chat_id IS NULL) OR (created_chat_id IS NULL)))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat_automation_events IS 'Every trigger invocation produces an event row regardless of outcome. This table is the audit trail and the data source for rate-limit window counts. Rows are append-only and expected to be purged by a background job after a retention period.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_events.payload IS 'The raw payload that was evaluated. For webhooks this is the HTTP body; for cron triggers it is a synthetic JSON envelope with schedule metadata.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_events.filter_matched IS 'Whether the trigger filter conditions matched. False means the event was dropped before any chat interaction.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_events.resolved_labels IS 'Labels resolved from the payload via label_paths. Stored so the event log shows exactly which labels were computed.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_events.matched_chat_id IS 'ID of an existing chat that was found via label matching and continued with a new message.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_events.created_chat_id IS 'ID of a newly created chat (mutually exclusive with matched_chat_id in practice).';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_events.status IS 'Outcome of the event: filtered — filter did not match; preview — automation is in preview mode; created — new chat was created; continued — existing chat was continued; rate_limited — rate limit prevented chat action; error — something went wrong.';
|
||||
|
||||
CREATE TABLE chat_automation_triggers (
|
||||
id uuid NOT NULL,
|
||||
automation_id uuid NOT NULL,
|
||||
type chat_automation_trigger_type NOT NULL,
|
||||
webhook_secret text,
|
||||
webhook_secret_key_id text,
|
||||
cron_schedule text,
|
||||
last_triggered_at timestamp with time zone,
|
||||
filter jsonb,
|
||||
label_paths jsonb,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
CONSTRAINT chat_automation_triggers_cron_fields CHECK (((type <> 'cron'::chat_automation_trigger_type) OR ((cron_schedule IS NOT NULL) AND (webhook_secret IS NULL) AND (webhook_secret_key_id IS NULL)))),
|
||||
CONSTRAINT chat_automation_triggers_webhook_fields CHECK (((type <> 'webhook'::chat_automation_trigger_type) OR ((webhook_secret IS NOT NULL) AND (cron_schedule IS NULL) AND (last_triggered_at IS NULL))))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat_automation_triggers IS 'Triggers define how an automation is invoked. Each automation can have multiple triggers (e.g. one webhook + one cron schedule). Webhook and cron triggers share the same row shape with type-specific nullable columns to keep the schema simple.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_triggers.type IS 'Discriminator: webhook or cron. Determines which nullable columns are meaningful.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_triggers.webhook_secret IS 'HMAC-SHA256 shared secret for webhook signature verification (X-Hub-Signature-256 header). NULL for cron triggers.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_triggers.cron_schedule IS 'Standard 5-field cron expression (minute hour dom month dow), with optional CRON_TZ= prefix. NULL for webhook triggers.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_triggers.last_triggered_at IS 'Timestamp of the last successful cron fire. The scheduler computes next = cron.Next(last_triggered_at) and fires when next <= now. NULL means the trigger has never fired. Not used for webhook triggers.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_triggers.filter IS 'gjson path-to-value filter conditions evaluated against the incoming webhook payload. All conditions must match for the trigger to fire. NULL or empty means match everything.';
|
||||
|
||||
COMMENT ON COLUMN chat_automation_triggers.label_paths IS 'Maps chat label keys to gjson paths. When a trigger fires, labels are resolved from the payload and used to find an existing chat to continue (by label match) or set on a newly created chat.';
|
||||
|
||||
CREATE TABLE chat_automations (
|
||||
id uuid NOT NULL,
|
||||
owner_id uuid NOT NULL,
|
||||
organization_id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
description text DEFAULT ''::text NOT NULL,
|
||||
instructions text DEFAULT ''::text NOT NULL,
|
||||
model_config_id uuid,
|
||||
mcp_server_ids uuid[] DEFAULT '{}'::uuid[] NOT NULL,
|
||||
allowed_tools text[] DEFAULT '{}'::text[] NOT NULL,
|
||||
status chat_automation_status DEFAULT 'disabled'::chat_automation_status NOT NULL,
|
||||
max_chat_creates_per_hour integer DEFAULT 10 NOT NULL,
|
||||
max_messages_per_hour integer DEFAULT 60 NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
CONSTRAINT chat_automations_max_chat_creates_per_hour_check CHECK ((max_chat_creates_per_hour > 0)),
|
||||
CONSTRAINT chat_automations_max_messages_per_hour_check CHECK ((max_messages_per_hour > 0))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat_automations IS 'Chat automations bridge external events (webhooks, cron schedules) to Coder chats. A chat automation defines what to say, which model and tools to use, and how fast it is allowed to create or continue chats.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.owner_id IS 'The user on whose behalf chats are created. All RBAC checks and chat ownership are scoped to this user.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.organization_id IS 'Organization scope for RBAC. Combined with owner_id and name to form a unique constraint so automations are namespaced per user per org.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.instructions IS 'The user-role message injected into every chat this automation creates. This is the core prompt that tells the LLM what to do.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.model_config_id IS 'Optional model configuration override. When NULL the deployment default is used. SET NULL on delete so automations survive config changes gracefully.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.mcp_server_ids IS 'MCP servers to attach to chats created by this automation. Stored as an array of UUIDs rather than a join table because the set is small and always read/written atomically.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.allowed_tools IS 'Tool allowlist. Empty means all tools available to the model config are permitted.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.status IS 'Lifecycle state: disabled — trigger events are silently dropped; preview — events are logged but no chat is created (dry-run); active — events create or continue chats.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.max_chat_creates_per_hour IS 'Maximum number of new chats this automation may create in a rolling one-hour window. Prevents runaway webhook storms from flooding the system.';
|
||||
|
||||
COMMENT ON COLUMN chat_automations.max_messages_per_hour IS 'Maximum total messages (creates + continues) this automation may send in a rolling one-hour window. A second, broader throttle that catches high-frequency continuation patterns.';
|
||||
|
||||
CREATE TABLE chat_diff_statuses (
|
||||
chat_id uuid NOT NULL,
|
||||
url text,
|
||||
@@ -1404,7 +1533,8 @@ CREATE TABLE chats (
|
||||
agent_id uuid,
|
||||
pin_order integer DEFAULT 0 NOT NULL,
|
||||
last_read_message_id bigint,
|
||||
last_injected_context jsonb
|
||||
last_injected_context jsonb,
|
||||
automation_id uuid
|
||||
);
|
||||
|
||||
CREATE TABLE connection_logs (
|
||||
@@ -3320,6 +3450,15 @@ ALTER TABLE ONLY audit_logs
|
||||
ALTER TABLE ONLY boundary_usage_stats
|
||||
ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
|
||||
|
||||
ALTER TABLE ONLY chat_automation_events
|
||||
ADD CONSTRAINT chat_automation_events_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY chat_automation_triggers
|
||||
ADD CONSTRAINT chat_automation_triggers_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY chat_automations
|
||||
ADD CONSTRAINT chat_automations_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY chat_diff_statuses
|
||||
ADD CONSTRAINT chat_diff_statuses_pkey PRIMARY KEY (chat_id);
|
||||
|
||||
@@ -3705,6 +3844,20 @@ CREATE INDEX idx_audit_log_user_id ON audit_logs USING btree (user_id);
|
||||
|
||||
CREATE INDEX idx_audit_logs_time_desc ON audit_logs USING btree ("time" DESC);
|
||||
|
||||
CREATE INDEX idx_chat_automation_events_automation_id_received_at ON chat_automation_events USING btree (automation_id, received_at DESC);
|
||||
|
||||
CREATE INDEX idx_chat_automation_events_rate_limit ON chat_automation_events USING btree (automation_id, received_at) WHERE (status = ANY (ARRAY['created'::chat_automation_event_status, 'continued'::chat_automation_event_status]));
|
||||
|
||||
CREATE INDEX idx_chat_automation_events_received_at ON chat_automation_events USING btree (received_at);
|
||||
|
||||
CREATE INDEX idx_chat_automation_triggers_automation_id ON chat_automation_triggers USING btree (automation_id);
|
||||
|
||||
CREATE INDEX idx_chat_automations_organization_id ON chat_automations USING btree (organization_id);
|
||||
|
||||
CREATE INDEX idx_chat_automations_owner_id ON chat_automations USING btree (owner_id);
|
||||
|
||||
CREATE UNIQUE INDEX idx_chat_automations_owner_org_name ON chat_automations USING btree (owner_id, organization_id, name);
|
||||
|
||||
CREATE INDEX idx_chat_diff_statuses_stale_at ON chat_diff_statuses USING btree (stale_at);
|
||||
|
||||
CREATE INDEX idx_chat_files_org ON chat_files USING btree (organization_id);
|
||||
@@ -3733,6 +3886,8 @@ CREATE INDEX idx_chat_providers_enabled ON chat_providers USING btree (enabled);
|
||||
|
||||
CREATE INDEX idx_chat_queued_messages_chat_id ON chat_queued_messages USING btree (chat_id);
|
||||
|
||||
CREATE INDEX idx_chats_automation_id ON chats USING btree (automation_id);
|
||||
|
||||
CREATE INDEX idx_chats_labels ON chats USING gin (labels);
|
||||
|
||||
CREATE INDEX idx_chats_last_model_config_id ON chats USING btree (last_model_config_id);
|
||||
@@ -4006,6 +4161,33 @@ ALTER TABLE ONLY aibridge_interceptions
|
||||
ALTER TABLE ONLY api_keys
|
||||
ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_automation_events
|
||||
ADD CONSTRAINT chat_automation_events_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_automation_events
|
||||
ADD CONSTRAINT chat_automation_events_created_chat_id_fkey FOREIGN KEY (created_chat_id) REFERENCES chats(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY chat_automation_events
|
||||
ADD CONSTRAINT chat_automation_events_matched_chat_id_fkey FOREIGN KEY (matched_chat_id) REFERENCES chats(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY chat_automation_events
|
||||
ADD CONSTRAINT chat_automation_events_trigger_id_fkey FOREIGN KEY (trigger_id) REFERENCES chat_automation_triggers(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY chat_automation_triggers
|
||||
ADD CONSTRAINT chat_automation_triggers_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_automation_triggers
|
||||
ADD CONSTRAINT chat_automation_triggers_webhook_secret_key_id_fkey FOREIGN KEY (webhook_secret_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
ALTER TABLE ONLY chat_automations
|
||||
ADD CONSTRAINT chat_automations_model_config_id_fkey FOREIGN KEY (model_config_id) REFERENCES chat_model_configs(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY chat_automations
|
||||
ADD CONSTRAINT chat_automations_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_automations
|
||||
ADD CONSTRAINT chat_automations_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_diff_statuses
|
||||
ADD CONSTRAINT chat_diff_statuses_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -4042,6 +4224,9 @@ ALTER TABLE ONLY chat_queued_messages
|
||||
ALTER TABLE ONLY chats
|
||||
ADD CONSTRAINT chats_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY chats
|
||||
ADD CONSTRAINT chats_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY chats
|
||||
ADD CONSTRAINT chats_build_id_fkey FOREIGN KEY (build_id) REFERENCES workspace_builds(id) ON DELETE SET NULL;
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@ const (
|
||||
ForeignKeyAiSeatStateUserID ForeignKeyConstraint = "ai_seat_state_user_id_fkey" // ALTER TABLE ONLY ai_seat_state ADD CONSTRAINT ai_seat_state_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyAibridgeInterceptionsInitiatorID ForeignKeyConstraint = "aibridge_interceptions_initiator_id_fkey" // ALTER TABLE ONLY aibridge_interceptions ADD CONSTRAINT aibridge_interceptions_initiator_id_fkey FOREIGN KEY (initiator_id) REFERENCES users(id);
|
||||
ForeignKeyAPIKeysUserIDUUID ForeignKeyConstraint = "api_keys_user_id_uuid_fkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatAutomationEventsAutomationID ForeignKeyConstraint = "chat_automation_events_automation_id_fkey" // ALTER TABLE ONLY chat_automation_events ADD CONSTRAINT chat_automation_events_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatAutomationEventsCreatedChatID ForeignKeyConstraint = "chat_automation_events_created_chat_id_fkey" // ALTER TABLE ONLY chat_automation_events ADD CONSTRAINT chat_automation_events_created_chat_id_fkey FOREIGN KEY (created_chat_id) REFERENCES chats(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatAutomationEventsMatchedChatID ForeignKeyConstraint = "chat_automation_events_matched_chat_id_fkey" // ALTER TABLE ONLY chat_automation_events ADD CONSTRAINT chat_automation_events_matched_chat_id_fkey FOREIGN KEY (matched_chat_id) REFERENCES chats(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatAutomationEventsTriggerID ForeignKeyConstraint = "chat_automation_events_trigger_id_fkey" // ALTER TABLE ONLY chat_automation_events ADD CONSTRAINT chat_automation_events_trigger_id_fkey FOREIGN KEY (trigger_id) REFERENCES chat_automation_triggers(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatAutomationTriggersAutomationID ForeignKeyConstraint = "chat_automation_triggers_automation_id_fkey" // ALTER TABLE ONLY chat_automation_triggers ADD CONSTRAINT chat_automation_triggers_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatAutomationTriggersWebhookSecretKeyID ForeignKeyConstraint = "chat_automation_triggers_webhook_secret_key_id_fkey" // ALTER TABLE ONLY chat_automation_triggers ADD CONSTRAINT chat_automation_triggers_webhook_secret_key_id_fkey FOREIGN KEY (webhook_secret_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyChatAutomationsModelConfigID ForeignKeyConstraint = "chat_automations_model_config_id_fkey" // ALTER TABLE ONLY chat_automations ADD CONSTRAINT chat_automations_model_config_id_fkey FOREIGN KEY (model_config_id) REFERENCES chat_model_configs(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatAutomationsOrganizationID ForeignKeyConstraint = "chat_automations_organization_id_fkey" // ALTER TABLE ONLY chat_automations ADD CONSTRAINT chat_automations_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatAutomationsOwnerID ForeignKeyConstraint = "chat_automations_owner_id_fkey" // ALTER TABLE ONLY chat_automations ADD CONSTRAINT chat_automations_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatDiffStatusesChatID ForeignKeyConstraint = "chat_diff_statuses_chat_id_fkey" // ALTER TABLE ONLY chat_diff_statuses ADD CONSTRAINT chat_diff_statuses_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatFilesOrganizationID ForeignKeyConstraint = "chat_files_organization_id_fkey" // ALTER TABLE ONLY chat_files ADD CONSTRAINT chat_files_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatFilesOwnerID ForeignKeyConstraint = "chat_files_owner_id_fkey" // ALTER TABLE ONLY chat_files ADD CONSTRAINT chat_files_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
@@ -21,6 +30,7 @@ const (
|
||||
ForeignKeyChatProvidersCreatedBy ForeignKeyConstraint = "chat_providers_created_by_fkey" // ALTER TABLE ONLY chat_providers ADD CONSTRAINT chat_providers_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id);
|
||||
ForeignKeyChatQueuedMessagesChatID ForeignKeyConstraint = "chat_queued_messages_chat_id_fkey" // ALTER TABLE ONLY chat_queued_messages ADD CONSTRAINT chat_queued_messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatsAgentID ForeignKeyConstraint = "chats_agent_id_fkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatsAutomationID ForeignKeyConstraint = "chats_automation_id_fkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatsBuildID ForeignKeyConstraint = "chats_build_id_fkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_build_id_fkey FOREIGN KEY (build_id) REFERENCES workspace_builds(id) ON DELETE SET NULL;
|
||||
ForeignKeyChatsLastModelConfigID ForeignKeyConstraint = "chats_last_model_config_id_fkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_last_model_config_id_fkey FOREIGN KEY (last_model_config_id) REFERENCES chat_model_configs(id);
|
||||
ForeignKeyChatsOwnerID ForeignKeyConstraint = "chats_owner_id_fkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -27,6 +27,7 @@ func TestCustomQueriesSyncedRowScan(t *testing.T) {
|
||||
"GetWorkspaces": "GetAuthorizedWorkspaces",
|
||||
"GetUsers": "GetAuthorizedUsers",
|
||||
"GetChats": "GetAuthorizedChats",
|
||||
"GetChatAutomations": "GetAuthorizedChatAutomations",
|
||||
}
|
||||
|
||||
// Scan custom
|
||||
|
||||
@@ -15,6 +15,9 @@ const (
|
||||
LockIDReconcilePrebuilds
|
||||
LockIDReconcileSystemRoles
|
||||
LockIDBoundaryUsageStats
|
||||
// LockIDChatAutomationCron prevents concurrent cron trigger
|
||||
// evaluation across coderd replicas.
|
||||
LockIDChatAutomationCron
|
||||
)
|
||||
|
||||
// GenLockID generates a unique and consistent lock ID from a given string.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ALTER TABLE chats DROP COLUMN IF EXISTS automation_id;
|
||||
|
||||
DROP TABLE IF EXISTS chat_automation_events;
|
||||
|
||||
DROP TABLE IF EXISTS chat_automation_triggers;
|
||||
|
||||
DROP TABLE IF EXISTS chat_automations;
|
||||
|
||||
DROP TYPE IF EXISTS chat_automation_event_status;
|
||||
|
||||
DROP TYPE IF EXISTS chat_automation_trigger_type;
|
||||
|
||||
DROP TYPE IF EXISTS chat_automation_status;
|
||||
@@ -0,0 +1,238 @@
|
||||
-- Chat automations bridge external events (webhooks, cron schedules) to
|
||||
-- Coder chats. A chat automation defines *what* to say, *which* model
|
||||
-- and tools to use, and *how fast* it is allowed to create or continue
|
||||
-- chats.
|
||||
|
||||
CREATE TYPE chat_automation_status AS ENUM ('disabled', 'preview', 'active');
|
||||
CREATE TYPE chat_automation_trigger_type AS ENUM ('webhook', 'cron');
|
||||
CREATE TYPE chat_automation_event_status AS ENUM ('filtered', 'preview', 'created', 'continued', 'rate_limited', 'error');
|
||||
|
||||
CREATE TABLE chat_automations (
|
||||
id uuid NOT NULL,
|
||||
-- The user on whose behalf chats are created. All RBAC checks and
|
||||
-- chat ownership are scoped to this user.
|
||||
owner_id uuid NOT NULL,
|
||||
-- Organization scope for RBAC. Combined with owner_id and name to
|
||||
-- form a unique constraint so automations are namespaced per user
|
||||
-- per org.
|
||||
organization_id uuid NOT NULL,
|
||||
-- Human-readable identifier. Unique within (owner_id, organization_id).
|
||||
name text NOT NULL,
|
||||
-- Optional long-form description shown in the UI.
|
||||
description text NOT NULL DEFAULT '',
|
||||
-- The user-role message injected into every chat this automation
|
||||
-- creates. This is the core prompt that tells the LLM what to do.
|
||||
instructions text NOT NULL DEFAULT '',
|
||||
-- Optional model configuration override. When NULL the deployment
|
||||
-- default is used. SET NULL on delete so automations survive config
|
||||
-- changes gracefully.
|
||||
model_config_id uuid,
|
||||
-- MCP servers to attach to chats created by this automation.
|
||||
-- Stored as an array of UUIDs rather than a join table because
|
||||
-- the set is small and always read/written atomically.
|
||||
mcp_server_ids uuid[] NOT NULL DEFAULT '{}',
|
||||
-- Tool allowlist. Empty means all tools available to the model
|
||||
-- config are permitted.
|
||||
allowed_tools text[] NOT NULL DEFAULT '{}',
|
||||
-- Lifecycle state:
|
||||
-- disabled — trigger events are silently dropped.
|
||||
-- preview — events are logged but no chat is created (dry-run).
|
||||
-- active — events create or continue chats.
|
||||
status chat_automation_status NOT NULL DEFAULT 'disabled',
|
||||
-- Maximum number of *new* chats this automation may create in a
|
||||
-- rolling one-hour window. Prevents runaway webhook storms from
|
||||
-- flooding the system. Approximate under concurrency; the
|
||||
-- check-then-insert is not serialized, so brief bursts may
|
||||
-- slightly exceed the cap.
|
||||
max_chat_creates_per_hour integer NOT NULL DEFAULT 10,
|
||||
-- Maximum total messages (creates + continues) this automation may
|
||||
-- send in a rolling one-hour window. A second, broader throttle
|
||||
-- that catches high-frequency continuation patterns. Same
|
||||
-- approximate-under-concurrency caveat as above.
|
||||
max_messages_per_hour integer NOT NULL DEFAULT 60,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (model_config_id) REFERENCES chat_model_configs(id) ON DELETE SET NULL,
|
||||
CONSTRAINT chat_automations_max_chat_creates_per_hour_check CHECK (max_chat_creates_per_hour > 0),
|
||||
CONSTRAINT chat_automations_max_messages_per_hour_check CHECK (max_messages_per_hour > 0)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_chat_automations_owner_id ON chat_automations (owner_id);
|
||||
CREATE INDEX idx_chat_automations_organization_id ON chat_automations (organization_id);
|
||||
|
||||
-- Enforces that automation names are unique per user per org so they
|
||||
-- can be referenced unambiguously in CLI/API calls.
|
||||
CREATE UNIQUE INDEX idx_chat_automations_owner_org_name ON chat_automations (owner_id, organization_id, name);
|
||||
|
||||
-- Triggers define *how* an automation is invoked. Each automation can
|
||||
-- have multiple triggers (e.g. one webhook + one cron schedule).
|
||||
-- Webhook and cron triggers share the same row shape with type-specific
|
||||
-- nullable columns to keep the schema simple.
|
||||
CREATE TABLE chat_automation_triggers (
|
||||
id uuid NOT NULL,
|
||||
-- Parent automation. CASCADE delete ensures orphan triggers are
|
||||
-- cleaned up when an automation is removed.
|
||||
automation_id uuid NOT NULL,
|
||||
-- Discriminator: 'webhook' or 'cron'. Determines which nullable
|
||||
-- columns are meaningful.
|
||||
type chat_automation_trigger_type NOT NULL,
|
||||
-- HMAC-SHA256 shared secret for webhook signature verification
|
||||
-- (X-Hub-Signature-256 header). NULL for cron triggers.
|
||||
webhook_secret text,
|
||||
-- Identifier of the dbcrypt key used to encrypt webhook_secret.
|
||||
-- NULL means the secret is not yet encrypted. When dbcrypt is
|
||||
-- enabled, this references the active key digest used for
|
||||
-- AES-256-GCM encryption.
|
||||
webhook_secret_key_id text REFERENCES dbcrypt_keys(active_key_digest),
|
||||
-- Standard 5-field cron expression (minute hour dom month dow),
|
||||
-- with optional CRON_TZ= prefix. NULL for webhook triggers.
|
||||
cron_schedule text,
|
||||
-- Timestamp of the last successful cron fire. The scheduler
|
||||
-- computes next = cron.Next(last_triggered_at) and fires when
|
||||
-- next <= now. NULL means the trigger has never fired; the
|
||||
-- scheduler falls back to created_at as the reference time.
|
||||
-- Not used for webhook triggers.
|
||||
last_triggered_at timestamp with time zone,
|
||||
-- gjson path→value filter conditions evaluated against the
|
||||
-- incoming webhook payload. All conditions must match for the
|
||||
-- trigger to fire. NULL or empty means "match everything".
|
||||
filter jsonb,
|
||||
-- Maps chat label keys to gjson paths. When a trigger fires,
|
||||
-- labels are resolved from the payload and used to find an
|
||||
-- existing chat to continue (by label match) or set on a
|
||||
-- newly created chat. This is how automations route events
|
||||
-- to the right conversation.
|
||||
label_paths jsonb,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE CASCADE,
|
||||
CONSTRAINT chat_automation_triggers_webhook_fields CHECK (
|
||||
type != 'webhook' OR (webhook_secret IS NOT NULL AND cron_schedule IS NULL AND last_triggered_at IS NULL)
|
||||
),
|
||||
CONSTRAINT chat_automation_triggers_cron_fields CHECK (
|
||||
type != 'cron' OR (cron_schedule IS NOT NULL AND webhook_secret IS NULL AND webhook_secret_key_id IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_chat_automation_triggers_automation_id ON chat_automation_triggers (automation_id);
|
||||
|
||||
-- Every trigger invocation produces an event row regardless of outcome.
|
||||
-- This table is the audit trail and the data source for rate-limit
|
||||
-- window counts. Rows are append-only and expected to be purged by a
|
||||
-- background job after a retention period.
|
||||
CREATE TABLE chat_automation_events (
|
||||
id uuid NOT NULL,
|
||||
-- The automation that owns this event.
|
||||
automation_id uuid NOT NULL,
|
||||
-- The trigger that produced this event. SET NULL on delete so
|
||||
-- historical events survive trigger removal.
|
||||
trigger_id uuid,
|
||||
-- When the event was received (webhook delivery time or cron
|
||||
-- evaluation time). Used for rate-limit window calculations and
|
||||
-- purge cutoffs.
|
||||
received_at timestamp with time zone NOT NULL,
|
||||
-- The raw payload that was evaluated. For webhooks this is the
|
||||
-- HTTP body; for cron triggers it is a synthetic JSON envelope
|
||||
-- with schedule metadata.
|
||||
payload jsonb NOT NULL,
|
||||
-- Whether the trigger's filter conditions matched. False means
|
||||
-- the event was dropped before any chat interaction.
|
||||
filter_matched boolean NOT NULL,
|
||||
-- Labels resolved from the payload via label_paths. Stored so
|
||||
-- the event log shows exactly which labels were computed.
|
||||
resolved_labels jsonb,
|
||||
-- ID of an existing chat that was found via label matching and
|
||||
-- continued with a new message.
|
||||
matched_chat_id uuid,
|
||||
-- ID of a newly created chat (mutually exclusive with
|
||||
-- matched_chat_id in practice).
|
||||
created_chat_id uuid,
|
||||
-- Outcome of the event:
|
||||
-- filtered — filter did not match, event dropped.
|
||||
-- preview — automation is in preview mode, no chat action.
|
||||
-- created — new chat was created.
|
||||
-- continued — existing chat was continued.
|
||||
-- rate_limited — rate limit prevented chat action.
|
||||
-- error — something went wrong (see error column).
|
||||
status chat_automation_event_status NOT NULL,
|
||||
-- Human-readable error description when status = 'error' or
|
||||
-- 'rate_limited'. NULL for successful outcomes.
|
||||
error text,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY (automation_id) REFERENCES chat_automations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (trigger_id) REFERENCES chat_automation_triggers(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (matched_chat_id) REFERENCES chats(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (created_chat_id) REFERENCES chats(id) ON DELETE SET NULL,
|
||||
CONSTRAINT chat_automation_events_chat_exclusivity CHECK (
|
||||
matched_chat_id IS NULL OR created_chat_id IS NULL
|
||||
)
|
||||
);
|
||||
|
||||
-- Composite index for listing events per automation in reverse
|
||||
-- chronological order (the primary UI query pattern).
|
||||
CREATE INDEX idx_chat_automation_events_automation_id_received_at ON chat_automation_events (automation_id, received_at DESC);
|
||||
|
||||
-- Standalone index on received_at for the purge job, which deletes
|
||||
-- events older than the retention period across all automations.
|
||||
CREATE INDEX idx_chat_automation_events_received_at ON chat_automation_events (received_at);
|
||||
|
||||
-- Partial index for rate-limit window count queries, which filter
|
||||
-- by automation_id and status IN ('created', 'continued').
|
||||
CREATE INDEX idx_chat_automation_events_rate_limit
|
||||
ON chat_automation_events (automation_id, received_at)
|
||||
WHERE status IN ('created', 'continued');
|
||||
|
||||
-- Link chats back to the automation that created them. SET NULL on
|
||||
-- delete so chats survive if the automation is removed. Indexed for
|
||||
-- lookup queries that list chats spawned by a given automation.
|
||||
ALTER TABLE chats ADD COLUMN automation_id uuid REFERENCES chat_automations(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX idx_chats_automation_id ON chats (automation_id);
|
||||
|
||||
-- Enum type comments.
|
||||
COMMENT ON TYPE chat_automation_status IS 'Lifecycle state of a chat automation: disabled, preview, or active.';
|
||||
COMMENT ON TYPE chat_automation_trigger_type IS 'Discriminator for chat automation triggers: webhook or cron.';
|
||||
COMMENT ON TYPE chat_automation_event_status IS 'Outcome of a chat automation event: filtered, preview, created, continued, rate_limited, or error.';
|
||||
|
||||
-- Table comments.
|
||||
COMMENT ON TABLE chat_automations IS 'Chat automations bridge external events (webhooks, cron schedules) to Coder chats. A chat automation defines what to say, which model and tools to use, and how fast it is allowed to create or continue chats.';
|
||||
COMMENT ON TABLE chat_automation_triggers IS 'Triggers define how an automation is invoked. Each automation can have multiple triggers (e.g. one webhook + one cron schedule). Webhook and cron triggers share the same row shape with type-specific nullable columns to keep the schema simple.';
|
||||
COMMENT ON TABLE chat_automation_events IS 'Every trigger invocation produces an event row regardless of outcome. This table is the audit trail and the data source for rate-limit window counts. Rows are append-only and expected to be purged by a background job after a retention period.';
|
||||
|
||||
-- Column comments for chat_automations.
|
||||
COMMENT ON COLUMN chat_automations.owner_id IS 'The user on whose behalf chats are created. All RBAC checks and chat ownership are scoped to this user.';
|
||||
COMMENT ON COLUMN chat_automations.organization_id IS 'Organization scope for RBAC. Combined with owner_id and name to form a unique constraint so automations are namespaced per user per org.';
|
||||
COMMENT ON COLUMN chat_automations.instructions IS 'The user-role message injected into every chat this automation creates. This is the core prompt that tells the LLM what to do.';
|
||||
COMMENT ON COLUMN chat_automations.model_config_id IS 'Optional model configuration override. When NULL the deployment default is used. SET NULL on delete so automations survive config changes gracefully.';
|
||||
COMMENT ON COLUMN chat_automations.mcp_server_ids IS 'MCP servers to attach to chats created by this automation. Stored as an array of UUIDs rather than a join table because the set is small and always read/written atomically.';
|
||||
COMMENT ON COLUMN chat_automations.allowed_tools IS 'Tool allowlist. Empty means all tools available to the model config are permitted.';
|
||||
COMMENT ON COLUMN chat_automations.status IS 'Lifecycle state: disabled — trigger events are silently dropped; preview — events are logged but no chat is created (dry-run); active — events create or continue chats.';
|
||||
COMMENT ON COLUMN chat_automations.max_chat_creates_per_hour IS 'Maximum number of new chats this automation may create in a rolling one-hour window. Prevents runaway webhook storms from flooding the system.';
|
||||
COMMENT ON COLUMN chat_automations.max_messages_per_hour IS 'Maximum total messages (creates + continues) this automation may send in a rolling one-hour window. A second, broader throttle that catches high-frequency continuation patterns.';
|
||||
|
||||
-- Column comments for chat_automation_triggers.
|
||||
COMMENT ON COLUMN chat_automation_triggers.type IS 'Discriminator: webhook or cron. Determines which nullable columns are meaningful.';
|
||||
COMMENT ON COLUMN chat_automation_triggers.webhook_secret IS 'HMAC-SHA256 shared secret for webhook signature verification (X-Hub-Signature-256 header). NULL for cron triggers.';
|
||||
COMMENT ON COLUMN chat_automation_triggers.cron_schedule IS 'Standard 5-field cron expression (minute hour dom month dow), with optional CRON_TZ= prefix. NULL for webhook triggers.';
|
||||
COMMENT ON COLUMN chat_automation_triggers.filter IS 'gjson path-to-value filter conditions evaluated against the incoming webhook payload. All conditions must match for the trigger to fire. NULL or empty means match everything.';
|
||||
COMMENT ON COLUMN chat_automation_triggers.label_paths IS 'Maps chat label keys to gjson paths. When a trigger fires, labels are resolved from the payload and used to find an existing chat to continue (by label match) or set on a newly created chat.';
|
||||
COMMENT ON COLUMN chat_automation_triggers.last_triggered_at IS 'Timestamp of the last successful cron fire. The scheduler computes next = cron.Next(last_triggered_at) and fires when next <= now. NULL means the trigger has never fired. Not used for webhook triggers.';
|
||||
|
||||
-- Column comments for chat_automation_events.
|
||||
COMMENT ON COLUMN chat_automation_events.payload IS 'The raw payload that was evaluated. For webhooks this is the HTTP body; for cron triggers it is a synthetic JSON envelope with schedule metadata.';
|
||||
COMMENT ON COLUMN chat_automation_events.filter_matched IS 'Whether the trigger filter conditions matched. False means the event was dropped before any chat interaction.';
|
||||
COMMENT ON COLUMN chat_automation_events.resolved_labels IS 'Labels resolved from the payload via label_paths. Stored so the event log shows exactly which labels were computed.';
|
||||
COMMENT ON COLUMN chat_automation_events.matched_chat_id IS 'ID of an existing chat that was found via label matching and continued with a new message.';
|
||||
COMMENT ON COLUMN chat_automation_events.created_chat_id IS 'ID of a newly created chat (mutually exclusive with matched_chat_id in practice).';
|
||||
COMMENT ON COLUMN chat_automation_events.status IS 'Outcome of the event: filtered — filter did not match; preview — automation is in preview mode; created — new chat was created; continued — existing chat was continued; rate_limited — rate limit prevented chat action; error — something went wrong.';
|
||||
|
||||
-- Add API key scope values for the new chat_automation resource type.
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_automation:create';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_automation:read';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_automation:update';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_automation:delete';
|
||||
ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'chat_automation:*';
|
||||
@@ -0,0 +1,87 @@
|
||||
INSERT INTO chat_automations (
|
||||
id,
|
||||
owner_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
instructions,
|
||||
model_config_id,
|
||||
mcp_server_ids,
|
||||
allowed_tools,
|
||||
status,
|
||||
max_chat_creates_per_hour,
|
||||
max_messages_per_hour,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
'b3d0fd0e-8e1a-4f2c-9a3b-1234567890ab',
|
||||
u.id,
|
||||
o.id,
|
||||
'fixture-automation',
|
||||
'Fixture automation for migration testing.',
|
||||
'You are a helpful assistant.',
|
||||
NULL,
|
||||
'{}',
|
||||
'{}',
|
||||
'active',
|
||||
10,
|
||||
60,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
FROM users u
|
||||
CROSS JOIN organizations o
|
||||
ORDER BY u.created_at, u.id
|
||||
LIMIT 1;
|
||||
|
||||
INSERT INTO chat_automation_triggers (
|
||||
id,
|
||||
automation_id,
|
||||
type,
|
||||
webhook_secret,
|
||||
webhook_secret_key_id,
|
||||
cron_schedule,
|
||||
last_triggered_at,
|
||||
filter,
|
||||
label_paths,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'c4e1fe1f-9f2b-4a3d-ab4c-234567890abc',
|
||||
'b3d0fd0e-8e1a-4f2c-9a3b-1234567890ab',
|
||||
'webhook',
|
||||
'whsec_fixture_secret',
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
'{"action": "opened"}'::jsonb,
|
||||
'{"repo": "repository.full_name"}'::jsonb,
|
||||
'2024-01-01 00:00:00+00',
|
||||
'2024-01-01 00:00:00+00'
|
||||
);
|
||||
|
||||
INSERT INTO chat_automation_events (
|
||||
id,
|
||||
automation_id,
|
||||
trigger_id,
|
||||
received_at,
|
||||
payload,
|
||||
filter_matched,
|
||||
resolved_labels,
|
||||
matched_chat_id,
|
||||
created_chat_id,
|
||||
status,
|
||||
error
|
||||
) VALUES (
|
||||
'd5f20f20-a03c-4b4e-bc5d-345678901bcd',
|
||||
'b3d0fd0e-8e1a-4f2c-9a3b-1234567890ab',
|
||||
'c4e1fe1f-9f2b-4a3d-ab4c-234567890abc',
|
||||
'2024-01-01 00:00:00+00',
|
||||
'{"action": "opened", "repository": {"full_name": "coder/coder"}}'::jsonb,
|
||||
TRUE,
|
||||
'{"repo": "coder/coder"}'::jsonb,
|
||||
NULL,
|
||||
NULL,
|
||||
'preview',
|
||||
NULL
|
||||
);
|
||||
@@ -182,6 +182,13 @@ func (r GetChatsRow) RBACObject() rbac.Object {
|
||||
return r.Chat.RBACObject()
|
||||
}
|
||||
|
||||
func (a ChatAutomation) RBACObject() rbac.Object {
|
||||
return rbac.ResourceChatAutomation.
|
||||
WithID(a.ID).
|
||||
WithOwner(a.OwnerID.String()).
|
||||
InOrg(a.OrganizationID)
|
||||
}
|
||||
|
||||
func (c ChatFile) RBACObject() rbac.Object {
|
||||
return rbac.ResourceChat.WithID(c.ID).WithOwner(c.OwnerID.String()).InOrg(c.OrganizationID)
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ type customQuerier interface {
|
||||
connectionLogQuerier
|
||||
aibridgeQuerier
|
||||
chatQuerier
|
||||
chatAutomationQuerier
|
||||
}
|
||||
|
||||
type templateQuerier interface {
|
||||
@@ -796,7 +797,70 @@ func (q *sqlQuerier) GetAuthorizedChats(ctx context.Context, arg GetChatsParams,
|
||||
&i.Chat.PinOrder,
|
||||
&i.Chat.LastReadMessageID,
|
||||
&i.Chat.LastInjectedContext,
|
||||
&i.HasUnread); err != nil {
|
||||
&i.Chat.AutomationID,
|
||||
&i.HasUnread,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
type chatAutomationQuerier interface {
|
||||
GetAuthorizedChatAutomations(ctx context.Context, arg GetChatAutomationsParams, prepared rbac.PreparedAuthorized) ([]ChatAutomation, error)
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetAuthorizedChatAutomations(ctx context.Context, arg GetChatAutomationsParams, prepared rbac.PreparedAuthorized) ([]ChatAutomation, error) {
|
||||
authorizedFilter, err := prepared.CompileToSQL(ctx, regosql.ConvertConfig{
|
||||
VariableConverter: regosql.NoACLConverter(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("compile authorized filter: %w", err)
|
||||
}
|
||||
|
||||
filtered, err := insertAuthorizedFilter(getChatAutomations, fmt.Sprintf(" AND %s", authorizedFilter))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("insert authorized filter: %w", err)
|
||||
}
|
||||
|
||||
// The name comment is for metric tracking
|
||||
query := fmt.Sprintf("-- name: GetAuthorizedChatAutomations :many\n%s", filtered)
|
||||
rows, err := q.db.QueryContext(ctx, query,
|
||||
arg.OwnerID,
|
||||
arg.OrganizationID,
|
||||
arg.OffsetOpt,
|
||||
arg.LimitOpt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ChatAutomation
|
||||
for rows.Next() {
|
||||
var i ChatAutomation
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.OrganizationID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Instructions,
|
||||
&i.ModelConfigID,
|
||||
pq.Array(&i.MCPServerIDs),
|
||||
pq.Array(&i.AllowedTools),
|
||||
&i.Status,
|
||||
&i.MaxChatCreatesPerHour,
|
||||
&i.MaxMessagesPerHour,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
||||
+278
-1
@@ -224,6 +224,11 @@ const (
|
||||
ApiKeyScopeChatUpdate APIKeyScope = "chat:update"
|
||||
ApiKeyScopeChatDelete APIKeyScope = "chat:delete"
|
||||
ApiKeyScopeChat APIKeyScope = "chat:*"
|
||||
ApiKeyScopeChatAutomationCreate APIKeyScope = "chat_automation:create"
|
||||
ApiKeyScopeChatAutomationRead APIKeyScope = "chat_automation:read"
|
||||
ApiKeyScopeChatAutomationUpdate APIKeyScope = "chat_automation:update"
|
||||
ApiKeyScopeChatAutomationDelete APIKeyScope = "chat_automation:delete"
|
||||
ApiKeyScopeChatAutomation APIKeyScope = "chat_automation:*"
|
||||
)
|
||||
|
||||
func (e *APIKeyScope) Scan(src interface{}) error {
|
||||
@@ -467,7 +472,12 @@ func (e APIKeyScope) Valid() bool {
|
||||
ApiKeyScopeChatRead,
|
||||
ApiKeyScopeChatUpdate,
|
||||
ApiKeyScopeChatDelete,
|
||||
ApiKeyScopeChat:
|
||||
ApiKeyScopeChat,
|
||||
ApiKeyScopeChatAutomationCreate,
|
||||
ApiKeyScopeChatAutomationRead,
|
||||
ApiKeyScopeChatAutomationUpdate,
|
||||
ApiKeyScopeChatAutomationDelete,
|
||||
ApiKeyScopeChatAutomation:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -680,6 +690,11 @@ func AllAPIKeyScopeValues() []APIKeyScope {
|
||||
ApiKeyScopeChatUpdate,
|
||||
ApiKeyScopeChatDelete,
|
||||
ApiKeyScopeChat,
|
||||
ApiKeyScopeChatAutomationCreate,
|
||||
ApiKeyScopeChatAutomationRead,
|
||||
ApiKeyScopeChatAutomationUpdate,
|
||||
ApiKeyScopeChatAutomationDelete,
|
||||
ApiKeyScopeChatAutomation,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,6 +1122,198 @@ func AllBuildReasonValues() []BuildReason {
|
||||
}
|
||||
}
|
||||
|
||||
// Outcome of a chat automation event: filtered, preview, created, continued, rate_limited, or error.
|
||||
type ChatAutomationEventStatus string
|
||||
|
||||
const (
|
||||
ChatAutomationEventStatusFiltered ChatAutomationEventStatus = "filtered"
|
||||
ChatAutomationEventStatusPreview ChatAutomationEventStatus = "preview"
|
||||
ChatAutomationEventStatusCreated ChatAutomationEventStatus = "created"
|
||||
ChatAutomationEventStatusContinued ChatAutomationEventStatus = "continued"
|
||||
ChatAutomationEventStatusRateLimited ChatAutomationEventStatus = "rate_limited"
|
||||
ChatAutomationEventStatusError ChatAutomationEventStatus = "error"
|
||||
)
|
||||
|
||||
func (e *ChatAutomationEventStatus) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = ChatAutomationEventStatus(s)
|
||||
case string:
|
||||
*e = ChatAutomationEventStatus(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for ChatAutomationEventStatus: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullChatAutomationEventStatus struct {
|
||||
ChatAutomationEventStatus ChatAutomationEventStatus `json:"chat_automation_event_status"`
|
||||
Valid bool `json:"valid"` // Valid is true if ChatAutomationEventStatus is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullChatAutomationEventStatus) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.ChatAutomationEventStatus, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.ChatAutomationEventStatus.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullChatAutomationEventStatus) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.ChatAutomationEventStatus), nil
|
||||
}
|
||||
|
||||
func (e ChatAutomationEventStatus) Valid() bool {
|
||||
switch e {
|
||||
case ChatAutomationEventStatusFiltered,
|
||||
ChatAutomationEventStatusPreview,
|
||||
ChatAutomationEventStatusCreated,
|
||||
ChatAutomationEventStatusContinued,
|
||||
ChatAutomationEventStatusRateLimited,
|
||||
ChatAutomationEventStatusError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllChatAutomationEventStatusValues() []ChatAutomationEventStatus {
|
||||
return []ChatAutomationEventStatus{
|
||||
ChatAutomationEventStatusFiltered,
|
||||
ChatAutomationEventStatusPreview,
|
||||
ChatAutomationEventStatusCreated,
|
||||
ChatAutomationEventStatusContinued,
|
||||
ChatAutomationEventStatusRateLimited,
|
||||
ChatAutomationEventStatusError,
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle state of a chat automation: disabled, preview, or active.
|
||||
type ChatAutomationStatus string
|
||||
|
||||
const (
|
||||
ChatAutomationStatusDisabled ChatAutomationStatus = "disabled"
|
||||
ChatAutomationStatusPreview ChatAutomationStatus = "preview"
|
||||
ChatAutomationStatusActive ChatAutomationStatus = "active"
|
||||
)
|
||||
|
||||
func (e *ChatAutomationStatus) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = ChatAutomationStatus(s)
|
||||
case string:
|
||||
*e = ChatAutomationStatus(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for ChatAutomationStatus: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullChatAutomationStatus struct {
|
||||
ChatAutomationStatus ChatAutomationStatus `json:"chat_automation_status"`
|
||||
Valid bool `json:"valid"` // Valid is true if ChatAutomationStatus is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullChatAutomationStatus) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.ChatAutomationStatus, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.ChatAutomationStatus.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullChatAutomationStatus) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.ChatAutomationStatus), nil
|
||||
}
|
||||
|
||||
func (e ChatAutomationStatus) Valid() bool {
|
||||
switch e {
|
||||
case ChatAutomationStatusDisabled,
|
||||
ChatAutomationStatusPreview,
|
||||
ChatAutomationStatusActive:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllChatAutomationStatusValues() []ChatAutomationStatus {
|
||||
return []ChatAutomationStatus{
|
||||
ChatAutomationStatusDisabled,
|
||||
ChatAutomationStatusPreview,
|
||||
ChatAutomationStatusActive,
|
||||
}
|
||||
}
|
||||
|
||||
// Discriminator for chat automation triggers: webhook or cron.
|
||||
type ChatAutomationTriggerType string
|
||||
|
||||
const (
|
||||
ChatAutomationTriggerTypeWebhook ChatAutomationTriggerType = "webhook"
|
||||
ChatAutomationTriggerTypeCron ChatAutomationTriggerType = "cron"
|
||||
)
|
||||
|
||||
func (e *ChatAutomationTriggerType) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = ChatAutomationTriggerType(s)
|
||||
case string:
|
||||
*e = ChatAutomationTriggerType(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for ChatAutomationTriggerType: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullChatAutomationTriggerType struct {
|
||||
ChatAutomationTriggerType ChatAutomationTriggerType `json:"chat_automation_trigger_type"`
|
||||
Valid bool `json:"valid"` // Valid is true if ChatAutomationTriggerType is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullChatAutomationTriggerType) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.ChatAutomationTriggerType, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.ChatAutomationTriggerType.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullChatAutomationTriggerType) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.ChatAutomationTriggerType), nil
|
||||
}
|
||||
|
||||
func (e ChatAutomationTriggerType) Valid() bool {
|
||||
switch e {
|
||||
case ChatAutomationTriggerTypeWebhook,
|
||||
ChatAutomationTriggerTypeCron:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllChatAutomationTriggerTypeValues() []ChatAutomationTriggerType {
|
||||
return []ChatAutomationTriggerType{
|
||||
ChatAutomationTriggerTypeWebhook,
|
||||
ChatAutomationTriggerTypeCron,
|
||||
}
|
||||
}
|
||||
|
||||
type ChatMessageRole string
|
||||
|
||||
const (
|
||||
@@ -4176,6 +4383,76 @@ type Chat struct {
|
||||
PinOrder int32 `db:"pin_order" json:"pin_order"`
|
||||
LastReadMessageID sql.NullInt64 `db:"last_read_message_id" json:"last_read_message_id"`
|
||||
LastInjectedContext pqtype.NullRawMessage `db:"last_injected_context" json:"last_injected_context"`
|
||||
AutomationID uuid.NullUUID `db:"automation_id" json:"automation_id"`
|
||||
}
|
||||
|
||||
// Chat automations bridge external events (webhooks, cron schedules) to Coder chats. A chat automation defines what to say, which model and tools to use, and how fast it is allowed to create or continue chats.
|
||||
type ChatAutomation struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
// The user on whose behalf chats are created. All RBAC checks and chat ownership are scoped to this user.
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
// Organization scope for RBAC. Combined with owner_id and name to form a unique constraint so automations are namespaced per user per org.
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
// The user-role message injected into every chat this automation creates. This is the core prompt that tells the LLM what to do.
|
||||
Instructions string `db:"instructions" json:"instructions"`
|
||||
// Optional model configuration override. When NULL the deployment default is used. SET NULL on delete so automations survive config changes gracefully.
|
||||
ModelConfigID uuid.NullUUID `db:"model_config_id" json:"model_config_id"`
|
||||
// MCP servers to attach to chats created by this automation. Stored as an array of UUIDs rather than a join table because the set is small and always read/written atomically.
|
||||
MCPServerIDs []uuid.UUID `db:"mcp_server_ids" json:"mcp_server_ids"`
|
||||
// Tool allowlist. Empty means all tools available to the model config are permitted.
|
||||
AllowedTools []string `db:"allowed_tools" json:"allowed_tools"`
|
||||
// Lifecycle state: disabled — trigger events are silently dropped; preview — events are logged but no chat is created (dry-run); active — events create or continue chats.
|
||||
Status ChatAutomationStatus `db:"status" json:"status"`
|
||||
// Maximum number of new chats this automation may create in a rolling one-hour window. Prevents runaway webhook storms from flooding the system.
|
||||
MaxChatCreatesPerHour int32 `db:"max_chat_creates_per_hour" json:"max_chat_creates_per_hour"`
|
||||
// Maximum total messages (creates + continues) this automation may send in a rolling one-hour window. A second, broader throttle that catches high-frequency continuation patterns.
|
||||
MaxMessagesPerHour int32 `db:"max_messages_per_hour" json:"max_messages_per_hour"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// Every trigger invocation produces an event row regardless of outcome. This table is the audit trail and the data source for rate-limit window counts. Rows are append-only and expected to be purged by a background job after a retention period.
|
||||
type ChatAutomationEvent struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
AutomationID uuid.UUID `db:"automation_id" json:"automation_id"`
|
||||
TriggerID uuid.NullUUID `db:"trigger_id" json:"trigger_id"`
|
||||
ReceivedAt time.Time `db:"received_at" json:"received_at"`
|
||||
// The raw payload that was evaluated. For webhooks this is the HTTP body; for cron triggers it is a synthetic JSON envelope with schedule metadata.
|
||||
Payload json.RawMessage `db:"payload" json:"payload"`
|
||||
// Whether the trigger filter conditions matched. False means the event was dropped before any chat interaction.
|
||||
FilterMatched bool `db:"filter_matched" json:"filter_matched"`
|
||||
// Labels resolved from the payload via label_paths. Stored so the event log shows exactly which labels were computed.
|
||||
ResolvedLabels pqtype.NullRawMessage `db:"resolved_labels" json:"resolved_labels"`
|
||||
// ID of an existing chat that was found via label matching and continued with a new message.
|
||||
MatchedChatID uuid.NullUUID `db:"matched_chat_id" json:"matched_chat_id"`
|
||||
// ID of a newly created chat (mutually exclusive with matched_chat_id in practice).
|
||||
CreatedChatID uuid.NullUUID `db:"created_chat_id" json:"created_chat_id"`
|
||||
// Outcome of the event: filtered — filter did not match; preview — automation is in preview mode; created — new chat was created; continued — existing chat was continued; rate_limited — rate limit prevented chat action; error — something went wrong.
|
||||
Status ChatAutomationEventStatus `db:"status" json:"status"`
|
||||
Error sql.NullString `db:"error" json:"error"`
|
||||
}
|
||||
|
||||
// Triggers define how an automation is invoked. Each automation can have multiple triggers (e.g. one webhook + one cron schedule). Webhook and cron triggers share the same row shape with type-specific nullable columns to keep the schema simple.
|
||||
type ChatAutomationTrigger struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
AutomationID uuid.UUID `db:"automation_id" json:"automation_id"`
|
||||
// Discriminator: webhook or cron. Determines which nullable columns are meaningful.
|
||||
Type ChatAutomationTriggerType `db:"type" json:"type"`
|
||||
// HMAC-SHA256 shared secret for webhook signature verification (X-Hub-Signature-256 header). NULL for cron triggers.
|
||||
WebhookSecret sql.NullString `db:"webhook_secret" json:"webhook_secret"`
|
||||
WebhookSecretKeyID sql.NullString `db:"webhook_secret_key_id" json:"webhook_secret_key_id"`
|
||||
// Standard 5-field cron expression (minute hour dom month dow), with optional CRON_TZ= prefix. NULL for webhook triggers.
|
||||
CronSchedule sql.NullString `db:"cron_schedule" json:"cron_schedule"`
|
||||
// Timestamp of the last successful cron fire. The scheduler computes next = cron.Next(last_triggered_at) and fires when next <= now. NULL means the trigger has never fired. Not used for webhook triggers.
|
||||
LastTriggeredAt sql.NullTime `db:"last_triggered_at" json:"last_triggered_at"`
|
||||
// gjson path-to-value filter conditions evaluated against the incoming webhook payload. All conditions must match for the trigger to fire. NULL or empty means match everything.
|
||||
Filter pqtype.NullRawMessage `db:"filter" json:"filter"`
|
||||
// Maps chat label keys to gjson paths. When a trigger fires, labels are resolved from the payload and used to find an existing chat to continue (by label match) or set on a newly created chat.
|
||||
LabelPaths pqtype.NullRawMessage `db:"label_paths" json:"label_paths"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
type ChatDiffStatus struct {
|
||||
|
||||
@@ -74,10 +74,21 @@ type sqlcQuerier interface {
|
||||
CleanTailnetCoordinators(ctx context.Context) error
|
||||
CleanTailnetLostPeers(ctx context.Context) error
|
||||
CleanTailnetTunnels(ctx context.Context) error
|
||||
CleanupDeletedMCPServerIDsFromChatAutomations(ctx context.Context) error
|
||||
CleanupDeletedMCPServerIDsFromChats(ctx context.Context) error
|
||||
CountAIBridgeInterceptions(ctx context.Context, arg CountAIBridgeInterceptionsParams) (int64, error)
|
||||
CountAIBridgeSessions(ctx context.Context, arg CountAIBridgeSessionsParams) (int64, error)
|
||||
CountAuditLogs(ctx context.Context, arg CountAuditLogsParams) (int64, error)
|
||||
// Counts new-chat events in the rate-limit window. This count is
|
||||
// approximate under concurrency: concurrent webhook handlers may
|
||||
// each read the same count before any of them insert, so brief
|
||||
// bursts can slightly exceed the configured cap.
|
||||
CountChatAutomationChatCreatesInWindow(ctx context.Context, arg CountChatAutomationChatCreatesInWindowParams) (int64, error)
|
||||
// Counts total message events (creates + continues) in the rate-limit
|
||||
// window. This count is approximate under concurrency: concurrent
|
||||
// webhook handlers may each read the same count before any of them
|
||||
// insert, so brief bursts can slightly exceed the configured cap.
|
||||
CountChatAutomationMessagesInWindow(ctx context.Context, arg CountChatAutomationMessagesInWindowParams) (int64, error)
|
||||
CountConnectionLogs(ctx context.Context, arg CountConnectionLogsParams) (int64, error)
|
||||
// Counts enabled, non-deleted model configs that lack both input and
|
||||
// output pricing in their JSONB options.cost configuration.
|
||||
@@ -100,6 +111,8 @@ type sqlcQuerier interface {
|
||||
// be recreated.
|
||||
DeleteAllWebpushSubscriptions(ctx context.Context) error
|
||||
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteChatAutomationByID(ctx context.Context, id uuid.UUID) error
|
||||
DeleteChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) error
|
||||
DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error
|
||||
DeleteChatProviderByID(ctx context.Context, id uuid.UUID) error
|
||||
DeleteChatQueuedMessage(ctx context.Context, arg DeleteChatQueuedMessageParams) error
|
||||
@@ -197,6 +210,10 @@ type sqlcQuerier interface {
|
||||
GetAPIKeysByUserID(ctx context.Context, arg GetAPIKeysByUserIDParams) ([]APIKey, error)
|
||||
GetAPIKeysLastUsedAfter(ctx context.Context, lastUsed time.Time) ([]APIKey, error)
|
||||
GetActiveAISeatCount(ctx context.Context) (int64, error)
|
||||
// Returns all cron triggers whose parent automation is active or in
|
||||
// preview mode. The scheduler uses this to evaluate which triggers
|
||||
// are due.
|
||||
GetActiveChatAutomationCronTriggers(ctx context.Context) ([]GetActiveChatAutomationCronTriggersRow, error)
|
||||
GetActivePresetPrebuildSchedules(ctx context.Context) ([]TemplateVersionPresetPrebuildSchedule, error)
|
||||
GetActiveUserCount(ctx context.Context, includeSystem bool) (int64, error)
|
||||
GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error)
|
||||
@@ -223,6 +240,11 @@ type sqlcQuerier interface {
|
||||
// This function returns roles for authorization purposes. Implied member roles
|
||||
// are included.
|
||||
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
|
||||
GetChatAutomationByID(ctx context.Context, id uuid.UUID) (ChatAutomation, error)
|
||||
GetChatAutomationEventsByAutomationID(ctx context.Context, arg GetChatAutomationEventsByAutomationIDParams) ([]ChatAutomationEvent, error)
|
||||
GetChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) (ChatAutomationTrigger, error)
|
||||
GetChatAutomationTriggersByAutomationID(ctx context.Context, automationID uuid.UUID) ([]ChatAutomationTrigger, error)
|
||||
GetChatAutomations(ctx context.Context, arg GetChatAutomationsParams) ([]ChatAutomation, error)
|
||||
GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error)
|
||||
GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Chat, error)
|
||||
// Per-root-chat cost breakdown for a single user within a date range.
|
||||
@@ -696,6 +718,9 @@ type sqlcQuerier interface {
|
||||
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
|
||||
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
|
||||
InsertChat(ctx context.Context, arg InsertChatParams) (Chat, error)
|
||||
InsertChatAutomation(ctx context.Context, arg InsertChatAutomationParams) (ChatAutomation, error)
|
||||
InsertChatAutomationEvent(ctx context.Context, arg InsertChatAutomationEventParams) (ChatAutomationEvent, error)
|
||||
InsertChatAutomationTrigger(ctx context.Context, arg InsertChatAutomationTriggerParams) (ChatAutomationTrigger, error)
|
||||
InsertChatFile(ctx context.Context, arg InsertChatFileParams) (InsertChatFileRow, error)
|
||||
InsertChatMessages(ctx context.Context, arg InsertChatMessagesParams) ([]ChatMessage, error)
|
||||
InsertChatModelConfig(ctx context.Context, arg InsertChatModelConfigParams) (ChatModelConfig, error)
|
||||
@@ -822,6 +847,10 @@ type sqlcQuerier interface {
|
||||
// sequence, so this is acceptable.
|
||||
PinChatByID(ctx context.Context, id uuid.UUID) error
|
||||
PopNextQueuedMessage(ctx context.Context, chatID uuid.UUID) (ChatQueuedMessage, error)
|
||||
// Deletes old chat automation events in bounded batches to avoid
|
||||
// long-running locks on high-volume tables. Callers should loop
|
||||
// until zero rows are returned.
|
||||
PurgeOldChatAutomationEvents(ctx context.Context, arg PurgeOldChatAutomationEventsParams) (int64, error)
|
||||
ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error
|
||||
RegisterWorkspaceProxy(ctx context.Context, arg RegisterWorkspaceProxyParams) (WorkspaceProxy, error)
|
||||
RemoveUserFromGroups(ctx context.Context, arg RemoveUserFromGroupsParams) ([]uuid.UUID, error)
|
||||
@@ -852,6 +881,10 @@ type sqlcQuerier interface {
|
||||
UnsetDefaultChatModelConfigs(ctx context.Context) error
|
||||
UpdateAIBridgeInterceptionEnded(ctx context.Context, arg UpdateAIBridgeInterceptionEndedParams) (AIBridgeInterception, error)
|
||||
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
|
||||
UpdateChatAutomation(ctx context.Context, arg UpdateChatAutomationParams) (ChatAutomation, error)
|
||||
UpdateChatAutomationTrigger(ctx context.Context, arg UpdateChatAutomationTriggerParams) (ChatAutomationTrigger, error)
|
||||
UpdateChatAutomationTriggerLastTriggeredAt(ctx context.Context, arg UpdateChatAutomationTriggerLastTriggeredAtParams) error
|
||||
UpdateChatAutomationTriggerWebhookSecret(ctx context.Context, arg UpdateChatAutomationTriggerWebhookSecretParams) (ChatAutomationTrigger, error)
|
||||
UpdateChatBuildAgentBinding(ctx context.Context, arg UpdateChatBuildAgentBindingParams) (Chat, error)
|
||||
UpdateChatByID(ctx context.Context, arg UpdateChatByIDParams) (Chat, error)
|
||||
// Bumps the heartbeat timestamp for a running chat so that other
|
||||
|
||||
+866
-20
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
-- name: InsertChatAutomationEvent :one
|
||||
INSERT INTO chat_automation_events (
|
||||
id,
|
||||
automation_id,
|
||||
trigger_id,
|
||||
received_at,
|
||||
payload,
|
||||
filter_matched,
|
||||
resolved_labels,
|
||||
matched_chat_id,
|
||||
created_chat_id,
|
||||
status,
|
||||
error
|
||||
) VALUES (
|
||||
@id::uuid,
|
||||
@automation_id::uuid,
|
||||
sqlc.narg('trigger_id')::uuid,
|
||||
@received_at::timestamptz,
|
||||
@payload::jsonb,
|
||||
@filter_matched::boolean,
|
||||
sqlc.narg('resolved_labels')::jsonb,
|
||||
sqlc.narg('matched_chat_id')::uuid,
|
||||
sqlc.narg('created_chat_id')::uuid,
|
||||
@status::chat_automation_event_status,
|
||||
sqlc.narg('error')::text
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetChatAutomationEventsByAutomationID :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
chat_automation_events
|
||||
WHERE
|
||||
automation_id = @automation_id::uuid
|
||||
AND CASE
|
||||
WHEN sqlc.narg('status_filter')::chat_automation_event_status IS NOT NULL THEN status = sqlc.narg('status_filter')::chat_automation_event_status
|
||||
ELSE true
|
||||
END
|
||||
ORDER BY
|
||||
received_at DESC
|
||||
OFFSET @offset_opt
|
||||
LIMIT
|
||||
COALESCE(NULLIF(@limit_opt :: int, 0), 50);
|
||||
|
||||
-- name: CountChatAutomationChatCreatesInWindow :one
|
||||
-- Counts new-chat events in the rate-limit window. This count is
|
||||
-- approximate under concurrency: concurrent webhook handlers may
|
||||
-- each read the same count before any of them insert, so brief
|
||||
-- bursts can slightly exceed the configured cap.
|
||||
SELECT COUNT(*)
|
||||
FROM chat_automation_events
|
||||
WHERE automation_id = @automation_id::uuid
|
||||
AND status = 'created'
|
||||
AND received_at > @window_start::timestamptz;
|
||||
|
||||
-- name: CountChatAutomationMessagesInWindow :one
|
||||
-- Counts total message events (creates + continues) in the rate-limit
|
||||
-- window. This count is approximate under concurrency: concurrent
|
||||
-- webhook handlers may each read the same count before any of them
|
||||
-- insert, so brief bursts can slightly exceed the configured cap.
|
||||
SELECT COUNT(*)
|
||||
FROM chat_automation_events
|
||||
WHERE automation_id = @automation_id::uuid
|
||||
AND status IN ('created', 'continued')
|
||||
AND received_at > @window_start::timestamptz;
|
||||
|
||||
-- name: PurgeOldChatAutomationEvents :execrows
|
||||
-- Deletes old chat automation events in bounded batches to avoid
|
||||
-- long-running locks on high-volume tables. Callers should loop
|
||||
-- until zero rows are returned.
|
||||
WITH old_events AS (
|
||||
SELECT id
|
||||
FROM chat_automation_events
|
||||
WHERE received_at < @before::timestamptz
|
||||
ORDER BY received_at ASC
|
||||
LIMIT @limit_count
|
||||
)
|
||||
DELETE FROM chat_automation_events
|
||||
USING old_events
|
||||
WHERE chat_automation_events.id = old_events.id;
|
||||
@@ -0,0 +1,85 @@
|
||||
-- name: InsertChatAutomation :one
|
||||
INSERT INTO chat_automations (
|
||||
id,
|
||||
owner_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
instructions,
|
||||
model_config_id,
|
||||
mcp_server_ids,
|
||||
allowed_tools,
|
||||
status,
|
||||
max_chat_creates_per_hour,
|
||||
max_messages_per_hour,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id::uuid,
|
||||
@owner_id::uuid,
|
||||
@organization_id::uuid,
|
||||
@name::text,
|
||||
@description::text,
|
||||
@instructions::text,
|
||||
sqlc.narg('model_config_id')::uuid,
|
||||
COALESCE(@mcp_server_ids::uuid[], '{}'::uuid[]),
|
||||
COALESCE(@allowed_tools::text[], '{}'::text[]),
|
||||
@status::chat_automation_status,
|
||||
@max_chat_creates_per_hour::integer,
|
||||
@max_messages_per_hour::integer,
|
||||
@created_at::timestamptz,
|
||||
@updated_at::timestamptz
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetChatAutomationByID :one
|
||||
SELECT * FROM chat_automations WHERE id = @id::uuid;
|
||||
|
||||
-- name: GetChatAutomations :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
chat_automations
|
||||
WHERE
|
||||
CASE
|
||||
WHEN @owner_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN chat_automations.owner_id = @owner_id
|
||||
ELSE true
|
||||
END
|
||||
AND CASE
|
||||
WHEN @organization_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN chat_automations.organization_id = @organization_id
|
||||
ELSE true
|
||||
END
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedChatAutomations
|
||||
-- @authorize_filter
|
||||
ORDER BY
|
||||
created_at DESC, id DESC
|
||||
OFFSET @offset_opt
|
||||
LIMIT
|
||||
COALESCE(NULLIF(@limit_opt :: int, 0), 50);
|
||||
|
||||
-- name: UpdateChatAutomation :one
|
||||
UPDATE chat_automations SET
|
||||
name = @name::text,
|
||||
description = @description::text,
|
||||
instructions = @instructions::text,
|
||||
model_config_id = sqlc.narg('model_config_id')::uuid,
|
||||
mcp_server_ids = COALESCE(@mcp_server_ids::uuid[], '{}'::uuid[]),
|
||||
allowed_tools = COALESCE(@allowed_tools::text[], '{}'::text[]),
|
||||
status = @status::chat_automation_status,
|
||||
max_chat_creates_per_hour = @max_chat_creates_per_hour::integer,
|
||||
max_messages_per_hour = @max_messages_per_hour::integer,
|
||||
updated_at = @updated_at::timestamptz
|
||||
WHERE id = @id::uuid
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteChatAutomationByID :exec
|
||||
DELETE FROM chat_automations WHERE id = @id::uuid;
|
||||
|
||||
-- name: CleanupDeletedMCPServerIDsFromChatAutomations :exec
|
||||
UPDATE chat_automations
|
||||
SET mcp_server_ids = (
|
||||
SELECT COALESCE(array_agg(sid), '{}')
|
||||
FROM unnest(chat_automations.mcp_server_ids) AS sid
|
||||
WHERE sid IN (SELECT id FROM mcp_server_configs)
|
||||
)
|
||||
WHERE mcp_server_ids != '{}'
|
||||
AND NOT (mcp_server_ids <@ COALESCE((SELECT array_agg(id) FROM mcp_server_configs), '{}'));
|
||||
@@ -0,0 +1,87 @@
|
||||
-- name: InsertChatAutomationTrigger :one
|
||||
INSERT INTO chat_automation_triggers (
|
||||
id,
|
||||
automation_id,
|
||||
type,
|
||||
webhook_secret,
|
||||
webhook_secret_key_id,
|
||||
cron_schedule,
|
||||
filter,
|
||||
label_paths,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id::uuid,
|
||||
@automation_id::uuid,
|
||||
@type::chat_automation_trigger_type,
|
||||
sqlc.narg('webhook_secret')::text,
|
||||
sqlc.narg('webhook_secret_key_id')::text,
|
||||
sqlc.narg('cron_schedule')::text,
|
||||
sqlc.narg('filter')::jsonb,
|
||||
sqlc.narg('label_paths')::jsonb,
|
||||
@created_at::timestamptz,
|
||||
@updated_at::timestamptz
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetChatAutomationTriggerByID :one
|
||||
SELECT * FROM chat_automation_triggers WHERE id = @id::uuid;
|
||||
|
||||
-- name: GetChatAutomationTriggersByAutomationID :many
|
||||
SELECT * FROM chat_automation_triggers
|
||||
WHERE automation_id = @automation_id::uuid
|
||||
ORDER BY created_at ASC;
|
||||
|
||||
-- name: UpdateChatAutomationTrigger :one
|
||||
UPDATE chat_automation_triggers SET
|
||||
cron_schedule = COALESCE(sqlc.narg('cron_schedule'), cron_schedule),
|
||||
filter = COALESCE(sqlc.narg('filter'), filter),
|
||||
label_paths = COALESCE(sqlc.narg('label_paths'), label_paths),
|
||||
updated_at = @updated_at::timestamptz
|
||||
WHERE id = @id::uuid
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateChatAutomationTriggerWebhookSecret :one
|
||||
UPDATE chat_automation_triggers SET
|
||||
webhook_secret = sqlc.narg('webhook_secret')::text,
|
||||
webhook_secret_key_id = sqlc.narg('webhook_secret_key_id')::text,
|
||||
updated_at = @updated_at::timestamptz
|
||||
WHERE id = @id::uuid
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteChatAutomationTriggerByID :exec
|
||||
DELETE FROM chat_automation_triggers WHERE id = @id::uuid;
|
||||
|
||||
-- name: GetActiveChatAutomationCronTriggers :many
|
||||
-- Returns all cron triggers whose parent automation is active or in
|
||||
-- preview mode. The scheduler uses this to evaluate which triggers
|
||||
-- are due.
|
||||
SELECT
|
||||
t.id,
|
||||
t.automation_id,
|
||||
t.type,
|
||||
t.cron_schedule,
|
||||
t.filter,
|
||||
t.label_paths,
|
||||
t.last_triggered_at,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
a.status AS automation_status,
|
||||
a.owner_id AS automation_owner_id,
|
||||
a.instructions AS automation_instructions,
|
||||
a.name AS automation_name,
|
||||
a.organization_id AS automation_organization_id,
|
||||
a.model_config_id AS automation_model_config_id,
|
||||
a.mcp_server_ids AS automation_mcp_server_ids,
|
||||
a.allowed_tools AS automation_allowed_tools,
|
||||
a.max_chat_creates_per_hour AS automation_max_chat_creates_per_hour,
|
||||
a.max_messages_per_hour AS automation_max_messages_per_hour
|
||||
FROM chat_automation_triggers t
|
||||
JOIN chat_automations a ON a.id = t.automation_id
|
||||
WHERE t.type = 'cron'
|
||||
AND t.cron_schedule IS NOT NULL
|
||||
AND a.status IN ('active', 'preview');
|
||||
|
||||
-- name: UpdateChatAutomationTriggerLastTriggeredAt :exec
|
||||
UPDATE chat_automation_triggers
|
||||
SET last_triggered_at = @last_triggered_at::timestamptz
|
||||
WHERE id = @id::uuid;
|
||||
@@ -247,6 +247,8 @@ sql:
|
||||
mcp_server_tool_snapshots: MCPServerToolSnapshots
|
||||
mcp_server_config_id: MCPServerConfigID
|
||||
mcp_server_ids: MCPServerIDs
|
||||
automation_mcp_server_ids: AutomationMCPServerIDs
|
||||
webhook_secret_key_id: WebhookSecretKeyID
|
||||
icon_url: IconURL
|
||||
oauth2_client_id: OAuth2ClientID
|
||||
oauth2_client_secret: OAuth2ClientSecret
|
||||
|
||||
@@ -15,6 +15,9 @@ const (
|
||||
UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
UniqueBoundaryUsageStatsPkey UniqueConstraint = "boundary_usage_stats_pkey" // ALTER TABLE ONLY boundary_usage_stats ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
|
||||
UniqueChatAutomationEventsPkey UniqueConstraint = "chat_automation_events_pkey" // ALTER TABLE ONLY chat_automation_events ADD CONSTRAINT chat_automation_events_pkey PRIMARY KEY (id);
|
||||
UniqueChatAutomationTriggersPkey UniqueConstraint = "chat_automation_triggers_pkey" // ALTER TABLE ONLY chat_automation_triggers ADD CONSTRAINT chat_automation_triggers_pkey PRIMARY KEY (id);
|
||||
UniqueChatAutomationsPkey UniqueConstraint = "chat_automations_pkey" // ALTER TABLE ONLY chat_automations ADD CONSTRAINT chat_automations_pkey PRIMARY KEY (id);
|
||||
UniqueChatDiffStatusesPkey UniqueConstraint = "chat_diff_statuses_pkey" // ALTER TABLE ONLY chat_diff_statuses ADD CONSTRAINT chat_diff_statuses_pkey PRIMARY KEY (chat_id);
|
||||
UniqueChatFilesPkey UniqueConstraint = "chat_files_pkey" // ALTER TABLE ONLY chat_files ADD CONSTRAINT chat_files_pkey PRIMARY KEY (id);
|
||||
UniqueChatMessagesPkey UniqueConstraint = "chat_messages_pkey" // ALTER TABLE ONLY chat_messages ADD CONSTRAINT chat_messages_pkey PRIMARY KEY (id);
|
||||
@@ -125,6 +128,7 @@ const (
|
||||
UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id);
|
||||
UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id);
|
||||
UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type);
|
||||
UniqueIndexChatAutomationsOwnerOrgName UniqueConstraint = "idx_chat_automations_owner_org_name" // CREATE UNIQUE INDEX idx_chat_automations_owner_org_name ON chat_automations USING btree (owner_id, organization_id, name);
|
||||
UniqueIndexChatModelConfigsSingleDefault UniqueConstraint = "idx_chat_model_configs_single_default" // CREATE UNIQUE INDEX idx_chat_model_configs_single_default ON chat_model_configs USING btree ((1)) WHERE ((is_default = true) AND (deleted = false));
|
||||
UniqueIndexConnectionLogsConnectionIDWorkspaceIDAgentName UniqueConstraint = "idx_connection_logs_connection_id_workspace_id_agent_name" // CREATE UNIQUE INDEX idx_connection_logs_connection_id_workspace_id_agent_name ON connection_logs USING btree (connection_id, workspace_id, agent_name);
|
||||
UniqueIndexCustomRolesNameLowerOrganizationID UniqueConstraint = "idx_custom_roles_name_lower_organization_id" // CREATE UNIQUE INDEX idx_custom_roles_name_lower_organization_id ON custom_roles USING btree (lower(name), COALESCE(organization_id, '00000000-0000-0000-0000-000000000000'::uuid));
|
||||
|
||||
@@ -82,6 +82,16 @@ var (
|
||||
Type: "chat",
|
||||
}
|
||||
|
||||
// ResourceChatAutomation
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create a chat automation
|
||||
// - "ActionDelete" :: delete a chat automation
|
||||
// - "ActionRead" :: read chat automation configuration
|
||||
// - "ActionUpdate" :: update a chat automation
|
||||
ResourceChatAutomation = Object{
|
||||
Type: "chat_automation",
|
||||
}
|
||||
|
||||
// ResourceConnectionLog
|
||||
// Valid Actions
|
||||
// - "ActionRead" :: read connection logs
|
||||
@@ -440,6 +450,7 @@ func AllResources() []Objecter {
|
||||
ResourceAuditLog,
|
||||
ResourceBoundaryUsage,
|
||||
ResourceChat,
|
||||
ResourceChatAutomation,
|
||||
ResourceConnectionLog,
|
||||
ResourceCryptoKey,
|
||||
ResourceDebugInfo,
|
||||
|
||||
@@ -84,6 +84,13 @@ var chatActions = map[Action]ActionDefinition{
|
||||
ActionDelete: "delete a chat",
|
||||
}
|
||||
|
||||
var chatAutomationActions = map[Action]ActionDefinition{
|
||||
ActionCreate: "create a chat automation",
|
||||
ActionRead: "read chat automation configuration",
|
||||
ActionUpdate: "update a chat automation",
|
||||
ActionDelete: "delete a chat automation",
|
||||
}
|
||||
|
||||
// RBACPermissions is indexed by the type
|
||||
var RBACPermissions = map[string]PermissionDefinition{
|
||||
// Wildcard is every object, and the action "*" provides all actions.
|
||||
@@ -113,6 +120,9 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
"chat": {
|
||||
Actions: chatActions,
|
||||
},
|
||||
"chat_automation": {
|
||||
Actions: chatAutomationActions,
|
||||
},
|
||||
// Dormant workspaces have the same perms as workspaces.
|
||||
"workspace_dormant": {
|
||||
Actions: workspaceActions,
|
||||
|
||||
@@ -1082,6 +1082,19 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Chat automations are admin-managed. Regular org
|
||||
// members cannot manage automations even if they
|
||||
// are the owner. The owner_id field is for audit
|
||||
// tracking, not RBAC grants.
|
||||
Name: "ChatAutomation",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceChatAutomation.InOrg(orgID).WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner, orgAdmin},
|
||||
false: {setOtherOrg, memberMe, agentsAccessUser, orgAuditor, orgUserAdmin, orgTemplateAdmin, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
}
|
||||
// Build coverage set from test case definitions statically,
|
||||
// so we don't need shared mutable state during execution.
|
||||
|
||||
@@ -32,6 +32,10 @@ const (
|
||||
ScopeChatDelete ScopeName = "chat:delete"
|
||||
ScopeChatRead ScopeName = "chat:read"
|
||||
ScopeChatUpdate ScopeName = "chat:update"
|
||||
ScopeChatAutomationCreate ScopeName = "chat_automation:create"
|
||||
ScopeChatAutomationDelete ScopeName = "chat_automation:delete"
|
||||
ScopeChatAutomationRead ScopeName = "chat_automation:read"
|
||||
ScopeChatAutomationUpdate ScopeName = "chat_automation:update"
|
||||
ScopeConnectionLogRead ScopeName = "connection_log:read"
|
||||
ScopeConnectionLogUpdate ScopeName = "connection_log:update"
|
||||
ScopeCryptoKeyCreate ScopeName = "crypto_key:create"
|
||||
@@ -196,6 +200,10 @@ func (e ScopeName) Valid() bool {
|
||||
ScopeChatDelete,
|
||||
ScopeChatRead,
|
||||
ScopeChatUpdate,
|
||||
ScopeChatAutomationCreate,
|
||||
ScopeChatAutomationDelete,
|
||||
ScopeChatAutomationRead,
|
||||
ScopeChatAutomationUpdate,
|
||||
ScopeConnectionLogRead,
|
||||
ScopeConnectionLogUpdate,
|
||||
ScopeCryptoKeyCreate,
|
||||
@@ -361,6 +369,10 @@ func AllScopeNameValues() []ScopeName {
|
||||
ScopeChatDelete,
|
||||
ScopeChatRead,
|
||||
ScopeChatUpdate,
|
||||
ScopeChatAutomationCreate,
|
||||
ScopeChatAutomationDelete,
|
||||
ScopeChatAutomationRead,
|
||||
ScopeChatAutomationUpdate,
|
||||
ScopeConnectionLogRead,
|
||||
ScopeConnectionLogUpdate,
|
||||
ScopeCryptoKeyCreate,
|
||||
|
||||
@@ -38,6 +38,11 @@ const (
|
||||
APIKeyScopeChatDelete APIKeyScope = "chat:delete"
|
||||
APIKeyScopeChatRead APIKeyScope = "chat:read"
|
||||
APIKeyScopeChatUpdate APIKeyScope = "chat:update"
|
||||
APIKeyScopeChatAutomationAll APIKeyScope = "chat_automation:*"
|
||||
APIKeyScopeChatAutomationCreate APIKeyScope = "chat_automation:create"
|
||||
APIKeyScopeChatAutomationDelete APIKeyScope = "chat_automation:delete"
|
||||
APIKeyScopeChatAutomationRead APIKeyScope = "chat_automation:read"
|
||||
APIKeyScopeChatAutomationUpdate APIKeyScope = "chat_automation:update"
|
||||
APIKeyScopeCoderAll APIKeyScope = "coder:all"
|
||||
APIKeyScopeCoderApikeysManageSelf APIKeyScope = "coder:apikeys.manage_self"
|
||||
APIKeyScopeCoderApplicationConnect APIKeyScope = "coder:application_connect"
|
||||
|
||||
@@ -12,6 +12,7 @@ const (
|
||||
ResourceAuditLog RBACResource = "audit_log"
|
||||
ResourceBoundaryUsage RBACResource = "boundary_usage"
|
||||
ResourceChat RBACResource = "chat"
|
||||
ResourceChatAutomation RBACResource = "chat_automation"
|
||||
ResourceConnectionLog RBACResource = "connection_log"
|
||||
ResourceCryptoKey RBACResource = "crypto_key"
|
||||
ResourceDebugInfo RBACResource = "debug_info"
|
||||
@@ -84,6 +85,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceAuditLog: {ActionCreate, ActionRead},
|
||||
ResourceBoundaryUsage: {ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceChat: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceChatAutomation: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceConnectionLog: {ActionRead, ActionUpdate},
|
||||
ResourceCryptoKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceDebugInfo: {ActionRead},
|
||||
|
||||
Generated
+20
-20
@@ -193,10 +193,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -326,10 +326,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -459,10 +459,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -554,10 +554,10 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
@@ -960,9 +960,9 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Property | Value(s) |
|
||||
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `action` | `application_connect`, `assign`, `create`, `create_agent`, `delete`, `delete_agent`, `read`, `read_personal`, `share`, `ssh`, `start`, `stop`, `unassign`, `update`, `update_agent`, `update_personal`, `use`, `view_insights` |
|
||||
| `resource_type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
Generated
+6
-6
@@ -1286,9 +1286,9 @@
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `chat:*`, `chat:create`, `chat:delete`, `chat:read`, `chat:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace:update_agent`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_dormant:update_agent`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` |
|
||||
| Value(s) |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `aibridge_interception:*`, `aibridge_interception:create`, `aibridge_interception:read`, `aibridge_interception:update`, `all`, `api_key:*`, `api_key:create`, `api_key:delete`, `api_key:read`, `api_key:update`, `application_connect`, `assign_org_role:*`, `assign_org_role:assign`, `assign_org_role:create`, `assign_org_role:delete`, `assign_org_role:read`, `assign_org_role:unassign`, `assign_org_role:update`, `assign_role:*`, `assign_role:assign`, `assign_role:read`, `assign_role:unassign`, `audit_log:*`, `audit_log:create`, `audit_log:read`, `boundary_usage:*`, `boundary_usage:delete`, `boundary_usage:read`, `boundary_usage:update`, `chat:*`, `chat:create`, `chat:delete`, `chat:read`, `chat:update`, `chat_automation:*`, `chat_automation:create`, `chat_automation:delete`, `chat_automation:read`, `chat_automation:update`, `coder:all`, `coder:apikeys.manage_self`, `coder:application_connect`, `coder:templates.author`, `coder:templates.build`, `coder:workspaces.access`, `coder:workspaces.create`, `coder:workspaces.delete`, `coder:workspaces.operate`, `connection_log:*`, `connection_log:read`, `connection_log:update`, `crypto_key:*`, `crypto_key:create`, `crypto_key:delete`, `crypto_key:read`, `crypto_key:update`, `debug_info:*`, `debug_info:read`, `deployment_config:*`, `deployment_config:read`, `deployment_config:update`, `deployment_stats:*`, `deployment_stats:read`, `file:*`, `file:create`, `file:read`, `group:*`, `group:create`, `group:delete`, `group:read`, `group:update`, `group_member:*`, `group_member:read`, `idpsync_settings:*`, `idpsync_settings:read`, `idpsync_settings:update`, `inbox_notification:*`, `inbox_notification:create`, `inbox_notification:read`, `inbox_notification:update`, `license:*`, `license:create`, `license:delete`, `license:read`, `notification_message:*`, `notification_message:create`, `notification_message:delete`, `notification_message:read`, `notification_message:update`, `notification_preference:*`, `notification_preference:read`, `notification_preference:update`, `notification_template:*`, `notification_template:read`, `notification_template:update`, `oauth2_app:*`, `oauth2_app:create`, `oauth2_app:delete`, `oauth2_app:read`, `oauth2_app:update`, `oauth2_app_code_token:*`, `oauth2_app_code_token:create`, `oauth2_app_code_token:delete`, `oauth2_app_code_token:read`, `oauth2_app_secret:*`, `oauth2_app_secret:create`, `oauth2_app_secret:delete`, `oauth2_app_secret:read`, `oauth2_app_secret:update`, `organization:*`, `organization:create`, `organization:delete`, `organization:read`, `organization:update`, `organization_member:*`, `organization_member:create`, `organization_member:delete`, `organization_member:read`, `organization_member:update`, `prebuilt_workspace:*`, `prebuilt_workspace:delete`, `prebuilt_workspace:update`, `provisioner_daemon:*`, `provisioner_daemon:create`, `provisioner_daemon:delete`, `provisioner_daemon:read`, `provisioner_daemon:update`, `provisioner_jobs:*`, `provisioner_jobs:create`, `provisioner_jobs:read`, `provisioner_jobs:update`, `replicas:*`, `replicas:read`, `system:*`, `system:create`, `system:delete`, `system:read`, `system:update`, `tailnet_coordinator:*`, `tailnet_coordinator:create`, `tailnet_coordinator:delete`, `tailnet_coordinator:read`, `tailnet_coordinator:update`, `task:*`, `task:create`, `task:delete`, `task:read`, `task:update`, `template:*`, `template:create`, `template:delete`, `template:read`, `template:update`, `template:use`, `template:view_insights`, `usage_event:*`, `usage_event:create`, `usage_event:read`, `usage_event:update`, `user:*`, `user:create`, `user:delete`, `user:read`, `user:read_personal`, `user:update`, `user:update_personal`, `user_secret:*`, `user_secret:create`, `user_secret:delete`, `user_secret:read`, `user_secret:update`, `webpush_subscription:*`, `webpush_subscription:create`, `webpush_subscription:delete`, `webpush_subscription:read`, `workspace:*`, `workspace:application_connect`, `workspace:create`, `workspace:create_agent`, `workspace:delete`, `workspace:delete_agent`, `workspace:read`, `workspace:share`, `workspace:ssh`, `workspace:start`, `workspace:stop`, `workspace:update`, `workspace:update_agent`, `workspace_agent_devcontainers:*`, `workspace_agent_devcontainers:create`, `workspace_agent_resource_monitor:*`, `workspace_agent_resource_monitor:create`, `workspace_agent_resource_monitor:read`, `workspace_agent_resource_monitor:update`, `workspace_dormant:*`, `workspace_dormant:application_connect`, `workspace_dormant:create`, `workspace_dormant:create_agent`, `workspace_dormant:delete`, `workspace_dormant:delete_agent`, `workspace_dormant:read`, `workspace_dormant:share`, `workspace_dormant:ssh`, `workspace_dormant:start`, `workspace_dormant:stop`, `workspace_dormant:update`, `workspace_dormant:update_agent`, `workspace_proxy:*`, `workspace_proxy:create`, `workspace_proxy:delete`, `workspace_proxy:read`, `workspace_proxy:update` |
|
||||
|
||||
## codersdk.AddLicenseRequest
|
||||
|
||||
@@ -8080,9 +8080,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| Value(s) |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
|
||||
## codersdk.RateLimitConfig
|
||||
|
||||
|
||||
Generated
+5
-5
@@ -849,11 +849,11 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| `login_type` | `github`, `oidc`, `password`, `token` |
|
||||
| `scope` | `all`, `application_connect` |
|
||||
| Property | Value(s) |
|
||||
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `type` | `*`, `aibridge_interception`, `api_key`, `assign_org_role`, `assign_role`, `audit_log`, `boundary_usage`, `chat`, `chat_automation`, `connection_log`, `crypto_key`, `debug_info`, `deployment_config`, `deployment_stats`, `file`, `group`, `group_member`, `idpsync_settings`, `inbox_notification`, `license`, `notification_message`, `notification_preference`, `notification_template`, `oauth2_app`, `oauth2_app_code_token`, `oauth2_app_secret`, `organization`, `organization_member`, `prebuilt_workspace`, `provisioner_daemon`, `provisioner_jobs`, `replicas`, `system`, `tailnet_coordinator`, `task`, `template`, `usage_event`, `user`, `user_secret`, `webpush_subscription`, `workspace`, `workspace_agent_devcontainers`, `workspace_agent_resource_monitor`, `workspace_dormant`, `workspace_proxy` |
|
||||
| `login_type` | `github`, `oidc`, `password`, `token` |
|
||||
| `scope` | `all`, `application_connect` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
)
|
||||
|
||||
// Rotate rotates the database encryption keys by re-encrypting all user tokens
|
||||
@@ -109,6 +111,30 @@ func Rotate(ctx context.Context, log slog.Logger, sqlDB *sql.DB, ciphers []Ciphe
|
||||
log.Debug(ctx, "encrypted chat provider key", slog.F("provider", provider.Provider), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
}
|
||||
|
||||
// Re-encrypt chat automation webhook secrets.
|
||||
triggerIDs, err := fetchEncryptedTriggerIDs(ctx, sqlDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(ctx, "encrypting chat automation webhook secrets", slog.F("trigger_count", len(triggerIDs)))
|
||||
for _, triggerID := range triggerIDs {
|
||||
trigger, err := cryptDB.GetChatAutomationTriggerByID(ctx, triggerID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get chat automation trigger %s: %w", triggerID, err)
|
||||
}
|
||||
if trigger.WebhookSecretKeyID.String == ciphers[0].HexDigest() {
|
||||
continue // Already encrypted with the primary key.
|
||||
}
|
||||
if _, err := cryptDB.UpdateChatAutomationTriggerWebhookSecret(ctx, database.UpdateChatAutomationTriggerWebhookSecretParams{
|
||||
WebhookSecret: trigger.WebhookSecret, // decrypted by cryptDB
|
||||
WebhookSecretKeyID: sql.NullString{}, // dbcrypt will set the new primary key
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: triggerID,
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("re-encrypt chat automation trigger %s: %w", triggerID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke old keys
|
||||
for _, c := range ciphers[1:] {
|
||||
if err := db.RevokeDBCryptKey(ctx, c.HexDigest()); err != nil {
|
||||
@@ -221,6 +247,27 @@ func Decrypt(ctx context.Context, log slog.Logger, sqlDB *sql.DB, ciphers []Ciph
|
||||
log.Debug(ctx, "decrypted chat provider key", slog.F("provider", provider.Provider), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
}
|
||||
|
||||
// Decrypt chat automation webhook secrets.
|
||||
triggerIDs, err := fetchEncryptedTriggerIDs(ctx, sqlDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(ctx, "decrypting chat automation webhook secrets", slog.F("trigger_count", len(triggerIDs)))
|
||||
for _, triggerID := range triggerIDs {
|
||||
trigger, err := cryptDB.GetChatAutomationTriggerByID(ctx, triggerID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get chat automation trigger %s: %w", triggerID, err)
|
||||
}
|
||||
if _, err := cryptDB.UpdateChatAutomationTriggerWebhookSecret(ctx, database.UpdateChatAutomationTriggerWebhookSecretParams{
|
||||
WebhookSecret: trigger.WebhookSecret, // decrypted by cryptDB
|
||||
WebhookSecretKeyID: sql.NullString{}, // store in plaintext
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ID: triggerID,
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("decrypt chat automation trigger %s: %w", triggerID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke _all_ keys
|
||||
for _, c := range ciphers {
|
||||
if err := db.RevokeDBCryptKey(ctx, c.HexDigest()); err != nil {
|
||||
@@ -245,9 +292,33 @@ UPDATE chat_providers
|
||||
SET api_key = '',
|
||||
api_key_key_id = NULL
|
||||
WHERE api_key_key_id IS NOT NULL;
|
||||
DELETE FROM chat_automation_triggers WHERE webhook_secret_key_id IS NOT NULL;
|
||||
COMMIT;
|
||||
`
|
||||
|
||||
// fetchEncryptedTriggerIDs returns the IDs of all chat automation
|
||||
// triggers that have an encrypted webhook secret. It uses a raw
|
||||
// SQL query because there is no "get all triggers" SQLC query.
|
||||
func fetchEncryptedTriggerIDs(ctx context.Context, sqlDB *sql.DB) ([]uuid.UUID, error) {
|
||||
rows, err := sqlDB.QueryContext(ctx, `SELECT id FROM chat_automation_triggers WHERE webhook_secret_key_id IS NOT NULL`)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get encrypted chat automation triggers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var ids []uuid.UUID
|
||||
for rows.Next() {
|
||||
var id uuid.UUID
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, xerrors.Errorf("scan chat automation trigger id: %w", err)
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, xerrors.Errorf("iterate chat automation trigger ids: %w", err)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Delete deletes all user tokens and revokes all ciphers.
|
||||
// This is a destructive operation and should only be used
|
||||
// as a last resort, for example, if the database encryption key has been
|
||||
@@ -256,9 +327,9 @@ func Delete(ctx context.Context, log slog.Logger, sqlDB *sql.DB) error {
|
||||
store := database.New(sqlDB)
|
||||
_, err := sqlDB.ExecContext(ctx, sqlDeleteEncryptedUserTokens)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("delete encrypted tokens and chat provider keys: %w", err)
|
||||
return xerrors.Errorf("delete encrypted tokens, chat provider keys, and chat automation webhook secrets: %w", err)
|
||||
}
|
||||
log.Info(ctx, "deleted encrypted user tokens and chat provider API keys")
|
||||
log.Info(ctx, "deleted encrypted user tokens, chat provider API keys, and chat automation webhook secrets")
|
||||
|
||||
log.Info(ctx, "revoking all active keys")
|
||||
keys, err := store.GetDBCryptKeys(ctx)
|
||||
|
||||
@@ -385,6 +385,73 @@ func (db *dbCrypt) GetCryptoKeysByFeature(ctx context.Context, feature database.
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) GetChatAutomationTriggerByID(ctx context.Context, id uuid.UUID) (database.ChatAutomationTrigger, error) {
|
||||
trigger, err := db.Store.GetChatAutomationTriggerByID(ctx, id)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := db.decryptField(&trigger.WebhookSecret.String, trigger.WebhookSecretKeyID); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return trigger, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) GetChatAutomationTriggersByAutomationID(ctx context.Context, automationID uuid.UUID) ([]database.ChatAutomationTrigger, error) {
|
||||
triggers, err := db.Store.GetChatAutomationTriggersByAutomationID(ctx, automationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range triggers {
|
||||
if err := db.decryptField(&triggers[i].WebhookSecret.String, triggers[i].WebhookSecretKeyID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return triggers, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) InsertChatAutomationTrigger(ctx context.Context, params database.InsertChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
if !params.WebhookSecret.Valid || strings.TrimSpace(params.WebhookSecret.String) == "" {
|
||||
params.WebhookSecretKeyID = sql.NullString{}
|
||||
} else if err := db.encryptField(¶ms.WebhookSecret.String, ¶ms.WebhookSecretKeyID); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
trigger, err := db.Store.InsertChatAutomationTrigger(ctx, params)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := db.decryptField(&trigger.WebhookSecret.String, trigger.WebhookSecretKeyID); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return trigger, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) UpdateChatAutomationTrigger(ctx context.Context, params database.UpdateChatAutomationTriggerParams) (database.ChatAutomationTrigger, error) {
|
||||
trigger, err := db.Store.UpdateChatAutomationTrigger(ctx, params)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := db.decryptField(&trigger.WebhookSecret.String, trigger.WebhookSecretKeyID); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return trigger, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) UpdateChatAutomationTriggerWebhookSecret(ctx context.Context, params database.UpdateChatAutomationTriggerWebhookSecretParams) (database.ChatAutomationTrigger, error) {
|
||||
if !params.WebhookSecret.Valid || strings.TrimSpace(params.WebhookSecret.String) == "" {
|
||||
params.WebhookSecretKeyID = sql.NullString{}
|
||||
} else if err := db.encryptField(¶ms.WebhookSecret.String, ¶ms.WebhookSecretKeyID); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
trigger, err := db.Store.UpdateChatAutomationTriggerWebhookSecret(ctx, params)
|
||||
if err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
if err := db.decryptField(&trigger.WebhookSecret.String, trigger.WebhookSecretKeyID); err != nil {
|
||||
return database.ChatAutomationTrigger{}, err
|
||||
}
|
||||
return trigger, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) GetChatProviderByID(ctx context.Context, id uuid.UUID) (database.ChatProvider, error) {
|
||||
provider, err := db.Store.GetChatProviderByID(ctx, id)
|
||||
if err != nil {
|
||||
|
||||
@@ -1177,3 +1177,196 @@ func TestMCPServerUserTokens(t *testing.T) {
|
||||
requireEncryptedEquals(t, ciphers[0], rawTok.RefreshToken, refreshToken)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChatAutomationTriggers(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
const (
|
||||
//nolint:gosec // test credential
|
||||
webhookSecret = "whsec-test-secret-value"
|
||||
)
|
||||
|
||||
// insertTrigger creates a user, organization, chat automation, and
|
||||
// a webhook trigger with a secret through the encrypted store.
|
||||
insertTrigger := func(t *testing.T, crypt *dbCrypt, ciphers []Cipher) database.ChatAutomationTrigger {
|
||||
t.Helper()
|
||||
user := dbgen.User(t, crypt, database.User{})
|
||||
org := dbgen.Organization(t, crypt, database.Organization{})
|
||||
now := dbtime.Now()
|
||||
automation, err := crypt.InsertChatAutomation(ctx, database.InsertChatAutomationParams{
|
||||
ID: uuid.New(),
|
||||
OwnerID: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
Name: "test-automation-" + uuid.New().String()[:8],
|
||||
Description: "test automation",
|
||||
Instructions: "do stuff",
|
||||
MCPServerIDs: []uuid.UUID{},
|
||||
AllowedTools: []string{},
|
||||
Status: database.ChatAutomationStatusActive,
|
||||
MaxChatCreatesPerHour: 10,
|
||||
MaxMessagesPerHour: 60,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
trigger, err := crypt.InsertChatAutomationTrigger(ctx, database.InsertChatAutomationTriggerParams{
|
||||
ID: uuid.New(),
|
||||
AutomationID: automation.ID,
|
||||
Type: database.ChatAutomationTriggerTypeWebhook,
|
||||
WebhookSecret: sql.NullString{String: webhookSecret, Valid: true},
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, webhookSecret, trigger.WebhookSecret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), trigger.WebhookSecretKeyID.String)
|
||||
return trigger
|
||||
}
|
||||
|
||||
// requireTriggerRawEncrypted reads the trigger from the raw
|
||||
// (unwrapped) store and asserts the secret field is encrypted.
|
||||
requireTriggerRawEncrypted := func(
|
||||
t *testing.T,
|
||||
rawDB database.Store,
|
||||
triggerID uuid.UUID,
|
||||
ciphers []Cipher,
|
||||
wantSecret string,
|
||||
) {
|
||||
t.Helper()
|
||||
raw, err := rawDB.GetChatAutomationTriggerByID(ctx, triggerID)
|
||||
require.NoError(t, err)
|
||||
requireEncryptedEquals(t, ciphers[0], raw.WebhookSecret.String, wantSecret)
|
||||
}
|
||||
|
||||
t.Run("InsertChatAutomationTrigger", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
requireTriggerRawEncrypted(t, db, trigger.ID, ciphers, webhookSecret)
|
||||
})
|
||||
|
||||
t.Run("GetChatAutomationTriggerByID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
|
||||
got, err := crypt.GetChatAutomationTriggerByID(ctx, trigger.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, webhookSecret, got.WebhookSecret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), got.WebhookSecretKeyID.String)
|
||||
requireTriggerRawEncrypted(t, db, trigger.ID, ciphers, webhookSecret)
|
||||
})
|
||||
|
||||
t.Run("GetChatAutomationTriggersByAutomationID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
|
||||
triggers, err := crypt.GetChatAutomationTriggersByAutomationID(ctx, trigger.AutomationID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, triggers, 1)
|
||||
require.Equal(t, webhookSecret, triggers[0].WebhookSecret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), triggers[0].WebhookSecretKeyID.String)
|
||||
requireTriggerRawEncrypted(t, db, trigger.ID, ciphers, webhookSecret)
|
||||
})
|
||||
|
||||
t.Run("UpdateChatAutomationTrigger", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
|
||||
// UpdateChatAutomationTrigger does not change the webhook
|
||||
// secret itself; it updates cron_schedule/filter/label_paths.
|
||||
// The returned trigger should still have the decrypted secret.
|
||||
updated, err := crypt.UpdateChatAutomationTrigger(ctx, database.UpdateChatAutomationTriggerParams{
|
||||
ID: trigger.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, webhookSecret, updated.WebhookSecret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), updated.WebhookSecretKeyID.String)
|
||||
requireTriggerRawEncrypted(t, db, trigger.ID, ciphers, webhookSecret)
|
||||
})
|
||||
|
||||
t.Run("UpdateChatAutomationTriggerWebhookSecret", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
|
||||
const newSecret = "whsec-rotated-secret" //nolint:gosec // test credential
|
||||
updated, err := crypt.UpdateChatAutomationTriggerWebhookSecret(ctx, database.UpdateChatAutomationTriggerWebhookSecretParams{
|
||||
ID: trigger.ID,
|
||||
WebhookSecret: sql.NullString{String: newSecret, Valid: true},
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newSecret, updated.WebhookSecret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), updated.WebhookSecretKeyID.String)
|
||||
requireTriggerRawEncrypted(t, db, trigger.ID, ciphers, newSecret)
|
||||
})
|
||||
|
||||
t.Run("CronTriggerThroughDecryptLoop", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
|
||||
// Insert a cron trigger under the same automation. Cron
|
||||
// triggers have no webhook secret, so the secret fields
|
||||
// should remain NULL through the encrypt/decrypt loop.
|
||||
now := dbtime.Now()
|
||||
cronTrigger, err := crypt.InsertChatAutomationTrigger(ctx, database.InsertChatAutomationTriggerParams{
|
||||
ID: uuid.New(),
|
||||
AutomationID: trigger.AutomationID,
|
||||
Type: database.ChatAutomationTriggerTypeCron,
|
||||
CronSchedule: sql.NullString{String: "0 * * * *", Valid: true},
|
||||
WebhookSecret: sql.NullString{Valid: false},
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, cronTrigger.WebhookSecret.Valid)
|
||||
require.False(t, cronTrigger.WebhookSecretKeyID.Valid)
|
||||
|
||||
// Fetch both triggers by automation ID and verify each
|
||||
// comes back with the expected secret state.
|
||||
triggers, err := crypt.GetChatAutomationTriggersByAutomationID(ctx, trigger.AutomationID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, triggers, 2)
|
||||
|
||||
for _, tr := range triggers {
|
||||
switch tr.Type {
|
||||
case database.ChatAutomationTriggerTypeWebhook:
|
||||
require.Equal(t, webhookSecret, tr.WebhookSecret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), tr.WebhookSecretKeyID.String)
|
||||
case database.ChatAutomationTriggerTypeCron:
|
||||
require.False(t, tr.WebhookSecret.Valid, "cron trigger should have NULL secret")
|
||||
require.False(t, tr.WebhookSecretKeyID.Valid, "cron trigger should have NULL key_id")
|
||||
require.Equal(t, "0 * * * *", tr.CronSchedule.String)
|
||||
default:
|
||||
t.Fatalf("unexpected trigger type: %s", tr.Type)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ClearWebhookSecretToNULL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, crypt, ciphers := setup(t)
|
||||
trigger := insertTrigger(t, crypt, ciphers)
|
||||
|
||||
// The DB schema enforces that webhook-type triggers must
|
||||
// always have a non-NULL webhook_secret via the
|
||||
// chat_automation_triggers_webhook_fields constraint.
|
||||
// Attempting to clear it to NULL should fail. This verifies
|
||||
// the dbcrypt layer correctly clears the key_id and passes
|
||||
// the NULL through to the DB, which then rejects it.
|
||||
_, err := crypt.UpdateChatAutomationTriggerWebhookSecret(ctx, database.UpdateChatAutomationTriggerWebhookSecretParams{
|
||||
ID: trigger.ID,
|
||||
WebhookSecret: sql.NullString{Valid: false},
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "chat_automation_triggers_webhook_fields")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ export const RBACResourceActions: Partial<
|
||||
read: "read chat messages and metadata",
|
||||
update: "update chat title or settings",
|
||||
},
|
||||
chat_automation: {
|
||||
create: "create a chat automation",
|
||||
delete: "delete a chat automation",
|
||||
read: "read chat automation configuration",
|
||||
update: "update a chat automation",
|
||||
},
|
||||
connection_log: {
|
||||
read: "read connection logs",
|
||||
update: "upsert connection log entries",
|
||||
|
||||
Generated
+12
@@ -320,6 +320,11 @@ export type APIKeyScope =
|
||||
| "boundary_usage:read"
|
||||
| "boundary_usage:update"
|
||||
| "chat:*"
|
||||
| "chat_automation:*"
|
||||
| "chat_automation:create"
|
||||
| "chat_automation:delete"
|
||||
| "chat_automation:read"
|
||||
| "chat_automation:update"
|
||||
| "chat:create"
|
||||
| "chat:delete"
|
||||
| "chat:read"
|
||||
@@ -529,6 +534,11 @@ export const APIKeyScopes: APIKeyScope[] = [
|
||||
"boundary_usage:read",
|
||||
"boundary_usage:update",
|
||||
"chat:*",
|
||||
"chat_automation:*",
|
||||
"chat_automation:create",
|
||||
"chat_automation:delete",
|
||||
"chat_automation:read",
|
||||
"chat_automation:update",
|
||||
"chat:create",
|
||||
"chat:delete",
|
||||
"chat:read",
|
||||
@@ -5585,6 +5595,7 @@ export type RBACResource =
|
||||
| "audit_log"
|
||||
| "boundary_usage"
|
||||
| "chat"
|
||||
| "chat_automation"
|
||||
| "connection_log"
|
||||
| "crypto_key"
|
||||
| "debug_info"
|
||||
@@ -5631,6 +5642,7 @@ export const RBACResources: RBACResource[] = [
|
||||
"audit_log",
|
||||
"boundary_usage",
|
||||
"chat",
|
||||
"chat_automation",
|
||||
"connection_log",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
|
||||
Reference in New Issue
Block a user