@yielded/auth-persistence 0.1.0-beta.22 → 0.1.0-beta.24

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 (182) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +13 -5
  3. package/dist/Adapter.mjs +9 -2
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/authority-snapshot.mjs +53 -0
  11. package/dist/internal/authority-snapshot.mjs.map +1 -0
  12. package/dist/internal/configuration.d.mts +1 -1
  13. package/dist/internal/configuration.mjs.map +1 -1
  14. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  15. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  16. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  17. package/dist/internal/models/oauth-model.d.mts +467 -0
  18. package/dist/internal/models/oauth-model.mjs +49 -0
  19. package/dist/internal/models/oauth-model.mjs.map +1 -0
  20. package/dist/internal/models/password-model.d.mts +4 -97
  21. package/dist/internal/models/password-model.mjs +0 -3
  22. package/dist/internal/models/password-model.mjs.map +1 -1
  23. package/dist/internal/native-sql-table.d.mts +34 -0
  24. package/dist/internal/native-sql-table.mjs +111 -0
  25. package/dist/internal/native-sql-table.mjs.map +1 -0
  26. package/dist/internal/oauth/accounts.d.mts +8 -0
  27. package/dist/internal/oauth/accounts.mjs +379 -0
  28. package/dist/internal/oauth/accounts.mjs.map +1 -0
  29. package/dist/internal/oauth/connected-access.d.mts +9 -0
  30. package/dist/internal/oauth/connected-access.mjs +229 -0
  31. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  32. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  33. package/dist/internal/oauth/connected-collection.mjs +214 -0
  34. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  35. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  36. package/dist/internal/oauth/connected-custody.mjs +165 -0
  37. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  38. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  39. package/dist/internal/oauth/connected-flow.mjs +324 -0
  40. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  41. package/dist/internal/oauth/connected-input.mjs +122 -0
  42. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  43. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  44. package/dist/internal/oauth/connected-keys.mjs +22 -0
  45. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  46. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  47. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  48. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  49. package/dist/internal/oauth/connected-management.d.mts +9 -0
  50. package/dist/internal/oauth/connected-management.mjs +243 -0
  51. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  52. package/dist/internal/oauth/connected-reference.mjs +63 -0
  53. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  54. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  55. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  56. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  57. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  58. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  59. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  60. package/dist/internal/oauth/connected-state.d.mts +14 -0
  61. package/dist/internal/oauth/connected-state.mjs +469 -0
  62. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  63. package/dist/internal/oauth/connected-target.d.mts +21 -0
  64. package/dist/internal/oauth/connected-target.mjs +117 -0
  65. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  66. package/dist/internal/oauth/flow.d.mts +5 -0
  67. package/dist/internal/oauth/flow.mjs +279 -0
  68. package/dist/internal/oauth/flow.mjs.map +1 -0
  69. package/dist/internal/oauth/input.mjs +127 -0
  70. package/dist/internal/oauth/input.mjs.map +1 -0
  71. package/dist/internal/oauth/owner.d.mts +10 -0
  72. package/dist/internal/oauth/owner.mjs +10 -0
  73. package/dist/internal/oauth/owner.mjs.map +1 -0
  74. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  75. package/dist/internal/oauth/query-compiler.mjs +9 -0
  76. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  77. package/dist/internal/oauth/registration.d.mts +7 -0
  78. package/dist/internal/oauth/registration.mjs +393 -0
  79. package/dist/internal/oauth/registration.mjs.map +1 -0
  80. package/dist/internal/oauth/state.mjs +107 -0
  81. package/dist/internal/oauth/state.mjs.map +1 -0
  82. package/dist/internal/oauth/target.d.mts +31 -0
  83. package/dist/internal/oauth/target.mjs +273 -0
  84. package/dist/internal/oauth/target.mjs.map +1 -0
  85. package/dist/internal/oauth-kernel.d.mts +1423 -0
  86. package/dist/internal/oauth-kernel.mjs +187 -0
  87. package/dist/internal/oauth-kernel.mjs.map +1 -0
  88. package/dist/internal/oauth-proxy.d.mts +31 -0
  89. package/dist/internal/oauth-proxy.mjs +129 -0
  90. package/dist/internal/oauth-proxy.mjs.map +1 -0
  91. package/dist/internal/passkey/target.d.mts +1 -1
  92. package/dist/internal/passkey-kernel.d.mts +5 -5
  93. package/dist/internal/passkeys.mjs +1 -1
  94. package/dist/internal/password-credentials.d.mts +11 -0
  95. package/dist/internal/password-credentials.mjs +121 -0
  96. package/dist/internal/password-credentials.mjs.map +1 -0
  97. package/dist/internal/password-kernel.d.mts +15 -28
  98. package/dist/internal/password-kernel.mjs +35 -236
  99. package/dist/internal/password-kernel.mjs.map +1 -1
  100. package/dist/internal/persistence.d.mts +2 -0
  101. package/dist/internal/persistence.mjs +5 -5
  102. package/dist/internal/persistence.mjs.map +1 -1
  103. package/dist/internal/query-operations.d.mts +2 -0
  104. package/dist/internal/session-kernel.d.mts +5 -3
  105. package/dist/internal/session-kernel.mjs +95 -29
  106. package/dist/internal/session-kernel.mjs.map +1 -1
  107. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  108. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  109. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  110. package/dist/internal/sql-oauth-model.d.mts +60 -0
  111. package/dist/internal/sql-oauth-model.mjs +10 -0
  112. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  113. package/dist/internal/sql-persistence.mjs +3 -1
  114. package/dist/internal/sql-persistence.mjs.map +1 -1
  115. package/dist/internal/sql-query.d.mts +12 -0
  116. package/dist/internal/sql-query.mjs +15 -18
  117. package/dist/internal/sql-query.mjs.map +1 -1
  118. package/dist/internal/sql-storage-validation.mjs +54 -0
  119. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  120. package/dist/internal/storage-mapping.mjs +2 -67
  121. package/dist/internal/storage-mapping.mjs.map +1 -1
  122. package/dist/internal/storage-plans.d.mts +7 -0
  123. package/dist/internal/storage-plans.mjs +189 -0
  124. package/dist/internal/storage-plans.mjs.map +1 -0
  125. package/dist/internal/storage-tables.d.mts +0 -99
  126. package/dist/internal/storage-tables.mjs +0 -43
  127. package/dist/internal/storage-tables.mjs.map +1 -1
  128. package/dist/internal/storage-validation.d.mts +6 -1
  129. package/dist/internal/storage-validation.mjs +42 -1
  130. package/dist/internal/storage-validation.mjs.map +1 -1
  131. package/dist/internal/transaction-execution-kernel.d.mts +1 -1
  132. package/dist/internal/transaction-kernel.d.mts +1 -1
  133. package/dist/internal/transaction-kernel.mjs +3 -2
  134. package/dist/internal/transaction-kernel.mjs.map +1 -1
  135. package/package.json +1 -1
  136. package/src/Adapter.ts +97 -8
  137. package/src/OAuthPersistence.ts +427 -0
  138. package/src/index.ts +3 -0
  139. package/src/internal/authority-snapshot.ts +91 -0
  140. package/src/internal/configuration.ts +1 -8
  141. package/src/internal/models/oauth-connected-model.ts +396 -0
  142. package/src/internal/models/oauth-model.ts +708 -0
  143. package/src/internal/models/password-model.ts +0 -120
  144. package/src/internal/native-sql-table.ts +178 -0
  145. package/src/internal/oauth/accounts.ts +838 -0
  146. package/src/internal/oauth/connected-access.ts +388 -0
  147. package/src/internal/oauth/connected-collection.ts +345 -0
  148. package/src/internal/oauth/connected-custody.ts +308 -0
  149. package/src/internal/oauth/connected-flow.ts +562 -0
  150. package/src/internal/oauth/connected-input.ts +147 -0
  151. package/src/internal/oauth/connected-keys.ts +31 -0
  152. package/src/internal/oauth/connected-maintenance.ts +379 -0
  153. package/src/internal/oauth/connected-management.ts +391 -0
  154. package/src/internal/oauth/connected-reference.ts +135 -0
  155. package/src/internal/oauth/connected-settlement.ts +278 -0
  156. package/src/internal/oauth/connected-sign-in.ts +472 -0
  157. package/src/internal/oauth/connected-state.ts +924 -0
  158. package/src/internal/oauth/connected-target.ts +307 -0
  159. package/src/internal/oauth/flow.ts +505 -0
  160. package/src/internal/oauth/input.ts +201 -0
  161. package/src/internal/oauth/owner.ts +22 -0
  162. package/src/internal/oauth/query-compiler.ts +15 -0
  163. package/src/internal/oauth/registration.ts +676 -0
  164. package/src/internal/oauth/state.ts +218 -0
  165. package/src/internal/oauth/target.ts +803 -0
  166. package/src/internal/oauth-kernel.ts +294 -0
  167. package/src/internal/oauth-proxy.ts +176 -0
  168. package/src/internal/password-credentials.ts +245 -0
  169. package/src/internal/password-kernel.ts +98 -712
  170. package/src/internal/persistence.ts +5 -10
  171. package/src/internal/query-operations.ts +2 -0
  172. package/src/internal/session-kernel.ts +231 -81
  173. package/src/internal/sql-oauth-connected-model.ts +145 -0
  174. package/src/internal/sql-oauth-model.ts +263 -0
  175. package/src/internal/sql-persistence.ts +2 -0
  176. package/src/internal/sql-query.ts +16 -13
  177. package/src/internal/sql-storage-validation.ts +64 -0
  178. package/src/internal/storage-mapping.ts +0 -66
  179. package/src/internal/storage-plans.ts +218 -0
  180. package/src/internal/storage-tables.ts +0 -35
  181. package/src/internal/storage-validation.ts +58 -0
  182. package/src/internal/transaction-kernel.ts +6 -2
