@yielded/auth-persistence 0.1.0-beta.14 → 0.1.0-beta.16

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 (75) hide show
  1. package/dist/Adapter.d.mts +5 -3
  2. package/dist/Adapter.mjs +3 -1
  3. package/dist/index.d.mts +2 -1
  4. package/dist/index.mjs +2 -1
  5. package/dist/internal/browser-login.d.mts +10 -0
  6. package/dist/internal/browser-login.mjs +67 -0
  7. package/dist/internal/browser-login.mjs.map +1 -0
  8. package/dist/internal/email-kernel.d.mts +4 -3
  9. package/dist/internal/email-kernel.mjs +8 -0
  10. package/dist/internal/email-kernel.mjs.map +1 -1
  11. package/dist/internal/models/email-model.d.mts +5 -1
  12. package/dist/internal/models/email-model.mjs.map +1 -1
  13. package/dist/internal/models/passkey-model.d.mts +1 -1
  14. package/dist/internal/models/passkey-write-model.d.mts +1 -1
  15. package/dist/internal/models/password-model.d.mts +1 -1
  16. package/dist/internal/models/proof-model.d.mts +1 -1
  17. package/dist/internal/models/session-model.d.mts +1 -1
  18. package/dist/internal/models/step-up-model.d.mts +1 -1
  19. package/dist/internal/passkey/admission.d.mts +1 -1
  20. package/dist/internal/passkey/credentials.d.mts +1 -1
  21. package/dist/internal/passkey/flow.d.mts +1 -1
  22. package/dist/internal/passkey/registration-ceremony.d.mts +1 -1
  23. package/dist/internal/passkey/state.d.mts +1 -1
  24. package/dist/internal/passkey/target.d.mts +2 -1
  25. package/dist/internal/passkey/target.mjs +9 -8
  26. package/dist/internal/passkey/target.mjs.map +1 -1
  27. package/dist/internal/passkey/write-state.d.mts +1 -1
  28. package/dist/internal/passkey/write-target.mjs +4 -4
  29. package/dist/internal/passkey/write-target.mjs.map +1 -1
  30. package/dist/internal/passkey-kernel.d.mts +9 -9
  31. package/dist/internal/passkey-kernel.mjs +1 -1
  32. package/dist/internal/passkey-kernel.mjs.map +1 -1
  33. package/dist/internal/password-kernel.d.mts +3 -2
  34. package/dist/internal/password-kernel.mjs +4 -0
  35. package/dist/internal/password-kernel.mjs.map +1 -1
  36. package/dist/internal/persistence.d.mts +1 -1
  37. package/dist/internal/persistence.mjs +6 -6
  38. package/dist/internal/persistence.mjs.map +1 -1
  39. package/dist/internal/phone-kernel.d.mts +1 -1
  40. package/dist/internal/proof-kernel.d.mts +3 -2
  41. package/dist/internal/proof-kernel.mjs +6 -2
  42. package/dist/internal/proof-kernel.mjs.map +1 -1
  43. package/dist/internal/query-operations.d.mts +9 -4
  44. package/dist/internal/session-kernel.d.mts +7 -5
  45. package/dist/internal/session-kernel.mjs +9 -1
  46. package/dist/internal/session-kernel.mjs.map +1 -1
  47. package/dist/internal/storage-mapping.d.mts +59 -0
  48. package/dist/internal/storage-mapping.mjs +5 -0
  49. package/dist/internal/storage-mapping.mjs.map +1 -1
  50. package/dist/internal/storage-validation.d.mts +18 -0
  51. package/dist/internal/storage-validation.mjs +26 -1
  52. package/dist/internal/storage-validation.mjs.map +1 -1
  53. package/dist/internal/transaction-execution-kernel.d.mts +1 -1
  54. package/dist/internal/transaction-execution-kernel.mjs.map +1 -1
  55. package/dist/internal/transaction-kernel.d.mts +1 -1
  56. package/dist/internal/transaction-kernel.mjs +1 -1
  57. package/dist/internal/transaction-kernel.mjs.map +1 -1
  58. package/package.json +1 -1
  59. package/src/Adapter.ts +8 -4
  60. package/src/index.ts +1 -0
  61. package/src/internal/browser-login.ts +84 -0
  62. package/src/internal/email-kernel.ts +27 -2
  63. package/src/internal/models/email-model.ts +4 -0
  64. package/src/internal/passkey/target.ts +20 -9
  65. package/src/internal/passkey/write-target.ts +8 -8
  66. package/src/internal/passkey-kernel.ts +9 -1
  67. package/src/internal/password-kernel.ts +14 -1
  68. package/src/internal/persistence.ts +27 -6
  69. package/src/internal/proof-kernel.ts +50 -12
  70. package/src/internal/query-operations.ts +8 -0
  71. package/src/internal/session-kernel.ts +63 -8
  72. package/src/internal/storage-mapping.ts +5 -0
  73. package/src/internal/storage-validation.ts +83 -19
  74. package/src/internal/transaction-execution-kernel.ts +3 -2
  75. package/src/internal/transaction-kernel.ts +1 -1
