11//! Audit log handlers.
22//!
33//! Audit logs are managed by Ledger's event system. The `list_audit_logs`
4- //! handler serves paginated events for an organization, while `create_audit_log`
5- //! is an internal endpoint for ingesting Control-originated events .
4+ //! handler serves paginated events for an organization. Event ingestion
5+ //! is handled directly by Ledger .
66
7- use std:: collections:: HashMap ;
7+ use std:: { collections:: HashMap , time :: Instant } ;
88
99use axum:: {
1010 Extension , Json ,
1111 extract:: { Path , Query , State } ,
12- http:: StatusCode ,
1312} ;
14- use inferadb_control_core:: service ;
13+ use inferadb_control_core:: SdkResultExt ;
1514use inferadb_control_types:: Error as CoreError ;
16- use inferadb_ledger_sdk:: { EventFilter , EventOutcome , OrganizationSlug , SdkIngestEventEntry } ;
15+ use inferadb_ledger_sdk:: { EventFilter , EventOutcome , OrganizationSlug } ;
1716use serde:: { Deserialize , Serialize } ;
1817
18+ use super :: common:: require_ledger;
1919use crate :: {
2020 handlers:: auth:: { AppState , Result } ,
2121 middleware:: UserClaims ,
@@ -26,8 +26,8 @@ use crate::{
2626/// Query parameters for listing audit logs.
2727#[ derive( Debug , Deserialize ) ]
2828pub struct ListAuditLogsQuery {
29- /// Maximum entries per page (1..=1000, default 100).
30- pub limit : Option < u32 > ,
29+ /// Number of items per page (default 50, max 100).
30+ pub page_size : Option < u32 > ,
3131 /// Opaque cursor for the next page.
3232 pub page_token : Option < String > ,
3333 /// Filter by event type prefix (e.g., `"ledger.vault"`).
@@ -61,42 +61,8 @@ pub struct ListAuditLogsResponse {
6161 pub total_estimate : Option < u64 > ,
6262}
6363
64- /// Request body for creating an audit log entry (internal endpoint).
65- #[ derive( Debug , Deserialize ) ]
66- pub struct CreateAuditLogRequest {
67- /// Organization slug (external identifier).
68- pub organization : u64 ,
69- /// Hierarchical dot-separated event type (e.g., `"control.user.created"`).
70- pub event_type : String ,
71- /// Who performed the action.
72- pub principal : String ,
73- /// Outcome: `"success"`, `"failed"`, or `"denied"`.
74- #[ serde( default = "default_outcome" ) ]
75- pub outcome : String ,
76- /// Action-specific key-value context.
77- #[ serde( default ) ]
78- pub details : HashMap < String , String > ,
79- }
80-
81- fn default_outcome ( ) -> String {
82- "success" . to_string ( )
83- }
84-
85- /// Response from creating an audit log entry.
86- #[ derive( Debug , Serialize ) ]
87- pub struct CreateAuditLogResponse {
88- pub accepted_count : u32 ,
89- pub rejected_count : u32 ,
90- }
91-
9264// ── Helpers ─────────────────────────────────────────────────────────
9365
94- fn require_ledger (
95- state : & AppState ,
96- ) -> std:: result:: Result < & inferadb_ledger_sdk:: LedgerClient , CoreError > {
97- state. ledger . as_deref ( ) . ok_or_else ( || CoreError :: internal ( "Ledger client not configured" ) )
98- }
99-
10066fn format_outcome ( outcome : & inferadb_ledger_sdk:: EventOutcome ) -> String {
10167 match outcome {
10268 EventOutcome :: Success => "success" . to_string ( ) ,
@@ -148,45 +114,41 @@ fn build_event_filter(query: &ListAuditLogsQuery) -> std::result::Result<EventFi
148114 Ok ( filter)
149115}
150116
151- fn parse_request_outcome (
152- outcome : & str ,
153- details : & HashMap < String , String > ,
154- ) -> std:: result:: Result < EventOutcome , CoreError > {
155- match outcome {
156- "success" => Ok ( EventOutcome :: Success ) ,
157- "failed" => Ok ( EventOutcome :: Failed {
158- code : details. get ( "error_code" ) . cloned ( ) . unwrap_or_default ( ) ,
159- detail : details. get ( "error_detail" ) . cloned ( ) . unwrap_or_default ( ) ,
160- } ) ,
161- "denied" => Ok ( EventOutcome :: Denied {
162- reason : details. get ( "denial_reason" ) . cloned ( ) . unwrap_or_default ( ) ,
163- } ) ,
164- _ => Err ( CoreError :: validation ( format ! (
165- "invalid outcome '{outcome}': expected 'success', 'failed', or 'denied'"
166- ) ) ) ,
167- }
168- }
169-
170117// ── Handlers ────────────────────────────────────────────────────────
171118
172119/// List audit logs for an organization.
173120///
174121/// GET /control/v1/organizations/{org}/audit-logs
175122pub async fn list_audit_logs (
176123 State ( state) : State < AppState > ,
177- Extension ( _claims ) : Extension < UserClaims > ,
124+ Extension ( claims ) : Extension < UserClaims > ,
178125 Path ( org) : Path < u64 > ,
179126 Query ( query) : Query < ListAuditLogsQuery > ,
180127) -> Result < Json < ListAuditLogsResponse > > {
181128 let ledger = require_ledger ( & state) ?;
182129 let organization = OrganizationSlug :: new ( org) ;
183130
131+ // Verify the caller is a member of this organization.
132+ let start = Instant :: now ( ) ;
133+ ledger
134+ . get_organization ( organization, claims. user_slug )
135+ . await
136+ . map_sdk_err_instrumented ( "get_organization" , start) ?;
137+
184138 let page = if let Some ( ref page_token) = query. page_token {
185- service:: audit:: list_events_next ( ledger, organization, page_token) . await ?
139+ let start = Instant :: now ( ) ;
140+ ledger
141+ . list_events_next ( organization, page_token)
142+ . await
143+ . map_sdk_err_instrumented ( "list_events_next" , start) ?
186144 } else {
187145 let filter = build_event_filter ( & query) ?;
188- let limit = query. limit . unwrap_or ( 100 ) . clamp ( 1 , 1000 ) ;
189- service:: audit:: list_events ( ledger, organization, filter, limit) . await ?
146+ let limit = query. page_size . unwrap_or ( 50 ) . clamp ( 1 , 100 ) ;
147+ let start = Instant :: now ( ) ;
148+ ledger
149+ . list_events ( organization, filter, limit)
150+ . await
151+ . map_sdk_err_instrumented ( "list_events" , start) ?
190152 } ;
191153
192154 let entries = page. entries . into_iter ( ) . map ( sdk_entry_to_response) . collect ( ) ;
@@ -197,28 +159,3 @@ pub async fn list_audit_logs(
197159 total_estimate : page. total_estimate ,
198160 } ) )
199161}
200-
201- /// Record an audit log event (internal endpoint).
202- ///
203- /// POST /internal/audit
204- pub async fn create_audit_log (
205- State ( state) : State < AppState > ,
206- Json ( req) : Json < CreateAuditLogRequest > ,
207- ) -> Result < ( StatusCode , Json < CreateAuditLogResponse > ) > {
208- let ledger = require_ledger ( & state) ?;
209- let organization = OrganizationSlug :: new ( req. organization ) ;
210- let outcome = parse_request_outcome ( & req. outcome , & req. details ) ?;
211-
212- let event =
213- SdkIngestEventEntry :: new ( & req. event_type , & req. principal , outcome) . details ( req. details ) ;
214-
215- let result = service:: audit:: ingest_events ( ledger, organization, vec ! [ event] ) . await ?;
216-
217- Ok ( (
218- StatusCode :: CREATED ,
219- Json ( CreateAuditLogResponse {
220- accepted_count : result. accepted_count ,
221- rejected_count : result. rejected_count ,
222- } ) ,
223- ) )
224- }
0 commit comments