domainkit 0.12.2 → 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.
@@ -30,9 +30,27 @@ import * as Reason from "./Reason.ts";
30
30
  import * as Receipt from "./Receipt.ts";
31
31
  import * as Storage from "./Storage.ts";
32
32
  import * as Verify from "./Verify.ts";
33
+ /**
34
+ * What DomainKit already knows about the request when it asks the host who is making it. Only
35
+ * `/callback/:provider` passes one, because it is the only route that points at a flow DomainKit
36
+ * recorded earlier; every other route calls `principal` with the request alone.
37
+ */
38
+ export interface IdentityContext {
39
+ /** The route being served. */
40
+ readonly endpoint: EndpointName;
41
+ /** The flow this callback claims to finish, as DomainKit recorded it when the flow started. */
42
+ readonly continuation: Storage.ContinuationHeader;
43
+ }
33
44
  /** The only service a host must implement for the server. */
34
45
  export interface IdentityService {
35
- readonly principal: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;
46
+ /**
47
+ * `context` is present only on the callback, where a host that keeps per-request tenancy can use
48
+ * `context.continuation.ownerId` to pick the right tenant out of a session that holds several.
49
+ * Ignoring it is safe everywhere: DomainKit checks the principal against the recorded flow on
50
+ * that route regardless, so a host that answers with the wrong one is refused rather than
51
+ * trusted.
52
+ */
53
+ readonly principal: (request: HttpServerRequest.HttpServerRequest, context?: IdentityContext) => Effect.Effect<Principal.Interface, Errors.DomainKitError>;
36
54
  /**
37
55
  * Which routes this principal may reach, checked after `principal` on every request. Fail with
38
56
  * reason `Forbidden` for the 403 a UI expects. Without it every authenticated principal reaches
@@ -324,6 +342,8 @@ export declare const Readiness: Schema.Struct<{
324
342
  readonly attachmentId: Schema.NullOr<Schema.String>;
325
343
  readonly overall: Schema.Literals<readonly ["ready", "pending", "failed"]>;
326
344
  readonly requirements: Schema.$Array<Schema.Struct<{
345
+ /** `Verify.requirementKey(record)`, so a client pairs rows by content, not by position. */
346
+ readonly key: Schema.String;
327
347
  readonly operationId: Schema.NullOr<Schema.brand<Schema.String, "@domainkit/OperationId">>;
328
348
  readonly record: Schema.Union<readonly [typeof DnsRecord.A, typeof DnsRecord.AAAA, typeof DnsRecord.CNAME, typeof DnsRecord.TXT, typeof DnsRecord.MX, typeof DnsRecord.CAA, typeof DnsRecord.NS, typeof DnsRecord.SRV]>;
329
349
  readonly status: Schema.Literals<readonly ["satisfied", "missing", "mismatch", "unknown"]>;
@@ -4490,6 +4510,8 @@ export declare const group: HttpApiGroup.HttpApiGroup<"domainkit", HttpApiEndpoi
4490
4510
  readonly attachmentId: Schema.NullOr<Schema.String>;
4491
4511
  readonly overall: Schema.Literals<readonly ["ready", "pending", "failed"]>;
4492
4512
  readonly requirements: Schema.$Array<Schema.Struct<{
4513
+ /** `Verify.requirementKey(record)`, so a client pairs rows by content, not by position. */
4514
+ readonly key: Schema.String;
4493
4515
  readonly operationId: Schema.NullOr<Schema.brand<Schema.String, "@domainkit/OperationId">>;
4494
4516
  readonly record: Schema.Union<readonly [typeof DnsRecord.A, typeof DnsRecord.AAAA, typeof DnsRecord.CNAME, typeof DnsRecord.TXT, typeof DnsRecord.MX, typeof DnsRecord.CAA, typeof DnsRecord.NS, typeof DnsRecord.SRV]>;
4495
4517
  readonly status: Schema.Literals<readonly ["satisfied", "missing", "mismatch", "unknown"]>;
@@ -5326,6 +5348,431 @@ export declare const group: HttpApiGroup.HttpApiGroup<"domainkit", HttpApiEndpoi
5326
5348
  };
5327
5349
  }>, Schema.TaggedStruct<"DomainKitError", {
5328
5350
  readonly reason: Schema.suspend<Schema.Union<readonly [typeof Reason.InvalidInput, typeof Reason.Unauthenticated, typeof Reason.Forbidden, typeof Reason.NotFound, typeof Reason.Conflict, typeof Reason.Stale, typeof Reason.Expired, typeof Reason.Busy, typeof Reason.ProviderRejected, typeof Reason.ProviderUnavailable, typeof Reason.ProviderConflict, typeof Reason.Unsupported, typeof Reason.Reconnect, typeof Reason.StorageFailed, typeof Reason.CryptoFailed, typeof Reason.ResolverFailed]>>;
5351
+ }>, never, never>>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"readiness", "GET", "/domains/:domain/readiness", Schema.toCodecStringTree<Schema.Struct<{
5352
+ domain: Schema.String;
5353
+ }>>, never, never, never, Schema.toCodecJson<Schema.NullOr<Schema.Struct<{
5354
+ readonly domain: Schema.String;
5355
+ readonly attachmentId: Schema.NullOr<Schema.String>;
5356
+ readonly overall: Schema.Literals<readonly ["ready", "pending", "failed"]>;
5357
+ readonly requirements: Schema.$Array<Schema.Struct<{
5358
+ /** `Verify.requirementKey(record)`, so a client pairs rows by content, not by position. */
5359
+ readonly key: Schema.String;
5360
+ readonly operationId: Schema.NullOr<Schema.brand<Schema.String, "@domainkit/OperationId">>;
5361
+ readonly record: Schema.Union<readonly [typeof DnsRecord.A, typeof DnsRecord.AAAA, typeof DnsRecord.CNAME, typeof DnsRecord.TXT, typeof DnsRecord.MX, typeof DnsRecord.CAA, typeof DnsRecord.NS, typeof DnsRecord.SRV]>;
5362
+ readonly status: Schema.Literals<readonly ["satisfied", "missing", "mismatch", "unknown"]>;
5363
+ readonly evidence: Schema.$Array<Schema.Union<readonly [typeof Verify.ProviderEvidence, typeof Verify.PublicDnsEvidence, typeof Verify.HostEvidence]>>;
5364
+ }>>;
5365
+ readonly host: Schema.$Array<typeof Verify.HostEvidence>;
5366
+ readonly checkedAt: Schema.DateTimeUtcFromString;
5367
+ readonly nextCheckAt: Schema.NullOr<Schema.DateTimeUtcFromString>;
5368
+ }>>>, Schema.toCodecJson<Schema.decodeTo<Schema.declareConstructor<Errors.DomainKitError, {
5369
+ readonly _tag: "DomainKitError";
5370
+ readonly reason: {
5371
+ readonly _tag: "InvalidInput";
5372
+ readonly message: string;
5373
+ readonly field?: string;
5374
+ } | {
5375
+ readonly _tag: "Unauthenticated";
5376
+ readonly message: string;
5377
+ } | {
5378
+ readonly _tag: "Forbidden";
5379
+ readonly message: string;
5380
+ } | {
5381
+ readonly _tag: "NotFound";
5382
+ readonly entity: "approval" | "attachment" | "authorization" | "connection" | "continuation" | "plan" | "provider" | "receipt" | "record" | "zone";
5383
+ readonly id: string;
5384
+ } | {
5385
+ readonly _tag: "Conflict";
5386
+ readonly planId: string;
5387
+ readonly operations: readonly {
5388
+ readonly _tag: "Conflict";
5389
+ readonly id: string;
5390
+ readonly record: {
5391
+ readonly _tag: "A";
5392
+ readonly name: string;
5393
+ readonly ttl: number | null;
5394
+ readonly policy: "append" | "exclusive";
5395
+ readonly purpose?: string;
5396
+ readonly address: string;
5397
+ } | {
5398
+ readonly _tag: "AAAA";
5399
+ readonly name: string;
5400
+ readonly ttl: number | null;
5401
+ readonly policy: "append" | "exclusive";
5402
+ readonly purpose?: string;
5403
+ readonly address: string;
5404
+ } | {
5405
+ readonly _tag: "CNAME";
5406
+ readonly name: string;
5407
+ readonly ttl: number | null;
5408
+ readonly policy: "append" | "exclusive";
5409
+ readonly purpose?: string;
5410
+ readonly target: string;
5411
+ } | {
5412
+ readonly _tag: "TXT";
5413
+ readonly name: string;
5414
+ readonly ttl: number | null;
5415
+ readonly policy: "append" | "exclusive";
5416
+ readonly purpose?: string;
5417
+ readonly value: string;
5418
+ } | {
5419
+ readonly _tag: "MX";
5420
+ readonly name: string;
5421
+ readonly ttl: number | null;
5422
+ readonly policy: "append" | "exclusive";
5423
+ readonly purpose?: string;
5424
+ readonly exchange: string;
5425
+ readonly priority: number;
5426
+ } | {
5427
+ readonly _tag: "CAA";
5428
+ readonly name: string;
5429
+ readonly ttl: number | null;
5430
+ readonly policy: "append" | "exclusive";
5431
+ readonly purpose?: string;
5432
+ readonly flags: number;
5433
+ readonly tag: string;
5434
+ readonly value: string;
5435
+ } | {
5436
+ readonly _tag: "NS";
5437
+ readonly name: string;
5438
+ readonly ttl: number | null;
5439
+ readonly policy: "append" | "exclusive";
5440
+ readonly purpose?: string;
5441
+ readonly nameserver: string;
5442
+ } | {
5443
+ readonly _tag: "SRV";
5444
+ readonly name: string;
5445
+ readonly ttl: number | null;
5446
+ readonly policy: "append" | "exclusive";
5447
+ readonly purpose?: string;
5448
+ readonly target: string;
5449
+ readonly port: number;
5450
+ readonly priority: number;
5451
+ readonly weight: number;
5452
+ };
5453
+ readonly existing: readonly ({
5454
+ readonly _tag: "A";
5455
+ readonly name: string;
5456
+ readonly ttl: number | null;
5457
+ readonly policy: "append" | "exclusive";
5458
+ readonly purpose?: string;
5459
+ readonly address: string;
5460
+ } | {
5461
+ readonly _tag: "AAAA";
5462
+ readonly name: string;
5463
+ readonly ttl: number | null;
5464
+ readonly policy: "append" | "exclusive";
5465
+ readonly purpose?: string;
5466
+ readonly address: string;
5467
+ } | {
5468
+ readonly _tag: "CNAME";
5469
+ readonly name: string;
5470
+ readonly ttl: number | null;
5471
+ readonly policy: "append" | "exclusive";
5472
+ readonly purpose?: string;
5473
+ readonly target: string;
5474
+ } | {
5475
+ readonly _tag: "TXT";
5476
+ readonly name: string;
5477
+ readonly ttl: number | null;
5478
+ readonly policy: "append" | "exclusive";
5479
+ readonly purpose?: string;
5480
+ readonly value: string;
5481
+ } | {
5482
+ readonly _tag: "MX";
5483
+ readonly name: string;
5484
+ readonly ttl: number | null;
5485
+ readonly policy: "append" | "exclusive";
5486
+ readonly purpose?: string;
5487
+ readonly exchange: string;
5488
+ readonly priority: number;
5489
+ } | {
5490
+ readonly _tag: "CAA";
5491
+ readonly name: string;
5492
+ readonly ttl: number | null;
5493
+ readonly policy: "append" | "exclusive";
5494
+ readonly purpose?: string;
5495
+ readonly flags: number;
5496
+ readonly tag: string;
5497
+ readonly value: string;
5498
+ } | {
5499
+ readonly _tag: "NS";
5500
+ readonly name: string;
5501
+ readonly ttl: number | null;
5502
+ readonly policy: "append" | "exclusive";
5503
+ readonly purpose?: string;
5504
+ readonly nameserver: string;
5505
+ } | {
5506
+ readonly _tag: "SRV";
5507
+ readonly name: string;
5508
+ readonly ttl: number | null;
5509
+ readonly policy: "append" | "exclusive";
5510
+ readonly purpose?: string;
5511
+ readonly target: string;
5512
+ readonly port: number;
5513
+ readonly priority: number;
5514
+ readonly weight: number;
5515
+ } | {
5516
+ readonly _tag: "Opaque";
5517
+ readonly name: string;
5518
+ readonly type: string;
5519
+ readonly raw: unknown;
5520
+ })[];
5521
+ readonly reason: "cname-collision" | "exclusive-name" | "missing" | "opaque" | "value-mismatch";
5522
+ }[];
5523
+ } | {
5524
+ readonly _tag: "Stale";
5525
+ readonly planId: string;
5526
+ readonly digest: string;
5527
+ } | {
5528
+ readonly _tag: "Expired";
5529
+ readonly entity: "approval" | "continuation" | "credential" | "plan";
5530
+ readonly id: string;
5531
+ } | {
5532
+ readonly _tag: "Busy";
5533
+ readonly key: string;
5534
+ } | {
5535
+ readonly _tag: "ProviderRejected";
5536
+ readonly provider: string;
5537
+ readonly code?: string;
5538
+ readonly message: string;
5539
+ } | {
5540
+ readonly _tag: "ProviderUnavailable";
5541
+ readonly provider: string;
5542
+ readonly retryAfterMs?: number;
5543
+ readonly message: string;
5544
+ } | {
5545
+ readonly _tag: "ProviderConflict";
5546
+ readonly provider: string;
5547
+ readonly code?: string;
5548
+ readonly message: string;
5549
+ } | {
5550
+ readonly _tag: "Unsupported";
5551
+ readonly provider: string;
5552
+ readonly operation: string;
5553
+ readonly message: string;
5554
+ } | {
5555
+ readonly _tag: "Reconnect";
5556
+ readonly provider: string;
5557
+ readonly connectionId: string;
5558
+ } | {
5559
+ readonly _tag: "StorageFailed";
5560
+ readonly operation: string;
5561
+ readonly message: string;
5562
+ } | {
5563
+ readonly _tag: "CryptoFailed";
5564
+ readonly operation: "digest" | "open" | "seal";
5565
+ } | {
5566
+ readonly _tag: "ResolverFailed";
5567
+ readonly resolver: string;
5568
+ readonly message: string;
5569
+ };
5570
+ }, readonly [Schema.TaggedStruct<"DomainKitError", {
5571
+ readonly reason: Schema.suspend<Schema.Union<readonly [typeof Reason.InvalidInput, typeof Reason.Unauthenticated, typeof Reason.Forbidden, typeof Reason.NotFound, typeof Reason.Conflict, typeof Reason.Stale, typeof Reason.Expired, typeof Reason.Busy, typeof Reason.ProviderRejected, typeof Reason.ProviderUnavailable, typeof Reason.ProviderConflict, typeof Reason.Unsupported, typeof Reason.Reconnect, typeof Reason.StorageFailed, typeof Reason.CryptoFailed, typeof Reason.ResolverFailed]>>;
5572
+ }>], {
5573
+ readonly _tag: "DomainKitError";
5574
+ readonly reason: {
5575
+ readonly _tag: "InvalidInput";
5576
+ readonly message: string;
5577
+ readonly field?: string;
5578
+ } | {
5579
+ readonly _tag: "Unauthenticated";
5580
+ readonly message: string;
5581
+ } | {
5582
+ readonly _tag: "Forbidden";
5583
+ readonly message: string;
5584
+ } | {
5585
+ readonly _tag: "NotFound";
5586
+ readonly entity: "approval" | "attachment" | "authorization" | "connection" | "continuation" | "plan" | "provider" | "receipt" | "record" | "zone";
5587
+ readonly id: string;
5588
+ } | {
5589
+ readonly _tag: "Conflict";
5590
+ readonly planId: string & import("effect/Brand").Brand<"@domainkit/PlanId">;
5591
+ readonly operations: readonly {
5592
+ readonly _tag: "Conflict";
5593
+ readonly id: string & import("effect/Brand").Brand<"@domainkit/OperationId">;
5594
+ readonly record: {
5595
+ readonly _tag: "A";
5596
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5597
+ readonly ttl: number | null;
5598
+ readonly policy: "append" | "exclusive";
5599
+ readonly purpose?: string;
5600
+ readonly address: string;
5601
+ } | {
5602
+ readonly _tag: "AAAA";
5603
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5604
+ readonly ttl: number | null;
5605
+ readonly policy: "append" | "exclusive";
5606
+ readonly purpose?: string;
5607
+ readonly address: string;
5608
+ } | {
5609
+ readonly _tag: "CNAME";
5610
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5611
+ readonly ttl: number | null;
5612
+ readonly policy: "append" | "exclusive";
5613
+ readonly purpose?: string;
5614
+ readonly target: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5615
+ } | {
5616
+ readonly _tag: "TXT";
5617
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5618
+ readonly ttl: number | null;
5619
+ readonly policy: "append" | "exclusive";
5620
+ readonly purpose?: string;
5621
+ readonly value: string;
5622
+ } | {
5623
+ readonly _tag: "MX";
5624
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5625
+ readonly ttl: number | null;
5626
+ readonly policy: "append" | "exclusive";
5627
+ readonly purpose?: string;
5628
+ readonly exchange: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5629
+ readonly priority: number;
5630
+ } | {
5631
+ readonly _tag: "CAA";
5632
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5633
+ readonly ttl: number | null;
5634
+ readonly policy: "append" | "exclusive";
5635
+ readonly purpose?: string;
5636
+ readonly flags: number;
5637
+ readonly tag: string;
5638
+ readonly value: string;
5639
+ } | {
5640
+ readonly _tag: "NS";
5641
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5642
+ readonly ttl: number | null;
5643
+ readonly policy: "append" | "exclusive";
5644
+ readonly purpose?: string;
5645
+ readonly nameserver: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5646
+ } | {
5647
+ readonly _tag: "SRV";
5648
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5649
+ readonly ttl: number | null;
5650
+ readonly policy: "append" | "exclusive";
5651
+ readonly purpose?: string;
5652
+ readonly target: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5653
+ readonly port: number;
5654
+ readonly priority: number;
5655
+ readonly weight: number;
5656
+ };
5657
+ readonly existing: readonly ({
5658
+ readonly _tag: "A";
5659
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5660
+ readonly ttl: number | null;
5661
+ readonly policy: "append" | "exclusive";
5662
+ readonly purpose?: string;
5663
+ readonly address: string;
5664
+ } | {
5665
+ readonly _tag: "AAAA";
5666
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5667
+ readonly ttl: number | null;
5668
+ readonly policy: "append" | "exclusive";
5669
+ readonly purpose?: string;
5670
+ readonly address: string;
5671
+ } | {
5672
+ readonly _tag: "CNAME";
5673
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5674
+ readonly ttl: number | null;
5675
+ readonly policy: "append" | "exclusive";
5676
+ readonly purpose?: string;
5677
+ readonly target: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5678
+ } | {
5679
+ readonly _tag: "TXT";
5680
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5681
+ readonly ttl: number | null;
5682
+ readonly policy: "append" | "exclusive";
5683
+ readonly purpose?: string;
5684
+ readonly value: string;
5685
+ } | {
5686
+ readonly _tag: "MX";
5687
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5688
+ readonly ttl: number | null;
5689
+ readonly policy: "append" | "exclusive";
5690
+ readonly purpose?: string;
5691
+ readonly exchange: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5692
+ readonly priority: number;
5693
+ } | {
5694
+ readonly _tag: "CAA";
5695
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5696
+ readonly ttl: number | null;
5697
+ readonly policy: "append" | "exclusive";
5698
+ readonly purpose?: string;
5699
+ readonly flags: number;
5700
+ readonly tag: string;
5701
+ readonly value: string;
5702
+ } | {
5703
+ readonly _tag: "NS";
5704
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5705
+ readonly ttl: number | null;
5706
+ readonly policy: "append" | "exclusive";
5707
+ readonly purpose?: string;
5708
+ readonly nameserver: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5709
+ } | {
5710
+ readonly _tag: "SRV";
5711
+ readonly name: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5712
+ readonly ttl: number | null;
5713
+ readonly policy: "append" | "exclusive";
5714
+ readonly purpose?: string;
5715
+ readonly target: string & import("effect/Brand").Brand<"@domainkit/DomainName">;
5716
+ readonly port: number;
5717
+ readonly priority: number;
5718
+ readonly weight: number;
5719
+ } | {
5720
+ readonly _tag: "Opaque";
5721
+ readonly name: string;
5722
+ readonly type: string;
5723
+ readonly raw: unknown;
5724
+ })[];
5725
+ readonly reason: "cname-collision" | "exclusive-name" | "missing" | "opaque" | "value-mismatch";
5726
+ }[];
5727
+ } | {
5728
+ readonly _tag: "Stale";
5729
+ readonly planId: string & import("effect/Brand").Brand<"@domainkit/PlanId">;
5730
+ readonly digest: string & import("effect/Brand").Brand<"@domainkit/Digest">;
5731
+ } | {
5732
+ readonly _tag: "Expired";
5733
+ readonly entity: "approval" | "continuation" | "credential" | "plan";
5734
+ readonly id: string;
5735
+ } | {
5736
+ readonly _tag: "Busy";
5737
+ readonly key: string;
5738
+ } | {
5739
+ readonly _tag: "ProviderRejected";
5740
+ readonly provider: string;
5741
+ readonly code?: string;
5742
+ readonly message: string;
5743
+ } | {
5744
+ readonly _tag: "ProviderUnavailable";
5745
+ readonly provider: string;
5746
+ readonly retryAfterMs?: number;
5747
+ readonly message: string;
5748
+ } | {
5749
+ readonly _tag: "ProviderConflict";
5750
+ readonly provider: string;
5751
+ readonly code?: string;
5752
+ readonly message: string;
5753
+ } | {
5754
+ readonly _tag: "Unsupported";
5755
+ readonly provider: string;
5756
+ readonly operation: string;
5757
+ readonly message: string;
5758
+ } | {
5759
+ readonly _tag: "Reconnect";
5760
+ readonly provider: string;
5761
+ readonly connectionId: string;
5762
+ } | {
5763
+ readonly _tag: "StorageFailed";
5764
+ readonly operation: string;
5765
+ readonly message: string;
5766
+ } | {
5767
+ readonly _tag: "CryptoFailed";
5768
+ readonly operation: "digest" | "open" | "seal";
5769
+ } | {
5770
+ readonly _tag: "ResolverFailed";
5771
+ readonly resolver: string;
5772
+ readonly message: string;
5773
+ };
5774
+ }>, Schema.TaggedStruct<"DomainKitError", {
5775
+ readonly reason: Schema.suspend<Schema.Union<readonly [typeof Reason.InvalidInput, typeof Reason.Unauthenticated, typeof Reason.Forbidden, typeof Reason.NotFound, typeof Reason.Conflict, typeof Reason.Stale, typeof Reason.Expired, typeof Reason.Busy, typeof Reason.ProviderRejected, typeof Reason.ProviderUnavailable, typeof Reason.ProviderConflict, typeof Reason.Unsupported, typeof Reason.Reconnect, typeof Reason.StorageFailed, typeof Reason.CryptoFailed, typeof Reason.ResolverFailed]>>;
5329
5776
  }>, never, never>>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"receipt", "GET", "/receipts/:receiptId", Schema.toCodecStringTree<Schema.Struct<{
5330
5777
  receiptId: Schema.brand<Schema.String, "@domainkit/ReceiptId">;
5331
5778
  }>>, never, never, never, Schema.toCodecJson<typeof Receipt.Model>, Schema.toCodecJson<Schema.decodeTo<Schema.declareConstructor<Errors.DomainKitError, {
@@ -80,6 +80,18 @@ declare const Continuation_base: Schema.Class<Continuation, Schema.Struct<{
80
80
  /** Interactive-flow state between `Connect.start` and `Connect.complete`. */
81
81
  export declare class Continuation extends Continuation_base {
82
82
  }
83
+ declare const ContinuationHeader_base: Schema.Class<ContinuationHeader, Schema.Struct<{
84
+ readonly ownerId: Schema.String;
85
+ readonly actorId: Schema.String;
86
+ readonly provider: Schema.String;
87
+ readonly expiresAt: Schema.DateTimeUtcFromString;
88
+ }>, {}>;
89
+ /**
90
+ * Who a continuation belongs to, without the continuation itself. `payload` has no place here: it
91
+ * carries the PKCE `codeVerifier`, which only the owner-scoped read may reach.
92
+ */
93
+ export declare class ContinuationHeader extends ContinuationHeader_base {
94
+ }
83
95
  export declare const AttemptStatus: Schema.Literals<readonly ["planned", "approved", "applying", "complete", "partial", "failed", "expired", "rejected"]>;
84
96
  export type AttemptStatus = typeof AttemptStatus.Type;
85
97
  /** Who declined the plan, why, and when. Terminal: the domain needs a new plan. */
@@ -192,6 +204,16 @@ export interface Interface {
192
204
  readonly put: (continuation: Continuation) => Fx<void>;
193
205
  /** Read without spending; a late read fails `Expired`. */
194
206
  readonly get: (id: string) => Fx<Continuation>;
207
+ /**
208
+ * The flow's owner, actor, provider, and expiry, without a principal and without spending the
209
+ * row. The one read in this interface that is not tenant-scoped, because `domainkit/server`
210
+ * has to learn whose flow a provider callback finishes before it can resolve the principal to
211
+ * scope by; the callback carries a continuation id and a session cookie and nothing else.
212
+ *
213
+ * A missing row fails `NotFound` and a stale one fails `Expired`, exactly as `get` does, so an
214
+ * expired id never reaches a host. `payload` is out of reach here by construction.
215
+ */
216
+ readonly header: (id: string) => Effect.Effect<ContinuationHeader, Errors.DomainKitError>;
195
217
  /** Exactly-once: the second consume of the same id fails `NotFound`; a late one fails `Expired`. */
196
218
  readonly consume: (id: string) => Fx<Continuation>;
197
219
  };
@@ -287,6 +309,8 @@ export interface AsyncInterface {
287
309
  readonly continuations: {
288
310
  readonly put: (principal: Principal.Interface, continuation: Continuation) => Promise<void>;
289
311
  readonly get: (principal: Principal.Interface, id: string) => Promise<Continuation>;
312
+ /** Takes no principal: see `Interface.continuations.header`. Never returns `payload`. */
313
+ readonly header: (id: string) => Promise<ContinuationHeader>;
290
314
  readonly consume: (principal: Principal.Interface, id: string) => Promise<Continuation>;
291
315
  };
292
316
  readonly attempts: {
@@ -95,6 +95,8 @@ export interface VerificationGroup {
95
95
  readonly observe: (domain: string, options?: {
96
96
  readonly requirements?: ReadonlyArray<DnsRecord.Model>;
97
97
  }) => Fx<Readiness>;
98
+ /** The stored readiness, without observing. `null` until the domain has been observed once. */
99
+ readonly latest: (domain: string) => Fx<Readiness | null>;
98
100
  }
99
101
  export interface CleanupGroup {
100
102
  readonly plan: (receiptId: Receipt.ReceiptId) => Fx<Plan.Model>;
@@ -42,6 +42,8 @@ export declare class HostEvidence extends HostEvidence_base {
42
42
  export declare const Evidence: Schema.Union<readonly [typeof ProviderEvidence, typeof PublicDnsEvidence, typeof HostEvidence]>;
43
43
  export type Evidence = typeof Evidence.Type;
44
44
  export interface Requirement {
45
+ /** `requirementKey(record)`: what a host pairs its own row against, by content. */
46
+ readonly key: string;
45
47
  readonly operationId: Plan.OperationId | null;
46
48
  readonly record: DnsRecord.Model;
47
49
  readonly status: Storage.RequirementStatus;
@@ -81,6 +83,32 @@ export interface Interface {
81
83
  declare const Service_base: Context.ServiceClass<Service, "@domainkit/Verify", Interface>;
82
84
  export declare class Service extends Service_base {
83
85
  }
86
+ /**
87
+ * A requirement's identity: the record's type, name, and data, and nothing else. It is what a host
88
+ * pairs its own rows against readiness by, instead of trusting the order of the array.
89
+ * `@domainkit/react`'s `Records.identity` is this function.
90
+ */
91
+ export declare const requirementKey: (record: DnsRecord.Model) => string;
92
+ /** Counts across a readiness's requirements. `observed` is false when there is no readiness yet. */
93
+ export interface Summary {
94
+ readonly observed: boolean;
95
+ readonly total: number;
96
+ readonly satisfied: number;
97
+ readonly missing: number;
98
+ readonly mismatch: number;
99
+ readonly unknown: number;
100
+ }
101
+ /** The least a value has to carry to be summarised: core `Readiness` and the wire shape both do. */
102
+ export interface Summarisable {
103
+ readonly requirements: ReadonlyArray<{
104
+ readonly status: Storage.RequirementStatus;
105
+ }>;
106
+ }
107
+ /**
108
+ * How a readiness stands, as counts. Pure and total: a host renders "3 of 4 found" from the stored
109
+ * fact without deciding what an absent readiness means, because `observed` says so.
110
+ */
111
+ export declare const summary: (readiness: Summarisable | null) => Summary;
84
112
  export interface PolicyShape {
85
113
  /** Delay before the next check, given time since the first pending observation. Default ladder: 15s, 1m, 5m, 30m. */
86
114
  readonly backoff: (pendingForMs: number) => number;
@@ -93,6 +121,40 @@ export declare const defaults: PolicyShape;
93
121
  declare const Policy_base: Context.Reference<PolicyShape>;
94
122
  export declare class Policy extends Policy_base {
95
123
  }
124
+ /** Why readiness was written: a DNS observation, or host evidence merged into the stored row. */
125
+ export type Cause = "observe" | "evidence";
126
+ export interface ReadinessChanged {
127
+ readonly domain: string;
128
+ readonly readiness: Readiness;
129
+ readonly cause: Cause;
130
+ }
131
+ export interface ObserverShape {
132
+ /**
133
+ * Called once per stored readiness, after the write. A host that projects readiness onto its own
134
+ * rows, wakes a durable job at `nextCheckAt`, or notifies a customer hangs it here instead of
135
+ * mirroring the fact at every call site.
136
+ *
137
+ * Events for one domain are not ordered against each other: two observations that overlap both
138
+ * store, last write wins in `Storage`, and their callbacks can finish in either order. A
139
+ * projection compares `readiness.checkedAt`, which is the moment the row was written, and keeps
140
+ * the later one; `latest` remains the authority.
141
+ */
142
+ readonly readinessChanged: (event: ReadinessChanged) => Effect.Effect<void, unknown>;
143
+ }
144
+ declare const Observer_base: Context.Reference<ObserverShape>;
145
+ /**
146
+ * The host seam for "DomainKit wrote readiness". The default does nothing; provide one with
147
+ * `Effect.provideService(Verify.Observer, ...)` or `Layer.succeed(Verify.Observer, ...)` over
148
+ * `DomainKit.layer`, the same way `Policy` is overridden.
149
+ *
150
+ * It runs after `storage.readiness.put` returns, outside any transaction, because `Storage`
151
+ * exposes none. A failure or defect in the observer is logged and swallowed: the observation is
152
+ * already durable, and losing it because a projection failed would make the stored fact depend on
153
+ * the host's side effects. A host that needs the projection to be reliable enqueues durable work
154
+ * here rather than doing the work inline.
155
+ */
156
+ export declare class Observer extends Observer_base {
157
+ }
96
158
  /** A requirement is satisfied by an exact match; an exclusive one is contradicted by any same-set record. */
97
159
  export declare const statusAgainst: (record: DnsRecord.Model, observed: ReadonlyArray<DnsRecord.Observed>) => Storage.RequirementStatus;
98
160
  export declare const make: Effect.Effect<Interface, never, Storage.Service | Connect.Service | Resolver.Service>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainkit",
3
- "version": "0.12.2",
3
+ "version": "0.14.0",
4
4
  "description": "Build reviewable DNS plans for custom domains in TypeScript",
5
5
  "keywords": [
6
6
  "custom-domains",