@yielded/auth-persistence-drizzle 0.1.0-beta.13 → 0.1.0-beta.15

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 (190) hide show
  1. package/dist/D1.d.mts +123 -9
  2. package/dist/Libsql.d.mts +143 -29
  3. package/dist/Mysql2.d.mts +130 -16
  4. package/dist/Pglite.d.mts +130 -16
  5. package/dist/Postgres.d.mts +131 -17
  6. package/dist/SqliteBun.d.mts +141 -27
  7. package/dist/SqliteDo.d.mts +145 -31
  8. package/dist/SqliteNode.d.mts +135 -21
  9. package/dist/SqliteWasm.d.mts +145 -31
  10. package/dist/drizzle/d1-emails.d.mts +3 -3
  11. package/dist/drizzle/d1-emails.mjs +56 -11
  12. package/dist/drizzle/d1-emails.mjs.map +1 -1
  13. package/dist/drizzle/d1-identity.mjs +9 -2
  14. package/dist/drizzle/d1-identity.mjs.map +1 -1
  15. package/dist/drizzle/d1-oauth.d.mts +8 -8
  16. package/dist/drizzle/d1-oauth.mjs +8 -8
  17. package/dist/drizzle/d1-oauth.mjs.map +1 -1
  18. package/dist/drizzle/d1-passkey.d.mts +2 -1
  19. package/dist/drizzle/d1-passkey.mjs +10 -8
  20. package/dist/drizzle/d1-passkey.mjs.map +1 -1
  21. package/dist/drizzle/d1-password-prepared.d.mts +1 -1
  22. package/dist/drizzle/d1-password-prepared.mjs +16 -5
  23. package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
  24. package/dist/drizzle/d1-passwords.d.mts +2 -2
  25. package/dist/drizzle/d1-passwords.mjs +19 -6
  26. package/dist/drizzle/d1-passwords.mjs.map +1 -1
  27. package/dist/drizzle/d1-phone.d.mts +2 -1
  28. package/dist/drizzle/d1-phone.mjs +4 -2
  29. package/dist/drizzle/d1-phone.mjs.map +1 -1
  30. package/dist/drizzle/d1-proofs.d.mts +1 -1
  31. package/dist/drizzle/d1-proofs.mjs +18 -7
  32. package/dist/drizzle/d1-proofs.mjs.map +1 -1
  33. package/dist/drizzle/d1-sessions.d.mts +6 -5
  34. package/dist/drizzle/d1-sessions.mjs +29 -19
  35. package/dist/drizzle/d1-sessions.mjs.map +1 -1
  36. package/dist/drizzle/d1-totp.d.mts +2 -1
  37. package/dist/drizzle/d1-totp.mjs +4 -2
  38. package/dist/drizzle/d1-totp.mjs.map +1 -1
  39. package/dist/drizzle/email-registration.d.mts +5 -1
  40. package/dist/drizzle/email-registration.mjs +48 -4
  41. package/dist/drizzle/email-registration.mjs.map +1 -1
  42. package/dist/drizzle/email-target.d.mts +1 -0
  43. package/dist/drizzle/email-target.mjs +9 -0
  44. package/dist/drizzle/email-target.mjs.map +1 -1
  45. package/dist/drizzle/mysql-emails.d.mts +4 -4
  46. package/dist/drizzle/mysql-emails.mjs +8 -7
  47. package/dist/drizzle/mysql-emails.mjs.map +1 -1
  48. package/dist/drizzle/mysql-identity.mjs +11 -4
  49. package/dist/drizzle/mysql-identity.mjs.map +1 -1
  50. package/dist/drizzle/mysql-password-prepared.d.mts +2 -2
  51. package/dist/drizzle/mysql-password-prepared.mjs +4 -3
  52. package/dist/drizzle/mysql-password-prepared.mjs.map +1 -1
  53. package/dist/drizzle/mysql-passwords.d.mts +3 -3
  54. package/dist/drizzle/mysql-passwords.mjs +7 -6
  55. package/dist/drizzle/mysql-passwords.mjs.map +1 -1
  56. package/dist/drizzle/mysql-proofs.d.mts +2 -2
  57. package/dist/drizzle/mysql-proofs.mjs +4 -3
  58. package/dist/drizzle/mysql-proofs.mjs.map +1 -1
  59. package/dist/drizzle/mysql-sessions.d.mts +6 -6
  60. package/dist/drizzle/mysql-sessions.mjs +16 -15
  61. package/dist/drizzle/mysql-sessions.mjs.map +1 -1
  62. package/dist/drizzle/native-database.mjs +1 -1
  63. package/dist/drizzle/native-database.mjs.map +1 -1
  64. package/dist/drizzle/oauth-connected-drivers.mjs +10 -8
  65. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
  66. package/dist/drizzle/oauth-connected-flow.mjs +62 -13
  67. package/dist/drizzle/oauth-connected-flow.mjs.map +1 -1
  68. package/dist/drizzle/oauth-connected-input.mjs +6 -0
  69. package/dist/drizzle/oauth-connected-input.mjs.map +1 -1
  70. package/dist/drizzle/oauth-connected-maintenance.mjs +3 -3
  71. package/dist/drizzle/oauth-connected-maintenance.mjs.map +1 -1
  72. package/dist/drizzle/oauth-connected-target.d.mts +116 -2
  73. package/dist/drizzle/oauth-connected-target.mjs +5 -3
  74. package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
  75. package/dist/drizzle/oauth-drivers.mjs +8 -8
  76. package/dist/drizzle/oauth-drivers.mjs.map +1 -1
  77. package/dist/drizzle/oauth-target.d.mts +3 -3
  78. package/dist/drizzle/oauth-target.mjs +8 -6
  79. package/dist/drizzle/oauth-target.mjs.map +1 -1
  80. package/dist/drizzle/passkey-drivers.mjs +4 -4
  81. package/dist/drizzle/passkey-drivers.mjs.map +1 -1
  82. package/dist/drizzle/passkey-write-drivers.mjs +4 -4
  83. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -1
  84. package/dist/drizzle/passkey-write-target.d.mts +1 -1
  85. package/dist/drizzle/password-prepared-sql.mjs +5 -0
  86. package/dist/drizzle/password-prepared-sql.mjs.map +1 -1
  87. package/dist/drizzle/password-prepared-target.d.mts +2 -1
  88. package/dist/drizzle/password-prepared-target.mjs +5 -0
  89. package/dist/drizzle/password-prepared-target.mjs.map +1 -1
  90. package/dist/drizzle/password-registration.d.mts +1 -1
  91. package/dist/drizzle/password-registration.mjs +2 -0
  92. package/dist/drizzle/password-registration.mjs.map +1 -1
  93. package/dist/drizzle/password-target.d.mts +1 -0
  94. package/dist/drizzle/password-target.mjs +6 -0
  95. package/dist/drizzle/password-target.mjs.map +1 -1
  96. package/dist/drizzle/pg-emails.d.mts +4 -4
  97. package/dist/drizzle/pg-emails.mjs +8 -7
  98. package/dist/drizzle/pg-emails.mjs.map +1 -1
  99. package/dist/drizzle/pg-identity.mjs +12 -5
  100. package/dist/drizzle/pg-identity.mjs.map +1 -1
  101. package/dist/drizzle/pg-password-prepared.d.mts +2 -2
  102. package/dist/drizzle/pg-password-prepared.mjs +4 -3
  103. package/dist/drizzle/pg-password-prepared.mjs.map +1 -1
  104. package/dist/drizzle/pg-passwords.d.mts +3 -3
  105. package/dist/drizzle/pg-passwords.mjs +7 -6
  106. package/dist/drizzle/pg-passwords.mjs.map +1 -1
  107. package/dist/drizzle/pg-proofs.d.mts +2 -2
  108. package/dist/drizzle/pg-proofs.mjs +4 -3
  109. package/dist/drizzle/pg-proofs.mjs.map +1 -1
  110. package/dist/drizzle/pg-sessions.d.mts +6 -6
  111. package/dist/drizzle/pg-sessions.mjs +16 -15
  112. package/dist/drizzle/pg-sessions.mjs.map +1 -1
  113. package/dist/drizzle/phone-target.mjs +11 -2
  114. package/dist/drizzle/phone-target.mjs.map +1 -1
  115. package/dist/drizzle/proof-target.d.mts +1 -0
  116. package/dist/drizzle/proof-target.mjs +2 -0
  117. package/dist/drizzle/proof-target.mjs.map +1 -1
  118. package/dist/drizzle/query-operations.mjs +2 -0
  119. package/dist/drizzle/query-operations.mjs.map +1 -1
  120. package/dist/drizzle/session-target.d.mts +1 -0
  121. package/dist/drizzle/session-target.mjs +9 -6
  122. package/dist/drizzle/session-target.mjs.map +1 -1
  123. package/dist/drizzle/sqlite-emails.mjs +8 -7
  124. package/dist/drizzle/sqlite-emails.mjs.map +1 -1
  125. package/dist/drizzle/sqlite-identity.mjs +7 -2
  126. package/dist/drizzle/sqlite-identity.mjs.map +1 -1
  127. package/dist/drizzle/sqlite-password-prepared.mjs +4 -3
  128. package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -1
  129. package/dist/drizzle/sqlite-passwords.mjs +7 -6
  130. package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
  131. package/dist/drizzle/sqlite-proofs.mjs +4 -3
  132. package/dist/drizzle/sqlite-proofs.mjs.map +1 -1
  133. package/dist/drizzle/sqlite-sessions.mjs +16 -15
  134. package/dist/drizzle/sqlite-sessions.mjs.map +1 -1
  135. package/dist/drizzle/storage-validation.mjs +234 -0
  136. package/dist/drizzle/storage-validation.mjs.map +1 -0
  137. package/dist/drizzle/totp-target.mjs +5 -2
  138. package/dist/drizzle/totp-target.mjs.map +1 -1
  139. package/dist/drizzle/transaction-mysql.mjs +1 -1
  140. package/package.json +1 -1
  141. package/src/drizzle/d1-emails.ts +158 -40
  142. package/src/drizzle/d1-identity.ts +13 -0
  143. package/src/drizzle/d1-oauth.ts +8 -8
  144. package/src/drizzle/d1-passkey.ts +10 -8
  145. package/src/drizzle/d1-password-prepared.ts +14 -2
  146. package/src/drizzle/d1-passwords.ts +22 -4
  147. package/src/drizzle/d1-phone.ts +4 -2
  148. package/src/drizzle/d1-proofs.ts +55 -3
  149. package/src/drizzle/d1-sessions.ts +67 -18
  150. package/src/drizzle/d1-totp.ts +4 -2
  151. package/src/drizzle/email-registration.ts +160 -12
  152. package/src/drizzle/email-target.ts +22 -4
  153. package/src/drizzle/mysql-emails.ts +17 -7
  154. package/src/drizzle/mysql-identity.ts +13 -0
  155. package/src/drizzle/mysql-password-prepared.ts +7 -3
  156. package/src/drizzle/mysql-passwords.ts +13 -6
  157. package/src/drizzle/mysql-proofs.ts +7 -3
  158. package/src/drizzle/mysql-sessions.ts +31 -15
  159. package/src/drizzle/native-database.ts +5 -4
  160. package/src/drizzle/oauth-connected-drivers.ts +10 -8
  161. package/src/drizzle/oauth-connected-flow.ts +98 -10
  162. package/src/drizzle/oauth-connected-input.ts +7 -0
  163. package/src/drizzle/oauth-connected-maintenance.ts +3 -2
  164. package/src/drizzle/oauth-connected-target.ts +4 -2
  165. package/src/drizzle/oauth-drivers.ts +8 -8
  166. package/src/drizzle/oauth-target.ts +13 -7
  167. package/src/drizzle/passkey-drivers.ts +4 -4
  168. package/src/drizzle/passkey-write-drivers.ts +8 -5
  169. package/src/drizzle/password-prepared-sql.ts +13 -1
  170. package/src/drizzle/password-prepared-target.ts +7 -1
  171. package/src/drizzle/password-registration.ts +10 -3
  172. package/src/drizzle/password-target.ts +22 -4
  173. package/src/drizzle/pg-emails.ts +17 -7
  174. package/src/drizzle/pg-identity.ts +13 -0
  175. package/src/drizzle/pg-password-prepared.ts +7 -3
  176. package/src/drizzle/pg-passwords.ts +13 -6
  177. package/src/drizzle/pg-proofs.ts +7 -3
  178. package/src/drizzle/pg-sessions.ts +31 -15
  179. package/src/drizzle/phone-target.ts +30 -5
  180. package/src/drizzle/proof-target.ts +10 -2
  181. package/src/drizzle/query-operations.ts +2 -0
  182. package/src/drizzle/session-target.ts +20 -3
  183. package/src/drizzle/sqlite-emails.ts +17 -7
  184. package/src/drizzle/sqlite-identity.ts +5 -0
  185. package/src/drizzle/sqlite-password-prepared.ts +7 -3
  186. package/src/drizzle/sqlite-passwords.ts +13 -6
  187. package/src/drizzle/sqlite-proofs.ts +7 -3
  188. package/src/drizzle/sqlite-sessions.ts +31 -15
  189. package/src/drizzle/storage-validation.ts +285 -0
  190. package/src/drizzle/totp-target.ts +16 -4
