domainkit 0.13.0 → 0.14.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.
package/dist/Verify.mjs CHANGED
@@ -17,6 +17,7 @@ import { Context, DateTime, Duration, Effect, Layer, Option, Schema } from "effe
17
17
  var Verify_exports = /* @__PURE__ */ __exportAll({
18
18
  Evidence: () => Evidence,
19
19
  HostEvidence: () => HostEvidence,
20
+ Observer: () => Observer,
20
21
  Policy: () => Policy,
21
22
  ProviderEvidence: () => ProviderEvidence,
22
23
  PublicDnsEvidence: () => PublicDnsEvidence,
@@ -27,7 +28,9 @@ var Verify_exports = /* @__PURE__ */ __exportAll({
27
28
  layer: () => layer,
28
29
  make: () => make,
29
30
  observe: () => observe,
30
- statusAgainst: () => statusAgainst
31
+ requirementKey: () => requirementKey,
32
+ statusAgainst: () => statusAgainst,
33
+ summary: () => summary
31
34
  });
32
35
  /**
33
36
  * Every piece of evidence carries `values`, what the observer returned for the requirement's name
@@ -69,11 +72,49 @@ const Evidence = Schema.Union([
69
72
  HostEvidence
70
73
  ]);
71
74
  var Service = class extends Context.Service()("@domainkit/Verify") {};
75
+ /**
76
+ * A requirement's identity: the record's type, name, and data, and nothing else. It is what a host
77
+ * pairs its own rows against readiness by, instead of trusting the order of the array.
78
+ * `@domainkit/react`'s `Records.identity` is this function.
79
+ */
80
+ const requirementKey = (record) => [
81
+ record._tag,
82
+ record.name,
83
+ data(record)
84
+ ].join(":");
85
+ /**
86
+ * How a readiness stands, as counts. Pure and total: a host renders "3 of 4 found" from the stored
87
+ * fact without deciding what an absent readiness means, because `observed` says so.
88
+ */
89
+ const summary = (readiness) => {
90
+ const statuses = readiness?.requirements.map(({ status }) => status) ?? [];
91
+ const count = (status) => statuses.filter((candidate) => candidate === status).length;
92
+ return {
93
+ observed: readiness !== null,
94
+ total: statuses.length,
95
+ satisfied: count("satisfied"),
96
+ missing: count("missing"),
97
+ mismatch: count("mismatch"),
98
+ unknown: count("unknown")
99
+ };
100
+ };
72
101
  const defaults = {
73
102
  backoff: (pendingForMs) => pendingForMs < 6e4 ? 15e3 : pendingForMs < 6e5 ? 6e4 : pendingForMs < 36e5 ? 3e5 : 18e5,
74
103
  quorum: "any"
75
104
  };
76
105
  var Policy = class extends Context.Reference("@domainkit/Verify/Policy", { defaultValue: () => defaults }) {};
106
+ /**
107
+ * The host seam for "DomainKit wrote readiness". The default does nothing; provide one with
108
+ * `Effect.provideService(Verify.Observer, ...)` or `Layer.succeed(Verify.Observer, ...)` over
109
+ * `DomainKit.layer`, the same way `Policy` is overridden.
110
+ *
111
+ * It runs after `storage.readiness.put` returns, outside any transaction, because `Storage`
112
+ * exposes none. A failure or defect in the observer is logged and swallowed: the observation is
113
+ * already durable, and losing it because a projection failed would make the stored fact depend on
114
+ * the host's side effects. A host that needs the projection to be reliable enqueues durable work
115
+ * here rather than doing the work inline.
116
+ */
117
+ var Observer = class extends Context.Reference("@domainkit/Verify/Observer", { defaultValue: () => ({ readinessChanged: () => Effect.void }) }) {};
77
118
  const StoredEvidence = Schema.Array(Evidence);
78
119
  const StoredHost = Schema.Array(HostEvidence);
79
120
  /** A requirement is satisfied by an exact match; an exclusive one is contradicted by any same-set record. */
@@ -112,7 +153,7 @@ const overallOf = (requirements, host) => {
112
153
  return requirements.length + host.length > 0 && requirements.every(({ status }) => status === "satisfied") && host.every(({ status }) => status === "ok") ? "ready" : "pending";
113
154
  };
114
155
  /** The identity of a requirement set: type, name, data, and policy, independent of labels and order. */
115
- const requirementKey = (requirements) => requirements.map(({ record }) => `${record._tag} ${record.name} ${data(record)} ${record.policy}`).sort().join("\n");
156
+ const requirementSetKey = (requirements) => requirements.map(({ record }) => `${record._tag} ${record.name} ${data(record)} ${record.policy}`).sort().join("\n");
116
157
  const make = Effect.gen(function* () {
117
158
  const storage = yield* Service$3;
118
159
  const connect = yield* Service$4;
@@ -127,6 +168,7 @@ const make = Effect.gen(function* () {
127
168
  });
128
169
  const decodeRow = (row) => Effect.gen(function* () {
129
170
  const requirements = yield* Effect.forEach(row.requirements, (requirement) => decode$1(StoredEvidence, requirement.evidence, "evidence").pipe(Effect.map((evidence) => ({
171
+ key: requirementKey(requirement.record),
130
172
  operationId: requirement.operationId,
131
173
  record: requirement.record,
132
174
  status: requirement.status,
@@ -147,9 +189,10 @@ const make = Effect.gen(function* () {
147
189
  const store = (input) => Effect.gen(function* () {
148
190
  const principal = yield* Service$1;
149
191
  const policy = yield* Policy;
192
+ const observer = yield* Observer;
150
193
  const now = yield* DateTime.now;
151
194
  const overall = overallOf(input.requirements, input.host);
152
- const sameRequirements = Option.isSome(input.previous) && requirementKey(input.previous.value.requirements) === requirementKey(input.requirements);
195
+ const sameRequirements = Option.isSome(input.previous) && requirementSetKey(input.previous.value.requirements) === requirementSetKey(input.requirements);
153
196
  const pendingSince = overall === "ready" ? null : sameRequirements && Option.isSome(input.previous) && input.previous.value.pendingSince !== null ? input.previous.value.pendingSince : now;
154
197
  const nextCheckAt = pendingSince === null ? null : DateTime.addDuration(now, Duration.millis(policy.backoff(DateTime.toEpochMillis(now) - DateTime.toEpochMillis(pendingSince))));
155
198
  const row = new Readiness({
@@ -169,7 +212,7 @@ const make = Effect.gen(function* () {
169
212
  nextCheckAt
170
213
  });
171
214
  yield* storage.readiness.put(row);
172
- return {
215
+ const readiness = {
173
216
  domain: row.domain,
174
217
  attachmentId: row.attachmentId,
175
218
  overall,
@@ -178,6 +221,12 @@ const make = Effect.gen(function* () {
178
221
  checkedAt: now,
179
222
  nextCheckAt
180
223
  };
224
+ yield* observer.readinessChanged({
225
+ domain: row.domain,
226
+ readiness,
227
+ cause: input.cause
228
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning(`Verify.Observer failed for ${row.domain}`, cause)));
229
+ return readiness;
181
230
  });
182
231
  const defaultRequirements = (attachment) => Effect.gen(function* () {
183
232
  const latest = yield* storage.attempts.latest(attachment.id, "provisioning");
@@ -236,10 +285,12 @@ const make = Effect.gen(function* () {
236
285
  observedAt: now
237
286
  }));
238
287
  const publicStatus = quorumStatus(policy.quorum, publicEvidence.map(({ status }) => status));
288
+ const status = combine([...providerEvidence.map((evidence) => evidence.status), publicStatus]);
239
289
  return {
290
+ key: requirementKey(record),
240
291
  operationId,
241
292
  record,
242
- status: combine([...providerEvidence.map((evidence) => evidence.status), publicStatus]),
293
+ status,
243
294
  evidence: [...providerEvidence, ...publicEvidence]
244
295
  };
245
296
  }), { concurrency: "unbounded" });
@@ -250,7 +301,8 @@ const make = Effect.gen(function* () {
250
301
  attachment,
251
302
  requirements: observed,
252
303
  host,
253
- previous
304
+ previous,
305
+ cause: "observe"
254
306
  });
255
307
  });
256
308
  const attachEvidence = (input) => Effect.gen(function* () {
@@ -267,7 +319,8 @@ const make = Effect.gen(function* () {
267
319
  attachment,
268
320
  requirements: current.requirements,
269
321
  host: [...bySource.values()],
270
- previous
322
+ previous,
323
+ cause: "evidence"
271
324
  });
272
325
  });
273
326
  const latest = (domain) => Effect.gen(function* () {
@@ -287,6 +340,6 @@ const observe = accessor((service) => service.observe);
287
340
  const attachEvidence = accessor((service) => service.attachEvidence);
288
341
  const latest = accessor((service) => service.latest);
289
342
  //#endregion
290
- export { Evidence, HostEvidence, Policy, ProviderEvidence, PublicDnsEvidence, Service, Verify_exports, attachEvidence, defaults, latest, layer, make, observe, statusAgainst };
343
+ export { Evidence, HostEvidence, Observer, Policy, ProviderEvidence, PublicDnsEvidence, Service, Verify_exports, attachEvidence, defaults, latest, layer, make, observe, requirementKey, statusAgainst, summary };
291
344
 
292
345
  //# sourceMappingURL=Verify.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Verify.mjs","names":["Storage.RequirementStatus","DnsRecord.equals","DnsRecord.sameSet","DnsRecord.data","Storage.Service","Connect.Service","Resolver.Service","DomainName.decode","Errors.decode","Principal.Service","Storage.Readiness","Errors.fail","Reason.InvalidInput"],"sources":["../src/Verify.ts"],"sourcesContent":["/**\n * Observe provider and public DNS for an attachment, persist per-requirement readiness, and say\n * when to look again. The host keeps its own state machine (and any provider-side verification\n * such as SES) and feeds that in as `HostEvidence`.\n */\nimport { Context, DateTime, Duration, Effect, Layer, Option, Schema } from \"effect\";\n\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\n/**\n * Every piece of evidence carries `values`, what the observer returned for the requirement's name\n * and type (empty when nothing was found, the lookup timed out, or the resolver failed), and\n * `detail`, `null` when the requirement is satisfied and otherwise the mismatch summary or the\n * resolver's error text, so a host can render \"found X, expected Y\".\n */\n// Rows written before these fields existed decode with the empty values.\nconst Values = Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])));\nconst Detail = Schema.NullOr(Schema.String).pipe(\n Schema.withDecodingDefaultKey(Effect.succeed(null)),\n);\n\nexport class ProviderEvidence extends Schema.TaggedClass<ProviderEvidence>(\n \"@domainkit/Evidence/Provider\",\n)(\"Provider\", {\n provider: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\nexport class PublicDnsEvidence extends Schema.TaggedClass<PublicDnsEvidence>(\n \"@domainkit/Evidence/PublicDns\",\n)(\"PublicDns\", {\n resolver: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\n/** Anything the host knows that DomainKit cannot observe: SES identity status, a CDN cert, ... */\nexport class HostEvidence extends Schema.TaggedClass<HostEvidence>(\"@domainkit/Evidence/Host\")(\n \"Host\",\n {\n source: Schema.String,\n status: Schema.Literals([\"ok\", \"pending\", \"failed\"]),\n label: Schema.String,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n },\n) {}\nexport const Evidence = Schema.Union([ProviderEvidence, PublicDnsEvidence, HostEvidence]);\nexport type Evidence = typeof Evidence.Type;\n\nexport interface Requirement {\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n readonly status: Storage.RequirementStatus;\n readonly evidence: ReadonlyArray<Evidence>;\n}\n\nexport interface Readiness {\n readonly domain: string;\n /** `null` for observe-only domains that have no attachment. */\n readonly attachmentId: string | null;\n readonly overall: Storage.Overall;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly checkedAt: DateTime.Utc;\n readonly nextCheckAt: DateTime.Utc | null;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport interface Interface {\n /**\n * What to observe, in order of precedence: `requirements` when supplied (an attached domain\n * without a provisioning receipt, or a domain with no attachment whose records the customer\n * applies by hand), else the latest provisioning receipt of the domain's attachment (records it\n * applied or found in place). Neither is `InvalidInput`. Provider evidence is added when the\n * attachment's session can be built; public DNS is always observed, and readiness is stored\n * per domain either way.\n */\n readonly observe: (input: {\n readonly domain: string;\n readonly requirements?: ReadonlyArray<DnsRecord.Model>;\n }) => Fx<Readiness>;\n /** Merge host evidence into stored readiness without re-observing DNS. */\n readonly attachEvidence: (input: {\n readonly domain: string;\n readonly evidence: ReadonlyArray<HostEvidence>;\n }) => Fx<Readiness>;\n readonly latest: (domain: string) => Fx<Readiness | null>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Verify\") {}\n\nexport interface PolicyShape {\n /** Delay before the next check, given time since the first pending observation. Default ladder: 15s, 1m, 5m, 30m. */\n readonly backoff: (pendingForMs: number) => number;\n /** Which resolvers must agree for `satisfied`: default `any`. */\n readonly quorum: \"any\" | \"all\" | { readonly minimum: number };\n}\nexport const defaults: PolicyShape = {\n backoff: (pendingForMs) =>\n pendingForMs < 60_000\n ? 15_000\n : pendingForMs < 10 * 60_000\n ? 60_000\n : pendingForMs < 60 * 60_000\n ? 5 * 60_000\n : 30 * 60_000,\n quorum: \"any\",\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Verify/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst StoredEvidence = Schema.Array(Evidence);\n\ninterface ProviderSide {\n readonly provider: string;\n readonly records: ReadonlyArray<DnsRecord.Observed>;\n}\nconst StoredHost = Schema.Array(HostEvidence);\n\n/** A requirement is satisfied by an exact match; an exclusive one is contradicted by any same-set record. */\nexport const statusAgainst = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Storage.RequirementStatus => {\n if (observed.some((candidate) => DnsRecord.equals(candidate, record))) return \"satisfied\";\n if (\n record.policy === \"exclusive\" &&\n observed.some((candidate) => DnsRecord.sameSet(candidate, record))\n ) {\n return \"mismatch\";\n }\n return \"missing\";\n};\n\ninterface Observation {\n readonly status: Storage.RequirementStatus;\n readonly values: ReadonlyArray<string>;\n readonly detail: string | null;\n}\n\nconst render = (observed: DnsRecord.Observed): string =>\n observed._tag === \"Opaque\" ? JSON.stringify(observed.raw) : DnsRecord.data(observed);\n\n/** Status plus what the observer holds for the requirement's name and type. */\nconst observation = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Observation => {\n const status = statusAgainst(record, observed);\n const values = observed.filter((candidate) => DnsRecord.sameSet(candidate, record)).map(render);\n const detail =\n status === \"satisfied\"\n ? null\n : values.length === 0\n ? `no ${record._tag} record at ${record.name}`\n : `expected ${DnsRecord.data(record)}; found ${values.join(\", \")}`;\n return { status, values, detail };\n};\n\nconst combine = (statuses: ReadonlyArray<Storage.RequirementStatus>): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n if (known.length === 0) return \"unknown\";\n if (known.includes(\"mismatch\")) return \"mismatch\";\n return known.every((status) => status === \"satisfied\") ? \"satisfied\" : \"missing\";\n};\n\nconst quorumStatus = (\n quorum: PolicyShape[\"quorum\"],\n statuses: ReadonlyArray<Storage.RequirementStatus>,\n): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n const satisfied = known.filter((status) => status === \"satisfied\").length;\n const needed =\n quorum === \"any\" ? 1 : quorum === \"all\" ? Math.max(1, known.length) : quorum.minimum;\n if (known.includes(\"mismatch\")) return \"mismatch\";\n if (known.length > 0 && satisfied >= needed) return \"satisfied\";\n return known.length === 0 ? \"unknown\" : \"missing\";\n};\n\nconst overallOf = (\n requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>,\n host: ReadonlyArray<HostEvidence>,\n): Storage.Overall => {\n if (\n requirements.some(({ status }) => status === \"mismatch\") ||\n host.some(({ status }) => status === \"failed\")\n ) {\n return \"failed\";\n }\n const ready =\n requirements.length + host.length > 0 &&\n requirements.every(({ status }) => status === \"satisfied\") &&\n host.every(({ status }) => status === \"ok\");\n return ready ? \"ready\" : \"pending\";\n};\n\n/** The identity of a requirement set: type, name, data, and policy, independent of labels and order. */\nconst requirementKey = (requirements: ReadonlyArray<{ readonly record: DnsRecord.Model }>) =>\n requirements\n .map(({ record }) => `${record._tag} ${record.name} ${DnsRecord.data(record)} ${record.policy}`)\n .sort()\n .join(\"\\n\");\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Connect.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const connect = yield* Connect.Service;\n const resolver = yield* Resolver.Service;\n\n const attachmentFor = (\n input: string,\n ): Fx<{\n readonly domain: DomainName.Model;\n readonly attachment: Storage.Attachment | null;\n }> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const attachment = yield* storage.attachments.byDomain(domain);\n return { domain, attachment: Option.getOrNull(attachment) };\n });\n\n const decodeRow = (row: Storage.Readiness): Effect.Effect<Readiness, Errors.DomainKitError> =>\n Effect.gen(function* () {\n const requirements = yield* Effect.forEach(row.requirements, (requirement) =>\n Errors.decode(StoredEvidence, requirement.evidence, \"evidence\").pipe(\n Effect.map((evidence): Requirement => ({\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence,\n })),\n ),\n );\n const host = yield* Errors.decode(StoredHost, row.host, \"host\");\n return {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall: row.overall,\n requirements,\n host,\n checkedAt: row.checkedAt,\n nextCheckAt: row.nextCheckAt,\n };\n });\n\n /** Persist readiness, carrying the pending streak forward for the backoff ladder. */\n const store = (input: {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly previous: Option.Option<Storage.Readiness>;\n }): Fx<Readiness> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const now = yield* DateTime.now;\n const overall = overallOf(input.requirements, input.host);\n const sameRequirements =\n Option.isSome(input.previous) &&\n requirementKey(input.previous.value.requirements) === requirementKey(input.requirements);\n const pendingSince =\n overall === \"ready\"\n ? null\n : sameRequirements &&\n Option.isSome(input.previous) &&\n input.previous.value.pendingSince !== null\n ? input.previous.value.pendingSince\n : now;\n const nextCheckAt =\n pendingSince === null\n ? null\n : DateTime.addDuration(\n now,\n Duration.millis(\n policy.backoff(DateTime.toEpochMillis(now) - DateTime.toEpochMillis(pendingSince)),\n ),\n );\n const row = new Storage.Readiness({\n domain: input.domain,\n attachmentId: input.attachment?.id ?? null,\n ownerId: principal.ownerId,\n overall,\n requirements: input.requirements.map((requirement) => ({\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence: Schema.encodeSync(StoredEvidence)(requirement.evidence),\n })),\n host: Schema.encodeSync(StoredHost)(input.host),\n pendingSince,\n checkedAt: now,\n nextCheckAt,\n });\n yield* storage.readiness.put(row);\n return {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall,\n requirements: input.requirements,\n host: input.host,\n checkedAt: now,\n nextCheckAt,\n };\n });\n\n const defaultRequirements = (\n attachment: Storage.Attachment,\n ): Fx<\n ReadonlyArray<{\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n }>\n > =>\n Effect.gen(function* () {\n const latest = yield* storage.attempts.latest(attachment.id, \"provisioning\");\n if (Option.isNone(latest) || latest.value.receipt === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${attachment.domain} has no provisioning receipt; pass requirements to observe`,\n field: \"requirements\",\n }),\n );\n }\n const receipt = latest.value.receipt;\n const inPlace = new Set(\n receipt.outcomes.flatMap((outcome) =>\n outcome._tag === \"Applied\" || (outcome._tag === \"Skipped\" && outcome.reason === \"noop\")\n ? [outcome.operationId]\n : [],\n ),\n );\n return latest.value.plan.operations\n .filter((operation) => inPlace.has(operation.id))\n .map((operation) => ({ operationId: operation.id, record: operation.record }));\n });\n\n const observe: Interface[\"observe\"] = (input) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const { domain, attachment } = yield* attachmentFor(input.domain);\n if (input.requirements === undefined && attachment === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${domain} is not attached; pass requirements to observe public DNS`,\n field: \"requirements\",\n }),\n );\n }\n const requirements =\n input.requirements === undefined && attachment !== null\n ? yield* defaultRequirements(attachment)\n : (input.requirements ?? []).map((record) => ({ operationId: null, record }));\n if (requirements.length === 0) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: \"Nothing to observe: pass at least one requirement\",\n field: \"requirements\",\n }),\n );\n }\n const now = yield* DateTime.now;\n // Provider readback is evidence when the attachment's session can be built; when it\n // cannot (no attachment, revoked credential, provider outage) public DNS stands alone.\n const providerSide = yield* attachment === null\n ? Effect.succeed(Option.none<ProviderSide>())\n : Effect.gen(function* () {\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const { session, target } = yield* connect.session(attachment.id);\n const observed = yield* session.dns(target).list(target.zone);\n return Option.some<ProviderSide>({\n provider: authorization.provider,\n records: observed.map(({ record }) => record),\n });\n }).pipe(Effect.catch(() => Effect.succeed(Option.none<ProviderSide>())));\n const observed = yield* Effect.forEach(\n requirements,\n ({ operationId, record }) =>\n Effect.gen(function* () {\n const providerEvidence = Option.isSome(providerSide)\n ? [\n new ProviderEvidence({\n provider: providerSide.value.provider,\n ...observation(record, providerSide.value.records),\n observedAt: now,\n }),\n ]\n : [];\n const outcomes = yield* resolver.resolve(record.name, record._tag);\n const publicEvidence = outcomes.map((outcome) =>\n outcome._tag === \"Answered\"\n ? new PublicDnsEvidence({\n resolver: outcome.answer.resolver,\n ...observation(record, outcome.answer.records),\n observedAt: now,\n })\n : new PublicDnsEvidence({\n resolver: outcome.resolver,\n status: \"unknown\",\n values: [],\n detail:\n outcome._tag === \"TimedOut\"\n ? `${outcome.resolver} timed out`\n : outcome.message,\n observedAt: now,\n }),\n );\n const publicStatus = quorumStatus(\n policy.quorum,\n publicEvidence.map(({ status }) => status),\n );\n const status = combine([\n ...providerEvidence.map((evidence) => evidence.status),\n publicStatus,\n ]);\n return {\n operationId,\n record,\n status,\n evidence: [...providerEvidence, ...publicEvidence],\n } satisfies Requirement;\n }),\n { concurrency: \"unbounded\" },\n );\n const previous = yield* storage.readiness.get(domain);\n const host = Option.isSome(previous)\n ? yield* Errors.decode(StoredHost, previous.value.host, \"host\")\n : [];\n return yield* store({ domain, attachment, requirements: observed, host, previous });\n });\n\n const attachEvidence: Interface[\"attachEvidence\"] = (input) =>\n Effect.gen(function* () {\n const { domain, attachment } = yield* attachmentFor(input.domain);\n const previous = yield* storage.readiness.get(domain);\n const current = Option.isSome(previous)\n ? yield* decodeRow(previous.value)\n : {\n requirements: [] as ReadonlyArray<Requirement>,\n host: [] as ReadonlyArray<HostEvidence>,\n };\n const bySource = new Map(current.host.map((evidence) => [evidence.source, evidence]));\n for (const evidence of input.evidence) bySource.set(evidence.source, evidence);\n return yield* store({\n domain,\n attachment,\n requirements: current.requirements,\n host: [...bySource.values()],\n previous,\n });\n });\n\n const latest: Interface[\"latest\"] = (domain) =>\n Effect.gen(function* () {\n const { domain: name } = yield* attachmentFor(domain);\n const stored = yield* storage.readiness.get(name);\n return Option.isSome(stored) ? yield* decodeRow(stored.value) : null;\n });\n\n return { observe, attachEvidence, latest };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Connect.Service | Resolver.Service\n> = Layer.effect(Service)(make);\n\nconst accessor =\n <Args extends ReadonlyArray<unknown>, A>(\n pick: (service: Interface) => (...args: Args) => Fx<A>,\n ): ((...args: Args) => Effect.Effect<A, Errors.DomainKitError, Principal.Service | Service>) =>\n (...args) =>\n Effect.flatMap(Service, (service) => pick(service)(...args));\n\nexport const observe = accessor((service) => service.observe);\nexport const attachEvidence = accessor((service) => service.attachEvidence);\nexport const latest = accessor((service) => service.latest);\n"],"mappings":"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"}
1
+ {"version":3,"file":"Verify.mjs","names":["Storage.RequirementStatus","DnsRecord.data","DnsRecord.equals","DnsRecord.sameSet","Storage.Service","Connect.Service","Resolver.Service","DomainName.decode","Errors.decode","Principal.Service","Storage.Readiness","Errors.fail","Reason.InvalidInput"],"sources":["../src/Verify.ts"],"sourcesContent":["/**\n * Observe provider and public DNS for an attachment, persist per-requirement readiness, and say\n * when to look again. The host keeps its own state machine (and any provider-side verification\n * such as SES) and feeds that in as `HostEvidence`.\n */\nimport { Context, DateTime, Duration, Effect, Layer, Option, Schema } from \"effect\";\n\nimport * as Connect from \"./Connect.ts\";\nimport * as DnsRecord from \"./DnsRecord.ts\";\nimport * as Errors from \"./internal/error.ts\";\nimport * as Reason from \"./Reason.ts\";\nimport * as DomainName from \"./DomainName.ts\";\nimport type * as Plan from \"./Plan.ts\";\nimport * as Principal from \"./Principal.ts\";\nimport * as Resolver from \"./Resolver.ts\";\nimport * as Storage from \"./Storage.ts\";\n\n/**\n * Every piece of evidence carries `values`, what the observer returned for the requirement's name\n * and type (empty when nothing was found, the lookup timed out, or the resolver failed), and\n * `detail`, `null` when the requirement is satisfied and otherwise the mismatch summary or the\n * resolver's error text, so a host can render \"found X, expected Y\".\n */\n// Rows written before these fields existed decode with the empty values.\nconst Values = Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([])));\nconst Detail = Schema.NullOr(Schema.String).pipe(\n Schema.withDecodingDefaultKey(Effect.succeed(null)),\n);\n\nexport class ProviderEvidence extends Schema.TaggedClass<ProviderEvidence>(\n \"@domainkit/Evidence/Provider\",\n)(\"Provider\", {\n provider: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\nexport class PublicDnsEvidence extends Schema.TaggedClass<PublicDnsEvidence>(\n \"@domainkit/Evidence/PublicDns\",\n)(\"PublicDns\", {\n resolver: Schema.String,\n status: Storage.RequirementStatus,\n values: Values,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n}) {}\n/** Anything the host knows that DomainKit cannot observe: SES identity status, a CDN cert, ... */\nexport class HostEvidence extends Schema.TaggedClass<HostEvidence>(\"@domainkit/Evidence/Host\")(\n \"Host\",\n {\n source: Schema.String,\n status: Schema.Literals([\"ok\", \"pending\", \"failed\"]),\n label: Schema.String,\n detail: Detail,\n observedAt: Schema.DateTimeUtcFromString,\n },\n) {}\nexport const Evidence = Schema.Union([ProviderEvidence, PublicDnsEvidence, HostEvidence]);\nexport type Evidence = typeof Evidence.Type;\n\nexport interface Requirement {\n /** `requirementKey(record)`: what a host pairs its own row against, by content. */\n readonly key: string;\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n readonly status: Storage.RequirementStatus;\n readonly evidence: ReadonlyArray<Evidence>;\n}\n\nexport interface Readiness {\n readonly domain: string;\n /** `null` for observe-only domains that have no attachment. */\n readonly attachmentId: string | null;\n readonly overall: Storage.Overall;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly checkedAt: DateTime.Utc;\n readonly nextCheckAt: DateTime.Utc | null;\n}\n\ntype Fx<A> = Effect.Effect<A, Errors.DomainKitError, Principal.Service>;\n\nexport interface Interface {\n /**\n * What to observe, in order of precedence: `requirements` when supplied (an attached domain\n * without a provisioning receipt, or a domain with no attachment whose records the customer\n * applies by hand), else the latest provisioning receipt of the domain's attachment (records it\n * applied or found in place). Neither is `InvalidInput`. Provider evidence is added when the\n * attachment's session can be built; public DNS is always observed, and readiness is stored\n * per domain either way.\n */\n readonly observe: (input: {\n readonly domain: string;\n readonly requirements?: ReadonlyArray<DnsRecord.Model>;\n }) => Fx<Readiness>;\n /** Merge host evidence into stored readiness without re-observing DNS. */\n readonly attachEvidence: (input: {\n readonly domain: string;\n readonly evidence: ReadonlyArray<HostEvidence>;\n }) => Fx<Readiness>;\n readonly latest: (domain: string) => Fx<Readiness | null>;\n}\n\nexport class Service extends Context.Service<Service, Interface>()(\"@domainkit/Verify\") {}\n\n/**\n * A requirement's identity: the record's type, name, and data, and nothing else. It is what a host\n * pairs its own rows against readiness by, instead of trusting the order of the array.\n * `@domainkit/react`'s `Records.identity` is this function.\n */\nexport const requirementKey = (record: DnsRecord.Model): string =>\n [record._tag, record.name, DnsRecord.data(record)].join(\":\");\n\n/** Counts across a readiness's requirements. `observed` is false when there is no readiness yet. */\nexport interface Summary {\n readonly observed: boolean;\n readonly total: number;\n readonly satisfied: number;\n readonly missing: number;\n readonly mismatch: number;\n readonly unknown: number;\n}\n\n/** The least a value has to carry to be summarised: core `Readiness` and the wire shape both do. */\nexport interface Summarisable {\n readonly requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>;\n}\n\n/**\n * How a readiness stands, as counts. Pure and total: a host renders \"3 of 4 found\" from the stored\n * fact without deciding what an absent readiness means, because `observed` says so.\n */\nexport const summary = (readiness: Summarisable | null): Summary => {\n const statuses = readiness?.requirements.map(({ status }) => status) ?? [];\n const count = (status: Storage.RequirementStatus): number =>\n statuses.filter((candidate) => candidate === status).length;\n return {\n observed: readiness !== null,\n total: statuses.length,\n satisfied: count(\"satisfied\"),\n missing: count(\"missing\"),\n mismatch: count(\"mismatch\"),\n unknown: count(\"unknown\"),\n };\n};\n\nexport interface PolicyShape {\n /** Delay before the next check, given time since the first pending observation. Default ladder: 15s, 1m, 5m, 30m. */\n readonly backoff: (pendingForMs: number) => number;\n /** Which resolvers must agree for `satisfied`: default `any`. */\n readonly quorum: \"any\" | \"all\" | { readonly minimum: number };\n}\nexport const defaults: PolicyShape = {\n backoff: (pendingForMs) =>\n pendingForMs < 60_000\n ? 15_000\n : pendingForMs < 10 * 60_000\n ? 60_000\n : pendingForMs < 60 * 60_000\n ? 5 * 60_000\n : 30 * 60_000,\n quorum: \"any\",\n};\nexport class Policy extends Context.Reference<PolicyShape>(\"@domainkit/Verify/Policy\", {\n defaultValue: () => defaults,\n}) {}\n\n/** Why readiness was written: a DNS observation, or host evidence merged into the stored row. */\nexport type Cause = \"observe\" | \"evidence\";\n\nexport interface ReadinessChanged {\n readonly domain: string;\n readonly readiness: Readiness;\n readonly cause: Cause;\n}\n\nexport interface ObserverShape {\n /**\n * Called once per stored readiness, after the write. A host that projects readiness onto its own\n * rows, wakes a durable job at `nextCheckAt`, or notifies a customer hangs it here instead of\n * mirroring the fact at every call site.\n *\n * Events for one domain are not ordered against each other: two observations that overlap both\n * store, last write wins in `Storage`, and their callbacks can finish in either order. A\n * projection compares `readiness.checkedAt`, which is the moment the row was written, and keeps\n * the later one; `latest` remains the authority.\n */\n readonly readinessChanged: (event: ReadinessChanged) => Effect.Effect<void, unknown>;\n}\n\n/**\n * The host seam for \"DomainKit wrote readiness\". The default does nothing; provide one with\n * `Effect.provideService(Verify.Observer, ...)` or `Layer.succeed(Verify.Observer, ...)` over\n * `DomainKit.layer`, the same way `Policy` is overridden.\n *\n * It runs after `storage.readiness.put` returns, outside any transaction, because `Storage`\n * exposes none. A failure or defect in the observer is logged and swallowed: the observation is\n * already durable, and losing it because a projection failed would make the stored fact depend on\n * the host's side effects. A host that needs the projection to be reliable enqueues durable work\n * here rather than doing the work inline.\n */\nexport class Observer extends Context.Reference<ObserverShape>(\"@domainkit/Verify/Observer\", {\n defaultValue: (): ObserverShape => ({ readinessChanged: () => Effect.void }),\n}) {}\n\n// ---------------------------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------------------------\n\nconst StoredEvidence = Schema.Array(Evidence);\n\ninterface ProviderSide {\n readonly provider: string;\n readonly records: ReadonlyArray<DnsRecord.Observed>;\n}\nconst StoredHost = Schema.Array(HostEvidence);\n\n/** A requirement is satisfied by an exact match; an exclusive one is contradicted by any same-set record. */\nexport const statusAgainst = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Storage.RequirementStatus => {\n if (observed.some((candidate) => DnsRecord.equals(candidate, record))) return \"satisfied\";\n if (\n record.policy === \"exclusive\" &&\n observed.some((candidate) => DnsRecord.sameSet(candidate, record))\n ) {\n return \"mismatch\";\n }\n return \"missing\";\n};\n\ninterface Observation {\n readonly status: Storage.RequirementStatus;\n readonly values: ReadonlyArray<string>;\n readonly detail: string | null;\n}\n\nconst render = (observed: DnsRecord.Observed): string =>\n observed._tag === \"Opaque\" ? JSON.stringify(observed.raw) : DnsRecord.data(observed);\n\n/** Status plus what the observer holds for the requirement's name and type. */\nconst observation = (\n record: DnsRecord.Model,\n observed: ReadonlyArray<DnsRecord.Observed>,\n): Observation => {\n const status = statusAgainst(record, observed);\n const values = observed.filter((candidate) => DnsRecord.sameSet(candidate, record)).map(render);\n const detail =\n status === \"satisfied\"\n ? null\n : values.length === 0\n ? `no ${record._tag} record at ${record.name}`\n : `expected ${DnsRecord.data(record)}; found ${values.join(\", \")}`;\n return { status, values, detail };\n};\n\nconst combine = (statuses: ReadonlyArray<Storage.RequirementStatus>): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n if (known.length === 0) return \"unknown\";\n if (known.includes(\"mismatch\")) return \"mismatch\";\n return known.every((status) => status === \"satisfied\") ? \"satisfied\" : \"missing\";\n};\n\nconst quorumStatus = (\n quorum: PolicyShape[\"quorum\"],\n statuses: ReadonlyArray<Storage.RequirementStatus>,\n): Storage.RequirementStatus => {\n const known = statuses.filter((status) => status !== \"unknown\");\n const satisfied = known.filter((status) => status === \"satisfied\").length;\n const needed =\n quorum === \"any\" ? 1 : quorum === \"all\" ? Math.max(1, known.length) : quorum.minimum;\n if (known.includes(\"mismatch\")) return \"mismatch\";\n if (known.length > 0 && satisfied >= needed) return \"satisfied\";\n return known.length === 0 ? \"unknown\" : \"missing\";\n};\n\nconst overallOf = (\n requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>,\n host: ReadonlyArray<HostEvidence>,\n): Storage.Overall => {\n if (\n requirements.some(({ status }) => status === \"mismatch\") ||\n host.some(({ status }) => status === \"failed\")\n ) {\n return \"failed\";\n }\n const ready =\n requirements.length + host.length > 0 &&\n requirements.every(({ status }) => status === \"satisfied\") &&\n host.every(({ status }) => status === \"ok\");\n return ready ? \"ready\" : \"pending\";\n};\n\n/** The identity of a requirement set: type, name, data, and policy, independent of labels and order. */\nconst requirementSetKey = (requirements: ReadonlyArray<{ readonly record: DnsRecord.Model }>) =>\n requirements\n .map(({ record }) => `${record._tag} ${record.name} ${DnsRecord.data(record)} ${record.policy}`)\n .sort()\n .join(\"\\n\");\n\nexport const make: Effect.Effect<\n Interface,\n never,\n Storage.Service | Connect.Service | Resolver.Service\n> = Effect.gen(function* () {\n const storage = yield* Storage.Service;\n const connect = yield* Connect.Service;\n const resolver = yield* Resolver.Service;\n\n const attachmentFor = (\n input: string,\n ): Fx<{\n readonly domain: DomainName.Model;\n readonly attachment: Storage.Attachment | null;\n }> =>\n Effect.gen(function* () {\n const domain = yield* DomainName.decode(input);\n const attachment = yield* storage.attachments.byDomain(domain);\n return { domain, attachment: Option.getOrNull(attachment) };\n });\n\n const decodeRow = (row: Storage.Readiness): Effect.Effect<Readiness, Errors.DomainKitError> =>\n Effect.gen(function* () {\n const requirements = yield* Effect.forEach(row.requirements, (requirement) =>\n Errors.decode(StoredEvidence, requirement.evidence, \"evidence\").pipe(\n Effect.map((evidence): Requirement => ({\n key: requirementKey(requirement.record),\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence,\n })),\n ),\n );\n const host = yield* Errors.decode(StoredHost, row.host, \"host\");\n return {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall: row.overall,\n requirements,\n host,\n checkedAt: row.checkedAt,\n nextCheckAt: row.nextCheckAt,\n };\n });\n\n /** Persist readiness, carrying the pending streak forward for the backoff ladder. */\n const store = (input: {\n readonly domain: string;\n readonly attachment: Storage.Attachment | null;\n readonly requirements: ReadonlyArray<Requirement>;\n readonly host: ReadonlyArray<HostEvidence>;\n readonly previous: Option.Option<Storage.Readiness>;\n readonly cause: Cause;\n }): Fx<Readiness> =>\n Effect.gen(function* () {\n const principal = yield* Principal.Service;\n const policy = yield* Policy;\n const observer = yield* Observer;\n const now = yield* DateTime.now;\n const overall = overallOf(input.requirements, input.host);\n const sameRequirements =\n Option.isSome(input.previous) &&\n requirementSetKey(input.previous.value.requirements) ===\n requirementSetKey(input.requirements);\n const pendingSince =\n overall === \"ready\"\n ? null\n : sameRequirements &&\n Option.isSome(input.previous) &&\n input.previous.value.pendingSince !== null\n ? input.previous.value.pendingSince\n : now;\n const nextCheckAt =\n pendingSince === null\n ? null\n : DateTime.addDuration(\n now,\n Duration.millis(\n policy.backoff(DateTime.toEpochMillis(now) - DateTime.toEpochMillis(pendingSince)),\n ),\n );\n const row = new Storage.Readiness({\n domain: input.domain,\n attachmentId: input.attachment?.id ?? null,\n ownerId: principal.ownerId,\n overall,\n requirements: input.requirements.map((requirement) => ({\n operationId: requirement.operationId,\n record: requirement.record,\n status: requirement.status,\n evidence: Schema.encodeSync(StoredEvidence)(requirement.evidence),\n })),\n host: Schema.encodeSync(StoredHost)(input.host),\n pendingSince,\n checkedAt: now,\n nextCheckAt,\n });\n yield* storage.readiness.put(row);\n const readiness: Readiness = {\n domain: row.domain,\n attachmentId: row.attachmentId,\n overall,\n requirements: input.requirements,\n host: input.host,\n checkedAt: now,\n nextCheckAt,\n };\n yield* observer\n .readinessChanged({ domain: row.domain, readiness, cause: input.cause })\n .pipe(\n Effect.catchCause((cause) =>\n Effect.logWarning(`Verify.Observer failed for ${row.domain}`, cause),\n ),\n );\n return readiness;\n });\n\n const defaultRequirements = (\n attachment: Storage.Attachment,\n ): Fx<\n ReadonlyArray<{\n readonly operationId: Plan.OperationId | null;\n readonly record: DnsRecord.Model;\n }>\n > =>\n Effect.gen(function* () {\n const latest = yield* storage.attempts.latest(attachment.id, \"provisioning\");\n if (Option.isNone(latest) || latest.value.receipt === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${attachment.domain} has no provisioning receipt; pass requirements to observe`,\n field: \"requirements\",\n }),\n );\n }\n const receipt = latest.value.receipt;\n const inPlace = new Set(\n receipt.outcomes.flatMap((outcome) =>\n outcome._tag === \"Applied\" || (outcome._tag === \"Skipped\" && outcome.reason === \"noop\")\n ? [outcome.operationId]\n : [],\n ),\n );\n return latest.value.plan.operations\n .filter((operation) => inPlace.has(operation.id))\n .map((operation) => ({ operationId: operation.id, record: operation.record }));\n });\n\n const observe: Interface[\"observe\"] = (input) =>\n Effect.gen(function* () {\n const policy = yield* Policy;\n const { domain, attachment } = yield* attachmentFor(input.domain);\n if (input.requirements === undefined && attachment === null) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: `${domain} is not attached; pass requirements to observe public DNS`,\n field: \"requirements\",\n }),\n );\n }\n const requirements =\n input.requirements === undefined && attachment !== null\n ? yield* defaultRequirements(attachment)\n : (input.requirements ?? []).map((record) => ({ operationId: null, record }));\n if (requirements.length === 0) {\n return yield* Errors.fail(\n new Reason.InvalidInput({\n message: \"Nothing to observe: pass at least one requirement\",\n field: \"requirements\",\n }),\n );\n }\n const now = yield* DateTime.now;\n // Provider readback is evidence when the attachment's session can be built; when it\n // cannot (no attachment, revoked credential, provider outage) public DNS stands alone.\n const providerSide = yield* attachment === null\n ? Effect.succeed(Option.none<ProviderSide>())\n : Effect.gen(function* () {\n const connection = yield* storage.connections.get(attachment.connectionId);\n const authorization = yield* storage.authorizations.get(connection.authorizationId);\n const { session, target } = yield* connect.session(attachment.id);\n const observed = yield* session.dns(target).list(target.zone);\n return Option.some<ProviderSide>({\n provider: authorization.provider,\n records: observed.map(({ record }) => record),\n });\n }).pipe(Effect.catch(() => Effect.succeed(Option.none<ProviderSide>())));\n const observed = yield* Effect.forEach(\n requirements,\n ({ operationId, record }) =>\n Effect.gen(function* () {\n const providerEvidence = Option.isSome(providerSide)\n ? [\n new ProviderEvidence({\n provider: providerSide.value.provider,\n ...observation(record, providerSide.value.records),\n observedAt: now,\n }),\n ]\n : [];\n const outcomes = yield* resolver.resolve(record.name, record._tag);\n const publicEvidence = outcomes.map((outcome) =>\n outcome._tag === \"Answered\"\n ? new PublicDnsEvidence({\n resolver: outcome.answer.resolver,\n ...observation(record, outcome.answer.records),\n observedAt: now,\n })\n : new PublicDnsEvidence({\n resolver: outcome.resolver,\n status: \"unknown\",\n values: [],\n detail:\n outcome._tag === \"TimedOut\"\n ? `${outcome.resolver} timed out`\n : outcome.message,\n observedAt: now,\n }),\n );\n const publicStatus = quorumStatus(\n policy.quorum,\n publicEvidence.map(({ status }) => status),\n );\n const status = combine([\n ...providerEvidence.map((evidence) => evidence.status),\n publicStatus,\n ]);\n return {\n key: requirementKey(record),\n operationId,\n record,\n status,\n evidence: [...providerEvidence, ...publicEvidence],\n } satisfies Requirement;\n }),\n { concurrency: \"unbounded\" },\n );\n const previous = yield* storage.readiness.get(domain);\n const host = Option.isSome(previous)\n ? yield* Errors.decode(StoredHost, previous.value.host, \"host\")\n : [];\n return yield* store({\n domain,\n attachment,\n requirements: observed,\n host,\n previous,\n cause: \"observe\",\n });\n });\n\n const attachEvidence: Interface[\"attachEvidence\"] = (input) =>\n Effect.gen(function* () {\n const { domain, attachment } = yield* attachmentFor(input.domain);\n const previous = yield* storage.readiness.get(domain);\n const current = Option.isSome(previous)\n ? yield* decodeRow(previous.value)\n : {\n requirements: [] as ReadonlyArray<Requirement>,\n host: [] as ReadonlyArray<HostEvidence>,\n };\n const bySource = new Map(current.host.map((evidence) => [evidence.source, evidence]));\n for (const evidence of input.evidence) bySource.set(evidence.source, evidence);\n return yield* store({\n domain,\n attachment,\n requirements: current.requirements,\n host: [...bySource.values()],\n previous,\n cause: \"evidence\",\n });\n });\n\n const latest: Interface[\"latest\"] = (domain) =>\n Effect.gen(function* () {\n const { domain: name } = yield* attachmentFor(domain);\n const stored = yield* storage.readiness.get(name);\n return Option.isSome(stored) ? yield* decodeRow(stored.value) : null;\n });\n\n return { observe, attachEvidence, latest };\n});\n\nexport const layer: Layer.Layer<\n Service,\n never,\n Storage.Service | Connect.Service | Resolver.Service\n> = Layer.effect(Service)(make);\n\nconst accessor =\n <Args extends ReadonlyArray<unknown>, A>(\n pick: (service: Interface) => (...args: Args) => Fx<A>,\n ): ((...args: Args) => Effect.Effect<A, Errors.DomainKitError, Principal.Service | Service>) =>\n (...args) =>\n Effect.flatMap(Service, (service) => pick(service)(...args));\n\nexport const observe = accessor((service) => service.observe);\nexport const attachEvidence = accessor((service) => service.attachEvidence);\nexport const latest = accessor((service) => service.latest);\n"],"mappings":"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"}
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.13.0";
2
+ var version = "0.14.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -3,8 +3,9 @@
3
3
  * provides `Storage` and `Custody` beneath it, the same way `Persistence.layer` sits over a
4
4
  * backing store in Effect.
5
5
  *
6
- * Defaults: `Resolver.layer` (Cloudflare + Google DoH) and the `Policy` references. Override a
7
- * policy with `Effect.provideService`; pass `resolver` for a different pool.
6
+ * Defaults: `Resolver.layer` (Cloudflare + Google DoH) and the reference-shaped seams, `Verify.Policy`
7
+ * and `Verify.Observer`. Override either with `Effect.provideService` or `Layer.succeed` over this
8
+ * layer; pass `resolver` for a different pool.
8
9
  */
9
10
  import { Layer } from "effect";
10
11
  import * as Cleanup from "./Cleanup.ts";