domainkit 0.1.0-beta.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/{auth-Df9BuC25.mjs → auth-Vi-hhXbV.mjs} +24 -9
  2. package/dist/auth-Vi-hhXbV.mjs.map +1 -0
  3. package/dist/{client-DvXfJx_E.mjs → client-BnD7QLRF.mjs} +54 -21
  4. package/dist/client-BnD7QLRF.mjs.map +1 -0
  5. package/dist/{cloudflare-C_ixZsJo.mjs → cloudflare-C6ryPlCZ.mjs} +4 -2
  6. package/dist/{cloudflare-C_ixZsJo.mjs.map → cloudflare-C6ryPlCZ.mjs.map} +1 -1
  7. package/dist/{cloudflare-DClWuAm8.d.mts → cloudflare-tdjNS8vE.d.mts} +3 -3
  8. package/dist/cloudflare.d.mts +2 -2
  9. package/dist/cloudflare.mjs +1 -1
  10. package/dist/{dns-record-Dzo4flM5.mjs → dns-record-C_KwGPLf.mjs} +11 -1
  11. package/dist/dns-record-C_KwGPLf.mjs.map +1 -0
  12. package/dist/effect-cloudflare.d.mts +1 -1
  13. package/dist/effect-cloudflare.mjs +1 -1
  14. package/dist/effect-vercel.d.mts +1 -1
  15. package/dist/effect-vercel.mjs +1 -1
  16. package/dist/effect.d.mts +33 -23
  17. package/dist/effect.mjs +7 -7
  18. package/dist/effect.mjs.map +1 -1
  19. package/dist/{index-DQAjUrZu.d.mts → index-CEzk2gbZ.d.mts} +2 -2
  20. package/dist/{index-D1KtyjuI.d.mts → index-CS9Rl5LQ.d.mts} +2 -2
  21. package/dist/index.d.mts +72 -27
  22. package/dist/index.mjs +88 -25
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{manifest-DjH3BVB9.d.mts → manifest-CP85ePFn.d.mts} +5 -1
  25. package/dist/{receipt-C7PjnibZ.mjs → receipt-CGSnfZQ6.mjs} +2 -2
  26. package/dist/{receipt-C7PjnibZ.mjs.map → receipt-CGSnfZQ6.mjs.map} +1 -1
  27. package/dist/{resolver-BjZwN2JE.d.mts → resolver-BHPsADzE.d.mts} +115 -55
  28. package/dist/{resolver-D5nVrR10.mjs → resolver-tEXpKxmH.mjs} +69 -22
  29. package/dist/resolver-tEXpKxmH.mjs.map +1 -0
  30. package/dist/testing.d.mts +24 -18
  31. package/dist/testing.mjs +72 -24
  32. package/dist/testing.mjs.map +1 -1
  33. package/dist/{vercel-Vc_K_87s.d.mts → vercel-D-E2aqb1.d.mts} +3 -3
  34. package/dist/{vercel-BWz_9Y1v.mjs → vercel-DD89DCqH.mjs} +4 -2
  35. package/dist/{vercel-BWz_9Y1v.mjs.map → vercel-DD89DCqH.mjs.map} +1 -1
  36. package/dist/vercel.d.mts +2 -2
  37. package/dist/vercel.mjs +1 -1
  38. package/dist/{verify-DPB1OfZ5.d.mts → verify-B80xCBh2.d.mts} +457 -29
  39. package/dist/{verify-CeMW6XtK.mjs → verify-BjrR_4NF.mjs} +426 -134
  40. package/dist/verify-BjrR_4NF.mjs.map +1 -0
  41. package/package.json +4 -2
  42. package/dist/auth-Df9BuC25.mjs.map +0 -1
  43. package/dist/client-DvXfJx_E.mjs.map +0 -1
  44. package/dist/dns-record-Dzo4flM5.mjs.map +0 -1
  45. package/dist/resolver-D5nVrR10.mjs.map +0 -1
  46. package/dist/verify-CeMW6XtK.mjs.map +0 -1
@@ -1,64 +1,143 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { D as parse$1, O as Error$1, T as decode$3, _ as equals, b as Service, h as decode$4, m as data, s as Observed, u as Schema$5, w as Schema$4, y as Error$2 } from "./dns-record-Dzo4flM5.mjs";
3
- import { a as toAsync$2, m as Service$2, n as Service$4, o as Service$1, t as Error$3, u as Service$3 } from "./resolver-D5nVrR10.mjs";
4
- import { c as Value } from "./client-DvXfJx_E.mjs";
2
+ import { D as parse$1, O as Error$3, T as decode$4, _ as equals, b as Service, h as decode$5, m as data, s as Observed, u as Schema$6, w as Schema$5, y as Error$4 } from "./dns-record-C_KwGPLf.mjs";
3
+ import { a as toAsync$2, m as Service$3, n as Service$5, o as Service$4, t as Error$5, u as Service$2, v as Service$1 } from "./resolver-tEXpKxmH.mjs";
4
+ import { c as Value } from "./client-BnD7QLRF.mjs";
5
5
  import { Cause, Clock, Crypto, Effect, Layer, PlatformError, Schema, SchemaIssue, SchemaTransformation } from "effect";
6
6
  import * as oauth from "oauth4webapi";
7
7
  import { getDomain } from "tldts";
8
8
  import * as DnsPacket from "@leichtgewicht/dns-packet";
9
9
  //#region package.json
10
- var version = "0.1.0-beta.0";
10
+ var version = "0.1.0";
11
11
  //#endregion
12
12
  //#region src/auth/connection.ts
13
13
  var connection_exports = /* @__PURE__ */ __exportAll({
14
14
  AuthorizationError: () => AuthorizationError$1,
15
- Capability: () => Capability,
16
15
  Grant: () => Grant,
17
- Schema: () => Schema$3,
16
+ Schema: () => Schema$4,
18
17
  assertGrant: () => assertGrant,
19
- decode: () => decode$2,
20
- encode: () => encode$1,
21
- validate: () => validate
18
+ decode: () => decode$3,
19
+ encode: () => encode$2,
20
+ validate: () => validate$3
22
21
  });
23
- const Capability = Schema.Literals(["dns:read", "dns:write"]);
24
22
  const Grant = Schema.TaggedUnion({
25
23
  account: {},
26
- domains: { domains: Schema.Array(Schema$4) }
24
+ domains: { domains: Schema.Array(Schema$5) }
27
25
  });
