@nxgt/janus 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +118 -18
  2. package/dist/auth/config/janus-config.d.ts +2 -0
  3. package/dist/auth/config/janus-config.d.ts.map +1 -1
  4. package/dist/auth/config/resolved-config.d.ts +1 -0
  5. package/dist/auth/config/resolved-config.d.ts.map +1 -1
  6. package/dist/auth/index.d.ts +2 -1
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/port/assert-stores.d.ts.map +1 -1
  9. package/dist/auth/port/memory/sessions.d.ts.map +1 -1
  10. package/dist/auth/port/types/sessions.d.ts +16 -1
  11. package/dist/auth/port/types/sessions.d.ts.map +1 -1
  12. package/dist/auth/port/types/tokens.d.ts +7 -2
  13. package/dist/auth/port/types/tokens.d.ts.map +1 -1
  14. package/dist/auth/second-factor/{regenerate-attempts.d.ts → app-code-attempts.d.ts} +13 -10
  15. package/dist/auth/second-factor/app-code-attempts.d.ts.map +1 -0
  16. package/dist/auth/second-factor/codes-left.d.ts +14 -0
  17. package/dist/auth/second-factor/codes-left.d.ts.map +1 -0
  18. package/dist/auth/second-factor/flows.d.ts +1 -0
  19. package/dist/auth/second-factor/flows.d.ts.map +1 -1
  20. package/dist/auth/sessions/authenticate.d.ts.map +1 -1
  21. package/dist/auth/sessions/fresh.d.ts +26 -0
  22. package/dist/auth/sessions/fresh.d.ts.map +1 -0
  23. package/dist/auth/sessions/standing.d.ts +20 -0
  24. package/dist/auth/sessions/standing.d.ts.map +1 -0
  25. package/dist/auth/step-up/codes.d.ts +29 -0
  26. package/dist/auth/step-up/codes.d.ts.map +1 -0
  27. package/dist/auth/step-up/flows.d.ts +19 -0
  28. package/dist/auth/step-up/flows.d.ts.map +1 -0
  29. package/dist/auth/step-up/open.d.ts +23 -0
  30. package/dist/auth/step-up/open.d.ts.map +1 -0
  31. package/dist/auth/step-up/request.d.ts +12 -0
  32. package/dist/auth/step-up/request.d.ts.map +1 -0
  33. package/dist/auth/types/index.d.ts +1 -0
  34. package/dist/auth/types/index.d.ts.map +1 -1
  35. package/dist/auth/types/janus.d.ts +2 -1
  36. package/dist/auth/types/janus.d.ts.map +1 -1
  37. package/dist/auth/types/second-factor.d.ts +9 -0
  38. package/dist/auth/types/second-factor.d.ts.map +1 -1
  39. package/dist/auth/types/step-up.d.ts +79 -0
  40. package/dist/auth/types/step-up.d.ts.map +1 -0
  41. package/dist/auth/users/any-type-api.d.ts +2 -2
  42. package/dist/auth/users/any-type-api.d.ts.map +1 -1
  43. package/dist/auth/users/type-api.d.ts +1 -1
  44. package/dist/auth/users/type-api.d.ts.map +1 -1
  45. package/dist/chunks/{index-5e2fw7he.js → index-j2gd8fry.js} +12 -3
  46. package/dist/chunks/{index-5e2fw7he.js.map → index-j2gd8fry.js.map} +4 -4
  47. package/dist/chunks/{index-5tm04t5p.js → index-jp2kka6d.js} +2 -2
  48. package/dist/chunks/{index-6f6wja93.js → index-km7ffctx.js} +2 -2
  49. package/dist/chunks/{index-4m49cye4.js → index-rd2rddvv.js} +15 -12
  50. package/dist/chunks/{index-4m49cye4.js.map → index-rd2rddvv.js.map} +3 -3
  51. package/dist/conformance/cases/outage.d.ts.map +1 -1
  52. package/dist/conformance/cases/sessions/index.d.ts.map +1 -1
  53. package/dist/conformance/cases/sessions/reauthentication.d.ts +4 -0
  54. package/dist/conformance/cases/sessions/reauthentication.d.ts.map +1 -0
  55. package/dist/conformance/cases/tokens/index.d.ts.map +1 -1
  56. package/dist/conformance/cases/tokens/kinds.d.ts +4 -0
  57. package/dist/conformance/cases/tokens/kinds.d.ts.map +1 -0
  58. package/dist/conformance/index.js +147 -60
  59. package/dist/conformance/index.js.map +8 -6
  60. package/dist/errors/janus-error/codes.d.ts +7 -0
  61. package/dist/errors/janus-error/codes.d.ts.map +1 -1
  62. package/dist/errors/janus-error/index.d.ts +1 -1
  63. package/dist/errors/janus-error/index.d.ts.map +1 -1
  64. package/dist/errors/janus-error/refusals.d.ts +8 -0
  65. package/dist/errors/janus-error/refusals.d.ts.map +1 -1
  66. package/dist/errors/janus-error/status.d.ts.map +1 -1
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +184 -19
  70. package/dist/index.js.map +18 -11
  71. package/dist/permissions/index.js +3 -3
  72. package/docs/README.md +1 -0
  73. package/docs/guide/adapters.md +38 -4
  74. package/docs/guide/errors.md +1 -0
  75. package/docs/guide/events.md +21 -8
  76. package/docs/guide/second-factor.md +45 -16
  77. package/docs/guide/sessions.md +7 -0
  78. package/docs/guide/step-up.md +178 -0
  79. package/docs/guide/users.md +1 -0
  80. package/docs/guide/vocabulary.md +3 -2
  81. package/docs/roadmap.md +18 -23
  82. package/docs/troubleshooting.md +183 -8
  83. package/package.json +1 -1
  84. package/dist/auth/second-factor/regenerate-attempts.d.ts.map +0 -1
  85. /package/dist/chunks/{index-5tm04t5p.js.map → index-jp2kka6d.js.map} +0 -0
  86. /package/dist/chunks/{index-6f6wja93.js.map → index-km7ffctx.js.map} +0 -0
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  mintId2,
3
3
  createMemoryStores2
