@@ -125,6 +125,24 @@ public struct RecordDeleted has copy, drop {
125125 timestamp: u64 ,
126126}
127127
128+ /// Emitted when expired revoked-capability entries are removed from the denylist
129+ public struct RevokedCapabilitiesCleanedUp has copy , drop {
130+ trail_id: ID ,
131+ cleaned_count: u64 ,
132+ cleaned_by: address ,
133+ timestamp: u64 ,
134+ }
135+
136+ /// Returned when a capability is issued through the audit-trail API
137+ public struct CapabilityIssuedReceipt has copy , drop {
138+ target_key: ID ,
139+ capability_id: ID ,
140+ role: String ,
141+ issued_to: Option <address >,
142+ valid_from: Option <u64 >,
143+ valid_until: Option <u64 >,
144+ }
145+
128146// ===== Constructors =====
129147
130148/// Create immutable trail metadata
@@ -277,6 +295,7 @@ fun assert_record_tag_allowed<D: store + copy>(
277295/// Add a record to the trail
278296///
279297/// Records are added sequentially with auto-assigned sequence numbers.
298+ /// Returns the same receipt that is emitted as the `RecordAdded` event.
280299public fun add_record <D : store + copy >(
281300 self: &mut AuditTrail <D >,
282301 cap: &Capability ,
@@ -285,7 +304,7 @@ public fun add_record<D: store + copy>(
285304 record_tag: Option <String >,
286305 clock: &Clock ,
287306 ctx: &mut TxContext ,
288- ) {
307+ ): RecordAdded {
289308 assert ! (self.version == PACKAGE_VERSION , EPackageVersionMismatch );
290309 self
291310 .roles
@@ -320,12 +339,15 @@ public fun add_record<D: store + copy>(
320339 linked_table::push_back (&mut self.records, seq, record);
321340 self.sequence_number = self.sequence_number + 1 ;
322341
323- event:: emit ( RecordAdded {
342+ let output = RecordAdded {
324343 trail_id,
325344 sequence_number: seq,
326345 added_by: caller,
327346 timestamp,
328- });
347+ };
348+
349+ event::emit (copy output);
350+ output
329351}
330352
331353/// Delete a record from the trail by sequence number
@@ -377,14 +399,14 @@ public fun delete_record<D: store + copy + drop>(
377399/// Delete up to `limit` records from the front of the trail.
378400///
379401/// Requires `DeleteAllRecords` permission. Locked records are skipped.
380- /// Returns the number of records deleted in this batch.
402+ /// Returns the sequence numbers deleted in this batch, in deletion order .
381403public fun delete_records_batch <D : store + copy + drop >(
382404 self: &mut AuditTrail <D >,
383405 cap: &Capability ,
384406 limit: u64 ,
385407 clock: &Clock ,
386408 ctx: &mut TxContext ,
387- ): u64 {
409+ ): vector < u64 > {
388410 assert ! (self.version == PACKAGE_VERSION , EPackageVersionMismatch );
389411 self
390412 .roles
@@ -396,6 +418,7 @@ public fun delete_records_batch<D: store + copy + drop>(
396418 );
397419
398420 let mut deleted = 0 ;
421+ let mut deleted_sequence_numbers = vector ::empty <u64 >();
399422 let caller = ctx.sender ();
400423 let timestamp = clock.timestamp_ms ();
401424 let trail_id = self.id ();
@@ -431,11 +454,12 @@ public fun delete_records_batch<D: store + copy + drop>(
431454 deleted_by: caller,
432455 timestamp,
433456 });
457+ vector ::push_back (&mut deleted_sequence_numbers, sequence_number);
434458
435459 deleted = deleted + 1 ;
436460 };
437461
438- deleted
462+ deleted_sequence_numbers
439463}
440464
441465/// Delete an empty audit trail.
@@ -754,6 +778,7 @@ public fun delete_role<D: store + copy>(
754778/// Issues a new capability for an existing role.
755779///
756780/// The capability object is transferred to `issued_to` if provided, otherwise to the caller.
781+ /// Returns the same receipt that is emitted as the `CapabilityIssued` event.
757782public fun new_capability <D : store + copy >(
758783 self: &mut AuditTrail <D >,
759784 cap: &Capability ,
@@ -763,7 +788,7 @@ public fun new_capability<D: store + copy>(
763788 valid_until: Option <u64 >,
764789 clock: &Clock ,
765790 ctx: &mut TxContext ,
766- ) {
791+ ): CapabilityIssuedReceipt {
767792 assert ! (self.version == PACKAGE_VERSION , EPackageVersionMismatch );
768793
769794 let recipient = if (issued_to.is_some ()) {
@@ -783,7 +808,16 @@ public fun new_capability<D: store + copy>(
783808 clock,
784809 ctx,
785810 );
811+ let output = CapabilityIssuedReceipt {
812+ target_key: self.id (),
813+ capability_id: new_cap.id (),
814+ role: *new_cap.role (),
815+ issued_to: *new_cap.issued_to (),
816+ valid_from: *new_cap.valid_from (),
817+ valid_until: *new_cap.valid_until (),
818+ };
786819 transfer::public_transfer (new_cap, recipient);
820+ output
787821}
788822
789823/// Revokes an issued capability by ID.
@@ -878,6 +912,8 @@ public fun revoke_initial_admin_capability<D: store + copy>(
878912/// Entries with `valid_until == 0` (i.e. capabilities that had no expiry) are kept,
879913/// since they remain potentially valid and must stay on the denylist.
880914///
915+ /// Returns the same receipt that is emitted as the `RevokedCapabilitiesCleanedUp` event.
916+ ///
881917/// Parameters
882918/// ----------
883919/// - cap: Reference to the capability used to authorize this operation.
@@ -892,15 +928,25 @@ public fun cleanup_revoked_capabilities<D: store + copy>(
892928 cap: &Capability ,
893929 clock: &Clock ,
894930 ctx: &TxContext ,
895- ) {
931+ ): RevokedCapabilitiesCleanedUp {
896932 assert ! (self.version == PACKAGE_VERSION , EPackageVersionMismatch );
933+ let revoked_count_before = linked_table::length (role_map::revoked_capabilities (self.access ()));
897934 self
898935 .access_mut ()
899936 .cleanup_revoked_capabilities (
900937 cap,
901938 clock,
902939 ctx,
903940 );
941+ let revoked_count_after = linked_table::length (role_map::revoked_capabilities (self.access ()));
942+ let output = RevokedCapabilitiesCleanedUp {
943+ trail_id: self.id (),
944+ cleaned_count: revoked_count_before - revoked_count_after,
945+ cleaned_by: ctx.sender (),
946+ timestamp: clock::timestamp_ms (clock),
947+ };
948+ event::emit (copy output);
949+ output
904950}
905951
906952// ===== Trail Query Functions =====
0 commit comments