@@ -7,14 +7,22 @@ import {
77 WebhookDeliveryId ,
88 RUN_OPS_ID_LENGTH ,
99 RUN_OPS_ID_REGION_INDEX ,
10+ RUN_OPS_ID_SHARD_INDEX ,
1011 RUN_OPS_ID_VERSION ,
12+ RUN_OPS_ID_VERSION_2 ,
1113 RUN_OPS_ID_VERSION_INDEX ,
1214 base32hexDecode ,
1315 base32hexEncode ,
1416 generateRunOpsId ,
17+ generateRunOpsIdV2 ,
1518 parseRunId ,
19+ parseRunOpsIdBody ,
20+ parseRunOpsIdV2Body ,
1621} from "./friendlyId.js" ;
1722
23+ /** Every legal gen-2 shard char: the full DNS-safe lowercase range. */
24+ const SHARD_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789" . split ( "" ) ;
25+
1826const CUID_LEN = 25 ;
1927
2028describe ( "RunId + WaitpointId mint cuid by default; run-ops v1 via generateRunOpsId" , ( ) => {
@@ -173,7 +181,8 @@ describe("parseRunId — version-char discrimination (not length)", () => {
173181
174182 it ( "falls back to legacy on a malformed v1 (bad alphabet / wrong version char)" , ( ) => {
175183 expect ( parseRunId ( `run_${ "A" . repeat ( 25 ) } 1` ) . format ) . toBe ( "legacy" ) ; // uppercase core
176- expect ( parseRunId ( `run_${ "a" . repeat ( 25 ) } 2` ) . format ) . toBe ( "legacy" ) ; // wrong version
184+ expect ( parseRunId ( `run_${ "a" . repeat ( 25 ) } 9` ) . format ) . toBe ( "legacy" ) ; // unallocated version
185+ expect ( parseRunId ( `run_${ "a" . repeat ( 25 ) } 2` ) . format ) . toBe ( "b32hexV2" ) ; // "2" is now gen-2
177186 expect ( parseRunId ( `run_${ "a" . repeat ( 24 ) } -1` ) . format ) . toBe ( "legacy" ) ; // region char not [a-z0-9]
178187 expect ( parseRunId ( `run_${ "a" . repeat ( 27 ) } ` ) . format ) . toBe ( "legacy" ) ; // old 27-char shape
179188 } ) ;
@@ -250,3 +259,154 @@ describe("WebhookDeliveryId (time-encoded)", () => {
250259 expect ( WebhookDeliveryId . parseTimestamp ( `whd_${ "0" . repeat ( 24 ) } 9` ) ) . toBeUndefined ( ) ;
251260 } ) ;
252261} ) ;
262+
263+ describe ( "generateRunOpsIdV2 — gen-2 id spec (shard char at 24, version '2' at 25)" , ( ) => {
264+ afterEach ( ( ) => vi . useRealTimers ( ) ) ;
265+
266+ it ( "emits <24-char base32hex core><shard char><version '2'> — 26 chars total" , ( ) => {
267+ const id = generateRunOpsIdV2 ( "a" ) ;
268+ expect ( id . length ) . toBe ( RUN_OPS_ID_LENGTH ) ;
269+ expect ( id ) . toMatch ( / ^ [ 0 - 9 a - v ] { 24 } [ a - z 0 - 9 ] 2 $ / ) ;
270+ expect ( id [ RUN_OPS_ID_VERSION_INDEX ] ) . toBe ( RUN_OPS_ID_VERSION_2 ) ;
271+ expect ( id [ RUN_OPS_ID_SHARD_INDEX ] ) . toBe ( "a" ) ;
272+ } ) ;
273+
274+ it ( "round-trips every legal shard char [a-z0-9] through parseRunOpsIdV2Body" , ( ) => {
275+ for ( const c of SHARD_CHARS ) {
276+ const id = generateRunOpsIdV2 ( c ) ;
277+ const parsed = parseRunOpsIdV2Body ( id ) ;
278+ expect ( parsed ) . toBeDefined ( ) ;
279+ expect ( parsed ?. shard ) . toBe ( c ) ;
280+ expect ( parsed ?. version ) . toBe ( RUN_OPS_ID_VERSION_2 ) ;
281+ // the core survives the round-trip: its bytes re-encode to the id's first 24 chars
282+ expect ( base32hexEncode ( base32hexDecode ( id . slice ( 0 , 24 ) ) ) ) . toBe ( id . slice ( 0 , 24 ) ) ;
283+ }
284+ } ) ;
285+
286+ it ( "throws on a shard char outside [a-z0-9] (fail loud, never mint an unroutable id)" , ( ) => {
287+ for ( const bad of [ "" , "-" , "_" , "A" , "ab" , " " , "/" ] ) {
288+ expect ( ( ) => generateRunOpsIdV2 ( bad ) ) . toThrow ( / s h a r d / i) ;
289+ }
290+ } ) ;
291+
292+ it ( "only ever uses lowercase [a-z0-9] and NEVER '-' (DNS-1123 / pod-name invariant)" , ( ) => {
293+ for ( let i = 0 ; i < 5_000 ; i ++ ) {
294+ const id = generateRunOpsIdV2 ( SHARD_CHARS [ i % SHARD_CHARS . length ] ! ) ;
295+ expect ( id ) . toMatch ( / ^ [ a - z 0 - 9 ] + $ / ) ;
296+ expect ( id ) . not . toContain ( "-" ) ;
297+ }
298+ } ) ;
299+
300+ it ( "sorts lexicographically in creation order at ms resolution, like gen-1" , ( ) => {
301+ vi . useFakeTimers ( ) ;
302+ const t = new Date ( "2026-07-04T12:00:00.000Z" ) . getTime ( ) ;
303+ vi . setSystemTime ( t ) ;
304+ const a = generateRunOpsIdV2 ( "a" ) ;
305+ vi . setSystemTime ( t + 1000 ) ;
306+ const b = generateRunOpsIdV2 ( "a" ) ;
307+ vi . setSystemTime ( t + 3 ) ;
308+ const c = generateRunOpsIdV2 ( "a" ) ;
309+ expect ( [ b , c , a ] . sort ( ) ) . toEqual ( [ a , c , b ] ) ;
310+ } ) ;
311+
312+ it ( "decode recovers the exact ms timestamp" , ( ) => {
313+ vi . useFakeTimers ( ) ;
314+ const t = new Date ( "2026-07-04T12:34:56.789Z" ) ;
315+ vi . setSystemTime ( t ) ;
316+ expect ( parseRunOpsIdV2Body ( generateRunOpsIdV2 ( "e" ) ) ?. timestamp . getTime ( ) ) . toBe ( t . getTime ( ) ) ;
317+ } ) ;
318+
319+ it ( "is unique across many mints in the same ms (72 bits of CSPRNG)" , ( ) => {
320+ vi . useFakeTimers ( ) ;
321+ vi . setSystemTime ( new Date ( "2026-07-04T00:00:00.000Z" ) ) ;
322+ const n = 2_000 ;
323+ expect ( new Set ( Array . from ( { length : n } , ( ) => generateRunOpsIdV2 ( "a" ) ) ) . size ) . toBe ( n ) ;
324+ } ) ;
325+ } ) ;
326+
327+ describe ( "parseRunOpsIdV2Body — the mirror of the v1 shape check" , ( ) => {
328+ it ( "rejects a body that is not exactly 26 chars" , ( ) => {
329+ const core = "a" . repeat ( 24 ) ;
330+ expect ( parseRunOpsIdV2Body ( "" ) ) . toBeUndefined ( ) ;
331+ expect ( parseRunOpsIdV2Body ( `${ core } 2` ) ) . toBeUndefined ( ) ; // 25
332+ expect ( parseRunOpsIdV2Body ( `${ core } ee2` ) ) . toBeUndefined ( ) ; // 27
333+ expect ( parseRunOpsIdV2Body ( "a" . repeat ( 40 ) ) ) . toBeUndefined ( ) ;
334+ } ) ;
335+
336+ it ( "rejects a body without '2' at index 25" , ( ) => {
337+ const core = "a" . repeat ( 24 ) ;
338+ for ( const version of [ "1" , "0" , "3" , "z" , "-" ] ) {
339+ expect ( parseRunOpsIdV2Body ( `${ core } e${ version } ` ) ) . toBeUndefined ( ) ;
340+ }
341+ } ) ;
342+
343+ it ( "rejects a body whose 24-char core is not base32hex" , ( ) => {
344+ for ( const badCore of [ "w" . repeat ( 24 ) , "z" . repeat ( 24 ) , "A" . repeat ( 24 ) , `${ "a" . repeat ( 23 ) } -` ] ) {
345+ expect ( parseRunOpsIdV2Body ( `${ badCore } e2` ) ) . toBeUndefined ( ) ;
346+ }
347+ } ) ;
348+
349+ it ( "rejects a body whose char at index 24 is outside [a-z0-9]" , ( ) => {
350+ const core = "a" . repeat ( 24 ) ;
351+ for ( const badShard of [ "-" , "_" , "A" , "." , " " ] ) {
352+ expect ( parseRunOpsIdV2Body ( `${ core } ${ badShard } 2` ) ) . toBeUndefined ( ) ;
353+ }
354+ } ) ;
355+
356+ it ( "never throws, for any input string" , ( ) => {
357+ for ( const input of [ "" , "x" , "-" . repeat ( 26 ) , " " . repeat ( 26 ) , "\u{1F642}" . repeat ( 26 ) ] ) {
358+ expect ( ( ) => parseRunOpsIdV2Body ( input ) ) . not . toThrow ( ) ;
359+ }
360+ } ) ;
361+ } ) ;
362+
363+ describe ( "gen-1 and gen-2 parsers reject each other (the disjointness foundation)" , ( ) => {
364+ it ( "parseRunOpsIdBody rejects every gen-2 id" , ( ) => {
365+ for ( const c of SHARD_CHARS ) {
366+ expect ( parseRunOpsIdBody ( generateRunOpsIdV2 ( c ) ) ) . toBeUndefined ( ) ;
367+ }
368+ } ) ;
369+
370+ it ( "parseRunOpsIdV2Body rejects every gen-1 v1 id" , ( ) => {
371+ for ( const region of [ undefined , "us-east-1" , "us-west-2" , "eu-central-1" ] ) {
372+ expect ( parseRunOpsIdV2Body ( generateRunOpsId ( region ) ) ) . toBeUndefined ( ) ;
373+ }
374+ } ) ;
375+
376+ it ( "generateRunOpsId still mints v1 ids — the gen-1 generator is unchanged" , ( ) => {
377+ const id = generateRunOpsId ( "us-east-1" ) ;
378+ expect ( id ) . toMatch ( / ^ [ 0 - 9 a - v ] { 24 } [ a - z 0 - 9 ] 1 $ / ) ;
379+ expect ( id [ RUN_OPS_ID_VERSION_INDEX ] ) . toBe ( RUN_OPS_ID_VERSION ) ;
380+ expect ( parseRunOpsIdBody ( id ) ?. region ) . toBe ( "e" ) ;
381+ } ) ;
382+
383+ it ( "the shard index and the region index are the same position" , ( ) => {
384+ expect ( RUN_OPS_ID_SHARD_INDEX ) . toBe ( RUN_OPS_ID_REGION_INDEX ) ;
385+ } ) ;
386+ } ) ;
387+
388+ describe ( "parseRunId — v2 arm" , ( ) => {
389+ it ( "parses a gen-2 friendly id as partitioned with its shard + version" , ( ) => {
390+ const parsed = parseRunId ( `run_${ generateRunOpsIdV2 ( "e" ) } ` ) ;
391+ expect ( parsed ) . toMatchObject ( {
392+ format : "b32hexV2" ,
393+ table : "partitioned" ,
394+ shard : "e" ,
395+ version : "2" ,
396+ } ) ;
397+ } ) ;
398+
399+ it ( "still parses a gen-1 v1 friendly id as b32hex — the v1 arm is unchanged" , ( ) => {
400+ expect ( parseRunId ( `run_${ generateRunOpsId ( "us-west-2" ) } ` ) ) . toMatchObject ( {
401+ format : "b32hex" ,
402+ table : "partitioned" ,
403+ region : "w" ,
404+ version : "1" ,
405+ } ) ;
406+ } ) ;
407+
408+ it ( "classifies a gen-2 body without the run_ prefix, and under a wrong prefix, legacy" , ( ) => {
409+ expect ( parseRunId ( generateRunOpsIdV2 ( "a" ) ) . format ) . toBe ( "legacy" ) ;
410+ expect ( parseRunId ( `waitpoint_${ generateRunOpsIdV2 ( "a" ) } ` ) . format ) . toBe ( "legacy" ) ;
411+ } ) ;
412+ } ) ;
0 commit comments