4
- } from "../chunks/index-4m49cye4.js";
4
+ } from "../chunks/index-rd2rddvv.js";
5
5
  import {
6
6
  JanusError2,
7
7
  StoreFailure2,
8
8
  StoreConflict2,
9
9
  NotFoundError2,
10
10
  formatSubject2
11
- } from "../chunks/index-5e2fw7he.js";
11
+ } from "../chunks/index-j2gd8fry.js";
12
12
  import {
13
13
  createMemoryRelations2
14
- } from "../chunks/index-6f6wja93.js";
14
+ } from "../chunks/index-km7ffctx.js";
15
15
 
16
16
  // src/conformance/assert.ts
17
17
  function equal(actual, expected, what) {
@@ -184,6 +184,7 @@ var outageCases = [
184
184
  outage("users", "listUsers", ({ stores }) => stores.users.listUsers({ type: user.type, after: null, limit: 10 }), seed),
185
185
  outage("sessions", "findSessionByTokenHash", ({ stores }) => stores.sessions.findSessionByTokenHash(session.tokenHash), seed),
186
186
  outage("sessions", "extendSession", ({ stores }) => stores.sessions.extendSession(session.id, at("2099-06-01T00:00:00.000Z")), seed),
187
+ outage("sessions", "reauthenticateSession", ({ stores }) => stores.sessions.reauthenticateSession(session.id, now), seed),
187
188
  outage("sessions", "revokeSession", ({ stores }) => stores.sessions.revokeSession(session.id, now), seed),
188
189
  outage("sessions", "revokeUserSessions", ({ stores }) => stores.sessions.revokeUserSessions(user.id, now), seed),
189
190
  outage("tokens", "consumeToken", ({ stores }) => stores.tokens.consumeToken(token.tokenHash, "resetPassword", now), seed),
@@ -320,12 +321,53 @@ var sessionReadCases = [
320
321
  }
321
322
  ];
322
323
 
323
- // src/conformance/cases/sessions/revocation.ts
324
+ // src/conformance/cases/sessions/reauthentication.ts
324
325
  var group5 = "sessions";
326
+ var sessionReauthenticationCases = [
327
+ {
328
+ id: "sessions.reauthenticate",
329
+ group: group5,
330
+ name: "moves authenticatedAt on a standing session, and never brings back a revoked one",
331
+ async run({ stores }) {
332
+ const standing = sessionRecord();
333
+ const revoked = sessionRecord({
334
+ revokedAt: at("2026-01-02T00:00:00.000Z")
335
+ });
336
+ await stores.sessions.insertSession(standing);
337
+ await stores.sessions.insertSession(revoked);
338
+ const now = at("2026-02-01T00:00:00.000Z");
339
+ equal(await stores.sessions.reauthenticateSession(standing.id, now), { ...standing, authenticatedAt: now }, "reauthenticateSession should answer the session with its new authenticatedAt, and every other field as it was");
340
+ equal(await stores.sessions.findSessionByTokenHash(standing.tokenHash), { ...standing, authenticatedAt: now }, "reauthenticateSession should have written authenticatedAt, and read back the same");
341
+ isNull(await stores.sessions.reauthenticateSession(revoked.id, now), "reauthenticateSession on a revoked session — a confirmation racing a revocation must never bring it back");
342
+ equal(await stores.sessions.findSessionByTokenHash(revoked.tokenHash), revoked, "reauthenticateSession refused on a revoked session should have written nothing");
343
+ isNull(await stores.sessions.reauthenticateSession(mintId2(), now), "reauthenticateSession on an unknown session");
344
+ }
345
+ },
346
+ {
347
+ id: "sessions.reauthenticateRace",
348
+ group: group5,
349
+ name: "never brings back a session whose revocation races its confirmation",
350
+ async run({ stores }) {
351
+ const now = at("2026-02-01T00:00:00.000Z");
352
+ for (let round = 0;round < 10; round += 1) {
353
+ const record = sessionRecord();
354
+ await stores.sessions.insertSession(record);
355
+ await Promise.all([
356
+ stores.sessions.reauthenticateSession(record.id, now),
357
+ stores.sessions.revokeSession(record.id, now)
358
+ ]);
359
+ equal((await stores.sessions.findSessionByTokenHash(record.tokenHash))?.revokedAt, now, "reauthenticateSession racing revokeSession: the session should stay revoked — one conditional write, never a read then a write");
360
+ }
361
+ }
362
+ }
363
+ ];
364
+
365
+ // src/conformance/cases/sessions/revocation.ts
366
+ var group6 = "sessions";
325
367
  var sessionRevocationCases = [
326
368
  {
327
369
  id: "sessions.revoke",
328
- group: group5,
370
+ group: group6,
329
371
  name: "revokes once and keeps the first revokedAt; answers false only when there is no session",
330
372
  async run({ stores }) {
331
373
  const record = sessionRecord();
@@ -339,7 +381,7 @@ var sessionRevocationCases = [
339
381
  },
340
382
  {
341
383
  id: "sessions.revokeUser",
342
- group: group5,
384
+ group: group6,
343
385
  name: "revokes every standing session of one user but the one excepted, and counts only what it revoked",
344
386
  async run({ stores }) {
345
387
  const userId = mintId2();
@@ -369,15 +411,16 @@ var sessionStoreCases = [
369
411
  ...sessionReadCases,
370
412
  ...sessionInsertCases,
371
413
  ...sessionExtensionCases,
414
+ ...sessionReauthenticationCases,
372
415
  ...sessionRevocationCases,
373
416
  ...sessionDeletionCases
374
417
  ];
375
418
  // src/conformance/cases/tokens/attempts.ts
376
- var group6 = "tokens";
419
+ var group7 = "tokens";
377
420
  var tokenAttemptCases = [
378
421
  {
379
422
  id: "tokens.countAttempt",
380
- group: group6,
423
+ group: group7,
381
424
  name: "counts an attempt at a code, and answers the token as it is after the call",
382
425
  async run({ stores }) {
383
426
  const record = tokenRecord({
@@ -392,7 +435,7 @@ var tokenAttemptCases = [
392
435
  },
393
436
  {
394
437
  id: "tokens.countAttemptConcurrency",
395
- group: group6,
438
+ group: group7,
396
439
  name: "gives twenty concurrent attempts twenty distinct counts — one conditional write, never a read then a write",
397
440
  async run({ stores }) {
398
441
  const record = tokenRecord({ kind: "secondFactor" });
@@ -403,7 +446,7 @@ var tokenAttemptCases = [
403
446
  },
404
447
  {
405
448
  id: "tokens.countAttemptRace",
406
- group: group6,
449
+ group: group7,
407
450
  name: "never counts an attempt once the token is spent, when attempts and a redemption race",
408
451
  async run({ stores }) {
409
452
  const record = tokenRecord({ kind: "signInCode" });
@@ -426,7 +469,7 @@ var tokenAttemptCases = [
426
469
  },
427
470
  {
428
471
  id: "tokens.countAttemptSpent",
429
- group: group6,
472
+ group: group7,
430
473
  name: "does not count an attempt at a spent token, nor at a token of another kind, nor at none",
431
474
  async run({ stores }) {
432
475
  const spent = tokenRecord({
@@ -448,11 +491,11 @@ var tokenAttemptCases = [
448
491
  ];
449
492
 
450
493
  // src/conformance/cases/tokens/deletion.ts
451
- var group7 = "tokens";
494
+ var group8 = "tokens";
452
495
  var tokenDeletionCases = [
453
496
  {
454
497
  id: "tokens.deleteUser",
455
- group: group7,
498
+ group: group8,
456
499
  name: "deletes every token of one user, spent or not, of either kind, and counts them",
457
500
  async run({ stores }) {
458
501
  const userId = mintId2();
@@ -477,11 +520,11 @@ var tokenDeletionCases = [
477
520
  ];
478
521
 
479
522
  // src/conformance/cases/tokens/inserts.ts
480
- var group8 = "tokens";
523
+ var group9 = "tokens";
481
524
  var tokenInsertCases = [
482
525
  {
483
526
  id: "tokens.challenge",
484
- group: group8,
527
+ group: group9,
485
528
  name: "keeps a second-factor challenge, whose address is '' — nothing was sent for it",
486
529
  async run({ stores }) {
487
530
  const record = tokenRecord({ kind: "secondFactor", address: "" });
@@ -492,7 +535,7 @@ var tokenInsertCases = [
492
535
  },
493
536
  {
494
537
  id: "tokens.lapsed",
495
- group: group8,
538
+ group: group9,
496
539
  name: "spends a lapsed token all the same: expiry is compared by the core, after the call",
497
540
  async run({ stores }) {
498
541
  const record = tokenRecord({ expiresAt: at("2020-01-01T00:00:00.000Z") });
@@ -507,7 +550,7 @@ var tokenInsertCases = [
507
550
  },
508
551
  {
509
552
  id: "tokens.idempotentInsert",
510
- group: group8,
553
+ group: group9,
511
554
  name: "is idempotent under retry: inserting an existing token hash writes nothing",
512
555
  async run({ stores }) {
513
556
  const record = tokenRecord();
@@ -521,12 +564,55 @@ var tokenInsertCases = [
521
564
  }
522
565
  ];
523
566
 
567
+ // src/conformance/cases/tokens/kinds.ts
568
+ var group10 = "tokens";
569
+ var KINDS = Object.keys({
570
+ verifyEmail: true,
571
+ resetPassword: true,
572
+ secondFactor: true,
573
+ signInCode: true,
574
+ stepUp: true
575
+ });
576
+ var tokenKindCases = [
577
+ {
578
+ id: "tokens.everyKind",
579
+ group: group10,
580
+ name: "stores, counts and spends a token of every kind the port names",
581
+ async run({ stores }) {
582
+ const now = at("2026-02-01T00:00:00.000Z");
583
+ for (const kind of KINDS) {
584
+ const record = tokenRecord({ kind, codeHash: "c".repeat(64) });
585
+ await stores.tokens.insertToken(record);
586
+ equal(await stores.tokens.countAttempt(record.tokenHash, kind), { ...record, attempts: 1 }, `countAttempt on a ${kind} token should answer it with one attempt`);
587
+ equal(await stores.tokens.consumeToken(record.tokenHash, kind, now), { ...record, attempts: 1 }, `consumeToken on a ${kind} token should answer it as it was — spentAt null`);
588
+ }
589
+ }
590
+ },
591
+ {
592
+ id: "tokens.stepUpKind",
593
+ group: group10,
594
+ name: "keeps a step-up apart from a sign-in code: neither is counted, spent nor answered as the other",
595
+ async run({ stores }) {
596
+ const userId = mintId2();
597
+ const stepUp = tokenRecord({ userId, kind: "stepUp" });
598
+ const signInCode = tokenRecord({ userId, kind: "signInCode" });
599
+ await stores.tokens.insertToken(stepUp);
600
+ await stores.tokens.insertToken(signInCode);
601
+ const now = at("2026-02-01T00:00:00.000Z");
602
+ isNull(await stores.tokens.countAttempt(stepUp.tokenHash, "signInCode"), "countAttempt for a stepUp token counted as signInCode");
603
+ isNull(await stores.tokens.consumeToken(signInCode.tokenHash, "stepUp", now), "consumeToken for a signInCode token redeemed as stepUp");
604
+ equal(await stores.tokens.spendUserTokens(userId, "stepUp", now), 1, "spendUserTokens of stepUp: the step-up only");
605
+ equal(await stores.tokens.consumeToken(signInCode.tokenHash, "signInCode", now), signInCode, "spendUserTokens of stepUp should not touch a sign-in code, nor anything else have counted it");
606
+ }
607
+ }
608
+ ];
609
+
524
610
  // src/conformance/cases/tokens/redemption.ts
525
- var group9 = "tokens";
611
+ var group11 = "tokens";
526
612
  var tokenRedemptionCases = [
527
613
  {
528
614
  id: "tokens.consume",
529
- group: group9,
615
+ group: group11,
530
616
  name: "answers the token as it was before the call: spentAt null means this call spent it",
531
617
  async run({ stores }) {
532
618
  const record = tokenRecord();
@@ -538,7 +624,7 @@ var tokenRedemptionCases = [
538
624
  },
539
625
  {
540
626
  id: "tokens.concurrency",
541
- group: group9,
627
+ group: group11,
542
628
  name: "lets exactly one of twenty concurrent redemptions spend a token — one conditional write, never a read then a write",
543
629
  async run({ stores }) {
544
630
  const record = tokenRecord();
@@ -550,7 +636,7 @@ var tokenRedemptionCases = [
550
636
  },
551
637
  {
552
638
  id: "tokens.kind",
553
- group: group9,
639
+ group: group11,
554
640
  name: "does not know, and does not spend, a token of the other kind",
555
641
  async run({ stores }) {
556
642
  const record = tokenRecord({ kind: "verifyEmail" });
@@ -564,11 +650,11 @@ var tokenRedemptionCases = [
564
650
  ];
565
651
 
566
652
  // src/conformance/cases/tokens/spending.ts
567
- var group10 = "tokens";
653
+ var group12 = "tokens";
568
654
  var tokenSpendingCases = [
569
655
  {
570
656
  id: "tokens.spendUserTokens",
571
- group: group10,
657
+ group: group12,
572
658
  name: "spends every unspent token of one user and one kind, and counts them, leaving the rest as they were",
573
659
  async run({ stores }) {
574
660
  const userId = mintId2();
@@ -599,7 +685,7 @@ var tokenSpendingCases = [
599
685
  },
600
686
  {
601
687
  id: "tokens.spendUserTokensExcept",
602
- group: group10,
688
+ group: group12,
603
689
  name: "spares the token named by except, and spends the rest",
604
690
  async run({ stores }) {
605
691
  const userId = mintId2();
@@ -621,7 +707,7 @@ var tokenSpendingCases = [
621
707
  },
622
708
  {
623
709
  id: "tokens.spendUserTokensRace",
624
- group: group10,
710
+ group: group12,
625
711
  name: "spends a token once, when spending a user's tokens races a redemption",
626
712
  async run({ stores }) {
627
713
  const now = at("2026-02-01T00:00:00.000Z");
@@ -644,14 +730,15 @@ var tokenStoreCases = [
644
730
  ...tokenAttemptCases,
645
731
  ...tokenSpendingCases,
646
732
  ...tokenInsertCases,
733
+ ...tokenKindCases,
647
734
  ...tokenDeletionCases
648
735
  ];
649
736
  // src/conformance/cases/users/deletion.ts
650
- var group11 = "users";
737
+ var group13 = "users";
651
738
  var userDeletionCases = [
652
739
  {
653
740
  id: "users.delete",
654
- group: group11,
741
+ group: group13,
655
742
  name: "deletes a user and frees their logins, answers false for nobody, and touches no other user",
656
743
  async run({ stores }) {
657
744
  const record = userRecord();
@@ -671,11 +758,11 @@ var userDeletionCases = [
671
758
  ];
672
759
 
673
760
  // src/conformance/cases/users/inserts.ts
674
- var group12 = "users";
761
+ var group14 = "users";
675
762
  var userInsertCases = [
676
763
  {
677
764
  id: "users.idempotentInsert",
678
- group: group12,
765
+ group: group14,
679
766
  name: "is idempotent under retry: inserting an existing id answers the stored record and writes nothing",
680
767
  async run({ stores }) {
681
768
  const record = userRecord();
@@ -690,7 +777,7 @@ var userInsertCases = [
690
777
  },
691
778
  {
692
779
  id: "users.uniqueness",
693
- group: group12,
780
+ group: group14,
694
781
  name: "lets exactly one of twenty concurrent inserts hold a login: uniqueness is a constraint, not a read",
695
782
  async run({ stores }) {
696
783
  const records = Array.from({ length: 20 }, () => userRecord({ logins: ["contested@example.test"] }));
@@ -715,11 +802,11 @@ var userInsertCases = [
715
802
  ];
716
803
 
717
804
  // src/conformance/cases/users/listing.ts
718
- var group13 = "users";
805
+ var group15 = "users";
719
806
  var userListingCases = [
720
807
  {
721
808
  id: "users.pagination",
722
- group: group13,
809
+ group: group15,
723
810
  name: "pages 25 users of one type by 10 in ascending id order, with no gap, no repeat and no other type, and ends on a null cursor",
724
811
  async run({ stores }) {
725
812
  const records = Array.from({ length: 25 }, () => userRecord());
@@ -820,11 +907,11 @@ async function nullClearsCodes(stores, id) {
820
907
  }
821
908
 
822
909
  // src/conformance/cases/users/patches.ts
823
- var group14 = "users";
910
+ var group16 = "users";
824
911
  var userPatchCases = [
825
912
  {
826
913
  id: "users.omission",
827
- group: group14,
914
+ group: group16,
828
915
  name: "omission — the Kratos PUT trap: an update leaves every field it does not name exactly as it was",
829
916
  async run({ stores }) {
830
917
  const record = userRecord({
@@ -849,7 +936,7 @@ var userPatchCases = [
849
936
  },
850
937
  {
851
938
  id: "users.passwordSlot",
852
- group: group14,
939
+ group: group16,
853
940
  name: "keeps a password the patch does not name, and removes one the patch names null",
854
941
  async run({ stores }) {
855
942
  const record = userRecord();
@@ -863,7 +950,7 @@ var userPatchCases = [
863
950
  secondFactorSlotCase,
864
951
  {
865
952
  id: "users.loginsMove",
866
- group: group14,
953
+ group: group16,
867
954
  name: "moves logins on update: the old one is free, the new one found, one held elsewhere refused",
868
955
  async run({ stores }) {
869
956
  const record = userRecord();
@@ -884,11 +971,11 @@ var userPatchCases = [
884
971
  ];
885
972
 
886
973
  // src/conformance/cases/users/reads.ts
887
- var group15 = "users";
974
+ var group17 = "users";
888
975
  var userReadCases = [
889
976
  {
890
977
  id: "users.roundTrip",
891
- group: group15,
978
+ group: group17,
892
979
  name: 'round-trips a record byte for byte: nested fields, a 1 that stays a number, a "1" that stays a string',
893
980
  async run({ stores }) {
894
981
  const record = userRecord({
@@ -912,7 +999,7 @@ var userReadCases = [
912
999
  },
913
1000
  {
914
1001
  id: "users.edgeCharacters",
915
- group: group15,
1002
+ group: group17,
916
1003
  name: "round-trips every character the core lets through: control characters, U+FFFF and a surrogate pair",
917
1004
  async run({ stores }) {
918
1005
  const edge = "a\x01\x1F￿ \uD83D\uDE00 z";
@@ -927,7 +1014,7 @@ var userReadCases = [
927
1014
  },
928
1015
  {
929
1016
  id: "users.absence",
930
- group: group15,
1017
+ group: group17,
931
1018
  name: "answers null for an absence, never undefined, and an empty page for an empty store",
932
1019
  async run({ stores }) {
933
1020
  isNull(await stores.users.findUser(mintId2()), "findUser for an unknown id");
@@ -937,7 +1024,7 @@ var userReadCases = [
937
1024
  },
938
1025
  {
939
1026
  id: "users.loginBytes",
940
- group: group15,
1027
+ group: group17,
941
1028
  name: "compares logins as bytes, within one type: no case folding, no trimming, no collation",
942
1029
  async run({ stores }) {
943
1030
  const upper = userRecord({ logins: ["Ada@Example.test"] });
@@ -954,11 +1041,11 @@ var userReadCases = [
954
1041
  ];
955
1042
 
956
1043
  // src/conformance/cases/users/versions.ts
957
- var group16 = "users";
1044
+ var group18 = "users";
958
1045
  var userVersionCases = [
959
1046
  {
960
1047
  id: "users.versionIncrements",
961
- group: group16,
1048
+ group: group18,
962
1049
  name: "applies an update at the expected version, and answers the record written with its version one higher",
963
1050
  async run({ stores }) {
964
1051
  const record = userRecord();
@@ -976,7 +1063,7 @@ var userVersionCases = [
976
1063
  },
977
1064
  {
978
1065
  id: "users.versionConflict",
979
- group: group16,
1066
+ group: group18,
980
1067
  name: "refuses a stale version with both versions, and leaves the record identical byte for byte",
981
1068
  async run({ stores }) {
982
1069
  const record = userRecord();
@@ -997,7 +1084,7 @@ var userVersionCases = [
997
1084
  },
998
1085
  {
999
1086
  id: "users.unknownUpdate",
1000
- group: group16,
1087
+ group: group18,
1001
1088
  name: "refuses an update to an unknown id with NotFoundError, told apart from a version conflict",
1002
1089
  async run({ stores }) {
1003
1090
  const error = await rejects(stores.users.updateUser(mintId2(), { updatedAt: at("2026-02-01T00:00:00.000Z") }, 0), "updateUser on an unknown id should reject");
@@ -1140,11 +1227,11 @@ var tuple = (object, relation, subject) => ({ object, relation, subject });
1140
1227
  var sorted = (subjects) => subjects.map(formatSubject2).sort();
1141
1228
 
1142
1229
  // src/conformance/relations/cases/deletion.ts
1143
- var group17 = "relations";
1230
+ var group19 = "relations";
1144
1231
  var relationDeletionCases = [
1145
1232
  {
1146
1233
  id: "relations.deleteEntity",
1147
- group: group17,
1234
+ group: group19,
1148
1235
  name: "deletes every tuple naming an entity — as object, as subject, as a set’s entity — and counts them",
1149
1236
  async run({ store }) {
1150
1237
  const team = entity("team");
@@ -1171,11 +1258,11 @@ var relationDeletionCases = [
1171
1258
  ];
1172
1259
 
1173
1260
  // src/conformance/relations/cases/reads.ts
1174
- var group18 = "relations";
1261
+ var group20 = "relations";
1175
1262
  var relationReadCases = [
1176
1263
  {
1177
1264
  id: "relations.roundTrip",
1178
- group: group18,
1265
+ group: group20,
1179
1266
  name: "holds exactly the tuples written, byte for byte: staff:u1 is not patient:u1, and a set is not its entity",
1180
1267
  async run({ store }) {
1181
1268
  const record = entity("record", "R:1 — ÿ");
@@ -1191,7 +1278,7 @@ var relationReadCases = [
1191
1278
  },
1192
1279
  {
1193
1280
  id: "relations.edgeCharacters",
1194
- group: group18,
1281
+ group: group20,
1195
1282
  name: "round-trips every character the core lets through in an id: control characters, U+FFFF and a surrogate pair",
1196
1283
  async run({ store }) {
1197
1284
  const edge = "a\x01\x1F￿ \uD83D\uDE00 z";
@@ -1214,7 +1301,7 @@ var relationReadCases = [
1214
1301
  },
1215
1302
  {
1216
1303
  id: "relations.undefinedRelation",
1217
- group: group18,
1304
+ group: group20,
1218
1305
  name: "reads a subject whose relation is undefined as its entity, not as a set",
1219
1306
  async run({ store }) {
1220
1307
  const record = entity("record");
@@ -1244,7 +1331,7 @@ var relationReadCases = [
1244
1331
  },
1245
1332
  {
1246
1333
  id: "relations.absence",
1247
- group: group18,
1334
+ group: group20,
1248
1335
  name: "answers false, [], an empty page and 0 for an empty store — never null or undefined",
1249
1336
  async run({ store }) {
1250
1337
  const record = entity("record");
@@ -1265,11 +1352,11 @@ var relationReadCases = [
1265
1352
  ];
1266
1353
 
1267
1354
  // src/conformance/relations/cases/traversal.ts
1268
- var group19 = "relations";
1355
+ var group21 = "relations";
1269
1356
  var relationTraversalCases = [
1270
1357
  {
1271
1358
  id: "relations.oneHop",
1272
- group: group19,
1359
+ group: group21,
1273
1360
  name: "answers one hop: the subject sets and the entities holding one relation on one object, and nothing else",
1274
1361
  async run({ store }) {
1275
1362
  const record = entity("record");
@@ -1293,7 +1380,7 @@ var relationTraversalCases = [
1293
1380
  },
1294
1381
  {
1295
1382
  id: "relations.objects",
1296
- group: group19,
1383
+ group: group21,
1297
1384
  name: "pages 25 objects by 10 in ascending id order, for one type, relation and exact subject, and ends on a null cursor",
1298
1385
  async run({ store }) {
1299
1386
  const staff = entity("staff");
@@ -1340,11 +1427,11 @@ var relationTraversalCases = [
1340
1427
  ];
1341
1428
 
1342
1429
  // src/conformance/relations/cases/writes.ts
1343
- var group20 = "relations";
1430
+ var group22 = "relations";
1344
1431
  var relationWriteCases = [
1345
1432
  {
1346
1433
  id: "relations.idempotentWrite",
1347
- group: group20,
1434
+ group: group22,
1348
1435
  name: "is idempotent under retry: adding a stored tuple and removing an absent one write nothing",
1349
1436
  async run({ store }) {
1350
1437
  const record = entity("record");
@@ -1357,7 +1444,7 @@ var relationWriteCases = [
1357
1444
  },
1358
1445
  {
1359
1446
  id: "relations.uniqueness",
1360
- group: group20,
1447
+ group: group22,
1361
1448
  name: "keeps one tuple when twenty concurrent writes add it: uniqueness is a constraint, not a read",
1362
1449
  async run({ store }) {
1363
1450
  const record = entity("record");
@@ -1368,7 +1455,7 @@ var relationWriteCases = [
1368
1455
  },
1369
1456
  {
1370
1457
  id: "relations.removeThenAdd",
1371
- group: group20,
1458
+ group: group22,
1372
1459
  name: "applies one write’s removals and additions together",
1373
1460
  async run({ store }) {
1374
1461
  const record = entity("record");
@@ -1503,5 +1590,5 @@ export {
1503
1590
  userStoreCases
1504
1591
  };
1505
1592
 
1506
- //# debugId=62F9C4A440C84DC064756E2164756E21
1593
+ //# debugId=B2CA4E7F432AAE2464756E2164756E21
1507
1594
  //# sourceMappingURL=index.js.map