@@ -43,6 +43,7 @@ import {
43
43
  import type { TableModel as Table } from "./query-operations";
44
44
  import type { QueryFailure } from "./query-operations";
45
45
  import type { QueryOperations, SqlFragment, SqlColumn } from "./query-operations";
46
+ import type { NativeDatabase } from "./transaction-kernel";
46
47
 
47
48
  type AdapterFailure = QueryFailure | PersistenceMappingError | SqlError.SqlError;
48
49
 
@@ -661,8 +662,17 @@ export const makeProofKernel = <
661
662
  const makeSqlProofPersistence = Effect.fn("makeSqlProofPersistence")(function* (
662
663
  mapping: Mapping,
663
664
  configuration: ProofSqlConfiguration,
664
- ): Effect.fn.Return<ProofPersistence["Service"], never, LifecycleHooks | CurrentProofSql> {
665
+ ): Effect.fn.Return<
666
+ ProofPersistence["Service"],
667
+ ProofUnavailable,
668
+ LifecycleHooks | CurrentProofSql | NativeDatabase
669
+ > {
665
670
  const database = yield* CurrentProofSql;
671
+
672
+ if (!configuration.coordinated)
673
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
674
+ Effect.mapError(unavailable),
675
+ );
666
676
  const hooks = yield* LifecycleHooks;
667
677
 
668
678
  return ProofPersistence.of({
@@ -835,6 +845,34 @@ export const makeProofKernel = <
835
845
  | null
836
846
  | undefined;
837
847
 
848
+ const activeGeneration =
849
+ activeProofId === null || activeProofId === undefined
850
+ ? undefined
851
+ : (yield* readGeneration(
852
+ mapping,
853
+ input.record.moduleId,
854
+ activeProofId,
855
+ configuration.locking,
856
+ ))[0];
857
+
858
+ if (
859
+ activeProofId !== null &&
860
+ activeProofId !== undefined &&
861
+ (activeGeneration === undefined ||
862
+ activeGeneration[mapping.generation.purpose] !== input.record.purpose)
863
+ )
864
+ return yield* unavailable();
865
+
866
+ const replacementAuthorized =
867
+ activeGeneration === undefined ||
868
+ activeGeneration[mapping.generation.state] !== "active" ||
869
+ (yield* mapping.decodeInstant(activeGeneration[mapping.generation.expiresAt])) <=
870
+ now ||
871
+ sameBinding(
872
+ yield* mapping.generation.decodeBinding(activeGeneration),
873
+ input.record.binding,
874
+ );
875
+
838
876
  const supersedesCurrent =
839
877
  input.supersedes === undefined || input.supersedes === activeProofId;
840
878
 
@@ -844,6 +882,7 @@ export const makeProofKernel = <
844
882
  admitted.length === scopes.length &&
845
883
  input.record.issuedAtMillis <= now &&
846
884
  input.record.expiresAtMillis > now &&
885
+ replacementAuthorized &&
847
886
  supersedesCurrent &&
848
887
  (lastIssueAt === undefined ||
849
888
  now - lastIssueAt >= input.policy.abuse.resendCooldownMillis);
@@ -864,18 +903,17 @@ export const makeProofKernel = <
864
903
  journal,
865
904
  );
866
905
 
867
- if (admitted.length > 0)
868
- yield* insertScopeEvents(
869
- mapping,
870
- input.record.moduleId,
871
- input.record.purpose,
872
- "issue",
873
- input.record.requestId,
874
- admitted,
875
- now,
876
- retentionUntil,
877
- );
878
906
  if (!allowed) return prepared;
907
+ yield* insertScopeEvents(
908
+ mapping,
909
+ input.record.moduleId,
910
+ input.record.purpose,
911
+ "issue",
912
+ input.record.requestId,
913
+ scopes,
914
+ now,
915
+ retentionUntil,
916
+ );
879
917
  if (activeProofId !== null && activeProofId !== undefined)
880
918
  yield* transaction
881
919
  .update(mapping.generation.table)
@@ -1,5 +1,9 @@
1
+ import type { Effect } from "effect";
1
2
  import type { Statement } from "effect/sql/Statement";
2
3
 
4
+ import type { PersistenceMappingError } from "./mapping-error";
5
+ import type { NativeDatabase } from "./transaction-kernel";
6
+
3
7
  /** Adapter-owned table metadata. No driver or ORM type enters the shared models. */
4
8
  export interface TableModel {
5
9
  readonly table: object;
@@ -47,6 +51,10 @@ export interface QueryOperations<
47
51
  Fragment extends SqlFragment = SqlFragment,
48
52
  Column extends SqlColumn = SqlColumn,
49
53
  > {
54
+ /** Acquisition-only validation; adapters provide their captured root database. */
55
+ readonly validateStorage?: (
56
+ mapping: unknown,
57
+ ) => Effect.Effect<void, PersistenceMappingError, NativeDatabase>;
50
58
  readonly and: Predicate<Fragment>;
51
59
  readonly or: Predicate<Fragment>;
52
60
  readonly balancedD1And: Predicate<Fragment>;
@@ -61,6 +61,7 @@ import {
61
61
  validateStepUpPlan,
62
62
  stepUpRotationMatches,
63
63
  } from "./step-up-state";
64
+ import type { NativeDatabase } from "./transaction-kernel";
64
65
 
65
66
  type CommitMode = "interactive" | "synchronous";
66
67
 
@@ -109,6 +110,7 @@ export class CurrentSessionSql extends Context.Service<CurrentSessionSql, Sessio
109
110
  type Guard = Effect.Effect<void, SessionUnavailable>;
110
111
 
111
112
  export interface SessionSqlOptions {
113
+ readonly coordinated?: boolean;
112
114
  readonly mode: CommitMode;
113
115
  readonly locking: boolean;
114
116
  /** Target-specific ambient transaction preflight. It runs before IDs or writes. */
@@ -630,10 +632,15 @@ export const makeSessionKernel = (operations: QueryOperations) => {
630
632
  options: SessionSqlOptions,
631
633
  ): Effect.fn.Return<
632
634
  AuthenticationAuthority["Service"],
633
- never,
634
- LifecycleHooks | CurrentSessionSql
635
+ SessionUnavailable,
636
+ LifecycleHooks | CurrentSessionSql | NativeDatabase
635
637
  > {
636
638
  const database = yield* CurrentSessionSql;
639
+
640
+ if (!options.coordinated)
641
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
642
+ Effect.mapError(unavailable),
643
+ );
637
644
  const hooks = yield* LifecycleHooks;
638
645
 
639
646
  const service = {
@@ -790,8 +797,17 @@ export const makeSessionKernel = (operations: QueryOperations) => {
790
797
  const makeSqlPendingAuthentication = Effect.fnUntraced(function* <Claims>(
791
798
  mapping: PendingAuthenticationMapping<Claims, any, any, any, any, any>,
792
799
  options: SessionSqlOptions,
793
- ): Effect.fn.Return<PendingAuthentication<Claims>, never, LifecycleHooks | CurrentSessionSql> {
800
+ ): Effect.fn.Return<
801
+ PendingAuthentication<Claims>,
802
+ SessionUnavailable,
803
+ LifecycleHooks | CurrentSessionSql | NativeDatabase
804
+ > {
794
805
  const database = yield* CurrentSessionSql;
806
+
807
+ if (!options.coordinated)
808
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
809
+ Effect.mapError(unavailable),
810
+ );
795
811
  const hooks = yield* LifecycleHooks;
796
812
 
797
813
  const service = {
@@ -964,10 +980,15 @@ export const makeSessionKernel = (operations: QueryOperations) => {
964
980
  readonly statefulSessionPersistence: StatefulSessionPersistence<Claims>;
965
981
  readonly sessionRepository: SessionRepository;
966
982
  },
967
- never,
968
- LifecycleHooks | CurrentSessionSql
983
+ SessionUnavailable,
984
+ LifecycleHooks | CurrentSessionSql | NativeDatabase
969
985
  > {
970
986
  const database = yield* CurrentSessionSql;
987
+
988
+ if (!options.coordinated)
989
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
990
+ Effect.mapError(unavailable),
991
+ );
971
992
  const hooks = yield* LifecycleHooks;
972
993
 
973
994
  const c = sessionColumns(mapping);
@@ -1018,6 +1039,8 @@ export const makeSessionKernel = (operations: QueryOperations) => {
1018
1039
 
1019
1040
  const sessionId = yield* mapping.sessionId.toSession(nativeSessionId);
1020
1041
 
1042
+ if (sessionId === input.handoffSourceSessionId) return yield* SessionConflict.make({});
1043
+
1021
1044
  const version = yield* allocate(
1022
1045
  options.mode,
1023
1046
  mapping.session.allocateVersion,
@@ -1043,7 +1066,10 @@ export const makeSessionKernel = (operations: QueryOperations) => {
1043
1066
  version,
1044
1067
  subjectId: input.evidence.revision.subjectId,
1045
1068
  securityRevision: input.evidence.revision.securityRevision,
1046
- assurance: assessed.assurance,
1069
+ assurance:
1070
+ input.handoffSourceSessionId === undefined
1071
+ ? assessed.assurance
1072
+ : input.session.assurance,
1047
1073
  provenance: yield* snapshotSessionAuthenticationProvenance({
1048
1074
  evidence: input.evidence,
1049
1075
  }),
@@ -1067,6 +1093,17 @@ export const makeSessionKernel = (operations: QueryOperations) => {
1067
1093
  }),
1068
1094
  );
1069
1095
 
1096
+ // Writes can suspend after prepare. Recheck the capped lifetime and
1097
+ // original proof freshness while the authority rows remain locked.
1098
+ const commitNow = yield* freshNow;
1099
+
1100
+ if (
1101
+ DateTime.toEpochMillis(commitNow) < DateTime.toEpochMillis(record.issuedAt) ||
1102
+ DateTime.toEpochMillis(commitNow) >= DateTime.toEpochMillis(record.expiresAt) ||
1103
+ !(yield* assessAuthentication(input.evidence, requirement)).satisfied
1104
+ )
1105
+ return yield* stale();
1106
+
1070
1107
  return receipt;
1071
1108
  }).pipe(normalizeMutation(mapping)),
1072
1109
  ),
@@ -1388,8 +1425,17 @@ export const makeSessionKernel = (operations: QueryOperations) => {
1388
1425
  const makeSqlSignedValidity = Effect.fnUntraced(function* (
1389
1426
  mapping: SignedSessionValidityMapping<any, any, any, any>,
1390
1427
  options: SessionSqlOptions,
1391
- ): Effect.fn.Return<SignedSessionValidity, never, LifecycleHooks | CurrentSessionSql> {
1428
+ ): Effect.fn.Return<
1429
+ SignedSessionValidity,
1430
+ SessionUnavailable,
1431
+ LifecycleHooks | CurrentSessionSql | NativeDatabase
1432
+ > {
1392
1433
  const database = yield* CurrentSessionSql;
1434
+
1435
+ if (!options.coordinated)
1436
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
1437
+ Effect.mapError(unavailable),
1438
+ );
1393
1439
  const hooks = yield* LifecycleHooks;
1394
1440
 
1395
1441
  const subject = subjectColumns(mapping);
@@ -1780,8 +1826,17 @@ export const makeSessionKernel = (operations: QueryOperations) => {
1780
1826
  const makeSqlSessionStepUp = Effect.fnUntraced(function* <Claims>(
1781
1827
  mapping: SessionStepUpMapping<Claims, any, any, any, any, any, any, any>,
1782
1828
  options: SessionSqlOptions,
1783
- ): Effect.fn.Return<SessionStepUpPersistence<Claims>, never, LifecycleHooks | CurrentSessionSql> {
1829
+ ): Effect.fn.Return<
1830
+ SessionStepUpPersistence<Claims>,
1831
+ SessionUnavailable,
1832
+ LifecycleHooks | CurrentSessionSql | NativeDatabase
1833
+ > {
1784
1834
  const database = yield* CurrentSessionSql;
1835
+
1836
+ if (!options.coordinated)
1837
+ yield* (operations.validateStorage?.(mapping) ?? Effect.void).pipe(
1838
+ Effect.mapError(unavailable),
1839
+ );
1785
1840
  const hooks = yield* LifecycleHooks;
1786
1841
 
1787
1842
  const m = mapping.intent,
@@ -76,6 +76,11 @@ const ProofContinuation = Schema.Struct({
76
76
  version: ProofVersion,
77
77
  });
78
78
 
79
+ /** Derive shared row mappings from a managed or custom storage layout.
80
+ * Each factory requires its role tables to exist. Adapter authors supply typed
81
+ * table handles and dialect-specific clocks/commit predicates, and may refine
82
+ * authority policy for explicit services. This does not alter composed defaults.
83
+ */
79
84
  export const makeMappings = (input: MappingInput) => {
80
85
  const table = (role: StorageRole) => {
81
86
  const found = input.tables[role];
@@ -11,6 +11,13 @@ const PgColumns = Schema.Array(Schema.Struct({ name: Schema.String }));
11
11
  const PgKeys = Schema.Array(Schema.Struct({ columns: Schema.Array(Schema.String) }));
12
12
  const quote = (value: string) => '"' + value.replaceAll('"', '""') + '"';
13
13
 
14
+ /** Physical metadata needed for catalog validation; value codecs remain adapter-owned. */
15
+ export interface PhysicalStorageTable {
16
+ readonly name: string;
17
+ readonly schema?: string;
18
+ readonly columns: Readonly<Record<string, { readonly name: string }>>;
19
+ }
20
+
14
21
  export const qualifiedTableName = (table: Pick<StorageTable, "name" | "schema">) =>
15
22
  (table.schema === undefined ? "" : quote(table.schema) + ".") + quote(table.name);
16
23
 
@@ -18,8 +25,8 @@ export const qualifiedTableName = (table: Pick<StorageTable, "name" | "schema">)
18
25
  * partial or expression index cannot establish these unconditional guarantees. */
19
26
  export const validateStorage = Effect.fnUntraced(
20
27
  function* (
21
- dialect: "pg" | "sqlite",
22
- table: StorageTable,
28
+ dialect: "pg" | "mysql" | "sqlite",
29
+ table: PhysicalStorageTable,
23
30
  required: ReadonlyArray<ReadonlyArray<string>>,
24
31
  ) {
25
32
  const client = yield* SqlClient;
@@ -64,22 +71,76 @@ export const validateStorage = Effect.fnUntraced(
64
71
 
65
72
  return keys;
66
73
  })
67
- : Effect.gen(function* () {
68
- const relation = qualifiedTableName(table);
69
-
70
- const columns =
71
- yield* client`select attname as name from pg_attribute where attrelid = to_regclass(${relation}) and attnum > 0 and not attisdropped`.pipe(
72
- Effect.flatMap(Schema.decodeUnknownEffect(PgColumns)),
74
+ : dialect === "mysql"
75
+ ? Effect.gen(function* () {
76
+ const columns = yield* client`
77
+ select column_name as name from information_schema.columns
78
+ where table_schema = coalesce(${table.schema ?? null}, database())
79
+ and table_name = ${table.name}
80
+ `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(PgColumns)));
81
+
82
+ for (const column of Object.values(table.columns)) {
83
+ if (!columns.some((actual) => actual.name === column.name))
84
+ return yield* PersistenceConfigurationError.make({
85
+ reason: `Missing SQL column ${table.name}.${column.name}`,
86
+ });
87
+ }
88
+
89
+ const indexes = yield* client`
90
+ select index_name as name, column_name as columnName, sub_part as prefix
91
+ from information_schema.statistics
92
+ where table_schema = coalesce(${table.schema ?? null}, database())
93
+ and table_name = ${table.name} and non_unique = 0
94
+ order by index_name, seq_in_index
95
+ `.pipe(
96
+ Effect.flatMap(
97
+ Schema.decodeUnknownEffect(
98
+ Schema.Array(
99
+ Schema.Struct({
100
+ name: Schema.String,
101
+ columnName: Schema.NullOr(Schema.String),
102
+ prefix: Schema.NullOr(Schema.Number),
103
+ }),
104
+ ),
105
+ ),
106
+ ),
73
107
  );
74
108
 
75
- for (const column of Object.values(table.columns)) {
76
- if (!columns.some((actual) => actual.name === column.name))
77
- return yield* PersistenceConfigurationError.make({
78
- reason: `Missing SQL column ${relation}.${column.name}`,
79
- });
80
- }
81
-
82
- const indexes = yield* client`
109
+ const keys = new Map<string, Array<(typeof indexes)[number]>>();
110
+
111
+ for (const index of indexes) {
112
+ const columns = keys.get(index.name) ?? [];
113
+
114
+ columns.push(index);
115
+ keys.set(index.name, columns);
116
+ }
117
+
118
+ return [...keys.values()]
119
+ .filter((columns) =>
120
+ columns.every((column) => column.columnName !== null && column.prefix === null),
121
+ )
122
+ .map((columns) =>
123
+ columns.flatMap((column) =>
124
+ column.columnName === null ? [] : [column.columnName],
125
+ ),
126
+ );
127
+ })
128
+ : Effect.gen(function* () {
129
+ const relation = qualifiedTableName(table);
130
+
131
+ const columns =
132
+ yield* client`select attname as name from pg_attribute where attrelid = to_regclass(${relation}) and attnum > 0 and not attisdropped`.pipe(
133
+ Effect.flatMap(Schema.decodeUnknownEffect(PgColumns)),
134
+ );
135
+
136
+ for (const column of Object.values(table.columns)) {
137
+ if (!columns.some((actual) => actual.name === column.name))
138
+ return yield* PersistenceConfigurationError.make({
139
+ reason: `Missing SQL column ${relation}.${column.name}`,
140
+ });
141
+ }
142
+
143
+ const indexes = yield* client`
83
144
  select array_agg(a.attname order by k.ordinality) as columns
84
145
  from pg_index i
85
146
  cross join lateral unnest(i.indkey) with ordinality as k(attnum, ordinality)
@@ -90,8 +151,8 @@ export const validateStorage = Effect.fnUntraced(
90
151
  group by i.indexrelid
91
152
  `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(PgKeys)));
92
153
 
93
- return indexes.map((index) => index.columns);
94
- });
154
+ return indexes.map((index) => index.columns);
155
+ });
95
156
 
96
157
  for (const keys of required) {
97
158
  const columns = keys.map((key) => table.columns[key]?.name);
@@ -99,8 +160,11 @@ export const validateStorage = Effect.fnUntraced(
99
160
  if (
100
161
  columns.some((column) => column === undefined) ||
101
162
  !physicalKeys.some(
163
+ // A stronger key also guarantees the required tuple's uniqueness.
102
164
  (actual) =>
103
- actual.length === columns.length && actual.every((column) => columns.includes(column)),
165
+ actual.length > 0 &&
166
+ actual.length <= columns.length &&
167
+ actual.every((column) => columns.includes(column)),
104
168
  )
105
169
  )
106
170
  return yield* PersistenceConfigurationError.make({
@@ -203,6 +203,7 @@ export const makeTransactionExecutionKernel = (
203
203
  OwnerId,
204
204
  Resources,
205
205
  AllocationError,
206
+ AllocationRequirements,
206
207
  Services,
207
208
  Transaction,
208
209
  A,
@@ -212,7 +213,7 @@ export const makeTransactionExecutionKernel = (
212
213
  database: TransactionNativeDatabase,
213
214
  ownerTag: Context.Key<OwnerId, TransactionOwner<Failure>>,
214
215
  configuration: TransactionTargetConfiguration<Failure>,
215
- allocate: Effect.Effect<Resources, AllocationError>,
216
+ allocate: Effect.Effect<Resources, AllocationError, AllocationRequirements>,
216
217
  unavailable: () => Failure,
217
218
  nonce: () => string,
218
219
  services: (execution: TransactionExecution<Failure, OwnerId>, resources: Resources) => Services,
@@ -224,7 +225,7 @@ export const makeTransactionExecutionKernel = (
224
225
  ): Effect.Effect<
225
226
  A,
226
227
  TransactionCoordinatorError<E, Failure>,
227
- Exclude<R, CurrentCommitJournal> | LifecycleHooks
228
+ Exclude<R, CurrentCommitJournal> | LifecycleHooks | AllocationRequirements
228
229
  > => {
229
230
  return Effect.gen(function* () {
230
231
  if (yield* hasCommitScope) return yield* Effect.fail(unavailable());
@@ -31,7 +31,7 @@ export interface TransactionNativeDatabase {
31
31
  ) => Effect.Effect<A, E | SqlError, R>;
32
32
  }
33
33
 
34
- /** Root native database required only while constructing standalone services.
34
+ /** Captured root database for service construction and pre-transaction catalog checks.
35
35
  * Active transactions remain owned by their coordinator and journal. */
36
36
  export class NativeDatabase extends Context.Service<NativeDatabase, TransactionNativeDatabase>()(
37
37
  "effect-auth/persistence/NativeDatabase",