@@ -16,10 +16,7 @@ import {
16
16
  snapshotPasswordRequirement,
17
17
  snapshotPasswordRevision,
18
18
  type PasswordAction,
19
- type PasswordAttemptDecision,
20
- type PasswordCredentialSnapshot,
21
19
  type PasswordMutationDecision,
22
- type PasswordAttemptPolicy,
23
20
  } from "@yielded/auth/Password";
24
21
  import { reportPersistenceFailure } from "@yielded/auth/Persistence";
25
22
  import {
@@ -34,16 +31,23 @@ import {
34
31
  type SecurityRevision,
35
32
  } from "@yielded/auth/Sessions";
36
33
  import { Cause, Context, DateTime, Effect, Option, Redacted, Schema } from "effect";
34
+ import type { SqlClient } from "effect/sql/SqlClient";
37
35
  import type * as SqlError from "effect/sql/SqlError";
38
36
 
39
37
  import { PersistenceMappingError } from "./mapping-error";
40
38
  import {
41
39
  type AnyPasswordPersistenceMapping,
42
- type PasswordAttemptAction,
43
- type PasswordRateScopeKind,
44
- type PasswordScopeKeys,
45
40
  requiredPasswordConstraints,
46
41
  } from "./models/password-model";
42
+ import { makeNativeSqlTables, sqlMapping, type NativeSqlTables } from "./native-sql-table";
43
+ import {
44
+ makePasswordCredentials,
45
+ samePasswordCredentialSnapshot as sameCredentialSnapshot,
46
+ } from "./password-credentials";
47
+ import {
48
+ CurrentPasswordPreparedTransaction,
49
+ PasswordPreparedPostconditions,
50
+ } from "./PasswordPreparedPostconditions";
47
51
  import {
48
52
  CurrentProofSql,
49
53
  type ProofSqlConfiguration,
@@ -53,7 +57,7 @@ import {
53
57
  /* oxlint-disable no-explicit-any -- existing storage kernels erase foreign table shapes; domain errors remain typed. */
54
58
  import type { QueryFailure } from "./query-operations";
55
59
  import type { QueryOperations, SqlFragment, SqlColumn } from "./query-operations";
56
- import type { NativeDatabase } from "./transaction-kernel";
60
+ import { NativeDatabase } from "./transaction-kernel";
57
61
 
58
62
  type AdapterFailure = QueryFailure | PersistenceMappingError | SqlError.SqlError;
59
63
 
@@ -104,23 +108,18 @@ export interface PasswordSqlConfiguration {
104
108
 
105
109
  type Database = PasswordSqlDatabase;
106
110
 
107
- interface ScopeEntry {
108
- readonly kind: PasswordRateScopeKind;
109
- readonly key: string;
110
- readonly limit: number;
111
- readonly windowMillis: number;
112
- }
113
-
114
111
  export const makePasswordKernel = <
115
112
  Fragment extends SqlFragment = SqlFragment,
116
113
  Column extends SqlColumn = SqlColumn,
117
114
  >(
118
115
  operations: QueryOperations<Fragment, Column>,
119
116
  proofs: Pick<ReturnType<typeof makeProofKernel>, "completeProofPlanIn">,
117
+ nativeTables: (client: SqlClient, database: object) => NativeSqlTables = (client) =>
118
+ makeNativeSqlTables(client),
120
119
  ) => {
121
120
  type Mapping = AnyPasswordPersistenceMapping<Fragment>;
122
121
 
123
- const { and, eq, gt, gte, inArray, lte, notExists, sql, column, updateValues } = operations;
122
+ const { and, eq, inArray, column, updateValues } = operations;
124
123
  const { completeProofPlanIn } = proofs;
125
124
  const unavailable = () => PasswordUnavailable.make({});
126
125
 
@@ -179,44 +178,6 @@ export const makePasswordKernel = <
179
178
  revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision),
180
179
  });
181
180
 
182
- const attemptColumns = (mapping: Mapping) => ({
183
- moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
184
- action: column(mapping.attempt.table, mapping.attempt.action),
185
- attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
186
- identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
187
- identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
188
- subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
189
- credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
190
- securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
191
- credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
192
- verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
193
- identifierBindingRevision: column(
194
- mapping.attempt.table,
195
- mapping.attempt.identifierBindingRevision,
196
- ),
197
- admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
198
- deadline: column(mapping.attempt.table, mapping.attempt.deadline),
199
- retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
200
- state: column(mapping.attempt.table, mapping.attempt.state),
201
- });
202
-
203
- const scopeColumns = (mapping: Mapping) => ({
204
- moduleId: column(mapping.rateScope.table, mapping.rateScope.moduleId),
205
- action: column(mapping.rateScope.table, mapping.rateScope.action),
206
- scopeKind: column(mapping.rateScope.table, mapping.rateScope.scopeKind),
207
- scopeKey: column(mapping.rateScope.table, mapping.rateScope.scopeKey),
208
- });
209
-
210
- const chargeColumns = (mapping: Mapping) => ({
211
- moduleId: column(mapping.charge.table, mapping.charge.moduleId),
212
- action: column(mapping.charge.table, mapping.charge.action),
213
- scopeKind: column(mapping.charge.table, mapping.charge.scopeKind),
214
- scopeKey: column(mapping.charge.table, mapping.charge.scopeKey),
215
- attemptId: column(mapping.charge.table, mapping.charge.attemptId),
216
- occurredAt: column(mapping.charge.table, mapping.charge.occurredAt),
217
- retentionUntil: column(mapping.charge.table, mapping.charge.retentionUntil),
218
- });
219
-
220
181
  const commandColumns = (mapping: Mapping) => ({
221
182
  moduleId: column(mapping.command.table, mapping.command.moduleId),
222
183
  commandId: column(mapping.command.table, mapping.command.commandId),
@@ -270,28 +231,6 @@ export const makePasswordKernel = <
270
231
  const sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>
271
232
  left.namespace === right.namespace && left.value === right.value;
272
233
 
273
- const sameCredentialSnapshot = (
274
- left: PasswordCredentialSnapshot,
275
- right: PasswordCredentialSnapshot,
276
- ) =>
277
- left.moduleId === right.moduleId &&
278
- left.revision.subjectId === right.revision.subjectId &&
279
- left.revision.securityRevision === right.revision.securityRevision &&
280
- left.credentialId === right.credentialId &&
281
- left.credentialRevision === right.credentialRevision &&
282
- left.verifierVersion === right.verifierVersion &&
283
- left.normalization === right.normalization &&
284
- left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis &&
285
- left.revision.credentials.length === right.revision.credentials.length &&
286
- left.revision.credentials.every((entry) =>
287
- right.revision.credentials.some(
288
- (other) => other.credentialId === entry.credentialId && other.revision === entry.revision,
289
- ),
290
- ) &&
291
- left.identifierBindingRevision === right.identifierBindingRevision &&
292
- sameIdentifier(left.identifier, right.identifier) &&
293
- Redacted.value(left.verifier) === Redacted.value(right.verifier);
294
-
295
234
  const evidenceSatisfiedAt = (
296
235
  evidence: AuthenticationEvidence,
297
236
  requirement: AuthenticationRequirement,
@@ -482,145 +421,6 @@ export const makePasswordKernel = <
482
421
  };
483
422
  });
484
423
 
485
- const scopeEntries = (
486
- keys: PasswordScopeKeys,
487
- policy: PasswordAttemptPolicy,
488
- ): ReadonlyArray<ScopeEntry> => {
489
- const values: ScopeEntry[] = [
490
- { kind: "action", key: keys.action, ...policy.action },
491
- { kind: "identifier", key: keys.identifier, ...policy.identifier },
492
- ];
493
-
494
- if (keys.subject !== undefined)
495
- values.push({ kind: "subject", key: keys.subject, ...policy.subject });
496
-
497
- return values;
498
- };
499
-
500
- const lockScope = Effect.fn("DrizzlePassword.lockScope")(function* (
501
- mapping: Mapping,
502
- configuration: PasswordSqlConfiguration,
503
- moduleId: string,
504
- action: PasswordAttemptAction,
505
- entry: ScopeEntry,
506
- ) {
507
- const database = yield* CurrentPasswordSql;
508
-
509
- const query = database.insert(mapping.rateScope.table).values(
510
- mapping.rateScope.encodeInsert({
511
- moduleId,
512
- action,
513
- scopeKind: entry.kind,
514
- scopeKey: entry.key,
515
- }),
516
- );
517
-
518
- yield* configuration.insertIfAbsent(query, mapping.rateScope.scopeKey, entry.key);
519
- const c = scopeColumns(mapping);
520
-
521
- yield* selectRows(
522
- database
523
- .select()
524
- .from(mapping.rateScope.table)
525
- .where(
526
- and(
527
- eq(c.moduleId, moduleId),
528
- eq(c.action, action),
529
- eq(c.scopeKind, entry.kind),
530
- eq(c.scopeKey, entry.key),
531
- ),
532
- )
533
- .limit(1),
534
- configuration.locking,
535
- );
536
- });
537
-
538
- const scopeAdmits = Effect.fn("DrizzlePassword.scopeAdmits")(function* (
539
- mapping: Mapping,
540
- moduleId: string,
541
- action: PasswordAttemptAction,
542
- entry: ScopeEntry,
543
- policy: PasswordAttemptPolicy,
544
- identifier: LoginIdentifier,
545
- nativeSubjectId: unknown | undefined,
546
- now: number,
547
- ) {
548
- const database = yield* CurrentPasswordSql;
549
-
550
- const c = chargeColumns(mapping);
551
-
552
- const charges = yield* selectRows(
553
- database
554
- .select({ attemptId: c.attemptId })
555
- .from(mapping.charge.table)
556
- .where(
557
- and(
558
- eq(c.moduleId, moduleId),
559
- eq(c.action, action),
560
- eq(c.scopeKind, entry.kind),
561
- eq(c.scopeKey, entry.key),
562
- gte(c.occurredAt, mapping.encodeInstant(now - entry.windowMillis)),
563
- ),
564
- )
565
- .limit(entry.limit),
566
- true,
567
- );
568
-
569
- if (charges.length >= entry.limit) return false;
570
- const a = attemptColumns(mapping);
571
-
572
- // Retention is the pending horizon. Expired rows stay until cleanup, but they
573
- // no longer consume the admission budget after that horizon.
574
- const pendingWhere = and(
575
- entry.kind === "action"
576
- ? and(eq(a.moduleId, moduleId), eq(a.action, action))
577
- : entry.kind === "identifier"
578
- ? and(
579
- eq(a.moduleId, moduleId),
580
- eq(a.action, action),
581
- eq(a.identifierNamespace, identifier.namespace),
582
- eq(a.identifierValue, identifier.value),
583
- )
584
- : and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.subjectId, nativeSubjectId)),
585
- eq(a.state, "pending"),
586
- gt(a.retentionUntil, mapping.encodeInstant(now)),
587
- );
588
-
589
- const pending = yield* selectRows(
590
- database
591
- .select({ attemptId: a.attemptId })
592
- .from(mapping.attempt.table)
593
- .where(pendingWhere)
594
- .limit(policy.maximumPending),
595
- true,
596
- );
597
-
598
- return pending.length < policy.maximumPending;
599
- });
600
-
601
- const insertCharge = Effect.fn("Drizzle.insertCharge")(function* (
602
- mapping: Mapping,
603
- moduleId: string,
604
- action: PasswordAttemptAction,
605
- entry: ScopeEntry,
606
- attemptId: any,
607
- now: number,
608
- ) {
609
- const database = yield* CurrentPasswordSql;
610
-
611
- return yield* database.insert(mapping.charge.table).values(
612
- mapping.charge.encodeInsert({
613
- moduleId,
614
- action,
615
- scopeKind: entry.kind,
616
- scopeKey: entry.key,
617
- attemptId,
618
- occurredAtMillis: now,
619
- retentionUntilMillis: now + entry.windowMillis,
620
- }),
621
- );
622
- });
623
-
624
424
  const owned = <A, E, R>(
625
425
  database: Database,
626
426
  mapping: Mapping,
@@ -650,6 +450,25 @@ export const makePasswordKernel = <
650
450
  : run;
651
451
  };
