@@ -55,6 +55,7 @@ static final class Fixture {
5555 final List <MutationResult > results = new CopyOnWriteArrayList <>();
5656 boolean queryLogging = true ;
5757 boolean mutationLogging = true ;
58+ boolean failStandardAudit ;
5859 final List <Integer > itemInsertBatchSizes = new CopyOnWriteArrayList <>();
5960 final List <Integer > itemUpdateBatchSizes = new CopyOnWriteArrayList <>();
6061 final List <Integer > itemDeleteBatchSizes = new CopyOnWriteArrayList <>();
@@ -162,7 +163,15 @@ static final class Fixture {
162163 var ids = new AtomicLong (1000 );
163164 var builder = TeaQLRuntime .builder ().metadata (metadata ).dataService ("sqlite" , provider )
164165 .idGenerationService ((caller , entity ) -> ids .getAndIncrement ())
165- .logSink ((caller , entry ) -> sql .add (entry ));
166+ .logSink (new RuntimeLogSink () {
167+ @ Override public void writeExecutionLog (UserContext caller , ExecutionMetadata entry ) {
168+ sql .add (entry );
169+ }
170+
171+ @ Override public void writeAuditEvent (UserContext caller , RawAuditEvent event ) {
172+ if (failStandardAudit ) throw new IllegalStateException ("standard audit sink failure" );
173+ }
174+ });
166175 if (committedRecorder != null ) builder .committedMutationRecorder (committedRecorder );
167176 var runtime = builder .build ();
168177 context = new DefaultUserContext (runtime ) {
@@ -688,6 +697,67 @@ private static CheckedSave checkedSave(Fixture fixture, GraphEntity entity, Stri
688697 assertTrue ("no post-commit audit on rolled back save" , fixture .audit .isEmpty ());
689698 }
690699
700+ @ Test public void postCommitAuditFailureReportsCommittedOutcomeWithoutReplayingLedger () throws Exception {
701+ var fixture = new Fixture (true );
702+ fixture .context .putAttribute (AppAuditEventSink .class .getName (),
703+ (AppAuditEventSink ) (caller , event ) -> {
704+ throw new IllegalStateException ("diagnostic audit sink failure" );
705+ });
706+ var order = fixture .create ("CustomerOrder" , 199 , "committed before audit delivery" );
707+
708+ var failure = assertThrows (AuditDeliveryAfterCommitException .class ,
709+ () -> order .auditAs ("diagnose post-commit audit failure" ).save (fixture .context ));
710+ assertTrue (failure .getMessage ().contains ("AUDIT_DELIVERY_AFTER_COMMIT" ));
711+ assertEquals (1 , failure .failureCount ());
712+ assertEquals (1 , fixture .driver .queryForList (
713+ "SELECT id FROM customer_order_data WHERE id = ?" , new Object [] {199L }).size ());
714+ assertEquals (1 , fixture .committedRecorder .eventStore ()
715+ .outboxAfter ("java-graph-source" , 0 , 10 ).size ());
716+
717+ fixture .context .putAttribute (AppAuditEventSink .class .getName (),
718+ (AppAuditEventSink ) (caller , event ) -> fixture .audit .add (event ));
719+ order .auditAs ("retry after audit delivery failure" ).save (fixture .context );
720+ assertEquals (1 , fixture .committedRecorder .eventStore ()
721+ .outboxAfter ("java-graph-source" , 0 , 10 ).size ());
722+ }
723+
724+ @ Test public void postCommitAuditFailureStillAttemptsEveryGraphMember () throws Exception {
725+ var fixture = new Fixture (true );
726+ var attempts = new java .util .concurrent .atomic .AtomicInteger ();
727+ fixture .context .putAttribute (AppAuditEventSink .class .getName (),
728+ (AppAuditEventSink ) (caller , event ) -> {
729+ attempts .incrementAndGet ();
730+ throw new IllegalStateException ("diagnostic audit sink failure" );
731+ });
732+ var order = fixture .create ("CustomerOrder" , 198 , "root audit fails" );
733+ var item = fixture .create ("OrderItem" , 298 , "child audit fails" );
734+ order .__internalSet ("children" , List .of (item ));
735+
736+ var failure = assertThrows (AuditDeliveryAfterCommitException .class ,
737+ () -> order .auditAs ("commit graph before audit delivery" ).save (fixture .context ));
738+ assertEquals (2 , failure .failureCount ());
739+ assertEquals (2 , attempts .get ());
740+ assertEquals (1 , fixture .committedRecorder .eventStore ()
741+ .outboxAfter ("java-graph-source" , 0 , 10 ).size ());
742+ assertEquals (1 , fixture .driver .queryForList (
743+ "SELECT id FROM customer_order_data WHERE id = ?" , new Object [] {198L }).size ());
744+ assertEquals (1 , fixture .driver .queryForList (
745+ "SELECT id FROM order_item_data WHERE id = ?" , new Object [] {298L }).size ());
746+ }
747+
748+ @ Test public void standardAuditSinkFailureDoesNotSuppressApplicationAuditDelivery () throws Exception {
749+ var fixture = new Fixture (true );
750+ fixture .failStandardAudit = true ;
751+ var order = fixture .create ("CustomerOrder" , 197 , "both audit sinks are attempted" );
752+
753+ var failure = assertThrows (AuditDeliveryAfterCommitException .class ,
754+ () -> order .auditAs ("commit before standard audit delivery" ).save (fixture .context ));
755+ assertEquals (1 , failure .failureCount ());
756+ assertEquals (1 , fixture .audit .size ());
757+ assertEquals (1 , fixture .committedRecorder .eventStore ()
758+ .outboxAfter ("java-graph-source" , 0 , 10 ).size ());
759+ }
760+
691761 @ Test public void realSqliteProviderFailureKeepsAttemptedLineageWithoutCommittedAudit () throws Exception {
692762 var fixture = new Fixture ();
693763 fixture .driver .execute ("CREATE UNIQUE INDEX payment_name_unique ON payment_data(name)" );
0 commit comments