@@ -1,5 +1,6 @@
1
1
  /* oxlint-disable no-explicit-any -- D1 plans bridge consumer-owned Drizzle rows and SQL statements. */
2
2
  import type { D1Client } from "@effect/sql-d1/D1Client";
3
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
3
4
  import {
4
5
  coordinateCommit,
5
6
  CurrentCommitJournal,
@@ -62,6 +63,7 @@ import { CurrentD1PlanningDatabase, makeD1Owner } from "./d1-planning";
62
63
  import { compileD1ProofCompletionPlan, type D1ProtectedProofMutation } from "./d1-proofs";
63
64
  import { D1BatchStatements } from "./D1BatchStatements";
64
65
  import { column, isMappedConstraintConflict, PersistenceMappingError, updateValues } from "./model";
66
+ import { nativeDatabase } from "./native-database";
65
67
  import {
66
68
  type AnyPasswordRegistrationMapping,
67
69
  type D1PasswordPersistenceMapping,
@@ -74,6 +76,7 @@ import {
74
76
  } from "./password-model";
75
77
  import type { PasswordRegistrationAuthority } from "./password-registration";
76
78
  import type { D1ProofPersistenceMapping } from "./proof-model";
79
+ import { validateDrizzleStorage } from "./storage-validation";
77
80
  import type { SuppliedService } from "./SuppliedService";
78
81
 
79
82
  type PlanPrepare<Method extends (...args: any[]) => any, A> = (
@@ -2022,6 +2025,11 @@ export const makeD1PasswordPersistenceServices = Effect.fnUntraced(function* <
2022
2025
  ) {
2023
2026
  const database = yield* DatabaseService;
2024
2027
 
2028
+ yield* validateDrizzleStorage({ ...mapping, proof: proofMapping }).pipe(
2029
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
2030
+ Effect.mapError(unavailable),
2031
+ );
2032
+
2025
2033
  const hooks = yield* LifecycleHooks;
2026
2034
  const plans = makePasswordPlans(mapping as unknown as Mapping, proofMapping);
2027
2035
 
@@ -2112,8 +2120,12 @@ export function coordinateD1PasswordPersistence<
2112
2120
  CoordinatorError<E> | DatabaseError,
2113
2121
  Exclude<R, PasswordPersistence | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2114
2122
  > {
2115
- return Effect.flatMap(acquire, (database) =>
2123
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2116
2124
  Effect.gen(function* () {
2125
+ yield* validateDrizzleStorage({
2126
+ ...options.mapping,
2127
+ proof: options.proofMapping,
2128
+ }).pipe(Effect.mapError(unavailable));
2117
2129
  const hooks = yield* LifecycleHooks;
2118
2130
 
2119
2131
  if (yield* hasCommitScope) return yield* unavailable();
@@ -2234,7 +2246,7 @@ export function coordinateD1PasswordPersistence<
2234
2246
  ).pipe(Effect.provideService(LifecycleHooks, hooks));
2235
2247
 
2236
2248
  return result.value;
2237
- }),
2249
+ }).pipe(Effect.provideService(NativeDatabase, database)),
2238
2250
  );
2239
2251
  }
2240
2252
 
@@ -2466,6 +2478,11 @@ export const makeD1PasswordRegistrationServices = Effect.fnUntraced(function* <
2466
2478
  >(mapping: PasswordRegistrationMapping<Registration, S, I, C, AC, Rq, NativeId>) {
2467
2479
  const database = yield* DatabaseService;
2468
2480
 
2481
+ yield* validateDrizzleStorage(mapping).pipe(
2482
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
2483
+ Effect.mapError(unavailable),
2484
+ );
2485
+
2469
2486
  const hooks = yield* LifecycleHooks;
2470
2487
  const plans = makeRegistrationPlans(mapping as unknown as RegistrationMapping<Registration>);
2471
2488
 
@@ -2512,8 +2529,9 @@ export function coordinateD1PasswordRegistration<
2512
2529
  CoordinatorError<E> | DatabaseError,
2513
2530
  Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2514
2531
  > {
2515
- return Effect.flatMap(acquire, (database) =>
2532
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2516
2533
  Effect.gen(function* () {
2534
+ yield* validateDrizzleStorage(options.mapping).pipe(Effect.mapError(unavailable));
2517
2535
  const hooks = yield* LifecycleHooks;
2518
2536
 
2519
2537
  if (yield* hasCommitScope) return yield* unavailable();
@@ -2593,7 +2611,7 @@ export function coordinateD1PasswordRegistration<
2593
2611
  ).pipe(Effect.provideService(LifecycleHooks, hooks));
2594
2612
 
2595
2613
  return result.value;
2596
- }),
2614
+ }).pipe(Effect.provideService(NativeDatabase, database)),
2597
2615
  );
2598
2616
  }
