feat(coderd/database): add boundary_sessions and boundary_logs tables (#25441)

RFC: [Bridge ↔ Boundaries Correlation
RFC](https://www.notion.so/coderhq/Gateway-and-Firewall-Correlation-RFC-31ad579be592803aa8b3d48348ccdde9)

Add up/down migrations and matching sqlc queries for persisting Boundary
audit events, as specified in the Bridge/Boundaries Correlation RFC.

**Tables:**
- `boundary_sessions`: session metadata with `workspace_agent_id` FK,
`confined_process_name`, and timestamps (`started_at`, `updated_at`). ID
is externally supplied by the Boundary process (no DB-side default).
Created lazily when the first log for a session arrives.
- `boundary_logs`: individual audit events with `session_id` FK,
`sequence_number` (INT, primary ordering key), protocol/method/detail
fields, and `matched_rule` (nullable; non-NULL implies allowed).

**Indexes (per RFC):**
- `(session_id, sequence_number)` for the ordering query path
- `(captured_at)` for the retention purge path

**Queries:**
- `InsertBoundarySession` / `GetBoundarySessionByID`
- `InsertBoundaryLog` / `GetBoundaryLogByID`
- `ListBoundaryLogsBySessionID` with nullable `seq_after`/`seq_before`
exclusive bounds for fetching events between two known interception
sequence numbers
- `DeleteOldBoundaryLogs` with row limit to avoid long-running
transactions

**Also includes:** dbgen helpers (`BoundarySession`, `BoundaryLog`),
dbauthz implementations (reads gated on `ResourceAuditLog`, deletes on
`ResourceSystem`), and all generated wrappers (dbmock, dbmetrics).

No callers yet. A follow-up PR will add the dedicated `boundary_log`
RBAC resource type.

> Generated by Coder Agents
This commit is contained in:
Sas Swart
2026-05-25 11:14:36 +02:00
committed by GitHub
parent eddd4a8c2f
commit 3bf5f80277
16 changed files with 753 additions and 0 deletions
+1
View File
@@ -12,6 +12,7 @@ const (
CheckAiModelPricesOutputPriceCheck CheckConstraint = "ai_model_prices_output_price_check" // ai_model_prices
CheckAiProvidersNameCheck CheckConstraint = "ai_providers_name_check" // ai_providers
CheckAPIKeysAllowListNotEmpty CheckConstraint = "api_keys_allow_list_not_empty" // api_keys
CheckBoundaryLogsSequenceNumberCheck CheckConstraint = "boundary_logs_sequence_number_check" // boundary_logs
CheckChatModelConfigsAiProviderRequiredWhenActive CheckConstraint = "chat_model_configs_ai_provider_required_when_active" // chat_model_configs
CheckChatModelConfigsCompressionThresholdCheck CheckConstraint = "chat_model_configs_compression_threshold_check" // chat_model_configs
CheckChatModelConfigsContextLimitCheck CheckConstraint = "chat_model_configs_context_limit_check" // chat_model_configs
+42
View File
@@ -2161,6 +2161,14 @@ func (q *querier) DeleteOldAuditLogs(ctx context.Context, arg database.DeleteOld
return q.db.DeleteOldAuditLogs(ctx, arg)
}
// TODO (PR #24810): Replace rbac.ResourceSystem with dedicated boundary_log resource type.
func (q *querier) DeleteOldBoundaryLogs(ctx context.Context, arg database.DeleteOldBoundaryLogsParams) (int64, error) {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
return 0, err
}
return q.db.DeleteOldBoundaryLogs(ctx, arg)
}
func (q *querier) DeleteOldChatDebugRuns(ctx context.Context, arg database.DeleteOldChatDebugRunsParams) (int64, error) {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
return 0, err
@@ -2742,6 +2750,22 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
return q.db.GetAuthorizationUserRoles(ctx, userID)
}
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
func (q *querier) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
return database.BoundaryLog{}, err
}
return q.db.GetBoundaryLogByID(ctx, id)
}
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
func (q *querier) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (database.BoundarySession, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
return database.BoundarySession{}, err
}
return q.db.GetBoundarySessionByID(ctx, id)
}
func (q *querier) GetChatACLByID(ctx context.Context, id uuid.UUID) (database.GetChatACLByIDRow, error) {
chat, err := q.db.GetChatByID(ctx, id)
if err != nil {
@@ -5413,6 +5437,16 @@ func (q *querier) InsertAuditLog(ctx context.Context, arg database.InsertAuditLo
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertAuditLog)(ctx, arg)
}
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
func (q *querier) InsertBoundaryLog(ctx context.Context, arg database.InsertBoundaryLogParams) (database.BoundaryLog, error) {
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertBoundaryLog)(ctx, arg)
}
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
func (q *querier) InsertBoundarySession(ctx context.Context, arg database.InsertBoundarySessionParams) (database.BoundarySession, error) {
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertBoundarySession)(ctx, arg)
}
func (q *querier) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
return insert(q.log, q.auth, rbac.ResourceChat.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID), q.db.InsertChat)(ctx, arg)
}
@@ -6126,6 +6160,14 @@ func (q *querier) ListAIBridgeUserPromptsByInterceptionIDs(ctx context.Context,
return q.db.ListAIBridgeUserPromptsByInterceptionIDs(ctx, interceptionIDs)
}
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
func (q *querier) ListBoundaryLogsBySessionID(ctx context.Context, arg database.ListBoundaryLogsBySessionIDParams) ([]database.BoundaryLog, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
return nil, err
}
return q.db.ListBoundaryLogsBySessionID(ctx, arg)
}
func (q *querier) ListChatUsageLimitGroupOverrides(ctx context.Context) ([]database.ListChatUsageLimitGroupOverridesRow, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
+32
View File
@@ -440,6 +440,38 @@ func (s *MethodTestSuite) TestAuditLogs() {
}))
}
// TODO (PR #24810): These RBAC assertions use placeholder resource types.
// They will be updated when the dedicated boundary_log resource type is added.
func (s *MethodTestSuite) TestBoundaryLogs() {
s.Run("InsertBoundarySession", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.InsertBoundarySessionParams{}
dbm.EXPECT().InsertBoundarySession(gomock.Any(), arg).Return(database.BoundarySession{}, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceAuditLog, policy.ActionCreate)
}))
s.Run("GetBoundarySessionByID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetBoundarySessionByID(gomock.Any(), uuid.Nil).Return(database.BoundarySession{}, nil).AnyTimes()
check.Args(uuid.Nil).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
}))
s.Run("InsertBoundaryLog", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.InsertBoundaryLogParams{}
dbm.EXPECT().InsertBoundaryLog(gomock.Any(), arg).Return(database.BoundaryLog{}, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceAuditLog, policy.ActionCreate)
}))
s.Run("GetBoundaryLogByID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().GetBoundaryLogByID(gomock.Any(), uuid.Nil).Return(database.BoundaryLog{}, nil).AnyTimes()
check.Args(uuid.Nil).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
}))
s.Run("ListBoundaryLogsBySessionID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.ListBoundaryLogsBySessionIDParams{}
dbm.EXPECT().ListBoundaryLogsBySessionID(gomock.Any(), arg).Return([]database.BoundaryLog{}, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
}))
s.Run("DeleteOldBoundaryLogs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
dbm.EXPECT().DeleteOldBoundaryLogs(gomock.Any(), database.DeleteOldBoundaryLogsParams{}).Return(int64(0), nil).AnyTimes()
check.Args(database.DeleteOldBoundaryLogsParams{}).Asserts(rbac.ResourceSystem, policy.ActionDelete)
}))
}
func (s *MethodTestSuite) TestConnectionLogs() {
s.Run("BatchUpsertConnectionLogs", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.BatchUpsertConnectionLogsParams{}
+28
View File
@@ -453,6 +453,34 @@ func ConnectionLog(t testing.TB, db database.Store, seed database.UpsertConnecti
return database.ConnectionLog{} // unreachable
}
func BoundarySession(t testing.TB, db database.Store, seed database.BoundarySession) database.BoundarySession {
session, err := db.InsertBoundarySession(genCtx, database.InsertBoundarySessionParams{
ID: takeFirst(seed.ID, uuid.New()),
WorkspaceAgentID: takeFirst(seed.WorkspaceAgentID, uuid.New()),
ConfinedProcessName: takeFirst(seed.ConfinedProcessName, "claude-code"),
StartedAt: takeFirst(seed.StartedAt, dbtime.Now()),
UpdatedAt: takeFirst(seed.UpdatedAt, dbtime.Now()),
})
require.NoError(t, err, "insert boundary session")
return session
}
func BoundaryLog(t testing.TB, db database.Store, seed database.BoundaryLog) database.BoundaryLog {
log, err := db.InsertBoundaryLog(genCtx, database.InsertBoundaryLogParams{
ID: takeFirst(seed.ID, uuid.New()),
SessionID: seed.SessionID,
SequenceNumber: takeFirst(seed.SequenceNumber, 0),
CapturedAt: takeFirst(seed.CapturedAt, dbtime.Now()),
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
Proto: takeFirst(seed.Proto, "http"),
Method: takeFirst(seed.Method, "GET"),
Detail: takeFirst(seed.Detail, "https://example.com"),
MatchedRule: seed.MatchedRule,
})
require.NoError(t, err, "insert boundary log")
return log
}
func Template(t testing.TB, db database.Store, seed database.Template) database.Template {
id := takeFirst(seed.ID, uuid.New())
if seed.GroupACL == nil {
+48
View File
@@ -657,6 +657,14 @@ func (m queryMetricsStore) DeleteOldAuditLogs(ctx context.Context, arg database.
return r0, r1
}
func (m queryMetricsStore) DeleteOldBoundaryLogs(ctx context.Context, arg database.DeleteOldBoundaryLogsParams) (int64, error) {
start := time.Now()
r0, r1 := m.s.DeleteOldBoundaryLogs(ctx, arg)
m.queryLatencies.WithLabelValues("DeleteOldBoundaryLogs").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteOldBoundaryLogs").Inc()
return r0, r1
}
func (m queryMetricsStore) DeleteOldChatDebugRuns(ctx context.Context, arg database.DeleteOldChatDebugRunsParams) (int64, error) {
start := time.Now()
r0, r1 := m.s.DeleteOldChatDebugRuns(ctx, arg)
@@ -1249,6 +1257,22 @@ func (m queryMetricsStore) GetAuthorizationUserRoles(ctx context.Context, userID
return r0, r1
}
func (m queryMetricsStore) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
start := time.Now()
r0, r1 := m.s.GetBoundaryLogByID(ctx, id)
m.queryLatencies.WithLabelValues("GetBoundaryLogByID").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetBoundaryLogByID").Inc()
return r0, r1
}
func (m queryMetricsStore) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (database.BoundarySession, error) {
start := time.Now()
r0, r1 := m.s.GetBoundarySessionByID(ctx, id)
m.queryLatencies.WithLabelValues("GetBoundarySessionByID").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetBoundarySessionByID").Inc()
return r0, r1
}
func (m queryMetricsStore) GetChatACLByID(ctx context.Context, id uuid.UUID) (database.GetChatACLByIDRow, error) {
start := time.Now()
r0, r1 := m.s.GetChatACLByID(ctx, id)
@@ -3721,6 +3745,22 @@ func (m queryMetricsStore) InsertAuditLog(ctx context.Context, arg database.Inse
return r0, r1
}
func (m queryMetricsStore) InsertBoundaryLog(ctx context.Context, arg database.InsertBoundaryLogParams) (database.BoundaryLog, error) {
start := time.Now()
r0, r1 := m.s.InsertBoundaryLog(ctx, arg)
m.queryLatencies.WithLabelValues("InsertBoundaryLog").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertBoundaryLog").Inc()
return r0, r1
}
func (m queryMetricsStore) InsertBoundarySession(ctx context.Context, arg database.InsertBoundarySessionParams) (database.BoundarySession, error) {
start := time.Now()
r0, r1 := m.s.InsertBoundarySession(ctx, arg)
m.queryLatencies.WithLabelValues("InsertBoundarySession").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertBoundarySession").Inc()
return r0, r1
}
func (m queryMetricsStore) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
start := time.Now()
r0, r1 := m.s.InsertChat(ctx, arg)
@@ -4361,6 +4401,14 @@ func (m queryMetricsStore) ListAIBridgeUserPromptsByInterceptionIDs(ctx context.
return r0, r1
}
func (m queryMetricsStore) ListBoundaryLogsBySessionID(ctx context.Context, arg database.ListBoundaryLogsBySessionIDParams) ([]database.BoundaryLog, error) {
start := time.Now()
r0, r1 := m.s.ListBoundaryLogsBySessionID(ctx, arg)
m.queryLatencies.WithLabelValues("ListBoundaryLogsBySessionID").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "ListBoundaryLogsBySessionID").Inc()
return r0, r1
}
func (m queryMetricsStore) ListChatUsageLimitGroupOverrides(ctx context.Context) ([]database.ListChatUsageLimitGroupOverridesRow, error) {
start := time.Now()
r0, r1 := m.s.ListChatUsageLimitGroupOverrides(ctx)
+90
View File
@@ -1102,6 +1102,21 @@ func (mr *MockStoreMockRecorder) DeleteOldAuditLogs(ctx, arg any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOldAuditLogs", reflect.TypeOf((*MockStore)(nil).DeleteOldAuditLogs), ctx, arg)
}
// DeleteOldBoundaryLogs mocks base method.
func (m *MockStore) DeleteOldBoundaryLogs(ctx context.Context, arg database.DeleteOldBoundaryLogsParams) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteOldBoundaryLogs", ctx, arg)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DeleteOldBoundaryLogs indicates an expected call of DeleteOldBoundaryLogs.
func (mr *MockStoreMockRecorder) DeleteOldBoundaryLogs(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOldBoundaryLogs", reflect.TypeOf((*MockStore)(nil).DeleteOldBoundaryLogs), ctx, arg)
}
// DeleteOldChatDebugRuns mocks base method.
func (m *MockStore) DeleteOldChatDebugRuns(ctx context.Context, arg database.DeleteOldChatDebugRunsParams) (int64, error) {
m.ctrl.T.Helper()
@@ -2310,6 +2325,36 @@ func (mr *MockStoreMockRecorder) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedWorkspacesAndAgentsByOwnerID", reflect.TypeOf((*MockStore)(nil).GetAuthorizedWorkspacesAndAgentsByOwnerID), ctx, ownerID, prepared)
}
// GetBoundaryLogByID mocks base method.
func (m *MockStore) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBoundaryLogByID", ctx, id)
ret0, _ := ret[0].(database.BoundaryLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBoundaryLogByID indicates an expected call of GetBoundaryLogByID.
func (mr *MockStoreMockRecorder) GetBoundaryLogByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoundaryLogByID", reflect.TypeOf((*MockStore)(nil).GetBoundaryLogByID), ctx, id)
}
// GetBoundarySessionByID mocks base method.
func (m *MockStore) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (database.BoundarySession, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBoundarySessionByID", ctx, id)
ret0, _ := ret[0].(database.BoundarySession)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBoundarySessionByID indicates an expected call of GetBoundarySessionByID.
func (mr *MockStoreMockRecorder) GetBoundarySessionByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBoundarySessionByID", reflect.TypeOf((*MockStore)(nil).GetBoundarySessionByID), ctx, id)
}
// GetChatACLByID mocks base method.
func (m *MockStore) GetChatACLByID(ctx context.Context, id uuid.UUID) (database.GetChatACLByIDRow, error) {
m.ctrl.T.Helper()
@@ -6989,6 +7034,36 @@ func (mr *MockStoreMockRecorder) InsertAuditLog(ctx, arg any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAuditLog", reflect.TypeOf((*MockStore)(nil).InsertAuditLog), ctx, arg)
}
// InsertBoundaryLog mocks base method.
func (m *MockStore) InsertBoundaryLog(ctx context.Context, arg database.InsertBoundaryLogParams) (database.BoundaryLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertBoundaryLog", ctx, arg)
ret0, _ := ret[0].(database.BoundaryLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertBoundaryLog indicates an expected call of InsertBoundaryLog.
func (mr *MockStoreMockRecorder) InsertBoundaryLog(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBoundaryLog", reflect.TypeOf((*MockStore)(nil).InsertBoundaryLog), ctx, arg)
}
// InsertBoundarySession mocks base method.
func (m *MockStore) InsertBoundarySession(ctx context.Context, arg database.InsertBoundarySessionParams) (database.BoundarySession, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertBoundarySession", ctx, arg)
ret0, _ := ret[0].(database.BoundarySession)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertBoundarySession indicates an expected call of InsertBoundarySession.
func (mr *MockStoreMockRecorder) InsertBoundarySession(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBoundarySession", reflect.TypeOf((*MockStore)(nil).InsertBoundarySession), ctx, arg)
}
// InsertChat mocks base method.
func (m *MockStore) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
m.ctrl.T.Helper()
@@ -8249,6 +8324,21 @@ func (mr *MockStoreMockRecorder) ListAuthorizedAIBridgeSessions(ctx, arg, prepar
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAuthorizedAIBridgeSessions", reflect.TypeOf((*MockStore)(nil).ListAuthorizedAIBridgeSessions), ctx, arg, prepared)
}
// ListBoundaryLogsBySessionID mocks base method.
func (m *MockStore) ListBoundaryLogsBySessionID(ctx context.Context, arg database.ListBoundaryLogsBySessionIDParams) ([]database.BoundaryLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListBoundaryLogsBySessionID", ctx, arg)
ret0, _ := ret[0].([]database.BoundaryLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListBoundaryLogsBySessionID indicates an expected call of ListBoundaryLogsBySessionID.
func (mr *MockStoreMockRecorder) ListBoundaryLogsBySessionID(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBoundaryLogsBySessionID", reflect.TypeOf((*MockStore)(nil).ListBoundaryLogsBySessionID), ctx, arg)
}
// ListChatUsageLimitGroupOverrides mocks base method.
func (m *MockStore) ListChatUsageLimitGroupOverrides(ctx context.Context) ([]database.ListChatUsageLimitGroupOverridesRow, error) {
m.ctrl.T.Helper()
+67
View File
@@ -1377,6 +1377,57 @@ CREATE TABLE audit_logs (
resource_icon text NOT NULL
);
CREATE TABLE boundary_logs (
id uuid NOT NULL,
session_id uuid NOT NULL,
sequence_number integer NOT NULL,
captured_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
proto text DEFAULT ''::text NOT NULL,
method text DEFAULT ''::text NOT NULL,
detail text DEFAULT ''::text NOT NULL,
matched_rule text,
CONSTRAINT boundary_logs_sequence_number_check CHECK ((sequence_number >= 0))
);
COMMENT ON TABLE boundary_logs IS 'Persisted boundary audit events. Each row is a single audit event processed by a Boundary proxy.';
COMMENT ON COLUMN boundary_logs.session_id IS 'The session ID generated by the Boundary process on startup. Groups all events from one invocation.';
COMMENT ON COLUMN boundary_logs.sequence_number IS 'Monotonically increasing integer assigned by Boundary, starting at 0 per session. Primary ordering key when Boundary is in use.';
COMMENT ON COLUMN boundary_logs.captured_at IS 'When the log was sent to the DB.';
COMMENT ON COLUMN boundary_logs.created_at IS 'When the event happened on the workspace.';
COMMENT ON COLUMN boundary_logs.proto IS 'The protocol of the audited action. e.g. http, dns, git, fs.';
COMMENT ON COLUMN boundary_logs.method IS 'The operation within the protocol. e.g. GET/POST for http, clone for git, A for dns, read/write for fs.';
COMMENT ON COLUMN boundary_logs.detail IS 'Protocol-specific detail. e.g. the full URL for http, the hostname for dns, the path for fs.';
COMMENT ON COLUMN boundary_logs.matched_rule IS 'The allow-list rule that matched. NULL when the request was denied; non-NULL implies the request was allowed.';
CREATE TABLE boundary_sessions (
id uuid NOT NULL,
workspace_agent_id uuid NOT NULL,
confined_process_name text NOT NULL,
started_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
COMMENT ON TABLE boundary_sessions IS 'Boundary session metadata. Each row represents a single invocation of a Boundary process wrapping a confined agent.';
COMMENT ON COLUMN boundary_sessions.id IS 'The unique session ID generated by the Boundary process on startup.';
COMMENT ON COLUMN boundary_sessions.workspace_agent_id IS 'The workspace agent that this Boundary session is associated with.';
COMMENT ON COLUMN boundary_sessions.confined_process_name IS 'Name of the confined process (e.g. claude-code, codex, copilot).';
COMMENT ON COLUMN boundary_sessions.started_at IS 'Time when the first log for this session was received by coderd.';
COMMENT ON COLUMN boundary_sessions.updated_at IS 'Time when the session was last updated.';
CREATE TABLE boundary_usage_stats (
replica_id uuid NOT NULL,
unique_workspaces_count bigint DEFAULT 0 NOT NULL,
@@ -3614,6 +3665,12 @@ ALTER TABLE ONLY api_keys
ALTER TABLE ONLY audit_logs
ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
ALTER TABLE ONLY boundary_logs
ADD CONSTRAINT boundary_logs_pkey PRIMARY KEY (id);
ALTER TABLE ONLY boundary_sessions
ADD CONSTRAINT boundary_sessions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY boundary_usage_stats
ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
@@ -4023,6 +4080,10 @@ 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_boundary_logs_captured_at ON boundary_logs USING btree (captured_at);
CREATE INDEX idx_boundary_logs_session_seq ON boundary_logs USING btree (session_id, sequence_number);
CREATE INDEX idx_chat_debug_runs_chat_started ON chat_debug_runs USING btree (chat_id, started_at DESC);
CREATE UNIQUE INDEX idx_chat_debug_runs_id_chat ON chat_debug_runs USING btree (id, chat_id);
@@ -4365,6 +4426,12 @@ 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 boundary_logs
ADD CONSTRAINT boundary_logs_session_id_fkey FOREIGN KEY (session_id) REFERENCES boundary_sessions(id) ON DELETE CASCADE;
ALTER TABLE ONLY boundary_sessions
ADD CONSTRAINT boundary_sessions_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id);
ALTER TABLE ONLY chat_debug_runs
ADD CONSTRAINT chat_debug_runs_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
@@ -12,6 +12,8 @@ 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;
ForeignKeyBoundaryLogsSessionID ForeignKeyConstraint = "boundary_logs_session_id_fkey" // ALTER TABLE ONLY boundary_logs ADD CONSTRAINT boundary_logs_session_id_fkey FOREIGN KEY (session_id) REFERENCES boundary_sessions(id) ON DELETE CASCADE;
ForeignKeyBoundarySessionsWorkspaceAgentID ForeignKeyConstraint = "boundary_sessions_workspace_agent_id_fkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id);
ForeignKeyChatDebugRunsChatID ForeignKeyConstraint = "chat_debug_runs_chat_id_fkey" // ALTER TABLE ONLY chat_debug_runs ADD CONSTRAINT chat_debug_runs_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
ForeignKeyChatDebugStepsChatID ForeignKeyConstraint = "chat_debug_steps_chat_id_fkey" // ALTER TABLE ONLY chat_debug_steps ADD CONSTRAINT chat_debug_steps_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(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;
@@ -0,0 +1,4 @@
DROP INDEX IF EXISTS idx_boundary_logs_captured_at;
DROP INDEX IF EXISTS idx_boundary_logs_session_seq;
DROP TABLE IF EXISTS boundary_logs;
DROP TABLE IF EXISTS boundary_sessions;
@@ -0,0 +1,43 @@
CREATE TABLE boundary_sessions (
id UUID PRIMARY KEY,
workspace_agent_id UUID NOT NULL REFERENCES workspace_agents(id),
confined_process_name TEXT NOT NULL,
started_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
COMMENT ON TABLE boundary_sessions IS 'Boundary session metadata. Each row represents a single invocation of a Boundary process wrapping a confined agent.';
COMMENT ON COLUMN boundary_sessions.id IS 'The unique session ID generated by the Boundary process on startup.';
COMMENT ON COLUMN boundary_sessions.workspace_agent_id IS 'The workspace agent that this Boundary session is associated with.';
COMMENT ON COLUMN boundary_sessions.confined_process_name IS 'Name of the confined process (e.g. claude-code, codex, copilot).';
COMMENT ON COLUMN boundary_sessions.started_at IS 'Time when the first log for this session was received by coderd.';
COMMENT ON COLUMN boundary_sessions.updated_at IS 'Time when the session was last updated.';
CREATE TABLE boundary_logs (
id UUID NOT NULL,
session_id UUID NOT NULL REFERENCES boundary_sessions(id) ON DELETE CASCADE,
sequence_number INT NOT NULL CHECK (sequence_number >= 0),
captured_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
proto TEXT NOT NULL DEFAULT '',
method TEXT NOT NULL DEFAULT '',
detail TEXT NOT NULL DEFAULT '',
matched_rule TEXT,
PRIMARY KEY (id)
);
COMMENT ON TABLE boundary_logs IS 'Persisted boundary audit events. Each row is a single audit event processed by a Boundary proxy.';
COMMENT ON COLUMN boundary_logs.session_id IS 'The session ID generated by the Boundary process on startup. Groups all events from one invocation.';
COMMENT ON COLUMN boundary_logs.sequence_number IS 'Monotonically increasing integer assigned by Boundary, starting at 0 per session. Primary ordering key when Boundary is in use.';
COMMENT ON COLUMN boundary_logs.captured_at IS 'When the log was sent to the DB.';
COMMENT ON COLUMN boundary_logs.created_at IS 'When the event happened on the workspace.';
COMMENT ON COLUMN boundary_logs.proto IS 'The protocol of the audited action. e.g. http, dns, git, fs.';
COMMENT ON COLUMN boundary_logs.method IS 'The operation within the protocol. e.g. GET/POST for http, clone for git, A for dns, read/write for fs.';
COMMENT ON COLUMN boundary_logs.detail IS 'Protocol-specific detail. e.g. the full URL for http, the hostname for dns, the path for fs.';
COMMENT ON COLUMN boundary_logs.matched_rule IS 'The allow-list rule that matched. NULL when the request was denied; non-NULL implies the request was allowed.';
-- Ordering query path: list events for a session, sorted by sequence number.
CREATE INDEX idx_boundary_logs_session_seq ON boundary_logs (session_id, sequence_number);
-- Retention purge path: delete old rows by capture time.
CREATE INDEX idx_boundary_logs_captured_at ON boundary_logs (captured_at);
@@ -0,0 +1,35 @@
INSERT INTO boundary_sessions (
id,
workspace_agent_id,
confined_process_name,
started_at,
updated_at
) VALUES (
'a1b2c3d4-e5f6-4890-abcd-ef1234567890',
'45e89705-e09d-4850-bcec-f9a937f5d78d',
'claude-code',
'2026-04-01 10:00:00+00',
'2026-04-01 10:00:00+00'
);
INSERT INTO boundary_logs (
id,
session_id,
sequence_number,
captured_at,
created_at,
proto,
method,
detail,
matched_rule
) VALUES (
'b2c3d4e5-f6a7-4901-bcde-f12345678901',
'a1b2c3d4-e5f6-4890-abcd-ef1234567890',
0,
'2026-04-01 10:00:01+00',
'2026-04-01 10:00:00+00',
'http',
'GET',
'https://api.anthropic.com/v1/messages',
'domain=api.anthropic.com'
);
+35
View File
@@ -4510,6 +4510,41 @@ type AuditLog struct {
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
}
// Persisted boundary audit events. Each row is a single audit event processed by a Boundary proxy.
type BoundaryLog struct {
ID uuid.UUID `db:"id" json:"id"`
// The session ID generated by the Boundary process on startup. Groups all events from one invocation.
SessionID uuid.UUID `db:"session_id" json:"session_id"`
// Monotonically increasing integer assigned by Boundary, starting at 0 per session. Primary ordering key when Boundary is in use.
SequenceNumber int32 `db:"sequence_number" json:"sequence_number"`
// When the log was sent to the DB.
CapturedAt time.Time `db:"captured_at" json:"captured_at"`
// When the event happened on the workspace.
CreatedAt time.Time `db:"created_at" json:"created_at"`
// The protocol of the audited action. e.g. http, dns, git, fs.
Proto string `db:"proto" json:"proto"`
// The operation within the protocol. e.g. GET/POST for http, clone for git, A for dns, read/write for fs.
Method string `db:"method" json:"method"`
// Protocol-specific detail. e.g. the full URL for http, the hostname for dns, the path for fs.
Detail string `db:"detail" json:"detail"`
// The allow-list rule that matched. NULL when the request was denied; non-NULL implies the request was allowed.
MatchedRule sql.NullString `db:"matched_rule" json:"matched_rule"`
}
// Boundary session metadata. Each row represents a single invocation of a Boundary process wrapping a confined agent.
type BoundarySession struct {
// The unique session ID generated by the Boundary process on startup.
ID uuid.UUID `db:"id" json:"id"`
// The workspace agent that this Boundary session is associated with.
WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
// Name of the confined process (e.g. claude-code, codex, copilot).
ConfinedProcessName string `db:"confined_process_name" json:"confined_process_name"`
// Time when the first log for this session was received by coderd.
StartedAt time.Time `db:"started_at" json:"started_at"`
// Time when the session was last updated.
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
// Per-replica boundary usage statistics for telemetry aggregation.
type BoundaryUsageStat struct {
// The unique identifier of the replica reporting stats.
+11
View File
@@ -152,6 +152,9 @@ type sqlcQuerier interface {
// connection events (connect, disconnect, open, close) which are handled
// separately by DeleteOldAuditLogConnectionEvents.
DeleteOldAuditLogs(ctx context.Context, arg DeleteOldAuditLogsParams) (int64, error)
// Deletes boundary logs older than the given time, bounded by a row limit
// to avoid long-running transactions.
DeleteOldBoundaryLogs(ctx context.Context, arg DeleteOldBoundaryLogsParams) (int64, error)
// updated_at is the retention clock, so the window starts after the run
// stops being written to.
// Intentionally no finished_at IS NOT NULL guard: abandoned in-flight rows
@@ -313,6 +316,8 @@ type sqlcQuerier interface {
// This function returns roles for authorization purposes. Implied member roles
// are included.
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (BoundaryLog, error)
GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (BoundarySession, error)
GetChatACLByID(ctx context.Context, id uuid.UUID) (GetChatACLByIDRow, error)
// GetChatAdvisorConfig returns the deployment-wide runtime configuration
// for the experimental chat advisor as a JSON blob. Callers unmarshal the
@@ -915,6 +920,8 @@ type sqlcQuerier interface {
// every member of the org.
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
InsertBoundaryLog(ctx context.Context, arg InsertBoundaryLogParams) (BoundaryLog, error)
InsertBoundarySession(ctx context.Context, arg InsertBoundarySessionParams) (BoundarySession, error)
InsertChat(ctx context.Context, arg InsertChatParams) (Chat, error)
// updated_at is the retention clock used by DeleteOldChatDebugRuns.
// Set it on every write to keep retention semantics correct.
@@ -1039,6 +1046,10 @@ type sqlcQuerier interface {
ListAIBridgeTokenUsagesByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]AIBridgeTokenUsage, error)
ListAIBridgeToolUsagesByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]AIBridgeToolUsage, error)
ListAIBridgeUserPromptsByInterceptionIDs(ctx context.Context, interceptionIds []uuid.UUID) ([]AIBridgeUserPrompt, error)
// Lists boundary logs for a session, sorted by sequence number ascending.
// Supports optional exclusive sequence number bounds (seq_after, seq_before)
// for fetching events between two known interceptions.
ListBoundaryLogsBySessionID(ctx context.Context, arg ListBoundaryLogsBySessionIDParams) ([]BoundaryLog, error)
ListChatUsageLimitGroupOverrides(ctx context.Context) ([]ListChatUsageLimitGroupOverridesRow, error)
ListChatUsageLimitOverrides(ctx context.Context) ([]ListChatUsageLimitOverridesRow, error)
ListProvisionerKeysByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerKey, error)
+237
View File
@@ -3549,6 +3549,243 @@ func (q *sqlQuerier) InsertAuditLog(ctx context.Context, arg InsertAuditLogParam
return i, err
}
const deleteOldBoundaryLogs = `-- name: DeleteOldBoundaryLogs :execrows
WITH old_logs AS (
SELECT id
FROM boundary_logs
WHERE captured_at < $1::timestamptz
ORDER BY captured_at ASC
LIMIT $2
)
DELETE FROM boundary_logs
USING old_logs
WHERE boundary_logs.id = old_logs.id
`
type DeleteOldBoundaryLogsParams struct {
BeforeTime time.Time `db:"before_time" json:"before_time"`
LimitCount int32 `db:"limit_count" json:"limit_count"`
}
// Deletes boundary logs older than the given time, bounded by a row limit
// to avoid long-running transactions.
func (q *sqlQuerier) DeleteOldBoundaryLogs(ctx context.Context, arg DeleteOldBoundaryLogsParams) (int64, error) {
result, err := q.db.ExecContext(ctx, deleteOldBoundaryLogs, arg.BeforeTime, arg.LimitCount)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const getBoundaryLogByID = `-- name: GetBoundaryLogByID :one
SELECT id, session_id, sequence_number, captured_at, created_at, proto, method, detail, matched_rule FROM boundary_logs WHERE id = $1
`
func (q *sqlQuerier) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (BoundaryLog, error) {
row := q.db.QueryRowContext(ctx, getBoundaryLogByID, id)
var i BoundaryLog
err := row.Scan(
&i.ID,
&i.SessionID,
&i.SequenceNumber,
&i.CapturedAt,
&i.CreatedAt,
&i.Proto,
&i.Method,
&i.Detail,
&i.MatchedRule,
)
return i, err
}
const getBoundarySessionByID = `-- name: GetBoundarySessionByID :one
SELECT id, workspace_agent_id, confined_process_name, started_at, updated_at FROM boundary_sessions WHERE id = $1
`
func (q *sqlQuerier) GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (BoundarySession, error) {
row := q.db.QueryRowContext(ctx, getBoundarySessionByID, id)
var i BoundarySession
err := row.Scan(
&i.ID,
&i.WorkspaceAgentID,
&i.ConfinedProcessName,
&i.StartedAt,
&i.UpdatedAt,
)
return i, err
}
const insertBoundaryLog = `-- name: InsertBoundaryLog :one
INSERT INTO boundary_logs (
id,
session_id,
sequence_number,
captured_at,
created_at,
proto,
method,
detail,
matched_rule
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9
) RETURNING id, session_id, sequence_number, captured_at, created_at, proto, method, detail, matched_rule
`
type InsertBoundaryLogParams struct {
ID uuid.UUID `db:"id" json:"id"`
SessionID uuid.UUID `db:"session_id" json:"session_id"`
SequenceNumber int32 `db:"sequence_number" json:"sequence_number"`
CapturedAt time.Time `db:"captured_at" json:"captured_at"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Proto string `db:"proto" json:"proto"`
Method string `db:"method" json:"method"`
Detail string `db:"detail" json:"detail"`
MatchedRule sql.NullString `db:"matched_rule" json:"matched_rule"`
}
func (q *sqlQuerier) InsertBoundaryLog(ctx context.Context, arg InsertBoundaryLogParams) (BoundaryLog, error) {
row := q.db.QueryRowContext(ctx, insertBoundaryLog,
arg.ID,
arg.SessionID,
arg.SequenceNumber,
arg.CapturedAt,
arg.CreatedAt,
arg.Proto,
arg.Method,
arg.Detail,
arg.MatchedRule,
)
var i BoundaryLog
err := row.Scan(
&i.ID,
&i.SessionID,
&i.SequenceNumber,
&i.CapturedAt,
&i.CreatedAt,
&i.Proto,
&i.Method,
&i.Detail,
&i.MatchedRule,
)
return i, err
}
const insertBoundarySession = `-- name: InsertBoundarySession :one
INSERT INTO boundary_sessions (
id,
workspace_agent_id,
confined_process_name,
started_at,
updated_at
) VALUES (
$1,
$2,
$3,
$4,
$5
) RETURNING id, workspace_agent_id, confined_process_name, started_at, updated_at
`
type InsertBoundarySessionParams struct {
ID uuid.UUID `db:"id" json:"id"`
WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
ConfinedProcessName string `db:"confined_process_name" json:"confined_process_name"`
StartedAt time.Time `db:"started_at" json:"started_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
func (q *sqlQuerier) InsertBoundarySession(ctx context.Context, arg InsertBoundarySessionParams) (BoundarySession, error) {
row := q.db.QueryRowContext(ctx, insertBoundarySession,
arg.ID,
arg.WorkspaceAgentID,
arg.ConfinedProcessName,
arg.StartedAt,
arg.UpdatedAt,
)
var i BoundarySession
err := row.Scan(
&i.ID,
&i.WorkspaceAgentID,
&i.ConfinedProcessName,
&i.StartedAt,
&i.UpdatedAt,
)
return i, err
}
const listBoundaryLogsBySessionID = `-- name: ListBoundaryLogsBySessionID :many
SELECT id, session_id, sequence_number, captured_at, created_at, proto, method, detail, matched_rule
FROM boundary_logs
WHERE
session_id = $1
AND CASE
WHEN $2::int IS NOT NULL THEN sequence_number > $2
ELSE true
END
AND CASE
WHEN $3::int IS NOT NULL THEN sequence_number < $3
ELSE true
END
ORDER BY sequence_number ASC
LIMIT COALESCE(NULLIF($4::int, 0), 100)
`
type ListBoundaryLogsBySessionIDParams struct {
SessionID uuid.UUID `db:"session_id" json:"session_id"`
SeqAfter sql.NullInt32 `db:"seq_after" json:"seq_after"`
SeqBefore sql.NullInt32 `db:"seq_before" json:"seq_before"`
LimitOpt int32 `db:"limit_opt" json:"limit_opt"`
}
// Lists boundary logs for a session, sorted by sequence number ascending.
// Supports optional exclusive sequence number bounds (seq_after, seq_before)
// for fetching events between two known interceptions.
func (q *sqlQuerier) ListBoundaryLogsBySessionID(ctx context.Context, arg ListBoundaryLogsBySessionIDParams) ([]BoundaryLog, error) {
rows, err := q.db.QueryContext(ctx, listBoundaryLogsBySessionID,
arg.SessionID,
arg.SeqAfter,
arg.SeqBefore,
arg.LimitOpt,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []BoundaryLog
for rows.Next() {
var i BoundaryLog
if err := rows.Scan(
&i.ID,
&i.SessionID,
&i.SequenceNumber,
&i.CapturedAt,
&i.CreatedAt,
&i.Proto,
&i.Method,
&i.Detail,
&i.MatchedRule,
); 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
}
const getAndResetBoundaryUsageSummary = `-- name: GetAndResetBoundaryUsageSummary :one
WITH deleted AS (
DELETE FROM boundary_usage_stats
+76
View File
@@ -0,0 +1,76 @@
-- name: InsertBoundarySession :one
INSERT INTO boundary_sessions (
id,
workspace_agent_id,
confined_process_name,
started_at,
updated_at
) VALUES (
@id,
@workspace_agent_id,
@confined_process_name,
@started_at,
@updated_at
) RETURNING *;
-- name: GetBoundarySessionByID :one
SELECT * FROM boundary_sessions WHERE id = @id;
-- name: InsertBoundaryLog :one
INSERT INTO boundary_logs (
id,
session_id,
sequence_number,
captured_at,
created_at,
proto,
method,
detail,
matched_rule
) VALUES (
@id,
@session_id,
@sequence_number,
@captured_at,
@created_at,
@proto,
@method,
@detail,
@matched_rule
) RETURNING *;
-- name: GetBoundaryLogByID :one
SELECT * FROM boundary_logs WHERE id = @id;
-- name: ListBoundaryLogsBySessionID :many
-- Lists boundary logs for a session, sorted by sequence number ascending.
-- Supports optional exclusive sequence number bounds (seq_after, seq_before)
-- for fetching events between two known interceptions.
SELECT *
FROM boundary_logs
WHERE
session_id = @session_id
AND CASE
WHEN sqlc.narg('seq_after')::int IS NOT NULL THEN sequence_number > sqlc.narg('seq_after')
ELSE true
END
AND CASE
WHEN sqlc.narg('seq_before')::int IS NOT NULL THEN sequence_number < sqlc.narg('seq_before')
ELSE true
END
ORDER BY sequence_number ASC
LIMIT COALESCE(NULLIF(@limit_opt::int, 0), 100);
-- name: DeleteOldBoundaryLogs :execrows
-- Deletes boundary logs older than the given time, bounded by a row limit
-- to avoid long-running transactions.
WITH old_logs AS (
SELECT id
FROM boundary_logs
WHERE captured_at < @before_time::timestamptz
ORDER BY captured_at ASC
LIMIT @limit_count
)
DELETE FROM boundary_logs
USING old_logs
WHERE boundary_logs.id = old_logs.id;
+2
View File
@@ -17,6 +17,8 @@ const (
UniqueAibridgeUserPromptsPkey UniqueConstraint = "aibridge_user_prompts_pkey" // ALTER TABLE ONLY aibridge_user_prompts ADD CONSTRAINT aibridge_user_prompts_pkey PRIMARY KEY (id);
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);
UniqueBoundaryLogsPkey UniqueConstraint = "boundary_logs_pkey" // ALTER TABLE ONLY boundary_logs ADD CONSTRAINT boundary_logs_pkey PRIMARY KEY (id);
UniqueBoundarySessionsPkey UniqueConstraint = "boundary_sessions_pkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_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);
UniqueChatDebugRunsPkey UniqueConstraint = "chat_debug_runs_pkey" // ALTER TABLE ONLY chat_debug_runs ADD CONSTRAINT chat_debug_runs_pkey PRIMARY KEY (id);
UniqueChatDebugStepsPkey UniqueConstraint = "chat_debug_steps_pkey" // ALTER TABLE ONLY chat_debug_steps ADD CONSTRAINT chat_debug_steps_pkey PRIMARY KEY (id);