28
- const Schema$3 = Schema.Struct({
29
- accountId: Schema.String,
30
- capabilities: Schema.Array(Capability),
26
+ /** An owner-scoped grant bound to one provider authorization. */
27
+ const Schema$4 = Schema.Struct({
28
+ authorizationId: Schema.String,
31
29
  createdAt: Schema.DateFromString,
32
- expiresAt: Schema.NullOr(Schema.DateFromString),
33
30
  grant: Grant,
34
31
  id: Schema.String,
35
- kind: Schema.Literals(["oauth2", "token"]),
36
- providerId: Schema.String,
37
- scopes: Schema.Array(Schema.String),
38
- subjectId: Schema.String
32
+ ownerId: Schema.String
39
33
  });
40
- const decode$2 = Effect.fn("Connection.decode")((input) => Schema.decodeUnknownEffect(Schema$3)(input).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message }))));
41
- const validate = Effect.fn("Connection.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$3))(input).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message }))));
42
- const encode$1 = Schema.encodeSync(Schema$3);
34
+ const decode$3 = Effect.fn("Connection.decode")((input) => Schema.decodeUnknownEffect(Schema$4)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
35
+ const validate$3 = Effect.fn("Connection.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$4))(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
36
+ const encode$2 = Schema.encodeSync(Schema$4);
43
37
  var AuthorizationError$1 = class extends Schema.TaggedError()("AuthorizationError", { message: Schema.String }) {};
44
- function assertGrant(connection, request) {
45
- if (connection.providerId !== request.providerId || connection.accountId !== request.accountId) throw new AuthorizationError$1({ message: "Connection does not grant this provider account" });
46
- if (connection.expiresAt !== null) {
47
- if (Number.isNaN(connection.expiresAt.getTime())) throw new AuthorizationError$1({ message: "Connection expiration is invalid" });
48
- if (connection.expiresAt <= (request.now ?? /* @__PURE__ */ new Date())) throw new AuthorizationError$1({ message: "Connection has expired" });
38
+ function assertGrant(connection, authorization, request) {
39
+ if (connection.authorizationId !== authorization.id || authorization.providerId !== request.providerId) throw new AuthorizationError$1({ message: "Connection does not grant this provider account" });
40
+ if (authorization.expiresAt !== null) {
41
+ if (Number.isNaN(authorization.expiresAt.getTime())) throw new AuthorizationError$1({ message: "Connection expiration is invalid" });
42
+ if (authorization.expiresAt <= (request.now ?? /* @__PURE__ */ new Date())) throw new AuthorizationError$1({ message: "Connection has expired" });
49
43
  }
50
- if (!connection.capabilities.includes(request.capability)) throw new AuthorizationError$1({ message: `Connection lacks the ${request.capability} capability` });
44
+ if (!authorization.capabilities.includes(request.capability)) throw new AuthorizationError$1({ message: `Connection lacks the ${request.capability} capability` });
51
45
  let domain;
52
46
  try {
53
47
  domain = parse$1(request.domain);
54
48
  } catch (cause) {
55
- if (cause instanceof Error$1) throw cause;
56
- throw new Error$1({ message: cause instanceof Error ? cause.message : String(cause) });
49
+ if (cause instanceof Error$3) throw cause;
50
+ throw new Error$3({ message: cause instanceof Error ? cause.message : String(cause) });
57
51
  }
58
52
  if (connection.grant._tag === "domains" && !connection.grant.domains.includes(domain)) throw new AuthorizationError$1({ message: "Connection does not grant this domain" });
59
53
  return domain;
60
54
  }
61
55
  //#endregion
56
+ //#region src/auth/connection-lifecycle.ts
57
+ var connection_lifecycle_exports = /* @__PURE__ */ __exportAll({
58
+ Error: () => Error$2,
59
+ detach: () => detach,
60
+ updateGrant: () => updateGrant
61
+ });
62
+ var Error$2 = class extends Schema.TaggedError()("ConnectionLifecycleError", { message: Schema.String }) {};
63
+ /** Replace the grant on an owner binding after the host has obtained explicit consent. */
64
+ const updateGrant = Effect.fn("ConnectionLifecycle.updateGrant")(function* (input) {
65
+ const connectionStore = yield* Service$1;
66
+ const connection = yield* validate$3({
67
+ ...input.connection,
68
+ grant: input.grant
69
+ });
70
+ yield* connectionStore.put(connection);
71
+ return connection;
72
+ });
73
+ /**
74
+ * Detach one owner binding. The final binding invokes provider revocation before deleting local
75
+ * credential and authorization state. The callback keeps provider-specific revocation outside the
76
+ * portable authorization model.
77
+ */
78
+ function detach(input) {
79
+ return Effect.gen(function* () {
80
+ const authorizationStore = yield* Service$2;
81
+ const connectionStore = yield* Service$1;
82
+ const credentialStore = yield* Service$3;
83
+ const connection = yield* connectionStore.get(input.connectionId);
84
+ if (connection === null) return yield* new Error$2({ message: "Connection binding does not exist" });
85
+ const remainingBindings = (yield* connectionStore.listByAuthorizationId(connection.authorizationId)).filter(({ id }) => id !== connection.id);
86
+ if (remainingBindings.length > 0) {
87
+ yield* connectionStore.delete(connection.id);
88
+ return {
89
+ connection,
90
+ remainingBindings: remainingBindings.length,
91
+ revokedAuthorization: false
92
+ };
93
+ }
94
+ const authorization = yield* authorizationStore.get(connection.authorizationId);
95
+ if (authorization === null) {
96
+ yield* credentialStore.delete(connection.authorizationId);
97
+ yield* connectionStore.delete(connection.id);
98
+ return {
99
+ connection,
100
+ remainingBindings: 0,
101
+ revokedAuthorization: true
102
+ };
103
+ }
104
+ yield* input.revokeAuthorization(authorization);
105
+ yield* credentialStore.delete(authorization.id);
106
+ yield* authorizationStore.delete(authorization.id);
107
+ yield* connectionStore.delete(connection.id);
108
+ return {
109
+ connection,
110
+ remainingBindings: 0,
111
+ revokedAuthorization: true
112
+ };
113
+ });
114
+ }
115
+ //#endregion
116
+ //#region src/auth/authorization.ts
117
+ var authorization_exports = /* @__PURE__ */ __exportAll({
118
+ Capability: () => Capability,
119
+ Schema: () => Schema$3,
120
+ decode: () => decode$2,
121
+ encode: () => encode$1,
122
+ validate: () => validate$2
123
+ });
124
+ const Capability = Schema.Literals(["dns:read", "dns:write"]);
125
+ /** A provider credential authorization shared by one or more owner connections. */
126
+ const Schema$3 = Schema.Struct({
127
+ accountId: Schema.String,
128
+ capabilities: Schema.Array(Capability),
129
+ createdAt: Schema.DateFromString,
130
+ expiresAt: Schema.NullOr(Schema.DateFromString),
131
+ id: Schema.String,
132
+ kind: Schema.Literals(["oauth2", "token"]),
133
+ providerId: Schema.String,
134
+ scopes: Schema.Array(Schema.String),
135
+ subjectId: Schema.String
136
+ });
137
+ const decode$2 = Effect.fn("ProviderAuthorization.decode")((input) => Schema.decodeUnknownEffect(Schema$3)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
138
+ const validate$2 = Effect.fn("ProviderAuthorization.validate")((input) => Schema.decodeUnknownEffect(Schema.toType(Schema$3))(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
139
+ const encode$1 = Schema.encodeSync(Schema$3);
140
+ //#endregion
62
141
  //#region src/plan/canonical-json.ts
63
142
  var canonical_json_exports = /* @__PURE__ */ __exportAll({
64
143
  CryptoError: () => CryptoError,
@@ -120,7 +199,7 @@ var oauth_exports = /* @__PURE__ */ __exportAll({
120
199
  });
121
200
  function beginProgram(input) {
122
201
  return Effect.gen(function* () {
123
- const stateStore = yield* Service$1;
202
+ const stateStore = yield* Service$4;
124
203
  const cryptoService = yield* Crypto.Crypto;
125
204
  const now = yield* Clock.currentTimeMillis;
126
205
  const state = base64Url(yield* cryptoBytes(cryptoService, 32));
@@ -143,6 +222,7 @@ function beginProgram(input) {
143
222
  expiresAt: new Date(now + (input.ttlMs ?? 6e5)),
144
223
  grant: input.grant,
145
224
  method: input.method,
225
+ ownerId: input.ownerId,
146
226
  redirectUri: input.redirectUri,
147
227
  stateHash,
148
228
  subjectId: input.subjectId
@@ -152,8 +232,9 @@ function beginProgram(input) {
152
232
  }
153
233
  function completeProgram(input) {
154
234
  return Effect.gen(function* () {
155
- const stateStore = yield* Service$1;
156
- const connectionStore = yield* Service$2;
235
+ const stateStore = yield* Service$4;
236
+ const authorizationStore = yield* Service$2;
237
+ const connectionStore = yield* Service$1;
157
238
  const credentialStore = yield* Service$3;
158
239
  const cryptoService = yield* Crypto.Crypto;
159
240
  const state = input.callbackUrl.searchParams.get("state");
@@ -172,42 +253,54 @@ function completeProgram(input) {
172
253
  });
173
254
  const accessToken = Value.from(tokens.access_token);
174
255
  const subject = yield* input.resolveSubject(tokens, accessToken);
175
- const id = yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })));
176
- const connection = yield* validate({
256
+ const existingAuthorization = yield* authorizationStore.findByProviderAccount(input.providerId, subject.accountId);
257
+ const authorizationId = existingAuthorization?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message }))));
258
+ const authorization = yield* validate$2({
177
259
  accountId: subject.accountId,
178
260
  capabilities: [...continuation.method.capabilities],
179
- createdAt: new Date(now),
261
+ createdAt: existingAuthorization?.createdAt ?? new Date(now),
180
262
  expiresAt: subject.expiresAt,
181
- grant: continuation.grant,
182
- id,
263
+ id: authorizationId,
183
264
  kind: "oauth2",
184
265
  providerId: input.providerId,
185
266
  scopes: (tokens.scope ?? continuation.method.scopes.join(" ")).split(" ").filter(Boolean),
186
267
  subjectId: continuation.subjectId
187
268
  });
188
- yield* credentialStore.put(connection.id, {
269
+ const existingConnection = yield* connectionStore.find(continuation.ownerId, authorization.id);
270
+ const connection = yield* validate$3({
271
+ authorizationId: authorization.id,
272
+ createdAt: existingConnection?.createdAt ?? new Date(now),
273
+ grant: continuation.grant,
274
+ id: existingConnection?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))),
275
+ ownerId: continuation.ownerId
276
+ });
277
+ yield* credentialStore.put(authorization.id, {
189
278
  accessToken,
190
279
  refreshToken: tokens.refresh_token === void 0 ? null : Value.from(tokens.refresh_token),
191
280
  tokenType: tokens.token_type
192
281
  });
282
+ yield* authorizationStore.put(authorization);
193
283
  yield* connectionStore.put(connection);
194
- return connection;
284
+ return {
285
+ authorization,
286
+ connection
287
+ };
195
288
  });
196
289
  }
197
290
  function refreshProgram(input) {
198
291
  return Effect.gen(function* () {
199
292
  const credentialStore = yield* Service$3;
200
- const credential = yield* requireCredential(input.connection.id, credentialStore);
293
+ const credential = yield* requireCredential(input.authorization.id, credentialStore);
201
294
  const refreshToken = credential.refreshToken;
202
295
  if (refreshToken === null) return yield* new AuthorizationError$1({ message: "Connection has no refresh token" });
203
296
  const as = input.method.authorizationServer;
204
297
  const client = { client_id: input.client.clientId };
205
298
  const authentication = yield* effectClientAuthentication(input.method, input.client);
206
- const tokens = yield* providerRequest(input.connection.providerId, async (signal) => {
299
+ const tokens = yield* providerRequest(input.authorization.providerId, async (signal) => {
207
300
  const response = await oauth.refreshTokenGrantRequest(as, client, authentication, refreshToken.expose(), requestOptions(input.fetch, signal));
208
301
  return oauth.processRefreshTokenResponse(as, client, response);
209
302
  });
210
- yield* credentialStore.put(input.connection.id, {
303
+ yield* credentialStore.put(input.authorization.id, {
211
304
  accessToken: Value.from(tokens.access_token),
212
305
  refreshToken: tokens.refresh_token === void 0 ? credential.refreshToken : Value.from(tokens.refresh_token),
213
306
  tokenType: tokens.token_type
@@ -218,30 +311,30 @@ function revokeProgram(input) {
218
311
  return Effect.gen(function* () {
219
312
  if (input.method.authorizationServer.revocation_endpoint === void 0) return yield* new AuthorizationError$1({ message: "Provider does not advertise token revocation" });
220
313
  const credentialStore = yield* Service$3;
221
- const credential = yield* requireCredential(input.connection.id, credentialStore);
314
+ const credential = yield* requireCredential(input.authorization.id, credentialStore);
222
315
  const as = input.method.authorizationServer;
223
316
  const authentication = yield* effectClientAuthentication(input.method, input.client);
224
- yield* providerRequest(input.connection.providerId, async (signal) => {
317
+ yield* providerRequest(input.authorization.providerId, async (signal) => {
225
318
  const response = await oauth.revocationRequest(as, { client_id: input.client.clientId }, authentication, credential.accessToken.expose(), requestOptions(input.fetch, signal));
226
319
  await oauth.processRevocationResponse(response);
227
320
  });
228
- yield* credentialStore.delete(input.connection.id);
321
+ yield* credentialStore.delete(input.authorization.id);
229
322
  });
230
323
  }
231
324
  function effectClientAuthentication(method, client) {
232
325
  return Effect.try({
233
326
  try: () => clientAuthentication(method, client),
234
- catch: (cause) => cause instanceof Error$1 ? cause : new Error$1({ message: "OAuth client authentication is invalid" })
327
+ catch: (cause) => cause instanceof Error$3 ? cause : new Error$3({ message: "OAuth client authentication is invalid" })
235
328
  });
236
329
  }
237
330
  function clientAuthentication(method, client) {
238
331
  switch (method.clientAuth) {
239
332
  case "none": return oauth.None();
240
333
  case "client_secret_basic":
241
- if (client.clientSecret === void 0) throw new Error$1({ message: "Client secret is required" });
334
+ if (client.clientSecret === void 0) throw new Error$3({ message: "Client secret is required" });
242
335
  return oauth.ClientSecretBasic(client.clientSecret.expose());
243
336
  case "client_secret_post":
244
- if (client.clientSecret === void 0) throw new Error$1({ message: "Client secret is required" });
337
+ if (client.clientSecret === void 0) throw new Error$3({ message: "Client secret is required" });
245
338
  return oauth.ClientSecretPost(client.clientSecret.expose());
246
339
  }
247
340
  }
@@ -260,7 +353,7 @@ function requireCredential(connectionId, store) {
260
353
  function providerRequest(providerId, request) {
261
354
  return Effect.tryPromise({
262
355
  try: request,
263
- catch: (cause) => cause instanceof AuthorizationError$1 || cause instanceof Error$1 ? cause : new Error$2({
356
+ catch: (cause) => cause instanceof AuthorizationError$1 || cause instanceof Error$3 ? cause : new Error$4({
264
357
  cause,
265
358
  message: safeOAuthMessage(cause),
266
359
  operation: "oauthRequest",
@@ -320,36 +413,48 @@ const Schema$2 = Schema.Struct({
320
413
  methods: Schema.Array(Method),
321
414
  providerId: Schema.String
322
415
  });
323
- const decode$1 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$2)(input).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message }))));
416
+ const decode$1 = Effect.fn("ProviderAuth.decode")((input) => Schema.decodeUnknownEffect(Schema$2)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
324
417
  //#endregion
325
418
  //#region src/auth/token.ts
326
419
  var token_exports = /* @__PURE__ */ __exportAll({ connect: () => connect });
327
420
  const connect = Effect.fn("TokenConnection.connect")(function* (input) {
328
- const connectionStore = yield* Service$2;
421
+ const authorizationStore = yield* Service$2;
422
+ const connectionStore = yield* Service$1;
329
423
  const credentialStore = yield* Service$3;
330
424
  const cryptoService = yield* Crypto.Crypto;
331
425
  const validation = yield* input.validate(input.token);
332
426
  const createdAt = new Date(yield* Clock.currentTimeMillis);
333
- const id = yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })));
334
- const connection = yield* validate({
427
+ const existingAuthorization = yield* authorizationStore.findByProviderAccount(input.providerId, validation.accountId);
428
+ const authorization = yield* validate$2({
335
429
  accountId: validation.accountId,
336
430
  capabilities: [...validation.capabilities],
337
- createdAt,
431
+ createdAt: existingAuthorization?.createdAt ?? createdAt,
338
432
  expiresAt: validation.expiresAt,
339
- grant: input.grant,
340
- id,
433
+ id: existingAuthorization?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))),
341
434
  kind: "token",
342
435
  providerId: input.providerId,
343
436
  scopes: [...validation.scopes],
344
437
  subjectId: input.subjectId
345
438
  });
346
- yield* credentialStore.put(connection.id, {
439
+ const existingConnection = yield* connectionStore.find(input.ownerId, authorization.id);
440
+ const connection = yield* validate$3({
441
+ authorizationId: authorization.id,
442
+ createdAt: existingConnection?.createdAt ?? createdAt,
443
+ grant: input.grant,
444
+ id: existingConnection?.id ?? (yield* cryptoService.randomUUIDv4.pipe(Effect.mapError((cause) => new CryptoError({ message: cause.message })))),
445
+ ownerId: input.ownerId
446
+ });
447
+ yield* credentialStore.put(authorization.id, {
347
448
  accessToken: input.token,
348
449
  refreshToken: null,
349
450
  tokenType: "bearer"
350
451
  });
452
+ yield* authorizationStore.put(authorization);
351
453
  yield* connectionStore.put(connection);
352
- return connection;
454
+ return {
455
+ authorization,
456
+ connection
457
+ };
353
458
  });
354
459
  //#endregion
355
460
  //#region src/discovery/zones.ts
@@ -357,7 +462,7 @@ var zones_exports = /* @__PURE__ */ __exportAll({ candidates: () => candidates }
357
462
  function candidates(input) {
358
463
  const domain = parse$1(input);
359
464
  const registrable = getDomain(domain, { allowPrivateDomains: true });
360
- if (registrable === null) throw new Error$1({ message: "Domain does not have a registrable DNS zone" });
465
+ if (registrable === null) throw new Error$3({ message: "Domain does not have a registrable DNS zone" });
361
466
  const labels = domain.split(".");
362
467
  const registrableLabels = registrable.split(".").length;
363
468
  const values = [];
@@ -375,9 +480,9 @@ const Evidence = Schema.Struct({
375
480
  accountId: Schema.String,
376
481
  connectionId: Schema.String,
377
482
  decisiveNameserverMatch: Schema.Boolean,
378
- matchedNameservers: Schema.Array(Schema$4),
483
+ matchedNameservers: Schema.Array(Schema$5),
379
484
  providerId: Schema.String,
380
- zone: Schema$4
485
+ zone: Schema$5
381
486
  });
382
487
  const Selection = Schema.TaggedUnion({
383
488
  selected: {
@@ -397,8 +502,7 @@ function select(input) {
397
502
  const eligible = input.connectedZones.filter((connected) => {
398
503
  if (!zoneCandidates.has(parse$1(connected.zone))) return false;
399
504
  try {
400
- assertGrant(connected.connection, {
401
- accountId: connected.accountId,
505
+ assertGrant(connected.connection, connected.authorization, {
402
506
  capability: "dns:read",
403
507
  domain,
404
508
  ...input.now === void 0 ? {} : { now: input.now },
@@ -412,7 +516,7 @@ function select(input) {
412
516
  const evidence = eligible.map((connected) => evidenceFor(connected, authoritative));
413
517
  if (input.explicit !== void 0) {
414
518
  const explicitZone = parse$1(input.explicit.zone);
415
- const index = eligible.findIndex((connected) => connected.providerId === input.explicit?.providerId && connected.accountId === input.explicit.accountId && parse$1(connected.zone) === explicitZone);
519
+ const index = eligible.findIndex((connected) => connected.providerId === input.explicit?.providerId && connected.authorization.accountId === input.explicit.accountId && parse$1(connected.zone) === explicitZone);
416
520
  if (index < 0) throw new AuthorizationError$1({ message: "Explicit provider zone is not connected or granted" });
417
521
  const candidate = evidence[index];
418
522
  if (candidate === void 0) throw new Error("Eligible provider evidence is missing");
@@ -440,7 +544,7 @@ function evidenceFor(connected, authoritative) {
440
544
  const configured = new Set(nameserverSet(connected.nameservers));
441
545
  const matchedNameservers = authoritative.filter((nameserver) => configured.has(nameserver));
442
546
  return {
443
- accountId: connected.accountId,
547
+ accountId: connected.authorization.accountId,
444
548
  connectionId: connected.connection.id,
445
549
  decisiveNameserverMatch: authoritative.length > 0 && matchedNameservers.length === authoritative.length,
446
550
  matchedNameservers,
@@ -454,46 +558,46 @@ function nameserverSet(nameservers) {
454
558
  //#endregion
455
559
  //#region src/plan/types.ts
456
560
  var types_exports = /* @__PURE__ */ __exportAll({
457
- Authorization: () => Authorization,
458
- Operation: () => Operation,
459
- Receipt: () => Receipt,
561
+ Authorization: () => Authorization$1,
562
+ Operation: () => Operation$1,
563
+ Receipt: () => Receipt$1,
460
564
  Schema: () => Schema$1,
461
565
  decode: () => decode,
462
566
  decodeReceipt: () => decodeReceipt,
463
567
  encode: () => encode,
464
568
  encodeReceipt: () => encodeReceipt
465
569
  });
466
- const Operation = Schema.TaggedUnion({
570
+ const Operation$1 = Schema.TaggedUnion({
467
571
  create: {
468
572
  id: Schema.String,
469
- requirement: Schema$5
573
+ requirement: Schema$6
470
574
  },
471
575
  noop: {
472
576
  id: Schema.String,
473
- requirement: Schema$5,
577
+ requirement: Schema$6,
474
578
  ttlDrift: Schema.Boolean
475
579
  },
476
580
  conflict: {
477
581
  existing: Schema.Array(Observed),
478
582
  id: Schema.String,
479
583
  reason: Schema.String,
480
- requirement: Schema$5
584
+ requirement: Schema$6
481
585
  }
482
586
  });
483
587
  const Schema$1 = Schema.Struct({
484
588
  digest: Schema.String,
485
- operations: Schema.Array(Operation),
589
+ operations: Schema.Array(Operation$1),
486
590
  providerId: Schema.String,
487
591
  version: Schema.Literal("domainkit.dns-plan.v1"),
488
- zone: Schema$4
592
+ zone: Schema$5
489
593
  });
490
- const Authorization = Schema.Struct({
594
+ const Authorization$1 = Schema.Struct({
491
595
  allowPartial: Schema.Boolean,
492
596
  operationIds: Schema.Array(Schema.String),
493
597
  planDigest: Schema.String,
494
598
  version: Schema.Literal("domainkit.plan-authorization.v1")
495
599
  });
496
- const Receipt = Schema.Struct({
600
+ const Receipt$1 = Schema.Struct({
497
601
  appliedAt: Schema.DateFromString,
498
602
  operations: Schema.Array(Schema.Struct({
499
603
  operationId: Schema.String,
@@ -503,12 +607,12 @@ const Receipt = Schema.Struct({
503
607
  providerId: Schema.String,
504
608
  status: Schema.Literals(["complete", "partial"]),
505
609
  version: Schema.Literal("domainkit.apply-receipt.v1"),
506
- zone: Schema$4
610
+ zone: Schema$5
507
611
  });
508
- const decode = Effect.fn("DnsPlan.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message }))));
612
+ const decode = Effect.fn("DnsPlan.decode")((input) => Schema.decodeUnknownEffect(Schema$1)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
509
613
  const encode = Schema.encodeSync(Schema$1);
510
- const decodeReceipt = Effect.fn("DnsPlan.decodeReceipt")((input) => Schema.decodeUnknownEffect(Receipt)(input).pipe(Effect.mapError((cause) => new Error$1({ message: cause.message }))));
511
- const encodeReceipt = Schema.encodeSync(Receipt);
614
+ const decodeReceipt = Effect.fn("DnsPlan.decodeReceipt")((input) => Schema.decodeUnknownEffect(Receipt$1)(input).pipe(Effect.mapError((cause) => new Error$3({ message: cause.message }))));
615
+ const encodeReceipt = Schema.encodeSync(Receipt$1);
512
616
  //#endregion
513
617
  //#region src/plan/plan.ts
514
618
  var plan_exports = /* @__PURE__ */ __exportAll({
@@ -516,18 +620,19 @@ var plan_exports = /* @__PURE__ */ __exportAll({
516
620
  ConflictError: () => ConflictError,
517
621
  PartialApplyError: () => PartialApplyError,
518
622
  StaleError: () => StaleError,
519
- apply: () => apply,
520
- authorize: () => authorize$1,
521
- create: () => create,
522
- renderManualInstructions: () => renderManualInstructions
623
+ apply: () => apply$1,
624
+ authorize: () => authorize$2,
625
+ create: () => create$1,
626
+ renderManualInstructions: () => renderManualInstructions,
627
+ validate: () => validate$1
523
628
  });
524
- const UnsignedPlan = Schema.Struct({
525
- operations: Schema.Array(Operation),
629
+ const UnsignedPlan$1 = Schema.Struct({
630
+ operations: Schema.Array(Operation$1),
526
631
  providerId: Schema.String,
527
632
  version: Schema.Literal("domainkit.dns-plan.v1"),
528
- zone: Schema$4
633
+ zone: Schema$5
529
634
  });
530
- const RequirementDigest = Schema.Struct({ requirement: Schema$5 });
635
+ const RequirementDigest = Schema.Struct({ requirement: Schema$6 });
531
636
  var ConflictError = class extends Schema.TaggedError()("PlanConflictError", { operationIds: Schema.Array(Schema.String) }) {};
532
637
  var AuthorizationError = class extends Schema.TaggedError()("AuthorizationError", { message: Schema.String }) {};
533
638
  var StaleError = class extends Schema.TaggedError()("StalePlanError", {
@@ -544,13 +649,13 @@ var PartialApplyError = class extends Schema.TaggedError()("PartialApplyError",
544
649
  ]),
545
650
  failedOperationId: Schema.String,
546
651
  message: Schema.String,
547
- receipt: Receipt
652
+ receipt: Receipt$1
548
653
  }) {};
549
654
  /** Builds a deterministic DNS plan through the provider in the Effect environment. */
550
- const create = Effect.fn("Provisioning.create")(function* (input) {
655
+ const create$1 = Effect.fn("Provisioning.create")(function* (input) {
551
656
  const provider = yield* Service;
552
- const zone = yield* decode$3(input.zone);
553
- const requirements = (yield* Effect.forEach(input.requirements, decode$4, { concurrency: "unbounded" })).sort(compareRecords);
657
+ const zone = yield* decode$4(input.zone);
658
+ const requirements = (yield* Effect.forEach(input.requirements, decode$5, { concurrency: "unbounded" })).sort(compareRecords);
554
659
  const existing = [...yield* provider.listRecords(zone)].sort(compareRecords);
555
660
  const { operations } = yield* Effect.reduce(requirements, () => ({
556
661
  operations: [],
@@ -567,12 +672,12 @@ const create = Effect.fn("Provisioning.create")(function* (input) {
567
672
  };
568
673
  return {
569
674
  ...unsigned,
570
- digest: yield* sha256Encoded(UnsignedPlan, unsigned)
675
+ digest: yield* sha256Encoded(UnsignedPlan$1, unsigned)
571
676
  };
572
677
  });
573
678
  /** Authorizes all or a selected subset of create operations in a digest-bound plan. */
574
- const authorize$1 = Effect.fn("Provisioning.authorize")(function* (plan, operationIds, options = {}) {
575
- yield* validateDigest(plan);
679
+ const authorize$2 = Effect.fn("Provisioning.authorize")(function* (plan, operationIds, options = {}) {
680
+ yield* validate$1(plan);
576
681
  const createIds = plan.operations.filter((operation) => operation._tag === "create").map(({ id }) => id);
577
682
  const selected = [...new Set(operationIds ?? createIds)].sort();
578
683
  const unknown = selected.filter((id) => !createIds.includes(id));
@@ -586,20 +691,20 @@ const authorize$1 = Effect.fn("Provisioning.authorize")(function* (plan, operati
586
691
  };
587
692
  });
588
693
  /** Applies approved creates sequentially after full and per-operation revalidation. */
589
- const apply = Effect.fn("Provisioning.apply")(function* (input) {
694
+ const apply$1 = Effect.fn("Provisioning.apply")(function* (input) {
590
695
  const { authorization, plan } = input;
591
696
  const provider = yield* Service;
592
- yield* validateDigest(plan);
697
+ yield* validate$1(plan);
593
698
  if (authorization.planDigest !== plan.digest) return yield* new AuthorizationError({ message: "Authorization belongs to a different plan" });
594
699
  if (provider.id !== plan.providerId) return yield* new AuthorizationError({ message: "Provider does not match the approved plan" });
595
700
  const conflicts = plan.operations.filter((operation) => operation._tag === "conflict");
596
701
  if (conflicts.length > 0) return yield* new ConflictError({ operationIds: conflicts.map(({ id }) => id) });
597
- const creates = plan.operations.filter(Operation.guards.create);
702
+ const creates = plan.operations.filter(Operation$1.guards.create);
598
703
  const approved = new Set(authorization.operationIds);
599
704
  const unknown = authorization.operationIds.filter((id) => !creates.some((operation) => operation.id === id));
600
705
  if (unknown.length > 0) return yield* new AuthorizationError({ message: `Authorization contains unknown operations: ${unknown.join(", ")}` });
601
706
  if (!authorization.allowPartial && creates.some(({ id }) => !approved.has(id))) return yield* new AuthorizationError({ message: "Authorization does not cover every create operation" });
602
- const currentPlan = yield* create({
707
+ const currentPlan = yield* create$1({
603
708
  requirements: plan.operations.map(({ requirement }) => requirement),
604
709
  zone: plan.zone
605
710
  });
@@ -617,14 +722,14 @@ const apply = Effect.fn("Provisioning.apply")(function* (input) {
617
722
  operationId: operation.id,
618
723
  providerRecordId: result.providerRecordId
619
724
  });
620
- }).pipe(Effect.catch((failure) => operations.length === 0 ? Effect.fail(failure) : makeReceipt(input, operations, "partial").pipe(Effect.flatMap((receipt) => Effect.fail(new PartialApplyError({
725
+ }).pipe(Effect.catch((failure) => operations.length === 0 ? Effect.fail(failure) : makeReceipt$1(input, operations, "partial").pipe(Effect.flatMap((receipt) => Effect.fail(new PartialApplyError({
621
726
  causeTag: failure._tag,
622
727
  failedOperationId: operation.id,
623
728
  message: failureMessage(failure),
624
729
  receipt
625
730
  }))))), Effect.catchCause((cause) => {
626
731
  if (!Cause.hasInterruptsOnly(cause) || operations.length === 0) return Effect.failCause(cause);
627
- return makeReceipt(input, operations, "partial").pipe(Effect.flatMap((receipt) => Effect.fail(new PartialApplyError({
732
+ return makeReceipt$1(input, operations, "partial").pipe(Effect.flatMap((receipt) => Effect.fail(new PartialApplyError({
628
733
  causeTag: "Interrupted",
629
734
  failedOperationId: operation.id,
630
735
  message: "DNS apply was interrupted after confirmed writes",
@@ -632,7 +737,7 @@ const apply = Effect.fn("Provisioning.apply")(function* (input) {
632
737
  }))));
633
738
  }));
634
739
  }
635
- return yield* makeReceipt(input, operations, "complete");
740
+ return yield* makeReceipt$1(input, operations, "complete");
636
741
  });
637
742
  function renderManualInstructions(plan) {
638
743
  return plan.operations.map((operation) => {
@@ -641,7 +746,7 @@ function renderManualInstructions(plan) {
641
746
  return `${operation._tag.toUpperCase()} ${record._tag} ${record.name} ${data$2}`.trim();
642
747
  });
643
748
  }
644
- function makeReceipt(input, operations, status) {
749
+ function makeReceipt$1(input, operations, status) {
645
750
  return Clock.currentTimeMillis.pipe(Effect.map((now) => ({
646
751
  appliedAt: new Date(now),
647
752
  operations,
@@ -653,7 +758,7 @@ function makeReceipt(input, operations, status) {
653
758
  })));
654
759
  }
655
760
  function assertStillCreatable(plan, operation) {
656
- return create({
761
+ return create$1({
657
762
  requirements: [operation.requirement],
658
763
  zone: plan.zone
659
764
  }).pipe(Effect.flatMap((currentPlan) => {
@@ -697,14 +802,14 @@ function reconcileRequirement(requirement, allExisting) {
697
802
  };
698
803
  });
699
804
  }
700
- function validateDigest(plan) {
805
+ function validate$1(plan) {
701
806
  const unsigned = {
702
807
  operations: plan.operations,
703
808
  providerId: plan.providerId,
704
809
  version: plan.version,
705
810
  zone: plan.zone
706
811
  };
707
- return sha256Encoded(UnsignedPlan, unsigned).pipe(Effect.flatMap((digest) => digest === plan.digest ? Effect.void : Effect.fail(new AuthorizationError({ message: "Plan digest does not match its contents" }))));
812
+ return sha256Encoded(UnsignedPlan$1, unsigned).pipe(Effect.flatMap((digest) => digest === plan.digest ? Effect.void : Effect.fail(new AuthorizationError({ message: "Plan digest does not match its contents" }))));
708
813
  }
709
814
  function failureMessage(failure) {
710
815
  return failure._tag === "StalePlanError" ? "DNS state changed while applying the approved plan" : failure.message;
@@ -718,20 +823,207 @@ function compareRecords(left, right) {
718
823
  }
719
824
  //#endregion
720
825
  //#region src/plan/connection-authorization.ts
721
- var connection_authorization_exports = /* @__PURE__ */ __exportAll({ authorize: () => authorize });
722
- const authorize = Effect.fn("ConnectionAuthorization.authorize")(function* (input) {
723
- yield* Effect.try({
724
- try: () => assertGrant(input.connection, {
725
- accountId: input.accountId,
826
+ var connection_authorization_exports = /* @__PURE__ */ __exportAll({ authorize: () => authorize$1 });
827
+ const authorize$1 = Effect.fn("ConnectionAuthorization.authorize")(function* (input) {
828
+ const domain = yield* Effect.try({
829
+ try: () => assertGrant(input.connection, input.authorization, {
726
830
  capability: "dns:write",
727
- domain: input.plan.zone,
831
+ domain: input.domain,
728
832
  providerId: input.plan.providerId
729
833
  }),
730
- catch: (cause) => cause instanceof AuthorizationError$1 || cause instanceof Error$1 ? cause : new AuthorizationError$1({ message: "Connection grant is invalid" })
834
+ catch: (cause) => cause instanceof AuthorizationError$1 || cause instanceof Error$3 ? cause : new AuthorizationError$1({ message: "Connection grant is invalid" })
731
835
  });
732
- return yield* authorize$1(input.plan, input.operationIds, input.allowPartial === void 0 ? {} : { allowPartial: input.allowPartial });
836
+ const authorization = yield* authorize$2(input.plan, input.operationIds, input.allowPartial === void 0 ? {} : { allowPartial: input.allowPartial });
837
+ const selected = new Set(authorization.operationIds);
838
+ if (input.plan.operations.filter((operation) => operation._tag === "create" && selected.has(operation.id) && operation.requirement.name !== domain && !operation.requirement.name.endsWith(`.${domain}`)).length > 0) return yield* new AuthorizationError$1({ message: `Plan operations are outside the granted domain ${domain}` });
839
+ return authorization;
733
840
  });
734
841
  //#endregion
842
+ //#region src/plan/deletion.ts
843
+ var deletion_exports = /* @__PURE__ */ __exportAll({
844
+ Authorization: () => Authorization,
845
+ Error: () => Error$1,
846
+ Operation: () => Operation,
847
+ PartialError: () => PartialError,
848
+ Plan: () => Plan,
849
+ Receipt: () => Receipt,
850
+ apply: () => apply,
851
+ authorize: () => authorize,
852
+ create: () => create,
853
+ validate: () => validate
854
+ });
855
+ const Operation = Schema.Struct({
856
+ id: Schema.String,
857
+ providerRecordId: Schema.String,
858
+ record: Schema$6
859
+ });
860
+ const UnsignedPlan = Schema.Struct({
861
+ createdAt: Schema.DateFromString,
862
+ expiresAt: Schema.DateFromString,
863
+ operations: Schema.Array(Operation),
864
+ providerId: Schema.String,
865
+ sourcePlanDigest: Schema.String,
866
+ version: Schema.Literal("domainkit.deletion-plan.v1"),
867
+ zone: Schema$5
868
+ });
869
+ const Plan = Schema.Struct({
870
+ ...UnsignedPlan.fields,
871
+ digest: Schema.String
872
+ });
873
+ const Authorization = Schema.Struct({
874
+ operationIds: Schema.Array(Schema.String),
875
+ planDigest: Schema.String,
876
+ version: Schema.Literal("domainkit.deletion-authorization.v1")
877
+ });
878
+ const Receipt = Schema.Struct({
879
+ deletedAt: Schema.DateFromString,
880
+ operations: Schema.Array(Schema.Struct({
881
+ operationId: Schema.String,
882
+ providerRecordId: Schema.String
883
+ })),
884
+ planDigest: Schema.String,
885
+ providerId: Schema.String,
886
+ status: Schema.Literals(["complete", "partial"]),
887
+ version: Schema.Literal("domainkit.deletion-receipt.v1"),
888
+ zone: Schema$5
889
+ });
890
+ var Error$1 = class extends Schema.TaggedError()("UnsafeDeletionError", {
891
+ message: Schema.String,
892
+ operationId: Schema.optionalKey(Schema.String)
893
+ }) {};
894
+ var PartialError = class extends Schema.TaggedError()("PartialDeletionError", {
895
+ failedOperationId: Schema.String,
896
+ message: Schema.String,
897
+ receipt: Receipt
898
+ }) {};
899
+ /** Build a fresh deletion plan only from records proven by a prior create receipt. */
900
+ const create = Effect.fn("Deletion.create")(function* (input) {
901
+ const provider = yield* Service;
902
+ yield* validate$1(input.plan);
903
+ if (input.receipt.planDigest !== input.plan.digest || input.receipt.providerId !== input.plan.providerId || input.receipt.zone !== input.plan.zone) return yield* new Error$1({ message: "Create receipt does not belong to the source plan" });
904
+ if (provider.id !== input.plan.providerId) return yield* new Error$1({ message: "Provider does not match the source receipt" });
905
+ const operations = yield* Effect.forEach(input.receipt.operations, (applied) => Effect.gen(function* () {
906
+ const source = input.plan.operations.find((operation) => operation._tag === "create" && operation.id === applied.operationId);
907
+ if (source === void 0 || source._tag !== "create") return yield* new Error$1({
908
+ message: "Receipt operation is not a created source record",
909
+ operationId: applied.operationId
910
+ });
911
+ if (applied.providerRecordId === null) return yield* new Error$1({
912
+ message: "Provider record ID is required for safe deletion",
913
+ operationId: applied.operationId
914
+ });
915
+ yield* assertExact(provider, input.plan.zone, applied.providerRecordId, source.requirement, source.id);
916
+ return {
917
+ id: source.id,
918
+ providerRecordId: applied.providerRecordId,
919
+ record: source.requirement
920
+ };
921
+ }));
922
+ const now = yield* Clock.currentTimeMillis;
923
+ const unsigned = {
924
+ createdAt: new Date(now),
925
+ expiresAt: new Date(now + (input.ttlMs ?? 9e5)),
926
+ operations,
927
+ providerId: input.plan.providerId,
928
+ sourcePlanDigest: input.plan.digest,
929
+ version: "domainkit.deletion-plan.v1",
930
+ zone: input.plan.zone
931
+ };
932
+ return {
933
+ ...unsigned,
934
+ digest: yield* sha256Encoded(UnsignedPlan, unsigned)
935
+ };
936
+ });
937
+ /** Produce a separate digest-bound consent artifact for destructive operations. */
938
+ const authorize = Effect.fn("Deletion.authorize")(function* (plan, operationIds) {
939
+ yield* validate(plan);
940
+ const selected = [...new Set(operationIds ?? plan.operations.map(({ id }) => id))].sort();
941
+ const unknown = selected.filter((id) => !plan.operations.some((operation) => operation.id === id));
942
+ if (unknown.length > 0) return yield* new Error$1({ message: `Unknown deletion operation IDs: ${unknown.join(", ")}` });
943
+ return {
944
+ operationIds: selected,
945
+ planDigest: plan.digest,
946
+ version: "domainkit.deletion-authorization.v1"
947
+ };
948
+ });
949
+ /** Re-read every approved record before the first delete, then delete by provider record ID. */
950
+ const apply = Effect.fn("Deletion.apply")(function* (input) {
951
+ const provider = yield* Service;
952
+ yield* validate(input.plan);
953
+ if (input.authorization.planDigest !== input.plan.digest) return yield* new Error$1({ message: "Deletion authorization belongs to a different plan" });
954
+ if (provider.id !== input.plan.providerId) return yield* new Error$1({ message: "Provider does not match the deletion plan" });
955
+ const now = yield* Clock.currentTimeMillis;
956
+ if (input.plan.expiresAt <= new Date(now)) return yield* new Error$1({ message: "Deletion plan has expired" });
957
+ const selected = input.plan.operations.filter(({ id }) => input.authorization.operationIds.includes(id));
958
+ if (selected.length !== input.authorization.operationIds.length) return yield* new Error$1({ message: "Deletion authorization contains unknown operations" });
959
+ const previouslyDeleted = yield* validatePriorReceipt(input.plan, input.priorReceipt);
960
+ const completedIds = new Set(previouslyDeleted.map(({ operationId }) => operationId));
961
+ if (previouslyDeleted.some(({ operationId }) => !input.authorization.operationIds.includes(operationId))) return yield* new Error$1({ message: "Prior receipt contains an unauthorized operation" });
962
+ const remaining = selected.filter(({ id }) => !completedIds.has(id));
963
+ yield* Effect.forEach(remaining, (operation) => assertExact(provider, input.plan.zone, operation.providerRecordId, operation.record, operation.id));
964
+ const deleted = [...previouslyDeleted];
965
+ for (const operation of remaining) {
966
+ const failure = yield* Effect.gen(function* () {
967
+ yield* assertExact(provider, input.plan.zone, operation.providerRecordId, operation.record, operation.id);
968
+ yield* provider.deleteRecord(input.plan.zone, operation.providerRecordId);
969
+ deleted.push({
970
+ operationId: operation.id,
971
+ providerRecordId: operation.providerRecordId
972
+ });
973
+ }).pipe(Effect.match({
974
+ onFailure: (cause) => cause,
975
+ onSuccess: () => null
976
+ }));
977
+ if (failure !== null) {
978
+ if (deleted.length === 0) return yield* failure;
979
+ return yield* new PartialError({
980
+ failedOperationId: operation.id,
981
+ message: failure.message,
982
+ receipt: yield* makeReceipt(input.plan, deleted, "partial")
983
+ });
984
+ }
985
+ }
986
+ return yield* makeReceipt(input.plan, deleted, "complete");
987
+ });
988
+ function validate(plan) {
989
+ const unsigned = {
990
+ createdAt: plan.createdAt,
991
+ expiresAt: plan.expiresAt,
992
+ operations: plan.operations,
993
+ providerId: plan.providerId,
994
+ sourcePlanDigest: plan.sourcePlanDigest,
995
+ version: plan.version,
996
+ zone: plan.zone
997
+ };
998
+ return sha256Encoded(UnsignedPlan, unsigned).pipe(Effect.flatMap((digest) => digest === plan.digest ? Effect.void : Effect.fail(new Error$1({ message: "Deletion plan digest does not match its contents" }))));
999
+ }
1000
+ function validatePriorReceipt(plan, receipt) {
1001
+ if (receipt === void 0) return Effect.succeed([]);
1002
+ if (receipt.planDigest !== plan.digest || receipt.providerId !== plan.providerId || receipt.zone !== plan.zone || receipt.status !== "partial") return Effect.fail(new Error$1({ message: "Prior receipt does not belong to this deletion plan" }));
1003
+ for (const completed of receipt.operations) {
1004
+ const operation = plan.operations.find(({ id }) => id === completed.operationId);
1005
+ if (operation === void 0 || operation.providerRecordId !== completed.providerRecordId) return Effect.fail(new Error$1({ message: "Prior receipt operation does not match the deletion plan" }));
1006
+ }
1007
+ return Effect.succeed([...receipt.operations]);
1008
+ }
1009
+ function assertExact(provider, zone, providerRecordId, expected, operationId) {
1010
+ return provider.getRecord(zone, providerRecordId).pipe(Effect.flatMap((observed) => observed !== null && observed._tag !== "Opaque" && equals(observed, expected) ? Effect.void : Effect.fail(new Error$1({
1011
+ message: observed === null ? "Created record no longer exists" : "Created record content no longer matches its receipt",
1012
+ operationId
1013
+ }))));
1014
+ }
1015
+ function makeReceipt(plan, operations, status) {
1016
+ return Clock.currentTimeMillis.pipe(Effect.map((now) => ({
1017
+ deletedAt: new Date(now),
1018
+ operations,
1019
+ planDigest: plan.digest,
1020
+ providerId: plan.providerId,
1021
+ status,
1022
+ version: "domainkit.deletion-receipt.v1",
1023
+ zone: plan.zone
1024
+ })));
1025
+ }
1026
+ //#endregion
735
1027
  //#region src/verification/dns-data.ts
736
1028
  var dns_data_exports = /* @__PURE__ */ __exportAll({ parse: () => parse });
737
1029
  const CanonicalAddress = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
@@ -775,7 +1067,7 @@ function parse(type, input) {
775
1067
  try {
776
1068
  return Schema.decodeUnknownSync(schema)(input);
777
1069
  } catch (cause) {
778
- throw new Error$1({ message: cause instanceof Error ? cause.message : String(cause) });
1070
+ throw new Error$3({ message: cause instanceof Error ? cause.message : String(cause) });
779
1071
  }
780
1072
  }
781
1073
  function tokenizedDomain(length, domainIndex) {
@@ -812,19 +1104,19 @@ function make$1(options) {
812
1104
  timeoutMs,
813
1105
  endpoint: options.endpoint
814
1106
  }),
815
- catch: (cause) => cause instanceof Error$3 ? cause : new Error$3({
1107
+ catch: (cause) => cause instanceof Error$5 ? cause : new Error$5({
816
1108
  cause,
817
1109
  message: cause instanceof Error ? cause.message : "DNS resolution failed",
818
1110
  reason: "transport"
819
1111
  })
820
1112
  }).pipe(Effect.flatMap((message) => decodeResolution(query, message)))) };
821
1113
  }
822
- const layer$1 = (options) => Layer.succeed(Service$4, make$1(options));
1114
+ const layer$1 = (options) => Layer.succeed(Service$5, make$1(options));
823
1115
  const toAsync$1 = (options) => toAsync$2(make$1(options));
824
1116
  function decodeResolution(query, message) {
825
1117
  return Effect.try({
826
1118
  try: () => DnsPacket.decode(message),
827
- catch: (cause) => new Error$3({
1119
+ catch: (cause) => new Error$5({
828
1120
  cause,
829
1121
  message: "DoH returned an invalid DNS message",
830
1122
  reason: "response"
@@ -833,7 +1125,7 @@ function decodeResolution(query, message) {
833
1125
  }
834
1126
  function decodePacket(packet) {
835
1127
  if (packet.rcode === "NXDOMAIN") return Effect.succeed({ _tag: "nodata" });
836
- if (packet.rcode !== "NOERROR") return Effect.fail(new Error$3({
1128
+ if (packet.rcode !== "NOERROR") return Effect.fail(new Error$5({
837
1129
  message: `DNS resolver returned ${packet.rcode ?? "an unknown response code"}`,
838
1130
  reason: "response"
839
1131
  }));
@@ -859,7 +1151,7 @@ function validatePacket(query, packet) {
859
1151
  if (question.class !== "IN" || question.type !== query.type || parse$1(question.name) !== query.name) throw new Error("DoH response did not match the DNS question");
860
1152
  return packet;
861
1153
  },
862
- catch: (cause) => new Error$3({
1154
+ catch: (cause) => new Error$5({
863
1155
  cause,
864
1156
  message: cause instanceof Error ? cause.message : "DoH returned an invalid DNS response",
865
1157
  reason: "response"
@@ -871,23 +1163,23 @@ function decodeAnswer(answer) {
871
1163
  if (answer.class !== "IN") return null;
872
1164
  const decoded = yield* Effect.try({
873
1165
  try: () => answerData(answer),
874
- catch: (cause) => new Error$3({
1166
+ catch: (cause) => new Error$5({
875
1167
  cause,
876
1168
  message: "DoH returned invalid DNS record data",
877
1169
  reason: "response"
878
1170
  })
879
1171
  });
880
1172
  if (decoded === void 0) return null;
881
- const name = yield* decode$3(answer.name);
1173
+ const name = yield* decode$4(answer.name);
882
1174
  const data = yield* Effect.try({
883
1175
  try: () => parse(decoded.type, decoded.data),
884
- catch: (cause) => new Error$3({
1176
+ catch: (cause) => new Error$5({
885
1177
  cause,
886
1178
  message: "DoH returned invalid DNS record data",
887
1179
  reason: "response"
888
1180
  })
889
1181
  });
890
- if (answer.ttl === void 0) return yield* new Error$3({
1182
+ if (answer.ttl === void 0) return yield* new Error$5({
891
1183
  message: "DoH response omitted the DNS record TTL",
892
1184
  reason: "response"
893
1185
  });
@@ -897,7 +1189,7 @@ function decodeAnswer(answer) {
897
1189
  ttl: answer.ttl,
898
1190
  type: decoded.type
899
1191
  };
900
- }).pipe(Effect.mapError((cause) => cause instanceof Error$3 ? cause : new Error$3({
1192
+ }).pipe(Effect.mapError((cause) => cause instanceof Error$5 ? cause : new Error$5({
901
1193
  cause,
902
1194
  message: cause.message,
903
1195
  reason: "response"
@@ -981,18 +1273,18 @@ async function request(input) {
981
1273
  method: "POST",
982
1274
  signal: controller.signal
983
1275
  });
984
- if (!response.ok) throw new Error$3({
1276
+ if (!response.ok) throw new Error$5({
985
1277
  message: `DoH returned HTTP ${response.status}`,
986
1278
  reason: "transport"
987
1279
  });
988
1280
  const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
989
- if (contentType !== "application/dns-message") throw new Error$3({
1281
+ if (contentType !== "application/dns-message") throw new Error$5({
990
1282
  message: `DoH returned unsupported content type ${contentType ?? "<missing>"}`,
991
1283
  reason: "response"
992
1284
  });
993
1285
  return new Uint8Array(await response.arrayBuffer());
994
1286
  } catch (cause) {
995
- if (timedOut) throw new Error$3({
1287
+ if (timedOut) throw new Error$5({
996
1288
  message: "DNS query timed out",
997
1289
  reason: "timeout"
998
1290
  });
@@ -1017,7 +1309,7 @@ function make(options = {}) {
1017
1309
  endpoint: options.endpoint ?? endpoint
1018
1310
  });
1019
1311
  }
1020
- const layer = (options = {}) => Layer.succeed(Service$4, make(options));
1312
+ const layer = (options = {}) => Layer.succeed(Service$5, make(options));
1021
1313
  const toAsync = (options = {}) => toAsync$2(make(options));
1022
1314
  //#endregion
1023
1315
  //#region src/verification/verify.ts
@@ -1030,7 +1322,7 @@ var verify_exports = /* @__PURE__ */ __exportAll({
1030
1322
  const ProviderObservation = Schema.TaggedUnion({
1031
1323
  match: {},
1032
1324
  missing: {},
1033
- mismatch: { records: Schema.Array(Schema$5) },
1325
+ mismatch: { records: Schema.Array(Schema$6) },
1034
1326
  failure: { message: Schema.String }
1035
1327
  });
1036
1328
  const PublicDnsObservation = Schema.TaggedUnion({
@@ -1052,7 +1344,7 @@ const Result = Schema.Struct({
1052
1344
  });
1053
1345
  const record = Effect.fn("Verification.record")(function* (input) {
1054
1346
  const provider = yield* Service;
1055
- const resolver = yield* Service$4;
1347
+ const resolver = yield* Service$5;
1056
1348
  const [providerObservation, publicDns] = yield* Effect.all([observeProvider(provider, input.zone, input.record).pipe(Effect.catch((failure) => Effect.succeed({
1057
1349
  _tag: "failure",
1058
1350
  message: failure.message
@@ -1104,6 +1396,6 @@ function recordValue(requirement) {
1104
1396
  }
1105
1397
  }
1106
1398
  //#endregion
1107
- export { decode$1 as A, Capability as B, types_exports as C, token_exports as D, connect as E, refresh as F, decode$2 as G, Schema$3 as H, revoke as I, encode$1 as K, canonical_json_exports as L, begin as M, complete as N, Method as O, oauth_exports as P, webCryptoLayer as R, encodeReceipt as S, zones_exports as T, assertGrant as U, Grant as V, connection_exports as W, Receipt as _, doh_exports as a, decodeReceipt as b, authorize as c, authorize$1 as d, create as f, Operation as g, Authorization as h, toAsync as i, manifest_exports as j, Schema$2 as k, connection_authorization_exports as l, renderManualInstructions as m, verify_exports as n, toAsync$1 as o, plan_exports as p, version as q, cloudflare_doh_exports as r, dns_data_exports as s, record as t, apply as u, Schema$1 as v, selection_exports as w, encode as x, decode as y, AuthorizationError$1 as z };
1399
+ export { encode$1 as $, decodeReceipt as A, decode$1 as B, plan_exports as C, Receipt$1 as D, Operation$1 as E, zones_exports as F, refresh as G, begin as H, connect as I, webCryptoLayer as J, revoke as K, token_exports as L, encodeReceipt as M, types_exports as N, Schema$1 as O, selection_exports as P, decode$2 as Q, Method as R, create$1 as S, Authorization$1 as T, complete as U, manifest_exports as V, oauth_exports as W, Schema$3 as X, Capability as Y, authorization_exports as Z, deletion_exports as _, doh_exports as a, Grant as at, apply$1 as b, Authorization as c, connection_exports as ct, PartialError as d, version as dt, Error$2 as et, Plan as f, create as g, authorize as h, toAsync as i, AuthorizationError$1 as it, encode as j, decode as k, Error$1 as l, decode$3 as lt, apply as m, verify_exports as n, detach as nt, toAsync$1 as o, Schema$4 as ot, Receipt as p, canonical_json_exports as q, cloudflare_doh_exports as r, updateGrant as rt, dns_data_exports as s, assertGrant as st, record as t, connection_lifecycle_exports as tt, Operation as u, encode$2 as ut, authorize$1 as v, renderManualInstructions as w, authorize$2 as x, connection_authorization_exports as y, Schema$2 as z };
1108
1400
 
1109
- //# sourceMappingURL=verify-CeMW6XtK.mjs.map
1401
+ //# sourceMappingURL=verify-BjrR_4NF.mjs.map