2599
2617
 
@@ -1,4 +1,5 @@
1
1
  import type { D1Client } from "@effect/sql-d1/D1Client";
2
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
2
3
  import type { LifecycleHooks } from "@yielded/auth/Hooks";
3
4
  import {
4
5
  PhoneOtpUnavailable as PhoneUnavailable,
@@ -14,6 +15,7 @@ import { Context, Effect } from "effect";
14
15
  import { Database as DatabaseService } from "./d1-database";
15
16
  import { makeD1Owner } from "./d1-planning";
16
17
  import { D1BatchStatements } from "./D1BatchStatements";
18
+ import { nativeDatabase } from "./native-database";
17
19
  import type { D1PhoneMapping, PhoneMapping, PhoneMappingSource } from "./phone-model";
18
20
  import { coordinateTargetPhone, makePhoneTarget, type PhoneCoordinatorError } from "./phone-target";
19
21
  type Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };
@@ -61,7 +63,7 @@ export const coordinatePhonePersistence = <
61
63
  | RSetup
62
64
  | DatabaseRequirements
63
65
  > =>
64
- Effect.flatMap(acquire, (database) =>
66
+ Effect.flatMap(nativeDatabase(acquire), (database) =>
65
67
  coordinateTargetPhone(
66
68
  database,
67
69
  options.mapping,
@@ -96,5 +98,5 @@ export const coordinatePhonePersistence = <
96
98
 
97
99
  return yield* owner.close(Effect.provideContext(body, provided));
98
100
  }),
99
- ),
101
+ ).pipe(Effect.provideService(NativeDatabase, database)),
100
102
  );