652
452
 
453
+ // A coordinator that accepts postconditions re-runs this check against its
454
+ // current transaction after application work, before commit.
455
+ const registerFinal = Effect.fnUntraced(function* <E>(
456
+ check: Effect.Effect<boolean, E, CurrentPasswordSql>,
457
+ ) {
458
+ const postconditions = yield* Effect.serviceOption(PasswordPreparedPostconditions);
459
+
460
+ if (Option.isNone(postconditions)) return;
461
+
462
+ const postcondition = Effect.gen(function* () {
463
+ const transaction = yield* CurrentPasswordPreparedTransaction;
464
+
465
+ if (!(yield* check.pipe(Effect.provideService(CurrentPasswordSql, transaction))))
466
+ return yield* unavailable();
467
+ }).pipe(translateFailure);
468
+
469
+ if (!postconditions.value.register(postcondition)) return yield* unavailable();
470
+ });
471
+
653
472
  const safeRead = <A, E, R>(
654
473
  database: Database,
655
474
  configuration: PasswordSqlConfiguration,
@@ -1067,55 +886,16 @@ export const makePasswordKernel = <
1067
886
  );
1068
887
  yield* insertCommand(mapping, input, "add-password", commandNow ?? authority.now);
1069
888
 
1070
- const inserted = (yield* readPasswordCredential(
889
+ const applied = checkMutationApplied(
1071
890
  mapping,
1072
- input.moduleId,
891
+ input,
1073
892
  authority.nativeSubjectId,
1074
- false,
1075
- ))[0];
1076
-
1077
- const authorityInserted = (yield* database
1078
- .select()
1079
- .from(mapping.authorityCredential.table)
1080
- .where(
1081
- and(
1082
- eq(authorityCredentialColumns(mapping).subjectId, authority.nativeSubjectId),
1083
- eq(authorityCredentialColumns(mapping).credentialId, credentialId),
1084
- eq(authorityCredentialColumns(mapping).revision, credentialRevision),
1085
- ),
1086
- )
1087
- .limit(1))[0];
1088
-
1089
- const subjectUpdated = (yield* database
1090
- .select()
1091
- .from(mapping.subject.table)
1092
- .where(
1093
- and(
1094
- eq(subjectColumns(mapping).id, authority.nativeSubjectId),
1095
- eq(subjectColumns(mapping).securityRevision, nextSecurityRevision),
1096
- ),
1097
- )
1098
- .limit(1))[0];
893
+ revisions,
894
+ commandNow ?? authority.now,
895
+ );
1099
896
 
