@@ -326,16 +326,30 @@ pub fn logAudit(kind: u8, detail: []const u8) void {
326326 append (.audit , buf [0 .. 3 + detail .len ]);
327327}
328328
329- /// TRACK_UPDATE — peer_suffix[4]u8 outcome:u8 (0=fail, 1=success)
330- /// tag_len:u8 tag[tag_len]
331- pub fn logTrackUpdate (peer_suffix : * const [4 ]u8 , outcome : u8 , tag : []const u8 ) void {
329+ /// TRACK_UPDATE — client_kind:u8 outcome:u8 risk_tier:u8 duration_ms:u32
330+ /// timestamp_ms:u64 tag_len:u8 tag[tag_len]
331+ ///
332+ /// DD-29: keyed on client_kind (not peer_id/suffix) so the track record
333+ /// survives peer crash+restart — a fresh peer of the same client_kind
334+ /// inherits the track record.
335+ pub fn logTrackUpdate (
336+ client_kind : u8 ,
337+ outcome : u8 ,
338+ risk_tier : u8 ,
339+ duration_ms : u32 ,
340+ timestamp_ms : u64 ,
341+ tag : []const u8 ,
342+ ) void {
332343 if (tag .len > 64 ) return ;
333- var buf : [6 + 64 ]u8 = undefined ;
334- @memcpy (buf [0.. 4], peer_suffix );
335- buf [4 ] = outcome ;
336- buf [5 ] = @intCast (tag .len );
337- if (tag .len > 0 ) @memcpy (buf [6 .. 6 + tag .len ], tag );
338- append (.track_update , buf [0 .. 6 + tag .len ]);
344+ var buf : [16 + 64 ]u8 = undefined ;
345+ buf [0 ] = client_kind ;
346+ buf [1 ] = outcome ;
347+ buf [2 ] = risk_tier ;
348+ std .mem .writeInt (u32 , buf [3.. 7], duration_ms , .little );
349+ std .mem .writeInt (u64 , buf [7.. 15], timestamp_ms , .little );
350+ buf [15 ] = @intCast (tag .len );
351+ if (tag .len > 0 ) @memcpy (buf [16 .. 16 + tag .len ], tag );
352+ append (.track_update , buf [0 .. 16 + tag .len ]);
339353}
340354
341355// ═══════════════════════════════════════════════════════════════════════
@@ -442,16 +456,26 @@ pub fn decodeAudit(p: []const u8) ?Audit {
442456 return .{ .kind = p [0 ], .detail = p [3 .. 3 + n ] };
443457}
444458
445- pub const TrackUpdate = struct { suffix : [4 ]u8 , outcome : u8 , tag : []const u8 };
459+ pub const TrackUpdate = struct {
460+ client_kind : u8 ,
461+ outcome : u8 ,
462+ risk_tier : u8 ,
463+ duration_ms : u32 ,
464+ timestamp_ms : u64 ,
465+ tag : []const u8 ,
466+ };
446467pub fn decodeTrackUpdate (p : []const u8 ) ? TrackUpdate {
447- if (p .len < 6 ) return null ;
448- const n : usize = p [5 ];
449- if (p .len < 6 + n ) return null ;
450- var out : TrackUpdate = undefined ;
451- @memcpy (& out .suffix , p [0.. 4]);
452- out .outcome = p [4 ];
453- out .tag = p [6 .. 6 + n ];
454- return out ;
468+ if (p .len < 16 ) return null ;
469+ const n : usize = p [15 ];
470+ if (p .len < 16 + n ) return null ;
471+ return .{
472+ .client_kind = p [0 ],
473+ .outcome = p [1 ],
474+ .risk_tier = p [2 ],
475+ .duration_ms = std .mem .readInt (u32 , p [3.. 7], .little ),
476+ .timestamp_ms = std .mem .readInt (u64 , p [7.. 15], .little ),
477+ .tag = p [16 .. 16 + n ],
478+ };
455479}
456480
457481// ═══════════════════════════════════════════════════════════════════════
@@ -587,7 +611,7 @@ test "replay decodes every event type" {
587611 logQuarApprove (42 );
588612 logQuarReject (43 , "confabulated path" );
589613 logAudit (1 , "tier3-from-supervised" );
590- logTrackUpdate (& suffix , 1 , "proof-analysis" );
614+ logTrackUpdate (0 , 1 , 2 , 1234 , 1_700_000_000_000 , "proof-analysis" );
591615 logPeerRemove (0 );
592616 close ();
593617
@@ -612,6 +636,10 @@ test "replay decodes every event type" {
612636
613637 try std .testing .expectEqual (EventType .track_update , t_events [12 ]);
614638 const tr = decodeTrackUpdate (t_payloads [12 ][0.. t_payload_lens [12 ]]) orelse return error .DecodeFailed ;
639+ try std .testing .expectEqual (@as (u8 , 0 ), tr .client_kind );
615640 try std .testing .expectEqual (@as (u8 , 1 ), tr .outcome );
641+ try std .testing .expectEqual (@as (u8 , 2 ), tr .risk_tier );
642+ try std .testing .expectEqual (@as (u32 , 1234 ), tr .duration_ms );
643+ try std .testing .expectEqual (@as (u64 , 1_700_000_000_000 ), tr .timestamp_ms );
616644 try std .testing .expectEqualSlices (u8 , "proof-analysis" , tr .tag );
617645}
0 commit comments