@@ -1,5 +1,6 @@
1
1
  /* oxlint-disable no-explicit-any -- D1 planning bridges consumer Drizzle tables to Effect SQL statements. */
2
2
  import type { D1Client } from "@effect/sql-d1/D1Client";
3
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
3
4
  import {
4
5
  coordinateCommit,
5
6
  CurrentCommitJournal,
@@ -52,6 +53,7 @@ import { balancedD1And } from "./d1-generated-statement";
52
53
  import { CurrentD1PlanningDatabase, makeD1Owner } from "./d1-planning";
53
54
  import { D1BatchStatements } from "./D1BatchStatements";
54
55
  import { column, PersistenceMappingError, isMappedConstraintConflict, updateValues } from "./model";
56
+ import { nativeDatabase } from "./native-database";
55
57
  import {
56
58
  type D1ProofPersistenceMapping,
57
59
  requiredProofConstraints,
@@ -63,6 +65,7 @@ import {
63
65
  type ProofScopeKeys,
64
66
  type ProofScopeKind,
65
67
  } from "./proof-model";
68
+ import { validateDrizzleStorage } from "./storage-validation";
66
69
 
67
70
  type PlanPrepare<Method extends (...args: any[]) => any, A> = (
68
71
  value: Parameters<Parameters<Method>[1]>[0],
@@ -725,6 +728,28 @@ const issuePlan = Effect.fn("Drizzle.issuePlan")(function* <A>(
725
728
  const seriesVersion = yield* allocateVersion(mapping);
726
729
  const active = existingSeries?.[mapping.series.activeProofId] as string | null | undefined;
727
730
 
731
+ const activeGeneration =
732
+ active === null || active === undefined
733
+ ? undefined
734
+ : (yield* readGeneration(mapping, input.record.moduleId, active))[0];
735
+
736
+ if (
737
+ active !== null &&
738
+ active !== undefined &&
739
+ (activeGeneration === undefined ||
740
+ activeGeneration[mapping.generation.purpose] !== input.record.purpose)
741
+ )
742
+ return yield* unavailable();
743
+
744
+ const matchesActiveBinding =
745
+ activeGeneration !== undefined &&
746
+ sameBinding(yield* mapping.generation.decodeBinding(activeGeneration), input.record.binding);
747
+
748
+ const replacesLiveProof =
749
+ activeGeneration !== undefined &&
750
+ activeGeneration[mapping.generation.state] === "active" &&
751
+ (yield* mapping.decodeInstant(activeGeneration[mapping.generation.expiresAt])) > now;
752
+
728
753
  const lastIssue =
729
754
  existingSeries?.[mapping.series.lastIssueAt] === null ||
730
755
  existingSeries?.[mapping.series.lastIssueAt] === undefined
@@ -737,6 +762,7 @@ const issuePlan = Effect.fn("Drizzle.issuePlan")(function* <A>(
737
762
  admitted.length === scopes.length &&
738
763
  input.record.issuedAtMillis <= now &&
739
764
  input.record.expiresAtMillis > now &&
765
+ (!replacesLiveProof || matchesActiveBinding) &&
740
766
  (input.supersedes === undefined || input.supersedes === active) &&
741
767
  (lastIssue === undefined || now - lastIssue >= input.policy.abuse.resendCooldownMillis);
742
768
 
@@ -806,7 +832,7 @@ const issuePlan = Effect.fn("Drizzle.issuePlan")(function* <A>(
806
832
  input.record.purpose,
807
833
  "issue",
808
834
  input.record.requestId,
809
- admitted,
835
+ allowed ? scopes : [],
810
836
  now,
811
837
  retentionUntil,
812
838
  marker,
@@ -814,6 +840,25 @@ const issuePlan = Effect.fn("Drizzle.issuePlan")(function* <A>(
814
840
  );
815
841
  if (allowed) {
816
842
  const sc = seriesColumns(mapping);
843
+ const gc = generationColumns(mapping);
844
+
845
+ // Recheck replacement authority in the submitted batch. Generation bindings
846
+ // are immutable; the version and stored binding also fence a changed row.
847
+ const replacementCondition =
848
+ activeGeneration === undefined
849
+ ? sql`1 = 1`
850
+ : sql`exists(select 1 from ${mapping.generation.table} where ${and(
851
+ eq(gc.moduleId, input.record.moduleId),
852
+ eq(gc.proofId, activeGeneration[mapping.generation.proofId]),
853
+ eq(gc.version, activeGeneration[mapping.generation.version]),
854
+ eq(
855
+ column(mapping.generation.table, mapping.generation.binding),
856
+ activeGeneration[mapping.generation.binding],
857
+ ),
858
+ matchesActiveBinding
859
+ ? sql`1 = 1`
860
+ : sql`(${gc.state} <> 'active' or ${gc.expiresAt} <= ${mapping.d1.engineNow})`,
861
+ )})`;
817
862
 
818
863
  const seriesCondition = and(
819
864
  eq(sc.moduleId, input.record.moduleId),
@@ -834,6 +879,7 @@ const issuePlan = Effect.fn("Drizzle.issuePlan")(function* <A>(
834
879
  yield* assertion(
835
880
  and(
836
881
  current.condition,
882
+ replacementCondition,
837
883
  sql`${mapping.d1.engineNowMillis} >= ${input.record.issuedAtMillis}`,
838
884
  sql`${mapping.d1.engineNow} < ${sql.param(mapping.encodeInstant(input.record.expiresAtMillis), generationColumns(mapping).expiresAt)}`,
839
885
  sql`exists(select 1 from ${mapping.series.table} where ${seriesCondition})`,
@@ -2002,6 +2048,11 @@ export const makeD1ProofPersistenceServices = Effect.fnUntraced(function* <
2002
2048
  >(mapping: D1ProofPersistenceMapping<Rq, S, G, Cn, Rs, A, F, C, Sub, I, Cr, NativeId>) {
2003
2049
  const database = yield* DatabaseService;
2004
2050
 
2051
+ yield* validateDrizzleStorage(mapping).pipe(
2052
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
2053
+ Effect.mapError(unavailable),
2054
+ );
2055
+
2005
2056
  const hooks = yield* LifecycleHooks;
2006
2057
  const plans = makeProofPlans(mapping as unknown as Mapping);
2007
2058
 
@@ -2066,8 +2117,9 @@ export function coordinateD1ProofPersistence<
2066
2117
  CoordinatorError<E> | DatabaseError,
2067
2118
  Exclude<R, ProofPersistence | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2068
2119
  > {
2069
- return Effect.flatMap(acquire, (database) =>
2120
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2070
2121
  Effect.gen(function* () {
2122
+ yield* validateDrizzleStorage(options.mapping).pipe(Effect.mapError(unavailable));
2071
2123
  const hooks = yield* LifecycleHooks;
2072
2124
 
2073
2125
  if (yield* hasCommitScope) return yield* unavailable();
@@ -2157,7 +2209,7 @@ export function coordinateD1ProofPersistence<
2157
2209
  ).pipe(Effect.provideService(LifecycleHooks, hooks));
2158
2210
 
2159
2211
  return result.value;
2160
- }),
2212
+ }).pipe(Effect.provideService(NativeDatabase, database)),
2161
2213
  );
2162
2214
  }
2163
2215
 
@@ -1,4 +1,5 @@
1
1
  import {
2
+ NativeDatabase,
2
3
  decodeStepUpIntent,
3
4
  encodeStepUpIntent,
4
5
  stepUpIntentLive,
@@ -62,6 +63,7 @@ import {
62
63
  } from "./d1-planning";
63
64
  import { D1BatchStatements } from "./D1BatchStatements";
64
65
  import { column, PersistenceMappingError, isMappedConstraintConflict, updateValues } from "./model";
66
+ import { nativeDatabase } from "./native-database";
65
67
  import type {
66
68
  D1AuthenticationAuthorityMapping,
67
69
  D1PendingAuthenticationMapping,
@@ -69,6 +71,7 @@ import type {
69
71
  D1StatefulSessionMapping,
70
72
  } from "./session-model";
71
73
  import type { D1SessionStepUpMapping } from "./step-up-model";
74
+ import { validateDrizzleStorage } from "./storage-validation";
72
75
  import type { SuppliedService } from "./SuppliedService";
73
76
 
74
77
  type Database = D1PlanningDatabase;
@@ -1125,6 +1128,8 @@ export const makeD1StatefulSessions = <Claims>(
1125
1128
 
1126
1129
  const sessionId = yield* mapping.sessionId.toSession(nativeSessionId);
1127
1130
 
1131
+ if (sessionId === input.handoffSourceSessionId) return yield* SessionConflict.make({});
1132
+
1128
1133
  const version = yield* allocate(
1129
1134
  mapping.session.allocateVersion,
1130
1135
  mapping.session.allocateVersionSync,
@@ -1136,7 +1141,10 @@ export const makeD1StatefulSessions = <Claims>(
1136
1141
  version,
1137
1142
  subjectId: input.evidence.revision.subjectId,
1138
1143
  securityRevision: input.evidence.revision.securityRevision,
1139
- assurance: assessed.assurance,
1144
+ assurance:
1145
+ input.handoffSourceSessionId === undefined
1146
+ ? assessed.assurance
1147
+ : input.session.assurance,
1140
1148
  provenance: yield* snapshotSessionAuthenticationProvenance({
1141
1149
  evidence: input.evidence,
1142
1150
  }),
@@ -1146,7 +1154,7 @@ export const makeD1StatefulSessions = <Claims>(
1146
1154
  issuedAt: DateTime.makeUnsafe(DateTime.toEpochMillis(input.session.issuedAt)),
1147
1155
  };
1148
1156
 
1149
- let condition = and(
1157
+ const commitCondition = and(
1150
1158
  authorityCondition(mapping, nativeSubjectId, input.evidence),
1151
1159
  freshnessCondition(mapping, input.evidence, policy, true),
1152
1160
  sql`${DateTime.toEpochMillis(record.issuedAt)} <= ${mapping.d1.engineNowMillis}`,
@@ -1154,6 +1162,7 @@ export const makeD1StatefulSessions = <Claims>(
1154
1162
  sql`${DateTime.toEpochMillis(record.expiresAt)} <= ${DateTime.toEpochMillis(record.absoluteExpiresAt)}`,
1155
1163
  )!;
1156
1164
 
1165
+ let condition = commitCondition;
1157
1166
  const statements: Statement<any>[] = [];
1158
1167
 
1159
1168
  if (input.pending === undefined) {
@@ -1216,6 +1225,9 @@ export const makeD1StatefulSessions = <Claims>(
1216
1225
  }),
1217
1226
  ),
1218
1227
  ),
1228
+ // Recheck after insertion as well as admission; the capped source
1229
+ // expiry and proof freshness must still hold at the final batch step.
1230
+ yield* assertion(commitCondition),
1219
1231
  );
1220
1232
  const receipt = prepare(record, journal);
1221
1233
 
@@ -1626,6 +1638,8 @@ export const makeD1SessionServiceEffects = {
1626
1638
  authority: <Claims>(mapping: any) =>
1627
1639
  Effect.gen(function* () {
1628
1640
  const database = yield* CurrentD1PlanningDatabase;
1641
+
1642
+ yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1629
1643
  const hooks = yield* LifecycleHooks;
1630
1644
  const raw = makeD1AuthenticationAuthority<Claims>(mapping);
1631
1645
 
@@ -1654,6 +1668,8 @@ export const makeD1SessionServiceEffects = {
1654
1668
  pending: <Claims>(mapping: any) =>
1655
1669
  Effect.gen(function* () {
1656
1670
  const database = yield* CurrentD1PlanningDatabase;
1671
+
1672
+ yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1657
1673
  const hooks = yield* LifecycleHooks;
1658
1674
  const raw = makeD1PendingAuthentication<Claims>(mapping);
1659
1675
 
@@ -1683,6 +1699,8 @@ export const makeD1SessionServiceEffects = {
1683
1699
  stateful: <Claims>(mapping: any) =>
1684
1700
  Effect.gen(function* () {
1685
1701
  const database = yield* CurrentD1PlanningDatabase;
1702
+
1703
+ yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1686
1704
  const hooks = yield* LifecycleHooks;
1687
1705
  const raw = makeD1StatefulSessions<Claims>(mapping);
1688
1706
 
@@ -1718,6 +1736,8 @@ export const makeD1SessionServiceEffects = {
1718
1736
  validity: (mapping: any) =>
1719
1737
  Effect.gen(function* () {
1720
1738
  const database = yield* CurrentD1PlanningDatabase;
1739
+
1740
+ yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1721
1741
  const hooks = yield* LifecycleHooks;
1722
1742
  const raw = makeD1SignedValidity(mapping);
1723
1743
 
@@ -1759,12 +1779,19 @@ type D1CoordinatorError<E> = E | D1DomainError | HookConfigurationError;
1759
1779
  */
1760
1780
  export const coordinateD1SessionBatch = <Services, A, E, R>(
1761
1781
  database: Database,
1782
+ mapping: unknown,
1762
1783
  make: (batch: D1SessionBatch) => Effect.Effect<Services, never, LifecycleHooks>,
1763
1784
  owner: (services: Services, batch: D1SessionBatch) => Effect.Effect<A, E, R>,
1764
- ): Effect.Effect<A, D1CoordinatorError<E>, R | LifecycleHooks> =>
1765
- Effect.gen(function* (): Effect.fn.Return<A, D1CoordinatorError<E>, R | LifecycleHooks> {
1785
+ ): Effect.Effect<A, D1CoordinatorError<E>, R | LifecycleHooks | NativeDatabase> =>
1786
+ Effect.gen(function* (): Effect.fn.Return<
1787
+ A,
1788
+ D1CoordinatorError<E>,
1789
+ R | LifecycleHooks | NativeDatabase
1790
+ > {
1766
1791
  if (yield* hasCommitScope) return yield* unavailable();
1767
1792
 
1793
+ yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1794
+
1768
1795
  const result = yield* coordinateCommit(
1769
1796
  () =>
1770
1797
  Effect.gen(function* () {
@@ -1848,7 +1875,10 @@ export const makeD1AuthenticationAuthorityServices = <
1848
1875
  ) =>
1849
1876
  makeD1SessionServiceEffects
1850
1877
  .authority<Claims>(mapping as any)
1851
- .pipe(Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService));
1878
+ .pipe(
1879
+ Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1880
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1881
+ );
1852
1882
 
1853
1883
  export const makeD1PendingAuthenticationServices = <
1854
1884
  Claims,
@@ -1869,7 +1899,10 @@ export const makeD1PendingAuthenticationServices = <
1869
1899
  ) =>
1870
1900
  makeD1SessionServiceEffects
1871
1901
  .pending<Claims>(mapping as any)
1872
- .pipe(Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService));
1902
+ .pipe(
1903
+ Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1904
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1905
+ );
1873
1906
 
1874
1907
  export const makeD1StatefulSessionServices = <
1875
1908
  Claims,
@@ -1894,7 +1927,10 @@ export const makeD1StatefulSessionServices = <
1894
1927
  ) =>
1895
1928
  makeD1SessionServiceEffects
1896
1929
  .stateful<Claims>(mapping as any)
1897
- .pipe(Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService));
1930
+ .pipe(
1931
+ Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1932
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1933
+ );
1898
1934
 
1899
1935
  export const makeD1SignedSessionValidityServices = <
1900
1936
  Subject extends AnySQLiteTable,
@@ -1906,7 +1942,10 @@ export const makeD1SignedSessionValidityServices = <
1906
1942
  ) =>
1907
1943
  makeD1SessionServiceEffects
1908
1944
  .validity(mapping as any)
1909
- .pipe(Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService));
1945
+ .pipe(
1946
+ Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1947
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1948
+ );
1910
1949
 
1911
1950
  export function coordinateD1AuthenticationAuthority<
1912
1951
  Claims,
@@ -1938,9 +1977,10 @@ export function coordinateD1AuthenticationAuthority<
1938
1977
  D1CoordinatorError<E> | DatabaseError,
1939
1978
  Exclude<R, AuthenticationAuthority | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
1940
1979
  > {
1941
- return Effect.flatMap(acquire, (database) =>
1980
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
1942
1981
  coordinateD1SessionBatch(
1943
1982
  database,
1983
+ options.mapping,
1944
1984
  (batch) =>
1945
1985
  Effect.gen(function* () {
1946
1986
  const hooks = yield* LifecycleHooks;
@@ -1976,7 +2016,7 @@ export function coordinateD1AuthenticationAuthority<
1976
2016
 
1977
2017
  return Effect.provideContext(body, provided);
1978
2018
  },
1979
- ),
2019
+ ).pipe(Effect.provideService(NativeDatabase, database)),
1980
2020
  );
1981
2021
  }
1982
2022
 
@@ -2012,9 +2052,10 @@ export function coordinateD1PendingAuthentication<
2012
2052
  D1CoordinatorError<E> | DatabaseError,
2013
2053
  Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2014
2054
  > {
2015
- return Effect.flatMap(acquire, (database) =>
2055
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2016
2056
  coordinateD1SessionBatch(
2017
2057
  database,
2058
+ options.mapping,
2018
2059
  (batch) =>
2019
2060
  Effect.gen(function* () {
2020
2061
  const hooks = yield* LifecycleHooks;
@@ -2047,7 +2088,7 @@ export function coordinateD1PendingAuthentication<
2047
2088
 
2048
2089
  return Effect.provideContext(body, provided);
2049
2090
  },
2050
- ),
2091
+ ).pipe(Effect.provideService(NativeDatabase, database)),
2051
2092
  );
2052
2093
  }
2053
2094
 
@@ -2091,9 +2132,10 @@ export function coordinateD1StatefulSessions<
2091
2132
  | LifecycleHooks
2092
2133
  | DatabaseRequirements
2093
2134
  > {
2094
- return Effect.flatMap(acquire, (database) =>
2135
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2095
2136
  coordinateD1SessionBatch(
2096
2137
  database,
2138
+ options.mapping,
2097
2139
  (batch) =>
2098
2140
  Effect.gen(function* () {
2099
2141
  const hooks = yield* LifecycleHooks;
@@ -2142,7 +2184,7 @@ export function coordinateD1StatefulSessions<
2142
2184
 
2143
2185
  return Effect.provideContext(body, provided);
2144
2186
  },
2145
- ),
2187
+ ).pipe(Effect.provideService(NativeDatabase, database)),
2146
2188
  );
2147
2189
  }
2148
2190
 
@@ -2174,9 +2216,10 @@ export function coordinateD1SignedSessionValidity<
2174
2216
  D1CoordinatorError<E> | DatabaseError,
2175
2217
  Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2176
2218
  > {
2177
- return Effect.flatMap(acquire, (database) =>
2219
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2178
2220
  coordinateD1SessionBatch(
2179
2221
  database,
2222
+ options.mapping,
2180
2223
  (batch) =>
2181
2224
  Effect.gen(function* () {
2182
2225
  const hooks = yield* LifecycleHooks;
@@ -2208,7 +2251,7 @@ export function coordinateD1SignedSessionValidity<
2208
2251
 
2209
2252
  return Effect.provideContext(body, provided);
2210
2253
  },
2211
- ),
2254
+ ).pipe(Effect.provideService(NativeDatabase, database)),
2212
2255
  );
2213
2256
  }
2214
2257
 
@@ -2762,6 +2805,11 @@ export const makeD1SessionStepUpServices = Effect.fnUntraced(function* <
2762
2805
  ) {
2763
2806
  const database = yield* DatabaseService;
2764
2807
 
2808
+ yield* validateDrizzleStorage(mapping).pipe(
2809
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
2810
+ Effect.mapError(unavailable),
2811
+ );
2812
+
2765
2813
  const hooks = yield* LifecycleHooks;
2766
2814
  const raw = makeD1SessionStepUp<Claims>(mapping as any);
2767
2815
 
@@ -2826,9 +2874,10 @@ export function coordinateD1SessionStepUp<
2826
2874
  D1CoordinatorError<E> | DatabaseError,
2827
2875
  Exclude<R, Id | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2828
2876
  > {
2829
- return Effect.flatMap(acquire, (database) =>
2877
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
2830
2878
  coordinateD1SessionBatch(
2831
2879
  database,
2880
+ options.mapping,
2832
2881
  (batch) =>
2833
2882
  Effect.gen(function* () {
2834
2883
  const hooks = yield* LifecycleHooks;
@@ -2865,6 +2914,6 @@ export function coordinateD1SessionStepUp<
2865
2914
 
2866
2915
  return Effect.provideContext(body, provided);
2867
2916
  },
2868
- ),
2917
+ ).pipe(Effect.provideService(NativeDatabase, database)),
2869
2918
  );
2870
2919
  }
@@ -1,4 +1,5 @@
1
1
  import type { D1Client } from "@effect/sql-d1/D1Client";
2
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
2
3
  import type { LifecycleHooks } from "@yielded/auth/Hooks";
3
4
  import { TotpUnavailable, TotpPersistence } from "@yielded/auth/Totp";
4
5
  import type { AnyRelations } from "drizzle-orm";
@@ -9,6 +10,7 @@ import { Context, Effect } from "effect";
9
10
  import { Database as DatabaseService } from "./d1-database";
10
11
  import { makeD1Owner } from "./d1-planning";
11
12
  import { D1BatchStatements } from "./D1BatchStatements";
13
+ import { nativeDatabase } from "./native-database";
12
14
  import type { D1TotpMapping, TotpMapping, TotpMappingSource } from "./totp-model";
13
15
  import { coordinateTargetTotp, makeTotpTarget, type TotpCoordinatorError } from "./totp-target";
14
16
  type Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };
@@ -50,7 +52,7 @@ export const coordinateTotpPersistence = <
50
52
  TotpCoordinatorError<E> | DatabaseError,
51
53
  Exclude<R, TotpPersistence | D1BatchStatements> | LifecycleHooks | RSetup | DatabaseRequirements
52
54
  > =>
53
- Effect.flatMap(acquire, (database) =>
55
+ Effect.flatMap(nativeDatabase(acquire), (database) =>
54
56
  coordinateTargetTotp(
55
57
  database,
56
58
  options.mapping,
@@ -78,5 +80,5 @@ export const coordinateTotpPersistence = <
78
80
 
79
81
  return yield* owner.close(Effect.provideContext(body, provided));
80
82
  }),
81
- ),
83
+ ).pipe(Effect.provideService(NativeDatabase, database)),
82
84
  );