1100
- if (
1101
- inserted === undefined ||
1102
- inserted[mapping.credential.credentialId] !== credentialId ||
1103
- inserted[mapping.credential.credentialRevision] !== credentialRevision ||
1104
- inserted[mapping.credential.verifierVersion] !== verifierVersion ||
1105
- inserted[mapping.credential.verifier] !== Redacted.value(input.replacement.verifier) ||
1106
- inserted[mapping.credential.normalization] !== input.replacement.normalization ||
1107
- authorityInserted === undefined ||
1108
- subjectUpdated === undefined ||
1109
- !(yield* mutationPostconditions(
1110
- mapping,
1111
- input,
1112
- authority.nativeSubjectId,
1113
- subjectUpdated,
1114
- authorityInserted,
1115
- commandNow ?? authority.now,
1116
- ))
1117
- )
1118
- return yield* unavailable();
897
+ if (!(yield* applied)) return yield* unavailable();
898
+ yield* registerFinal(applied);
1119
899
 
1120
900
  return true;
1121
901
  });
@@ -1139,267 +919,19 @@ export const makePasswordKernel = <
1139
919
  ).pipe(Effect.mapError(unavailable));
1140
920
  const hooks = yield* LifecycleHooks;
1141
921
 
1142
- return PasswordPersistence.of({
1143
- admitAttempt: (input, prepare) =>
1144
- Effect.gen(function* () {
1145
- const attemptId = yield* allocate(
1146
- configuration.mode,
1147
- mapping.allocateAttemptId,
1148
- mapping.allocateAttemptIdSync,
1149
- );
1150
-
1151
- return yield* owned(
1152
- database,
1153
- mapping,
1154
- configuration,
1155
- Effect.gen(function* () {
1156
- const transaction = yield* CurrentPasswordSql;
1157
- const journal = yield* CurrentCommitJournal;
1158
-
1159
- const resolved = yield* resolveCredential(
1160
- mapping,
1161
- input.moduleId,
1162
- input.identifier,
1163
- input.subjectId,
1164
- configuration.locking,
1165
- );
1166
-
1167
- const keys = mapping.scopeKeys({
1168
- moduleId: input.moduleId,
1169
- action: input.action,
1170
- identifier: input.identifier,
1171
- ...(resolved?.snapshot === undefined
1172
- ? {}
1173
- : { subjectId: resolved.snapshot.revision.subjectId }),
1174
- });
1175
-
1176
- const scopes = scopeEntries(keys, input.policy);
1177
- const admitted: ScopeEntry[] = [];
1178
-
1179
- yield* lockScope(mapping, configuration, input.moduleId, input.action, scopes[0]!);
1180
- let currentNow = yield* nowMillis;
1181
-
1182
- if (
1183
- yield* scopeAdmits(
1184
- mapping,
1185
- input.moduleId,
1186
- input.action,
1187
- scopes[0]!,
1188
- input.policy,
1189
- input.identifier,
1190
- resolved?.nativeSubjectId,
1191
- currentNow,
1192
- )
1193
- )
1194
- admitted.push(scopes[0]!);
1195
- if (admitted.length === 1) {
1196
- for (const entry of scopes.slice(1)) {
1197
- yield* lockScope(mapping, configuration, input.moduleId, input.action, entry);
1198
- currentNow = yield* nowMillis;
1199
- if (
1200
- yield* scopeAdmits(
1201
- mapping,
1202
- input.moduleId,
1203
- input.action,
1204
- entry,
1205
- input.policy,
1206
- input.identifier,
1207
- resolved?.nativeSubjectId,
1208
- currentNow,
1209
- )
1210
- )
1211
- admitted.push(entry);
1212
- }
1213
- }
1214
- const now = yield* nowMillis;
1215
-
1216
- for (const entry of admitted)
1217
- yield* insertCharge(mapping, input.moduleId, input.action, entry, attemptId, now);
1218
- if (admitted.length !== scopes.length)
1219
- return prepare({ _tag: "Denied" as const }, journal);
1220
- const snapshot = resolved?.snapshot;
1221
- const deadline = now + input.policy.attemptLifetimeMillis;
1222
-
1223
- const retentionUntil = Math.max(
1224
- deadline,
1225
- ...scopes.map((entry) => now + entry.windowMillis),
1226
- );
1227
-
1228
- yield* transaction.insert(mapping.attempt.table).values(
1229
- mapping.attempt.encodeInsert(
1230
- {
1231
- moduleId: input.moduleId,
1232
- action: input.action,
1233
- attemptId,
1234
- identifier: input.identifier,
1235
- ...(snapshot === undefined
1236
- ? {}
1237
- : {
1238
- subjectId: snapshot.revision.subjectId,
1239
- credentialId: snapshot.credentialId,
1240
- securityRevision: snapshot.revision.securityRevision,
1241
- credentialRevision: snapshot.credentialRevision,
1242
- verifierVersion: snapshot.verifierVersion,
1243
- identifierBindingRevision: snapshot.identifierBindingRevision,
1244
- }),
1245
- admittedAtMillis: now,
1246
- deadlineMillis: deadline,
1247
- retentionUntilMillis: retentionUntil,
1248
- },
1249
- {
1250
- ...(resolved?.nativeSubjectId === undefined
1251
- ? {}
1252
- : { nativeSubjectId: resolved.nativeSubjectId }),
1253
- state: "pending",
1254
- },
1255
- ),
1256
- );
1257
-
1258
- return prepare(
1259
- {
1260
- _tag: "Admitted" as const,
1261
- attemptId,
1262
- ...(snapshot === undefined ? {} : { credential: snapshot }),
1263
- },
1264
- journal,
1265
- );
1266
- }),
1267
- );
1268
- }).pipe(
1269
- Effect.provideService(CurrentPasswordSql, database),
1270
- Effect.provideService(LifecycleHooks, hooks),
1271
- translateFailure,
1272
- ),
1273
- settleAttempt: (uncaptured, prepare) =>
1274
- Effect.gen(function* () {
1275
- const captured =
1276
- uncaptured.captured === undefined
1277
- ? undefined
1278
- : yield* snapshotPasswordCredential(uncaptured.captured);
1279
-
1280
- const input = { ...uncaptured, ...(captured === undefined ? {} : { captured }) };
1281
-
1282
- return yield* owned(
1283
- database,
1284
- mapping,
1285
- configuration,
1286
- Effect.gen(function* () {
1287
- const transaction = yield* CurrentPasswordSql;
1288
- const journal = yield* CurrentCommitJournal;
1289
-
1290
- const c = attemptColumns(mapping);
1291
-
1292
- const discovered = yield* selectRows(
1293
- transaction
1294
- .select()
1295
- .from(mapping.attempt.table)
1296
- .where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId)))
1297
- .limit(1),
1298
- false,
1299
- );
1300
-
1301
- let row = discovered[0];
1302
-
1303
- if (row === undefined || row[mapping.attempt.state] !== "pending")
1304
- return prepare("rejected", journal);
1305
- let decision: PasswordAttemptDecision = "rejected";
1306
- let current: any;
1307
- let attemptMatches = false;
1308
-
1309
- if (input.outcome === "verified" && input.captured !== undefined) {
1310
- current = yield* resolveCredential(
1311
- mapping,
1312
- input.moduleId,
1313
- input.captured.identifier,
1314
- input.captured.revision.subjectId,
1315
- configuration.locking,
1316
- );
1317
- attemptMatches =
1318
- input.captured.moduleId === input.moduleId &&
1319
- row[mapping.attempt.identifierNamespace] ===
1320
- input.captured.identifier.namespace &&
1321
- row[mapping.attempt.identifierValue] === input.captured.identifier.value &&
1322
- mapping.subjectId.equals(
1323
- row[mapping.attempt.subjectId],
1324
- current?.nativeSubjectId,
1325
- ) &&
1326
- row[mapping.attempt.credentialId] === input.captured.credentialId &&
1327
- row[mapping.attempt.securityRevision] ===
1328
- input.captured.revision.securityRevision &&
1329
- row[mapping.attempt.credentialRevision] === input.captured.credentialRevision &&
1330
- row[mapping.attempt.verifierVersion] === input.captured.verifierVersion &&
1331
- row[mapping.attempt.identifierBindingRevision] ===
1332
- input.captured.identifierBindingRevision;
1333
- }
1334
- row = (yield* selectRows(
1335
- transaction
1336
- .select()
1337
- .from(mapping.attempt.table)
1338
- .where(and(eq(c.moduleId, input.moduleId), eq(c.attemptId, input.attemptId)))
1339
- .limit(1),
1340
- configuration.locking,
1341
- ))[0];
1342
- if (row === undefined || row[mapping.attempt.state] !== "pending")
1343
- return prepare("rejected", journal);
1344
- const now = yield* nowMillis;
1345
- const deadline = yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
922
+ if (!validConstraints(mapping)) return yield* unavailable();
923
+ const root = yield* NativeDatabase;
1346
924
 
1347
- if (
1348
- attemptMatches &&
1349
- current?.snapshot !== undefined &&
1350
- input.captured !== undefined &&
1351
- deadline > now &&
1352
- sameCredentialSnapshot(current.snapshot, input.captured)
1353
- )
1354
- decision = "verified";
1355
- const prepared = prepare(decision, journal);
1356
-
1357
- yield* transaction
1358
- .update(mapping.attempt.table)
1359
- .set(updateValues([[mapping.attempt.state, decision]]))
1360
- .where(
1361
- and(
1362
- eq(c.moduleId, input.moduleId),
1363
- eq(c.attemptId, input.attemptId),
1364
- eq(c.state, "pending"),
1365
- ),
1366
- );
1367
- if (
1368
- decision === "verified" &&
1369
- input.rehash !== undefined &&
1370
- current?.snapshot !== undefined
1371
- ) {
1372
- const cc = credentialColumns(mapping);
1373
-
1374
- const nextVersion = yield* allocate(
1375
- configuration.mode,
1376
- mapping.allocateRevision,
1377
- mapping.allocateRevisionSync,
1378
- );
1379
-
1380
- yield* transaction
1381
- .update(mapping.credential.table)
1382
- .set(mapping.credential.encodeVerifier(input.rehash.nextVerifier, nextVersion))
1383
- .where(
1384
- and(
1385
- eq(cc.moduleId, input.moduleId),
1386
- eq(cc.subjectId, current.nativeSubjectId),
1387
- eq(cc.credentialId, current.snapshot.credentialId),
1388
- eq(cc.credentialRevision, current.snapshot.credentialRevision),
1389
- eq(cc.verifierVersion, input.rehash.expectedVersion),
1390
- eq(cc.verifier, Redacted.value(input.rehash.expectedVerifier)),
1391
- ),
1392
- );
1393
- }
925
+ const credentials = yield* makePasswordCredentials(
926
+ yield* sqlMapping(() => nativeTables(root.$client.withoutTransforms(), root)).pipe(
927
+ translateFailure,
928
+ ),
929
+ mapping,
930
+ configuration,
931
+ );
1394
932
 
1395
- return prepared;
1396
- }),
1397
- );
1398
- }).pipe(
1399
- Effect.provideService(CurrentPasswordSql, database),
1400
- Effect.provideService(LifecycleHooks, hooks),
1401
- translateFailure,
1402
- ),
933
+ return PasswordPersistence.of({
934
+ ...credentials,
1403
935
  readForSubject: (input) =>
1404
936
  safeRead(
1405
937
  database,
@@ -1691,13 +1223,30 @@ export const makePasswordKernel = <
1691
1223
  configuration.proof!.configuration,
1692
1224
  input.completion,
1693
1225
  Effect.gen(function* () {
1694
- return yield* writeReplacement(
1226
+ const revisions = {
1227
+ credentialRevision,
1228
+ verifierVersion,
1229
+ nextSecurityRevision,
1230
+ };
1231
+
1232
+ const applied = yield* writeReplacement(
1695
1233
  mapping,
1696
1234
  input,
1697
1235
  authority.nativeSubjectId,
1698
- { credentialRevision, verifierVersion, nextSecurityRevision },
1236
+ revisions,
1699
1237
  authority.now,
1700
1238
  );
1239
+
1240
+ if (applied)
1241
+ yield* registerReplacement(
1242
+ mapping,
1243
+ input,
1244
+ authority.nativeSubjectId,
1245
+ revisions,
1246
+ authority.now,
1247
+ );
1248
+
1249
+ return applied;
1701
1250
  }),
1702
1251
  (decision) => {
1703
1252
  passwordReceipt = prepare(
@@ -1718,197 +1267,6 @@ export const makePasswordKernel = <
1718
1267
  Effect.provideService(LifecycleHooks, hooks),
1719
1268
  translateFailure,
1720
1269
  ),
1721
- cleanupAttempts: (input, prepare) =>
1722
- owned(
1723
- database,
1724
- mapping,
1725
- configuration,
1726
- Effect.gen(function* () {
1727
- const transaction = yield* CurrentPasswordSql;
1728
- const journal = yield* CurrentCommitJournal;
1729
-
1730
- const now = yield* nowMillis;
1731
- const nativeNow = mapping.encodeInstant(now);
1732
- let remaining = input.limit;
1733
- let hasMore = false;
1734
- const a = attemptColumns(mapping);
1735
- const c = chargeColumns(mapping);
1736
-
1737
- const attempts = yield* selectRows(
1738
- transaction
1739
- .select({ attemptId: a.attemptId })
1740
- .from(mapping.attempt.table)
1741
- .where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow)))
1742
- .limit(remaining + 1),
1743
- false,
1744
- );
1745
-
1746
- if (attempts.length > remaining) hasMore = true;
1747
- const attemptCandidates = attempts.slice(0, remaining);
1748
-
1749
- remaining -= attemptCandidates.length;
1750
-
1751
- const charges = yield* selectRows(
1752
- transaction
1753
- .select({
1754
- action: c.action,
1755
- scopeKind: c.scopeKind,
1756
- scopeKey: c.scopeKey,
1757
- attemptId: c.attemptId,
1758
- })
1759
- .from(mapping.charge.table)
1760
- .where(and(eq(c.moduleId, input.moduleId), lte(c.retentionUntil, nativeNow)))
1761
- .limit(remaining + 1),
1762
- false,
1763
- );
1764
-
1765
- if (charges.length > remaining) hasMore = true;
1766
- const chargeCandidates = charges.slice(0, remaining);
1767
-
1768
- remaining -= chargeCandidates.length;
1769
- const sc = scopeColumns(mapping);
1770
-
1771
- const scopes = yield* selectRows(
1772
- transaction
1773
- .select({ action: sc.action, scopeKind: sc.scopeKind, scopeKey: sc.scopeKey })
1774
- .from(mapping.rateScope.table)
1775
- .where(
1776
- and(
1777
- eq(sc.moduleId, input.moduleId),
1778
- notExists(
1779
- transaction
1780
- .select({ one: sql`1` })
1781
- .from(mapping.charge.table)
1782
- .where(
1783
- and(
1784
- eq(c.moduleId, sc.moduleId),
1785
- eq(c.action, sc.action),
1786
- eq(c.scopeKind, sc.scopeKind),
1787
- eq(c.scopeKey, sc.scopeKey),
1788
- ),
1789
- ),
1790
- ),
1791
- ),
1792
- )
1793
- .limit(remaining + 1),
1794
- remaining === 0 ? false : configuration.locking,
1795
- );
1796
-
1797
- if (scopes.length > remaining) hasMore = true;
1798
- const selectedScopes = scopes.slice(0, remaining);
1799
-
1800
- remaining -= selectedScopes.length;
1801
- const selectedCharges: typeof chargeCandidates = [];
1802
-
1803
- for (const candidate of chargeCandidates) {
1804
- const locked = yield* selectRows(
1805
- transaction
1806
- .select({
1807
- action: c.action,
1808
- scopeKind: c.scopeKind,
1809
- scopeKey: c.scopeKey,
1810
- attemptId: c.attemptId,
1811
- })
1812
- .from(mapping.charge.table)
1813
- .where(
1814
- and(
1815
- eq(c.moduleId, input.moduleId),
1816
- eq(c.action, candidate.action),
1817
- eq(c.scopeKind, candidate.scopeKind),
1818
- eq(c.scopeKey, candidate.scopeKey),
1819
- eq(c.attemptId, candidate.attemptId),
1820
- lte(c.retentionUntil, nativeNow),
1821
- ),
1822
- )
1823
- .limit(1),
1824
- configuration.locking,
1825
- );
1826
-
1827
- if (locked[0] !== undefined) selectedCharges.push(locked[0] as any);
1828
- }
1829
- const selectedAttempts: typeof attemptCandidates = [];
1830
-
1831
- for (const candidate of attemptCandidates) {
1832
- const locked = yield* selectRows(
1833
- transaction
1834
- .select({ attemptId: a.attemptId })
1835
- .from(mapping.attempt.table)
1836
- .where(
1837
- and(
1838
- eq(a.moduleId, input.moduleId),
1839
- eq(a.attemptId, candidate.attemptId),
1840
- lte(a.retentionUntil, nativeNow),
1841
- ),
1842
- )
1843
- .limit(1),
1844
- configuration.locking,
1845
- );
1846
-
1847
- if (locked[0] !== undefined) selectedAttempts.push(locked[0] as any);
1848
- }
1849
-
1850
- const prepared = prepare(
1851
- {
1852
- removed: selectedAttempts.length + selectedCharges.length + selectedScopes.length,
1853
- hasMore,
1854
- },
1855
- journal,
1856
- );
1857
-
1858
- for (const row of selectedCharges)
1859
- yield* transaction
1860
- .delete(mapping.charge.table)
1861
- .where(
1862
- and(
1863
- eq(c.moduleId, input.moduleId),
1864
- eq(c.action, row.action),
1865
- eq(c.scopeKind, row.scopeKind),
1866
- eq(c.scopeKey, row.scopeKey),
1867
- eq(c.attemptId, row.attemptId),
1868
- lte(c.retentionUntil, nativeNow),
1869
- ),
1870
- );
1871
- if (selectedAttempts.length > 0)
1872
- yield* transaction.delete(mapping.attempt.table).where(
1873
- and(
1874
- eq(a.moduleId, input.moduleId),
1875
- inArray(
1876
- a.attemptId,
1877
- selectedAttempts.map((row: any) => row.attemptId),
1878
- ),
1879
- lte(a.retentionUntil, nativeNow),
1880
- ),
1881
- );
1882
- for (const row of selectedScopes)
1883
- yield* transaction.delete(mapping.rateScope.table).where(
1884
- and(
1885
- eq(sc.moduleId, input.moduleId),
1886
- eq(sc.action, row.action),
1887
- eq(sc.scopeKind, row.scopeKind),
1888
- eq(sc.scopeKey, row.scopeKey),
1889
- notExists(
1890
- transaction
1891
- .select({ one: sql`1` })
1892
- .from(mapping.charge.table)
1893
- .where(
1894
- and(
1895
- eq(c.moduleId, sc.moduleId),
1896
- eq(c.action, sc.action),
1897
- eq(c.scopeKind, sc.scopeKind),
1898
- eq(c.scopeKey, sc.scopeKey),
1899
- ),
1900
- ),
1901
- ),
1902
- ),
1903
- );
1904
-
1905
- return prepared;
1906
- }),
1907
- ).pipe(
1908
- Effect.provideService(CurrentPasswordSql, database),
1909
- Effect.provideService(LifecycleHooks, hooks),
1910
- translateFailure,
1911
- ),
1912
1270
  });
