@@ -110,7 +110,7 @@ describe('ingestOnePackagistMetadata', () => {
110110 } )
111111 }
112112
113- it ( 'persists both endpoints, audits the merged fields, and marks scanned with the fresh Last-Modified' , async ( ) => {
113+ it ( 'audits phase 1 immediately and phase 2 separately, then marks scanned with the fresh Last-Modified' , async ( ) => {
114114 happyMocks ( )
115115
116116 await ingestOnePackagistMetadata ( qx , candidate )
@@ -119,12 +119,10 @@ describe('ingestOnePackagistMetadata', () => {
119119 expect ( mockFetchP2 ) . toHaveBeenCalledWith ( 'monolog/monolog' , 'Tue, 30 Jun 2026 00:00:00 GMT' )
120120 expect ( mockExpand ) . toHaveBeenCalledWith ( minified )
121121 expect ( mockPersistMetadata ) . toHaveBeenCalledWith ( qx , PURL , expanded )
122- expect ( mockAudit ) . toHaveBeenCalledWith (
123- qx ,
124- 'packagist' ,
125- PURL ,
126- expect . arrayContaining ( [ 'packages.description' , 'versions.number' ] ) ,
127- )
122+ // Two separate audit rows, not one merged call — phase 1 is logged as soon as it's
123+ // committed, before the p2 fetch (which can throw) ever runs.
124+ expect ( mockAudit ) . toHaveBeenNthCalledWith ( 1 , qx , 'packagist' , PURL , [ 'packages.description' ] )
125+ expect ( mockAudit ) . toHaveBeenNthCalledWith ( 2 , qx , 'packagist' , PURL , [ 'versions.number' ] )
128126 expect ( mockMarkMetadata ) . toHaveBeenCalledWith (
129127 qx ,
130128 PURL ,
@@ -227,24 +225,32 @@ describe('ingestOnePackagistMetadata', () => {
227225 )
228226 } )
229227
230- it ( 'throws on a transient p2 result without marking scanned' , async ( ) => {
228+ it ( 'throws on a transient p2 result without marking scanned, but still audits phase 1 ' , async ( ) => {
231229 mockFetchStats . mockResolvedValue ( statsJson as never )
232- mockPersistInfo . mockResolvedValue ( { found : true , changedFields : [ ] } )
230+ mockPersistInfo . mockResolvedValue ( { found : true , changedFields : [ 'packages.description' ] } )
233231 mockFetchP2 . mockResolvedValue ( { kind : 'TRANSIENT' , message : 'HTTP 502' } as never )
234232
235233 await expect ( ingestOnePackagistMetadata ( qx , candidate ) ) . rejects . toThrow ( )
236234 expect ( mockMarkMetadata ) . not . toHaveBeenCalled ( )
235+ // phase-1 writes are already committed when the throw happens — a retry re-runs
236+ // phase 1 idempotently and reports no changes, so this audit event can't be deferred
237+ expect ( mockAudit ) . toHaveBeenCalledWith ( qx , 'packagist' , PURL , [ 'packages.description' ] )
237238 } )
238239} )
239240
240241// The monthly downloads-30d lane: dynamic fetch, window row only.
241242describe ( 'ingestOnePackagist30dWindow' , ( ) => {
242- it ( 'persists the observed rolling window and marks the run processed' , async ( ) => {
243+ it ( 'persists the observed rolling window, audits the change, and marks the run processed' , async ( ) => {
243244 mockFetchStats . mockResolvedValue ( statsJson as never )
245+ mockPersist30d . mockResolvedValue ( [ 'downloads_last_30d.count' , 'packages.downloads_last_30d' ] )
244246
245247 await ingestOnePackagist30dWindow ( qx , PURL , RUN_DATE )
246248
247249 expect ( mockPersist30d ) . toHaveBeenCalledWith ( qx , PURL , 300 , RUN_DATE )
250+ expect ( mockAudit ) . toHaveBeenCalledWith ( qx , 'packagist' , PURL , [
251+ 'downloads_last_30d.count' ,
252+ 'packages.downloads_last_30d' ,
253+ ] )
248254 expect ( mockMark30d ) . toHaveBeenCalledWith (
249255 qx ,
250256 PURL ,
@@ -284,25 +290,31 @@ describe('ingestOnePackagist30dWindow', () => {
284290describe ( 'ingestOnePackagistDailyDownload' , ( ) => {
285291 const candidate = { purl : PURL , packageId : '7' }
286292
287- it ( 'inserts the daily row and marks the run processed' , async ( ) => {
293+ it ( 'inserts the daily row, audits the change, and marks the run processed' , async ( ) => {
288294 mockFetchStats . mockResolvedValue ( statsJson as never )
295+ mockDaily . mockResolvedValue ( [ 'downloads_daily.date' , 'downloads_daily.count' ] )
289296
290297 await ingestOnePackagistDailyDownload ( qx , candidate , RUN_DATE )
291298
292299 expect ( mockDaily ) . toHaveBeenCalledWith ( qx , '7' , [ { day : RUN_DATE , downloads : 10 } ] )
300+ expect ( mockAudit ) . toHaveBeenCalledWith ( qx , 'packagist' , PURL , [
301+ 'downloads_daily.date' ,
302+ 'downloads_daily.count' ,
303+ ] )
293304 expect ( mockMarkDaily ) . toHaveBeenCalledWith (
294305 qx ,
295306 PURL ,
296307 expect . objectContaining ( { status : 'success' } ) ,
297308 )
298309 } )
299310
300- it ( 'marks success without inserting when the registry reports no daily count' , async ( ) => {
311+ it ( 'marks success without inserting or auditing when the registry reports no daily count' , async ( ) => {
301312 mockFetchStats . mockResolvedValue ( { package : { name : 'monolog/monolog' } } as never )
302313
303314 await ingestOnePackagistDailyDownload ( qx , candidate , RUN_DATE )
304315
305316 expect ( mockDaily ) . not . toHaveBeenCalled ( )
317+ expect ( mockAudit ) . not . toHaveBeenCalled ( )
306318 expect ( mockMarkDaily ) . toHaveBeenCalledWith (
307319 qx ,
308320 PURL ,
0 commit comments