Commit 4 of 17:
138ba99941
test: harden archived log correlation and alerts
previous
next
whole branch
Review threads live on the whole diff, not on one commit, so
none are shown here - a thread's line means something in the
branch's final form, and painting it into an intermediate
step would put it on code it is not about.
app/Archive/sql.ts modified +8 -0 Changes to app/Archive/sql.ts ⋯ 140 lines Show the 140 lines above this hunk @@ -141,6 +141,8 @@ export interface ArchiveFilters { 141 141 channel ? : string 142 142 environment ? : string 143 143 release ? : string 144 + traceId ? : string 145 + requestId ? : string 144 146 145 147 q ? : string 146 148 fromDay ? : string ⋯ 40 lines Show the 40 lines above this hunk @@ -187,6 +189,12 @@ function filterClauses(f: ArchiveFilters): string[] { 187 189 if ( f . release ) 188 190 where . push ( `"release" = ${sqlQuote(String(f.release).slice(0, 255))}` ) 189 191 192 + if ( f . traceId ) 193 + where . push ( `"trace_id" = ${sqlQuote(String(f.traceId).slice(0, 64))}` ) 194 + 195 + if ( f . requestId ) 196 + where . push ( `"request_id" = ${sqlQuote(String(f.requestId).slice(0, 64))}` ) 197 + 190 198 if ( f . q ) { 191 199 192 200
routes/analytics.ts modified +2 -0 Changes to routes/analytics.ts ⋯ 126 lines Show the 126 lines above this hunk @@ -127,6 +127,8 @@ route.get('/api/projects/{projectId}/archive/search', async (request: any) => { 127 127 channel : q . channel ? String ( q . channel ) : undefined , 128 128 environment : q . environment ? String ( q . environment ) : undefined , 129 129 release : q . release ? String ( q . release ) : undefined , 130 + traceId : q . trace ? String ( q . trace ) : undefined , 131 + requestId : q . request ? String ( q . request ) : undefined , 130 132 q : q . q ? String ( q . q ) : undefined , 131 133 fromDay : q . from ? String ( q . from ) : undefined , 132 134 toDay : q . to ? String ( q . to ) : undefined ,
tests/unit/alert-channels.test.ts added +22 -0 Changes to tests/unit/alert-channels.test.ts @@ -0,0 +1,22 @@ 1 + import { describe , expect , test } from 'bun:test' 2 + import { validateWebhook } from '../../app/Errors/channels' 3 + 4 + describe ( 'alert channel URL policy' , ( ) => { 5 + test ( 'accepts genuine Slack and Discord incoming webhooks' , ( ) => { 6 + expect ( validateWebhook ( 'slack' , 'https://hooks.slack.com/services/T/B/token' ) ) . toEqual ( { ok : true } ) 7 + expect ( validateWebhook ( 'discord' , 'https://discord.com/api/webhooks/123/token' ) ) . toEqual ( { ok : true } ) 8 + expect ( validateWebhook ( 'discord' , 'https://discord.com/api/v10/webhooks/123/token' ) ) . toEqual ( { ok : true } ) 9 + } ) 10 + 11 + test ( 'rejects non-HTTPS and lookalike provider hosts' , ( ) => { 12 + expect ( validateWebhook ( 'slack' , 'http://hooks.slack.com/services/T/B/token' ) . ok ) . toBe ( false ) 13 + expect ( validateWebhook ( 'slack' , 'https://hooks.slack.com.attacker.test/services/T/B/token' ) . ok ) . toBe ( false ) 14 + expect ( validateWebhook ( 'discord' , 'https://discord.com.attacker.test/api/webhooks/123/token' ) . ok ) . toBe ( false ) 15 + } ) 16 + 17 + test ( 'rejects provider pages that are not webhook endpoints' , ( ) => { 18 + expect ( validateWebhook ( 'slack' , 'https://hooks.slack.com/' ) . ok ) . toBe ( false ) 19 + expect ( validateWebhook ( 'discord' , 'https://discord.com/channels/123' ) . ok ) . toBe ( false ) 20 + expect ( validateWebhook ( 'email' , 'https://hooks.slack.com/services/T/B/token' ) . ok ) . toBe ( false ) 21 + } ) 22 + } )
tests/unit/archive-sql.test.ts modified +17 -0 Changes to tests/unit/archive-sql.test.ts ⋯ 163 lines Show the 163 lines above this hunk @@ -164,6 +164,23 @@ describe('buildSearchSql', () => { 164 164 expect ( sql ) . not . toMatch ( / OR '1' = '1' / ) 165 165 } ) 166 166 167 + test ( 'keeps trace and request correlation available after archival' , ( ) => { 168 + const sql = buildSearchSql ( 'logs' , [ 'project_id=demo/day=2026-09-12/logs.parquet' ] , { 169 + traceId : 'trace-42' , 170 + requestId : 'request-9' , 171 + } ) 172 + expect ( sql ) . toContain ( '"trace_id" = \'trace-42\'' ) 173 + expect ( sql ) . toContain ( '"request_id" = \'request-9\'' ) 174 + } ) 175 + 176 + test ( 'caps archive correlation filters to their stored column width' , ( ) => { 177 + const sql = buildSearchSql ( 'logs' , [ 'project_id=demo/day=2026-09-12/logs.parquet' ] , { 178 + traceId : 't' . repeat ( 100 ) , 179 + } ) 180 + expect ( sql ) . toContain ( `"trace_id" = '${'t'.repeat(64)}'` ) 181 + expect ( sql ) . not . toContain ( 't' . repeat ( 65 ) ) 182 + } ) 183 + 167 184 test ( 'pages on the whole (timestamp, id) pair, not timestamp alone' , ( ) => { 168 185 169 186