SIGN IN SIGN UP

fix(core): explain readiness evidence and emit portable declarations (#92)

## Summary

Two core gaps found by Samva's cutover.

1. Readiness evidence could not explain itself. `ProviderEvidence` and
`PublicDnsEvidence` now carry `values` (what the observer returned for
the requirement's name and type; empty on NXDOMAIN, timeout, or resolver
failure) and `detail` (`null` when satisfied, otherwise the mismatch
summary or the resolver's error text). `HostEvidence.detail` becomes
`NullOr(String)` so all three evidence classes share one absent-value
convention. Readiness rows store evidence as encoded JSON, so storage
adapters need no table change.
2. Bundled declarations were not portable. tsdown wrote a shared
declaration chunk outside `exports` and renamed colliding symbols
(`Interface$7`, `Model$2`), so a consumer compiling with `declaration:
true` failed with TS2883 on any inferred type carrying
`DnsRecord.Model`, `Plan.Model`, or `Verify.Readiness`. Declarations now
come from `tsc -p tsconfig.build.json` into `dist/types/`, one file per
module; `exports[*].types` point at the entry files there. `Testing`
names its conformance case types (`StorageCase`, `ProviderCase`).

## Evidence shape

```ts
PublicDnsEvidence { _tag: "PublicDns"; resolver: string; status: RequirementStatus; values: ReadonlyArray<string>; detail: string | null; observedAt: DateTime.Utc }
ProviderEvidence  { _tag: "Provider";  provider: string; status: RequirementStatus; values: ReadonlyArray<string>; detail: string | null; observedAt: DateTime.Utc }
HostEvidence      { _tag: "Host"; source: string; status: "ok" | "pending" | "failed"; label: string; detail: string | null; observedAt: DateTime.Utc }
```

`detail` examples: `expected edge.acme.dev; found other.acme.dev`, `no
TXT record at _acme.app.example.com`, `google timed out`. `values`
render through `DnsRecord.data` (`priority exchange` for MX, and so on);
opaque records render as JSON.

## Known limit on declaration emit

Per-module declarations make every type nameable that the consumer holds
a local alias for. TypeScript (5.9 and 7.0 alike) still cannot
synthesize `import("domainkit").Principal.Service` through an `export *
as` namespace re-export, so a consumer that exports an inferred
lifecycle effect without importing `Principal` (`export const planned =
Provision.plan(...)`) sees TS2742/TS2883 on `Principal.Service`. The fix
that removes that case is per-module subpath exports
(`domainkit/Principal`, ...), the shape Effect itself uses; that is a
public-surface decision, left for Saatvik. A types-only subpath map
would also satisfy the compiler but lies about runtime.

## Validation

- `bun run --filter domainkit release:check` (typecheck, 130 tests,
build, examples typecheck, 10 artifact tests; the packed consumer now
compiles with `declaration: true` and asserts its emitted declaration
references no `dist/` internals)
- Root `release:check` (lint, rules, format, domainkit, capsuledb,
react)
- Live provider tests are user-owned and were not run
S
Saatvik Arya committed
36d2706a05531ad23bcb4755328e40123f077d49
Parent: fc1459d
Committed by GitHub <noreply@github.com> on 9/3/2026, 7:11:40 PM