domainkit 0.13.0 → 0.14.1

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,51 @@ 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. The parameter
88
+ * is the least a value has to carry to be summarised, so core `Readiness` and the wire shape both
89
+ * pass without naming a type between them.
90
+ */
91
+ const summary = (readiness) => {
92
+ const statuses = readiness?.requirements.map(({ status }) => status) ?? [];
93
+ const count = (status) => statuses.filter((candidate) => candidate === status).length;
94
+ return {
95
+ observed: readiness !== null,
96
+ total: statuses.length,
97
+ satisfied: count("satisfied"),
98
+ missing: count("missing"),
99
+ mismatch: count("mismatch"),
100
+ unknown: count("unknown")
101
+ };
102
+ };
72
103
  const defaults = {
73
104
  backoff: (pendingForMs) => pendingForMs < 6e4 ? 15e3 : pendingForMs < 6e5 ? 6e4 : pendingForMs < 36e5 ? 3e5 : 18e5,
74
105
  quorum: "any"
75
106
  };
76
107
  var Policy = class extends Context.Reference("@domainkit/Verify/Policy", { defaultValue: () => defaults }) {};
108
+ /**
109
+ * The host seam for "DomainKit wrote readiness". The default does nothing; provide one with
110
+ * `Effect.provideService(Verify.Observer, ...)` or `Layer.succeed(Verify.Observer, ...)` over
111
+ * `DomainKit.layer`, the same way `Policy` is overridden.
112
+ *
113
+ * It runs after `storage.readiness.put` returns, outside any transaction, because `Storage`
114
+ * exposes none. A failure or defect in the observer is logged and swallowed: the observation is
115
+ * already durable, and losing it because a projection failed would make the stored fact depend on
116
+ * the host's side effects. A host that needs the projection to be reliable enqueues durable work
117
+ * here rather than doing the work inline.
118
+ */
119
+ var Observer = class extends Context.Reference("@domainkit/Verify/Observer", { defaultValue: () => ({ readinessChanged: () => Effect.void }) }) {};
77
120
  const StoredEvidence = Schema.Array(Evidence);
78
121
  const StoredHost = Schema.Array(HostEvidence);
79
122
  /** A requirement is satisfied by an exact match; an exclusive one is contradicted by any same-set record. */
@@ -112,7 +155,7 @@ const overallOf = (requirements, host) => {
112
155
  return requirements.length + host.length > 0 && requirements.every(({ status }) => status === "satisfied") && host.every(({ status }) => status === "ok") ? "ready" : "pending";
113
156
  };