1913
1271
  });
1914
1272
 
@@ -1987,8 +1345,14 @@ export const makePasswordKernel = <
1987
1345
  )
1988
1346
  .limit(1))[0];
1989
1347
 
1348
+ // SQL equality follows column collation; compare the persisted values exactly.
1990
1349
  return (
1991
1350
  credential !== undefined &&
1351
+ credential[mapping.credential.credentialId] === revisions.credentialId &&
1352
+ credential[mapping.credential.credentialRevision] === revisions.credentialRevision &&
1353
+ credential[mapping.credential.verifierVersion] === revisions.verifierVersion &&
1354
+ credential[mapping.credential.verifier] === Redacted.value(input.replacement.verifier) &&
1355
+ credential[mapping.credential.normalization] === input.replacement.normalization &&
1992
1356
  (yield* mutationPostconditions(
1993
1357
  mapping,
1994
1358
  input,
@@ -2069,6 +1433,27 @@ export const makePasswordKernel = <
2069
1433
  );
2070
1434
  });
2071
1435
 
1436
+ const registerReplacement = (
1437
+ mapping: Mapping,
1438
+ input: PasswordMutationInput,
1439
+ nativeSubjectId: unknown,
1440
+ revisions: {
1441
+ readonly credentialRevision: SecurityRevision;
1442
+ readonly verifierVersion: SecurityRevision;
1443
+ readonly nextSecurityRevision: SecurityRevision;
1444
+ },
1445
+ now: number,
1446
+ ) =>
1447
+ registerFinal(
1448
+ checkMutationApplied(
1449
+ mapping,
1450
+ input,
1451
+ nativeSubjectId,
1452
+ { ...revisions, credentialId: input.credential!.credentialId },
1453
+ now,
1454
+ ),
1455
+ );
1456
+
2072
1457
  const replaceIn = Effect.fn("Drizzle.replaceIn")(function* <A>(
2073
1458
  mapping: Mapping,
2074
1459
  configuration: PasswordSqlConfiguration,
@@ -2102,6 +1487,7 @@ export const makePasswordKernel = <
2102
1487
  ))
2103
1488
  )
2104
1489
  return yield* unavailable();
1490
+ yield* registerReplacement(mapping, input, authority.nativeSubjectId, revisions, authority.now);
2105
1491
 
2106
1492
  return prepared;
2107
1493
  });