114
157
  /** 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");
158
+ const requirementSetKey = (requirements) => requirements.map(({ record }) => `${record._tag} ${record.name} ${data(record)} ${record.policy}`).sort().join("\n");
116
159
  const make = Effect.gen(function* () {
117
160
  const storage = yield* Service$3;
118
161
  const connect = yield* Service$4;
@@ -127,6 +170,7 @@ const make = Effect.gen(function* () {
127
170
  });
128
171
  const decodeRow = (row) => Effect.gen(function* () {
129
172
  const requirements = yield* Effect.forEach(row.requirements, (requirement) => decode$1(StoredEvidence, requirement.evidence, "evidence").pipe(Effect.map((evidence) => ({
173
+ key: requirementKey(requirement.record),
130
174
  operationId: requirement.operationId,
131
175
  record: requirement.record,
132
176
  status: requirement.status,
@@ -147,9 +191,10 @@ const make = Effect.gen(function* () {
147
191
  const store = (input) => Effect.gen(function* () {
148
192
  const principal = yield* Service$1;
149
193
  const policy = yield* Policy;
194
+ const observer = yield* Observer;
150
195
  const now = yield* DateTime.now;
151
196
  const overall = overallOf(input.requirements, input.host);
152
- const sameRequirements = Option.isSome(input.previous) && requirementKey(input.previous.value.requirements) === requirementKey(input.requirements);
197
+ const sameRequirements = Option.isSome(input.previous) && requirementSetKey(input.previous.value.requirements) === requirementSetKey(input.requirements);
153
198
  const pendingSince = overall === "ready" ? null : sameRequirements && Option.isSome(input.previous) && input.previous.value.pendingSince !== null ? input.previous.value.pendingSince : now;
154
199
  const nextCheckAt = pendingSince === null ? null : DateTime.addDuration(now, Duration.millis(policy.backoff(DateTime.toEpochMillis(now) - DateTime.toEpochMillis(pendingSince))));
155
200
  const row = new Readiness({
@@ -169,7 +214,7 @@ const make = Effect.gen(function* () {
169
214
  nextCheckAt
170
215
  });
171
216
  yield* storage.readiness.put(row);
172
- return {
217
+ const readiness = {
173
218
  domain: row.domain,
174
219
  attachmentId: row.attachmentId,
175
220
  overall,
@@ -178,6 +223,13 @@ const make = Effect.gen(function* () {
178
223
  checkedAt: now,
179
224
  nextCheckAt
180
225
  };
226
+ yield* observer.readinessChanged({
227
+ ownerId: principal.ownerId,
228
+ domain: row.domain,
229
+ readiness,
230
+ cause: input.cause
231
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning(`Verify.Observer failed for ${row.domain}`, cause)));
232
+ return readiness;
181
233
  });
182
234
  const defaultRequirements = (attachment) => Effect.gen(function* () {
183
235
  const latest = yield* storage.attempts.latest(attachment.id, "provisioning");
@@ -236,10 +288,12 @@ const make = Effect.gen(function* () {
236
288
  observedAt: now
237
289
  }));
238
290
  const publicStatus = quorumStatus(policy.quorum, publicEvidence.map(({ status }) => status));
291
+ const status = combine([...providerEvidence.map((evidence) => evidence.status), publicStatus]);
239
292
  return {
293
+ key: requirementKey(record),
240
294
  operationId,
241
295
  record,
242
- status: combine([...providerEvidence.map((evidence) => evidence.status), publicStatus]),
296
+ status,
243
297
  evidence: [...providerEvidence, ...publicEvidence]
244
298
  };
245
299
  }), { concurrency: "unbounded" });
@@ -250,7 +304,8 @@ const make = Effect.gen(function* () {
250
304
  attachment,
251
305
  requirements: observed,
252
306
  host,
253
- previous
307
+ previous,
308
+ cause: "observe"
254
309
  });
255
310
  });
256
311
  const attachEvidence = (input) => Effect.gen(function* () {
@@ -267,7 +322,8 @@ const make = Effect.gen(function* () {
267
322
  attachment,
268
323
  requirements: current.requirements,
269
324
  host: [...bySource.values()],
270
- previous
325
+ previous,
326
+ cause: "evidence"
271
327
  });
272
328
  });
273
329
  const latest = (domain) => Effect.gen(function* () {
@@ -287,6 +343,6 @@ const observe = accessor((service) => service.observe);
287
343
  const attachEvidence = accessor((service) => service.attachEvidence);
288
344
  const latest = accessor((service) => service.latest);
289
345
  //#endregion
290
- export { Evidence, HostEvidence, Policy, ProviderEvidence, PublicDnsEvidence, Service, Verify_exports, attachEvidence, defaults, latest, layer, make, observe, statusAgainst };
346
+ export { Evidence, HostEvidence, Observer, Policy, ProviderEvidence, PublicDnsEvidence, Service, Verify_exports, attachEvidence, defaults, latest, layer, make, observe, requirementKey, statusAgainst, summary };
291
347
 
292
348
  //# 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/**\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. The parameter\n * is the least a value has to carry to be summarised, so core `Readiness` and the wire shape both\n * pass without naming a type between them.\n */\nexport const summary = (\n readiness: {\n readonly requirements: ReadonlyArray<{ readonly status: Storage.RequirementStatus }>;\n } | null,\n): 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 /** The tenant the row was written under, from the `Principal` the write ran as. */\n readonly ownerId: string;\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 * Readiness is stored per owner and domain, and an observer provided over `DomainKit.layer` is\n * scoped to the layer rather than to one request, so a host that serves more than one tenant\n * keys its projection by `ownerId` and `domain`: the same domain name can be attached in two\n * tenants at once.\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({\n ownerId: principal.ownerId,\n domain: row.domain,\n readiness,\n cause: input.cause,\n })\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.1";
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";