@spacefast/common 0.0.2
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/README.md +42 -0
- package/dist/brand.d.ts +15 -0
- package/dist/brand.js +24 -0
- package/dist/config/domains.d.ts +30 -0
- package/dist/config/domains.js +75 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +3 -0
- package/dist/contracts/abuse.d.ts +93 -0
- package/dist/contracts/abuse.js +50 -0
- package/dist/contracts/access.d.ts +409 -0
- package/dist/contracts/access.js +396 -0
- package/dist/contracts/account.d.ts +28 -0
- package/dist/contracts/account.js +54 -0
- package/dist/contracts/activity.d.ts +118 -0
- package/dist/contracts/activity.js +180 -0
- package/dist/contracts/analytics.d.ts +56 -0
- package/dist/contracts/analytics.js +91 -0
- package/dist/contracts/annotations.d.ts +327 -0
- package/dist/contracts/annotations.js +234 -0
- package/dist/contracts/api-keys.d.ts +517 -0
- package/dist/contracts/api-keys.js +235 -0
- package/dist/contracts/archives.d.ts +332 -0
- package/dist/contracts/archives.js +120 -0
- package/dist/contracts/auth.d.ts +86 -0
- package/dist/contracts/auth.js +82 -0
- package/dist/contracts/beta.d.ts +106 -0
- package/dist/contracts/beta.js +104 -0
- package/dist/contracts/billing.d.ts +328 -0
- package/dist/contracts/billing.js +203 -0
- package/dist/contracts/builds.d.ts +504 -0
- package/dist/contracts/builds.js +248 -0
- package/dist/contracts/channels.d.ts +49 -0
- package/dist/contracts/channels.js +51 -0
- package/dist/contracts/common.d.ts +238 -0
- package/dist/contracts/common.js +175 -0
- package/dist/contracts/deployments.d.ts +574 -0
- package/dist/contracts/deployments.js +97 -0
- package/dist/contracts/device-auth.d.ts +194 -0
- package/dist/contracts/device-auth.js +162 -0
- package/dist/contracts/domains.d.ts +6115 -0
- package/dist/contracts/domains.js +1435 -0
- package/dist/contracts/enums.d.ts +196 -0
- package/dist/contracts/enums.js +211 -0
- package/dist/contracts/error-code-meta.d.ts +1560 -0
- package/dist/contracts/error-code-meta.js +400 -0
- package/dist/contracts/error-codes.d.ts +13 -0
- package/dist/contracts/error-codes.js +416 -0
- package/dist/contracts/events.d.ts +398 -0
- package/dist/contracts/events.js +320 -0
- package/dist/contracts/fast-domain-search.d.ts +19 -0
- package/dist/contracts/fast-domain-search.js +22 -0
- package/dist/contracts/features.d.ts +240 -0
- package/dist/contracts/features.js +406 -0
- package/dist/contracts/git.d.ts +288 -0
- package/dist/contracts/git.js +116 -0
- package/dist/contracts/ids.d.ts +35 -0
- package/dist/contracts/ids.js +45 -0
- package/dist/contracts/intercom.d.ts +6 -0
- package/dist/contracts/intercom.js +5 -0
- package/dist/contracts/internal.d.ts +103 -0
- package/dist/contracts/internal.js +92 -0
- package/dist/contracts/mcp.d.ts +788 -0
- package/dist/contracts/mcp.js +161 -0
- package/dist/contracts/me.d.ts +81 -0
- package/dist/contracts/me.js +104 -0
- package/dist/contracts/oauth-resources.d.ts +30 -0
- package/dist/contracts/oauth-resources.js +61 -0
- package/dist/contracts/operations.d.ts +54 -0
- package/dist/contracts/operations.js +14 -0
- package/dist/contracts/plan-policy.d.ts +85 -0
- package/dist/contracts/plan-policy.js +75 -0
- package/dist/contracts/platform.d.ts +359 -0
- package/dist/contracts/platform.js +216 -0
- package/dist/contracts/push-new.d.ts +156 -0
- package/dist/contracts/push-new.js +52 -0
- package/dist/contracts/quotas.d.ts +16 -0
- package/dist/contracts/quotas.js +36 -0
- package/dist/contracts/repository-connections.d.ts +231 -0
- package/dist/contracts/repository-connections.js +106 -0
- package/dist/contracts/resources.d.ts +529 -0
- package/dist/contracts/resources.js +209 -0
- package/dist/contracts/routes.d.ts +45 -0
- package/dist/contracts/routes.js +31 -0
- package/dist/contracts/runtime-api.d.ts +449 -0
- package/dist/contracts/runtime-api.js +513 -0
- package/dist/contracts/sites.d.ts +393 -0
- package/dist/contracts/sites.js +121 -0
- package/dist/contracts/slugs.d.ts +31 -0
- package/dist/contracts/slugs.js +47 -0
- package/dist/contracts/space-config.d.ts +216 -0
- package/dist/contracts/space-config.js +237 -0
- package/dist/contracts/space-theme.d.ts +25 -0
- package/dist/contracts/space-theme.js +49 -0
- package/dist/contracts/spaces.d.ts +2760 -0
- package/dist/contracts/spaces.js +589 -0
- package/dist/contracts/superadmin-activity.d.ts +205 -0
- package/dist/contracts/superadmin-activity.js +107 -0
- package/dist/contracts/superadmin-emails.d.ts +212 -0
- package/dist/contracts/superadmin-emails.js +112 -0
- package/dist/contracts/superadmin-queues.d.ts +414 -0
- package/dist/contracts/superadmin-queues.js +202 -0
- package/dist/contracts/superadmin-runtime.d.ts +27 -0
- package/dist/contracts/superadmin-runtime.js +38 -0
- package/dist/contracts/superadmin-spaces.d.ts +429 -0
- package/dist/contracts/superadmin-spaces.js +82 -0
- package/dist/contracts/superadmin-tenants.d.ts +180 -0
- package/dist/contracts/superadmin-tenants.js +117 -0
- package/dist/contracts/superadmin.d.ts +654 -0
- package/dist/contracts/superadmin.js +309 -0
- package/dist/contracts/tags.d.ts +1140 -0
- package/dist/contracts/tags.js +1050 -0
- package/dist/contracts/teams.d.ts +233 -0
- package/dist/contracts/teams.js +174 -0
- package/dist/contracts/telemetry.d.ts +2 -0
- package/dist/contracts/telemetry.js +1 -0
- package/dist/contracts/transfers.d.ts +105 -0
- package/dist/contracts/transfers.js +48 -0
- package/dist/contracts/variables.d.ts +189 -0
- package/dist/contracts/variables.js +82 -0
- package/dist/contracts/webhooks.d.ts +159 -0
- package/dist/contracts/webhooks.js +59 -0
- package/dist/contracts/zero.d.ts +1538 -0
- package/dist/contracts/zero.js +755 -0
- package/dist/docs/agent-prose.d.ts +35 -0
- package/dist/docs/agent-prose.js +153 -0
- package/dist/docs/agent-setup.d.ts +6 -0
- package/dist/docs/agent-setup.js +164 -0
- package/dist/docs/agent-solutions.d.ts +110 -0
- package/dist/docs/agent-solutions.js +181 -0
- package/dist/docs/error-docs.d.ts +12 -0
- package/dist/docs/error-docs.js +1601 -0
- package/dist/docs/skill-distribution.d.ts +12 -0
- package/dist/docs/skill-distribution.js +18 -0
- package/dist/domain-exploration.d.ts +108 -0
- package/dist/domain-exploration.js +488 -0
- package/dist/domain-normalize.d.ts +1 -0
- package/dist/domain-normalize.js +14 -0
- package/dist/domain-zones.d.ts +1 -0
- package/dist/domain-zones.js +5272 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/access-policy.d.ts +39 -0
- package/dist/utils/access-policy.js +232 -0
- package/dist/utils/api.d.ts +4 -0
- package/dist/utils/api.js +13 -0
- package/dist/utils/auth-redirect.d.ts +1 -0
- package/dist/utils/auth-redirect.js +48 -0
- package/dist/utils/auth.d.ts +5 -0
- package/dist/utils/auth.js +13 -0
- package/dist/utils/build-settings.d.ts +49 -0
- package/dist/utils/build-settings.js +366 -0
- package/dist/utils/claim-token.d.ts +1 -0
- package/dist/utils/claim-token.js +9 -0
- package/dist/utils/collections.d.ts +1 -0
- package/dist/utils/collections.js +5 -0
- package/dist/utils/concurrency.d.ts +1 -0
- package/dist/utils/concurrency.js +18 -0
- package/dist/utils/content-type.d.ts +10 -0
- package/dist/utils/content-type.js +145 -0
- package/dist/utils/error-display.d.ts +7 -0
- package/dist/utils/error-display.js +42 -0
- package/dist/utils/error.d.ts +1 -0
- package/dist/utils/error.js +3 -0
- package/dist/utils/generate-space-name.d.ts +14 -0
- package/dist/utils/generate-space-name.js +95 -0
- package/dist/utils/git-repository.d.ts +5 -0
- package/dist/utils/git-repository.js +99 -0
- package/dist/utils/guards.d.ts +1 -0
- package/dist/utils/guards.js +3 -0
- package/dist/utils/publish-detection.d.ts +10 -0
- package/dist/utils/publish-detection.js +118 -0
- package/dist/utils/publish-policy.d.ts +69 -0
- package/dist/utils/publish-policy.js +429 -0
- package/dist/utils/query-client.d.ts +7 -0
- package/dist/utils/query-client.js +21 -0
- package/dist/utils/query-keys.d.ts +75 -0
- package/dist/utils/query-keys.js +78 -0
- package/dist/utils/runtime-paths.d.ts +7 -0
- package/dist/utils/runtime-paths.js +28 -0
- package/dist/utils/runtime-upload.d.ts +19 -0
- package/dist/utils/runtime-upload.js +27 -0
- package/dist/utils/slug.d.ts +5 -0
- package/dist/utils/slug.js +13 -0
- package/dist/utils/space-config.d.ts +75 -0
- package/dist/utils/space-config.js +415 -0
- package/dist/utils/space-theme.d.ts +16 -0
- package/dist/utils/space-theme.js +89 -0
- package/dist/utils/static-runtime-policy.d.ts +17 -0
- package/dist/utils/static-runtime-policy.js +103 -0
- package/dist/utils/upload-session.d.ts +34 -0
- package/dist/utils/upload-session.js +69 -0
- package/dist/utils/urls.d.ts +2 -0
- package/dist/utils/urls.js +7 -0
- package/dist/utils/version-path.d.ts +6 -0
- package/dist/utils/version-path.js +51 -0
- package/dist/utils/wpcom-auth-redirects.d.ts +14 -0
- package/dist/utils/wpcom-auth-redirects.js +49 -0
- package/dist/utils/zero-realtime-signature.d.ts +12 -0
- package/dist/utils/zero-realtime-signature.js +45 -0
- package/dist/vocabulary.d.ts +230 -0
- package/dist/vocabulary.js +218 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AnyObjectSchema, baseDiagnosticSchema, CursorPaginationSchema, cursorListQuerySchema, DnsInstructionSchema, mutationEnvelope, requiredActionsSchema, } from "./common.js";
|
|
3
|
+
import { PrincipalRefSchema } from "./resources.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Domain resource (internal-docs/platform.md L2387-2431). Pure inventory: a
|
|
6
|
+
// canonical name owned by a tenant-local principal, plus the registrar/dns
|
|
7
|
+
// facets where they apply and the verification read-model. Routing intent
|
|
8
|
+
// lives on DomainBinding, provider assignment on SiteDomainAssignment.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Registration (who you pay) facet — present only on apex records.
|
|
11
|
+
export const domainRegistrarFacetSchema = z.object({
|
|
12
|
+
mode: z
|
|
13
|
+
.enum(["stattic", "external"])
|
|
14
|
+
.describe("Whether Spacefast manages this registration or an external registrar holds it."),
|
|
15
|
+
provider: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Wrapped registrar provider key when mode is "stattic", e.g. "wpcom".'),
|
|
19
|
+
status: z
|
|
20
|
+
.enum(["active", "pending_purchase", "pending_transfer_in", "expiring", "expired", "locked"])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Registration lifecycle status; `expiring` is derived from the expiry date."),
|
|
23
|
+
expiresAt: z.string().datetime().optional().describe("When the registration expires."),
|
|
24
|
+
redemptionEndsAt: z
|
|
25
|
+
.string()
|
|
26
|
+
.datetime()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("End of the provider grace/redemption window after expiry."),
|
|
29
|
+
autoRenew: z.boolean().optional().describe("Whether the registration renews automatically."),
|
|
30
|
+
transferLock: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Whether outbound transfers are locked at the registrar."),
|
|
34
|
+
});
|
|
35
|
+
// DNS hosting (who answers queries) facet — present only on zone-cut-owning
|
|
36
|
+
// records: the apex, or a hostname with verified NS delegation.
|
|
37
|
+
export const domainDnsFacetSchema = z.object({
|
|
38
|
+
mode: z
|
|
39
|
+
.enum(["stattic", "external"])
|
|
40
|
+
.describe("Whether Spacefast hosts the authoritative zone or an external DNS provider does."),
|
|
41
|
+
managementKind: z
|
|
42
|
+
.enum(["unmanaged_external", "connected_provider", "registrar_dns", "stattic_nameservers"])
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Derived DNS management state. Additive; `mode` remains the v1 compatibility field."),
|
|
45
|
+
dnsMode: z
|
|
46
|
+
.enum(["authoritative", "adapter"])
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Hybrid DNS mode derived from `managementKind`: authoritative (Spacefast hosts the zone) or adapter (records managed/observed on the user's existing host). Additive read-model label."),
|
|
49
|
+
provider: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Wrapped DNS provider key when mode is "stattic", e.g. "route53" or "wpcom".'),
|
|
53
|
+
assignedNameservers: z
|
|
54
|
+
.array(z.string())
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('Managed-zone delegation set the registrar should point at (mode "stattic").'),
|
|
57
|
+
observedNameservers: z
|
|
58
|
+
.array(z.string())
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Nameservers currently observed in public DNS; polled in both modes."),
|
|
61
|
+
snapshot: z
|
|
62
|
+
.object({
|
|
63
|
+
freshness: z.enum(["fresh", "stale", "refresh_running", "refresh_failed", "never_fetched"]),
|
|
64
|
+
lastFetchedAt: z.string().datetime().nullable(),
|
|
65
|
+
version: z.string().nullable(),
|
|
66
|
+
})
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Connected-provider record snapshot freshness for portfolio lists."),
|
|
69
|
+
lastOperation: z
|
|
70
|
+
.object({
|
|
71
|
+
id: z.string(),
|
|
72
|
+
kind: z.string(),
|
|
73
|
+
status: z.enum(["queued", "running", "observing", "succeeded", "failed", "canceled"]),
|
|
74
|
+
updatedAt: z.string().datetime(),
|
|
75
|
+
})
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Latest DNS operation summary for portfolio and recovery surfaces."),
|
|
78
|
+
});
|
|
79
|
+
export const domainVerificationSchema = z.object({
|
|
80
|
+
status: z
|
|
81
|
+
.enum(["not_required", "pending", "verified", "failed"])
|
|
82
|
+
.describe("Current proof of DNS control. `failed` alone never tears down compiled serving state."),
|
|
83
|
+
requiredRecords: z
|
|
84
|
+
.array(DnsInstructionSchema)
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("DNS records the caller must create to prove control and route traffic."),
|
|
87
|
+
lastCheckedAt: z
|
|
88
|
+
.string()
|
|
89
|
+
.datetime()
|
|
90
|
+
.nullable()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("When observed DNS was last polled."),
|
|
93
|
+
lastVerifiedAt: z
|
|
94
|
+
.string()
|
|
95
|
+
.datetime()
|
|
96
|
+
.nullable()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Last time this domain proved DNS control."),
|
|
99
|
+
diagnostics: z
|
|
100
|
+
.array(baseDiagnosticSchema)
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Structured verification diagnostics with stable codes."),
|
|
103
|
+
});
|
|
104
|
+
export const domainHintSchema = z.object({
|
|
105
|
+
id: z.string(),
|
|
106
|
+
code: z.string(),
|
|
107
|
+
severity: z.enum(["info", "warning", "error"]),
|
|
108
|
+
painLevel: z.enum(["info", "needs_attention", "blocked", "broken", "dangerous"]),
|
|
109
|
+
subjectFacet: z.enum([
|
|
110
|
+
"dns_zone",
|
|
111
|
+
"registrar",
|
|
112
|
+
"hosting_binding",
|
|
113
|
+
"email",
|
|
114
|
+
"forwarding",
|
|
115
|
+
"provider_connection",
|
|
116
|
+
]),
|
|
117
|
+
subjectRef: z.object({
|
|
118
|
+
domainId: z.string(),
|
|
119
|
+
zoneId: z.string().optional(),
|
|
120
|
+
bindingId: z.string().optional(),
|
|
121
|
+
connectionId: z.string().optional(),
|
|
122
|
+
}),
|
|
123
|
+
owner: z.enum(["stattic", "user", "registrar", "provider", "support", "waiting"]),
|
|
124
|
+
status: z.enum(["active", "waiting", "running", "resolved", "dismissed"]),
|
|
125
|
+
summary: z.string(),
|
|
126
|
+
description: z.string(),
|
|
127
|
+
impact: z.string(),
|
|
128
|
+
evidence: z.array(z.object({
|
|
129
|
+
label: z.string(),
|
|
130
|
+
value: z.string(),
|
|
131
|
+
observedAt: z.string().datetime().optional(),
|
|
132
|
+
source: z.enum([
|
|
133
|
+
"provider_api",
|
|
134
|
+
"public_dns",
|
|
135
|
+
"registrar",
|
|
136
|
+
"stattic_db",
|
|
137
|
+
"operation",
|
|
138
|
+
"user_input",
|
|
139
|
+
]),
|
|
140
|
+
})),
|
|
141
|
+
suggestions: z.array(z.object({
|
|
142
|
+
label: z.string(),
|
|
143
|
+
description: z.string(),
|
|
144
|
+
action: z.discriminatedUnion("type", [
|
|
145
|
+
z.object({ type: z.literal("open_pending_action"), actionId: z.string() }),
|
|
146
|
+
z.object({ type: z.literal("run_action"), actionKind: z.string() }),
|
|
147
|
+
z.object({ type: z.literal("copy_records"), records: z.array(DnsInstructionSchema) }),
|
|
148
|
+
z.object({ type: z.literal("refresh"), target: z.string() }),
|
|
149
|
+
z.object({ type: z.literal("reconnect_provider"), connectionId: z.string() }),
|
|
150
|
+
z.object({ type: z.literal("open_external"), url: z.string().url() }),
|
|
151
|
+
z.object({ type: z.literal("wait"), retryAfter: z.string().datetime() }),
|
|
152
|
+
]),
|
|
153
|
+
primary: z.boolean().optional(),
|
|
154
|
+
destructive: z.boolean().optional(),
|
|
155
|
+
})),
|
|
156
|
+
docsUrl: z.string().url().optional(),
|
|
157
|
+
operationId: z.string().optional(),
|
|
158
|
+
requestId: z.string().optional(),
|
|
159
|
+
providerRequestId: z.string().optional(),
|
|
160
|
+
expiresAt: z.string().datetime().optional(),
|
|
161
|
+
});
|
|
162
|
+
export const DomainSchema = z.object({
|
|
163
|
+
id: z.string().describe("Domain inventory record id (`dom_...`)."),
|
|
164
|
+
tenantId: z.string().describe("Tenant that owns this record (`ten_...`)."),
|
|
165
|
+
principal: PrincipalRefSchema.describe("Tenant-local principal that owns this domain: a team in the main tenant, an external customer in platform integrations."),
|
|
166
|
+
name: z
|
|
167
|
+
.string()
|
|
168
|
+
.describe("Canonical lowercase ASCII A-labels (IDNA2008/punycode), no trailing dot, no wildcard label."),
|
|
169
|
+
unicodeName: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.describe("Read-only U-label form, present only when it differs from `name`."),
|
|
173
|
+
kind: z
|
|
174
|
+
.enum(["hostname", "apex"])
|
|
175
|
+
.describe("Derived, never create input: `apex` iff `name` equals `derivedGroup`."),
|
|
176
|
+
wildcardEnabled: z
|
|
177
|
+
.boolean()
|
|
178
|
+
.describe("Whether wildcard bindings may be created under this record."),
|
|
179
|
+
derivedGroup: z
|
|
180
|
+
.string()
|
|
181
|
+
.describe("PSL registrable domain of `name`; server-computed, never client-supplied."),
|
|
182
|
+
registrar: domainRegistrarFacetSchema
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Registration facet; present only on apex records."),
|
|
185
|
+
dns: domainDnsFacetSchema
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("DNS hosting facet; present only on zone-cut-owning records."),
|
|
188
|
+
verification: domainVerificationSchema.describe("Proof-of-control read model."),
|
|
189
|
+
hints: z
|
|
190
|
+
.array(domainHintSchema)
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("Projected domain hints derived from diagnostics, actions, observations, and operations."),
|
|
193
|
+
createdAt: z.string().datetime().describe("When the record was created."),
|
|
194
|
+
updatedAt: z.string().datetime().describe("When the record last changed."),
|
|
195
|
+
});
|
|
196
|
+
export const domainListQuerySchema = cursorListQuerySchema.extend({
|
|
197
|
+
teamId: z.string().min(1).optional().describe("Filter to domains owned by one team."),
|
|
198
|
+
});
|
|
199
|
+
export const domainListResponseSchema = z.object({
|
|
200
|
+
data: z.array(DomainSchema),
|
|
201
|
+
pagination: CursorPaginationSchema,
|
|
202
|
+
});
|
|
203
|
+
export const spaceCustomDomainSchema = DomainSchema.extend({
|
|
204
|
+
source: z.literal("custom").describe("Custom domain inventory row."),
|
|
205
|
+
managed: z.literal(false),
|
|
206
|
+
deletable: z.literal(true),
|
|
207
|
+
assignable: z.literal(true),
|
|
208
|
+
primary: z.boolean().describe("Whether this custom domain binding is the space's primary URL."),
|
|
209
|
+
});
|
|
210
|
+
export const spaceManagedDomainSchema = z.object({
|
|
211
|
+
id: z.string().describe("Stable managed row id derived from the default hostname row."),
|
|
212
|
+
source: z.literal("managed_view_fast").describe("System-managed view.fast hostname row."),
|
|
213
|
+
managed: z.literal(true),
|
|
214
|
+
deletable: z.literal(false),
|
|
215
|
+
assignable: z.literal(false),
|
|
216
|
+
primary: z.boolean().describe("True when no custom primary domain overrides the managed URL."),
|
|
217
|
+
name: z.string().describe("Managed view.fast hostname."),
|
|
218
|
+
unicodeName: z.string().optional(),
|
|
219
|
+
kind: z.literal("hostname"),
|
|
220
|
+
publicName: z.string().describe("Left-hand public project label for the managed hostname."),
|
|
221
|
+
verification: domainVerificationSchema.extend({
|
|
222
|
+
status: z.literal("not_required"),
|
|
223
|
+
}),
|
|
224
|
+
createdAt: z.string().datetime(),
|
|
225
|
+
updatedAt: z.string().datetime(),
|
|
226
|
+
});
|
|
227
|
+
export const spaceDomainSchema = z.discriminatedUnion("source", [
|
|
228
|
+
spaceManagedDomainSchema,
|
|
229
|
+
spaceCustomDomainSchema,
|
|
230
|
+
]);
|
|
231
|
+
export const spaceDomainListResponseSchema = z.object({
|
|
232
|
+
data: z.array(spaceDomainSchema),
|
|
233
|
+
pagination: CursorPaginationSchema,
|
|
234
|
+
});
|
|
235
|
+
export const teamManagedDomainSchema = spaceManagedDomainSchema.extend({
|
|
236
|
+
spaceId: z.string().describe("Space that owns this managed hostname."),
|
|
237
|
+
spaceSlug: z.string().describe("Current project slug for the owning space."),
|
|
238
|
+
spaceTitle: z.string().describe("Current title for the owning space."),
|
|
239
|
+
});
|
|
240
|
+
export const teamManagedDomainListResponseSchema = z.object({
|
|
241
|
+
data: z.array(teamManagedDomainSchema),
|
|
242
|
+
pagination: CursorPaginationSchema,
|
|
243
|
+
});
|
|
244
|
+
export const domainCreateRequestSchema = z.object({
|
|
245
|
+
name: z
|
|
246
|
+
.string()
|
|
247
|
+
.trim()
|
|
248
|
+
.min(1)
|
|
249
|
+
.max(253)
|
|
250
|
+
.describe("Apex or concrete hostname to add to inventory; canonicalized server-side."),
|
|
251
|
+
teamId: z
|
|
252
|
+
.string()
|
|
253
|
+
.min(1)
|
|
254
|
+
.optional()
|
|
255
|
+
.describe("Owning team. Defaults to the active team for sessions and the key's team."),
|
|
256
|
+
});
|
|
257
|
+
// Facet modes are never PATCHable: `dns.mode` changes only via POST /dns and
|
|
258
|
+
// `registrar.mode` only via POST /registration. The PATCHable surface is
|
|
259
|
+
// `wildcardEnabled` (and future metadata) only.
|
|
260
|
+
export const domainPatchSchema = z
|
|
261
|
+
.object({
|
|
262
|
+
wildcardEnabled: z
|
|
263
|
+
.boolean()
|
|
264
|
+
.optional()
|
|
265
|
+
.describe("Allow wildcard bindings under this domain record."),
|
|
266
|
+
})
|
|
267
|
+
.refine((value) => Object.keys(value).length > 0, {
|
|
268
|
+
message: "At least one domain field is required.",
|
|
269
|
+
});
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// DomainBinding (internal-docs/platform.md L2466-2498): a discriminated union
|
|
272
|
+
// on `mode` — serve a space, redirect, or proxy an upstream.
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// DomainBindingDiagnostic (L2508-2510): the shared Diagnostic shape plus a
|
|
275
|
+
// binding-specific suggested `action`.
|
|
276
|
+
export const domainBindingDiagnosticSchema = baseDiagnosticSchema.extend({
|
|
277
|
+
action: z
|
|
278
|
+
.enum(["add_dns", "wait", "verify", "confirm_move", "remove_conflict", "contact_support"])
|
|
279
|
+
.optional()
|
|
280
|
+
.describe("Suggested next step to resolve this binding diagnostic."),
|
|
281
|
+
});
|
|
282
|
+
export const domainBindingConflictSchema = z.object({
|
|
283
|
+
id: z.string().describe("Stable conflict id, passed back in activation `confirm`."),
|
|
284
|
+
scope: z
|
|
285
|
+
.enum(["same_space", "same_principal", "same_tenant", "external"])
|
|
286
|
+
.describe("Who holds the overlapping claim, relative to the caller."),
|
|
287
|
+
hostname: z.string().describe("Hostname both claims target."),
|
|
288
|
+
pathPrefix: z.string().optional().describe("Conflicting path scope, when path-based."),
|
|
289
|
+
existingBindingId: z.string().optional().describe("The incumbent binding, when visible."),
|
|
290
|
+
resolution: z
|
|
291
|
+
.enum([
|
|
292
|
+
"activate_moves_existing",
|
|
293
|
+
"choose_different_path",
|
|
294
|
+
"verify_to_takeover",
|
|
295
|
+
"cannot_resolve",
|
|
296
|
+
])
|
|
297
|
+
.optional()
|
|
298
|
+
.describe("How this conflict can be resolved."),
|
|
299
|
+
});
|
|
300
|
+
export const domainBindingStatusSchema = z
|
|
301
|
+
.enum(["draft", "pending", "blocked", "activating", "active", "failed", "deleted"])
|
|
302
|
+
.describe("Binding lifecycle status; the binding is the pollable resource for activation.");
|
|
303
|
+
const domainBindingBaseFields = {
|
|
304
|
+
id: z.string().describe("Domain binding id (`dbn_...`)."),
|
|
305
|
+
tenantId: z.string().describe("Tenant the binding belongs to (`ten_...`)."),
|
|
306
|
+
domainId: z.string().describe("Domain inventory record this binding routes (`dom_...`)."),
|
|
307
|
+
siteDomainAssignmentId: z
|
|
308
|
+
.string()
|
|
309
|
+
.nullable()
|
|
310
|
+
.describe("Provider assignment serving this hostname, once linked (`sda_...`)."),
|
|
311
|
+
hostname: z.string().describe("Hostname this binding claims (copied from the domain)."),
|
|
312
|
+
pathPrefix: z
|
|
313
|
+
.string()
|
|
314
|
+
.nullable()
|
|
315
|
+
.describe("Path scope like `/docs`; null claims the whole host. More specific paths win."),
|
|
316
|
+
wildcard: z
|
|
317
|
+
.boolean()
|
|
318
|
+
.describe("Whether this binding captures subdomains (requires domain wildcardEnabled)."),
|
|
319
|
+
status: domainBindingStatusSchema,
|
|
320
|
+
diagnostics: z
|
|
321
|
+
.array(domainBindingDiagnosticSchema)
|
|
322
|
+
.describe("Structured diagnostics with stable codes and suggested actions."),
|
|
323
|
+
requiredActions: requiredActionsSchema,
|
|
324
|
+
conflicts: z
|
|
325
|
+
.array(domainBindingConflictSchema)
|
|
326
|
+
.describe("Unresolved overlapping claims; conflicts block instead of dead-ending."),
|
|
327
|
+
providerState: AnyObjectSchema.describe("Raw provider-side state, for diagnostics only."),
|
|
328
|
+
createdAt: z.string().datetime().describe("When the binding was created."),
|
|
329
|
+
updatedAt: z.string().datetime().describe("When the binding last changed."),
|
|
330
|
+
};
|
|
331
|
+
export const domainBindingRedirectTargetSchema = z
|
|
332
|
+
.union([
|
|
333
|
+
z.object({
|
|
334
|
+
url: z.string().url().describe("Absolute URL to redirect to."),
|
|
335
|
+
}),
|
|
336
|
+
z.object({
|
|
337
|
+
spaceId: z.string().min(1).describe("Space to redirect to (`spc_...`)."),
|
|
338
|
+
path: z.string().max(255).optional().describe("Path on the target space."),
|
|
339
|
+
}),
|
|
340
|
+
])
|
|
341
|
+
.describe("Redirect destination: an absolute URL, or a space (optionally with a path).");
|
|
342
|
+
export const domainBindingRedirectStatusSchema = z
|
|
343
|
+
.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)])
|
|
344
|
+
.describe("HTTP redirect status; defaults to 308.");
|
|
345
|
+
export const domainBindingSchema = z.discriminatedUnion("mode", [
|
|
346
|
+
z.object({
|
|
347
|
+
...domainBindingBaseFields,
|
|
348
|
+
mode: z.literal("serve").describe("Serve a space's content on this hostname/path."),
|
|
349
|
+
spaceId: z.string().describe("Space served by this binding (`spc_...`)."),
|
|
350
|
+
channel: z.string().describe('Channel served on this hostname; defaults to "live".'),
|
|
351
|
+
primary: z
|
|
352
|
+
.boolean()
|
|
353
|
+
.describe("One primary hostname per space; the primary binding feeds `liveUrl`."),
|
|
354
|
+
}),
|
|
355
|
+
z.object({
|
|
356
|
+
...domainBindingBaseFields,
|
|
357
|
+
mode: z.literal("redirect").describe("Redirect requests to a URL or another space."),
|
|
358
|
+
to: domainBindingRedirectTargetSchema,
|
|
359
|
+
redirectStatus: domainBindingRedirectStatusSchema,
|
|
360
|
+
}),
|
|
361
|
+
z.object({
|
|
362
|
+
...domainBindingBaseFields,
|
|
363
|
+
mode: z.literal("proxy").describe("Proxy requests to an external upstream."),
|
|
364
|
+
upstream: z.object({
|
|
365
|
+
url: z.string().url().describe("Absolute upstream URL requests are proxied to."),
|
|
366
|
+
}),
|
|
367
|
+
}),
|
|
368
|
+
]);
|
|
369
|
+
function requireBindingModeFields(value, ctx) {
|
|
370
|
+
if (value.mode === "redirect" && value.to === undefined) {
|
|
371
|
+
ctx.addIssue({ code: "custom", path: ["to"], message: "Redirect bindings require `to`." });
|
|
372
|
+
}
|
|
373
|
+
if (value.mode === "proxy" && value.upstream === undefined) {
|
|
374
|
+
ctx.addIssue({
|
|
375
|
+
code: "custom",
|
|
376
|
+
path: ["upstream"],
|
|
377
|
+
message: "Proxy bindings require `upstream`.",
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (value.mode !== "redirect" && (value.to !== undefined || value.redirectStatus !== undefined)) {
|
|
381
|
+
ctx.addIssue({
|
|
382
|
+
code: "custom",
|
|
383
|
+
path: ["to"],
|
|
384
|
+
message: "`to`/`redirectStatus` apply only to redirect bindings.",
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (value.mode !== "proxy" && value.upstream !== undefined) {
|
|
388
|
+
ctx.addIssue({
|
|
389
|
+
code: "custom",
|
|
390
|
+
path: ["upstream"],
|
|
391
|
+
message: "`upstream` applies only to proxy bindings.",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
if (value.mode === "serve" || value.mode === undefined) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (value.channel !== undefined || value.primary !== undefined) {
|
|
398
|
+
ctx.addIssue({
|
|
399
|
+
code: "custom",
|
|
400
|
+
path: ["channel"],
|
|
401
|
+
message: "`channel`/`primary` apply only to serve bindings.",
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// One POST end to end (charter D5): pass `hostname` and the Domain inventory
|
|
406
|
+
// record is auto-created for the space's principal — no separate create-domain
|
|
407
|
+
// step. Pass `domainId` to bind an existing inventory record instead.
|
|
408
|
+
export const domainBindingCreateSchema = z
|
|
409
|
+
.object({
|
|
410
|
+
domainId: z
|
|
411
|
+
.string()
|
|
412
|
+
.min(1)
|
|
413
|
+
.optional()
|
|
414
|
+
.describe("Existing domain inventory record to bind (`dom_...`)."),
|
|
415
|
+
hostname: z
|
|
416
|
+
.string()
|
|
417
|
+
.trim()
|
|
418
|
+
.min(1)
|
|
419
|
+
.max(253)
|
|
420
|
+
.optional()
|
|
421
|
+
.describe("Hostname to bind. Auto-creates the Domain inventory record for the space's team when none exists."),
|
|
422
|
+
spaceId: z
|
|
423
|
+
.string()
|
|
424
|
+
.min(1)
|
|
425
|
+
.describe("Space this hostname is anchored to: the serve target, or for redirect/proxy bindings the space whose site serves the hostname."),
|
|
426
|
+
mode: z
|
|
427
|
+
.enum(["serve", "redirect", "proxy"])
|
|
428
|
+
.default("serve")
|
|
429
|
+
.describe("Routing intent: serve a space, redirect, or proxy an upstream."),
|
|
430
|
+
pathPrefix: z
|
|
431
|
+
.string()
|
|
432
|
+
.trim()
|
|
433
|
+
.max(255)
|
|
434
|
+
.nullable()
|
|
435
|
+
.optional()
|
|
436
|
+
.describe("Path scope like `/docs`; omit to claim the whole host."),
|
|
437
|
+
wildcard: z
|
|
438
|
+
.boolean()
|
|
439
|
+
.optional()
|
|
440
|
+
.describe("Capture subdomains too (requires domain wildcardEnabled)."),
|
|
441
|
+
channel: z
|
|
442
|
+
.string()
|
|
443
|
+
.trim()
|
|
444
|
+
.min(1)
|
|
445
|
+
.max(64)
|
|
446
|
+
.optional()
|
|
447
|
+
.describe('Channel to serve (serve mode); defaults to "live".'),
|
|
448
|
+
primary: z
|
|
449
|
+
.boolean()
|
|
450
|
+
.optional()
|
|
451
|
+
.describe("Make this the space's primary hostname (serve mode)."),
|
|
452
|
+
to: domainBindingRedirectTargetSchema.optional(),
|
|
453
|
+
redirectStatus: domainBindingRedirectStatusSchema.optional(),
|
|
454
|
+
upstream: z
|
|
455
|
+
.object({ url: z.string().url().describe("Absolute upstream URL.") })
|
|
456
|
+
.optional()
|
|
457
|
+
.describe("Proxy upstream (proxy mode)."),
|
|
458
|
+
})
|
|
459
|
+
.superRefine((value, ctx) => {
|
|
460
|
+
if (!value.domainId && !value.hostname) {
|
|
461
|
+
ctx.addIssue({
|
|
462
|
+
code: "custom",
|
|
463
|
+
path: ["hostname"],
|
|
464
|
+
message: "Pass `hostname` (auto-creates the domain) or `domainId`.",
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (value.domainId && value.hostname) {
|
|
468
|
+
ctx.addIssue({
|
|
469
|
+
code: "custom",
|
|
470
|
+
path: ["domainId"],
|
|
471
|
+
message: "Pass either `domainId` or `hostname`, not both.",
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
requireBindingModeFields(value, ctx);
|
|
475
|
+
});
|
|
476
|
+
export const domainBindingPatchSchema = z
|
|
477
|
+
.object({
|
|
478
|
+
mode: z
|
|
479
|
+
.enum(["serve", "redirect", "proxy"])
|
|
480
|
+
.optional()
|
|
481
|
+
.describe("Switch the routing intent; the new mode's fields are required alongside."),
|
|
482
|
+
pathPrefix: z.string().trim().max(255).nullable().optional().describe("New path scope."),
|
|
483
|
+
wildcard: z.boolean().optional().describe("Toggle subdomain capture."),
|
|
484
|
+
channel: z.string().trim().min(1).max(64).optional().describe("Channel to serve (serve mode)."),
|
|
485
|
+
primary: z
|
|
486
|
+
.boolean()
|
|
487
|
+
.optional()
|
|
488
|
+
.describe("Promote to the space's primary hostname (serve mode)."),
|
|
489
|
+
to: domainBindingRedirectTargetSchema.optional(),
|
|
490
|
+
redirectStatus: domainBindingRedirectStatusSchema.optional(),
|
|
491
|
+
upstream: z
|
|
492
|
+
.object({ url: z.string().url().describe("Absolute upstream URL.") })
|
|
493
|
+
.optional()
|
|
494
|
+
.describe("Proxy upstream (proxy mode)."),
|
|
495
|
+
})
|
|
496
|
+
.superRefine((value, ctx) => {
|
|
497
|
+
if (Object.keys(value).length === 0) {
|
|
498
|
+
ctx.addIssue({ code: "custom", message: "At least one binding field is required." });
|
|
499
|
+
}
|
|
500
|
+
if (value.mode !== undefined) {
|
|
501
|
+
requireBindingModeFields(value, ctx);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
// Binding activation is asynchronous; the binding is the pollable status
|
|
505
|
+
// resource. `confirm` carries conflict ids whose `confirm_move` resolution the
|
|
506
|
+
// caller accepts — activation performs the move (no public move API).
|
|
507
|
+
export const domainBindingActivateSchema = z.object({
|
|
508
|
+
confirm: z
|
|
509
|
+
.array(z.string())
|
|
510
|
+
.default([])
|
|
511
|
+
.describe("Conflict ids whose `activate_moves_existing` resolution the caller confirms."),
|
|
512
|
+
});
|
|
513
|
+
// Activation is asynchronous: the binding is the pollable status resource and
|
|
514
|
+
// `operation` tracks the work item (standard mutation envelope).
|
|
515
|
+
export const domainBindingActivateResponseSchema = mutationEnvelope(domainBindingSchema);
|
|
516
|
+
export const domainBindingListResponseSchema = z.object({
|
|
517
|
+
data: z.array(domainBindingSchema),
|
|
518
|
+
pagination: CursorPaginationSchema,
|
|
519
|
+
});
|
|
520
|
+
export const domainBindingListQuerySchema = cursorListQuerySchema;
|
|
521
|
+
// ---------------------------------------------------------------------------
|
|
522
|
+
// SiteDomainAssignment (internal-docs/platform.md L2445-2464): provider
|
|
523
|
+
// assignment of a hostname to a unified site. Mostly provider state managed by
|
|
524
|
+
// bindings; POST/DELETE exist as the canonical explicit mutation surface.
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
export const siteDomainAssignmentSchema = z.object({
|
|
527
|
+
id: z.string().describe("Site domain assignment id (`sda_...`)."),
|
|
528
|
+
tenantId: z.string().describe("Tenant the assignment belongs to (`ten_...`)."),
|
|
529
|
+
domainId: z.string().describe("Domain inventory record this hostname belongs to (`dom_...`)."),
|
|
530
|
+
siteId: z.string().nullable().describe("Unified site (`site_...`) the hostname is assigned to."),
|
|
531
|
+
hostname: z.string().describe("Hostname assigned at the provider."),
|
|
532
|
+
status: z
|
|
533
|
+
.enum([
|
|
534
|
+
"unassigned",
|
|
535
|
+
"pending_provider",
|
|
536
|
+
"pending_dns",
|
|
537
|
+
"pending_ssl",
|
|
538
|
+
"active",
|
|
539
|
+
"blocked",
|
|
540
|
+
"failed",
|
|
541
|
+
])
|
|
542
|
+
.describe("Provider assignment status; the assignment is pollable during activation."),
|
|
543
|
+
dnsInstructions: z
|
|
544
|
+
.array(DnsInstructionSchema)
|
|
545
|
+
.describe("DNS records required for this hostname to route and verify."),
|
|
546
|
+
providerState: AnyObjectSchema.describe("Raw provider-side state, for diagnostics only."),
|
|
547
|
+
diagnostics: z
|
|
548
|
+
.array(baseDiagnosticSchema)
|
|
549
|
+
.describe("Structured assignment diagnostics with stable codes."),
|
|
550
|
+
createdAt: z.string().datetime().describe("When the assignment was created."),
|
|
551
|
+
updatedAt: z.string().datetime().describe("When the assignment last changed."),
|
|
552
|
+
});
|
|
553
|
+
export const siteDomainAssignmentCreateSchema = z.object({
|
|
554
|
+
spaceId: z
|
|
555
|
+
.string()
|
|
556
|
+
.min(1)
|
|
557
|
+
.describe("Space whose site this domain's hostname is assigned to (`spc_...`)."),
|
|
558
|
+
});
|
|
559
|
+
export const siteDomainAssignmentListResponseSchema = z.object({
|
|
560
|
+
data: z.array(siteDomainAssignmentSchema),
|
|
561
|
+
pagination: CursorPaginationSchema,
|
|
562
|
+
});
|
|
563
|
+
// ---------------------------------------------------------------------------
|
|
564
|
+
// Registrar facet routes (feature-flagged dev-only per charter D5).
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
export const domainRegistrationSchema = z.object({
|
|
567
|
+
id: z.string().nullable().describe("Registration or transfer record id; null when unregistered."),
|
|
568
|
+
domainId: z.string().describe("Domain inventory record (`dom_...`)."),
|
|
569
|
+
hostname: z.string().describe("Registered hostname."),
|
|
570
|
+
provider: z
|
|
571
|
+
.enum(["dsapi", "manual"])
|
|
572
|
+
.nullable()
|
|
573
|
+
.describe("Wrapped registrar bridge the registration lives at."),
|
|
574
|
+
status: z.string().describe("Registration lifecycle status."),
|
|
575
|
+
expiresAt: z.string().datetime().nullable().describe("When the registration expires."),
|
|
576
|
+
redemptionEndsAt: z
|
|
577
|
+
.string()
|
|
578
|
+
.datetime()
|
|
579
|
+
.nullable()
|
|
580
|
+
.describe("End of the redemption window after expiry."),
|
|
581
|
+
autoRenew: z.boolean().describe("Whether the registration renews automatically."),
|
|
582
|
+
transferLock: z.boolean().describe("Whether outbound transfers are locked."),
|
|
583
|
+
privacySetting: z
|
|
584
|
+
.enum(["enable_privacy_service", "redact_contact_info", "disclose_contact_info"])
|
|
585
|
+
.nullable()
|
|
586
|
+
.describe("WHOIS privacy behavior."),
|
|
587
|
+
contactId: z.string().nullable().describe("Registrant contact record."),
|
|
588
|
+
failureReason: z.string().nullable().describe("Provider failure detail, when failed."),
|
|
589
|
+
createdAt: z.string().datetime().nullable(),
|
|
590
|
+
updatedAt: z.string().datetime().nullable(),
|
|
591
|
+
});
|
|
592
|
+
const domainRegistrationPeriodSchema = z
|
|
593
|
+
.number()
|
|
594
|
+
.int()
|
|
595
|
+
.min(1)
|
|
596
|
+
.max(10)
|
|
597
|
+
.optional()
|
|
598
|
+
.describe("Registration period in years.");
|
|
599
|
+
const domainRegistrationContactIdSchema = z
|
|
600
|
+
.string()
|
|
601
|
+
.trim()
|
|
602
|
+
.min(1)
|
|
603
|
+
.optional()
|
|
604
|
+
.describe("Registrant contact record to use.");
|
|
605
|
+
// `registrar.mode` changes only through these actions, never via PATCH. Keep
|
|
606
|
+
// mode-specific fields in the contract so clients get param-level validation.
|
|
607
|
+
export const domainRegistrationActionSchema = z.discriminatedUnion("action", [
|
|
608
|
+
z
|
|
609
|
+
.object({
|
|
610
|
+
action: z.literal("purchase"),
|
|
611
|
+
period: domainRegistrationPeriodSchema,
|
|
612
|
+
contactId: domainRegistrationContactIdSchema,
|
|
613
|
+
})
|
|
614
|
+
.strict(),
|
|
615
|
+
z
|
|
616
|
+
.object({
|
|
617
|
+
action: z.literal("renew"),
|
|
618
|
+
period: domainRegistrationPeriodSchema,
|
|
619
|
+
})
|
|
620
|
+
.strict(),
|
|
621
|
+
z
|
|
622
|
+
.object({
|
|
623
|
+
action: z.literal("transfer_in"),
|
|
624
|
+
authCode: z
|
|
625
|
+
.string()
|
|
626
|
+
.trim()
|
|
627
|
+
.min(1)
|
|
628
|
+
.max(255)
|
|
629
|
+
.describe("Transfer authorization code from the current registrar."),
|
|
630
|
+
contactId: domainRegistrationContactIdSchema,
|
|
631
|
+
})
|
|
632
|
+
.strict(),
|
|
633
|
+
z
|
|
634
|
+
.object({
|
|
635
|
+
action: z.literal("transfer_out"),
|
|
636
|
+
})
|
|
637
|
+
.strict(),
|
|
638
|
+
]);
|
|
639
|
+
// Registration actions are asynchronous: the registration is the pollable
|
|
640
|
+
// status resource and the operation tracks the provider submission.
|
|
641
|
+
export const domainRegistrationActionResponseSchema = mutationEnvelope(domainRegistrationSchema);
|
|
642
|
+
export const domainRegistrationPatchSchema = z
|
|
643
|
+
.object({
|
|
644
|
+
autoRenew: z.boolean().optional().describe("Toggle automatic renewal."),
|
|
645
|
+
transferLock: z.boolean().optional().describe("Toggle the registrar transfer lock."),
|
|
646
|
+
privacySetting: z
|
|
647
|
+
.enum(["enable_privacy_service", "redact_contact_info", "disclose_contact_info"])
|
|
648
|
+
.optional()
|
|
649
|
+
.describe("WHOIS privacy behavior."),
|
|
650
|
+
contactId: z
|
|
651
|
+
.string()
|
|
652
|
+
.trim()
|
|
653
|
+
.min(1)
|
|
654
|
+
.nullable()
|
|
655
|
+
.optional()
|
|
656
|
+
.describe("Registrant contact record."),
|
|
657
|
+
})
|
|
658
|
+
.refine((value) => Object.keys(value).length > 0, {
|
|
659
|
+
message: "At least one registration field is required.",
|
|
660
|
+
});
|
|
661
|
+
export const domainNameserverResponseSchema = z.object({
|
|
662
|
+
domainId: z.string().describe("Domain inventory record (`dom_...`)."),
|
|
663
|
+
mode: z.enum(["stattic", "external"]).describe("Current DNS hosting mode."),
|
|
664
|
+
nameservers: z.array(z.string()).describe("Delegation set at the registrar."),
|
|
665
|
+
assignedNameserverSetId: z
|
|
666
|
+
.string()
|
|
667
|
+
.optional()
|
|
668
|
+
.describe("Managed nameserver set identifier when Spacefast assigns the delegation."),
|
|
669
|
+
nameserverFamilyKey: z
|
|
670
|
+
.string()
|
|
671
|
+
.optional()
|
|
672
|
+
.describe("Provider-neutral family key for the assigned delegation set."),
|
|
673
|
+
observedNameservers: z
|
|
674
|
+
.array(z.string())
|
|
675
|
+
.optional()
|
|
676
|
+
.describe("Nameservers currently observed in public DNS."),
|
|
677
|
+
diagnostics: z
|
|
678
|
+
.array(baseDiagnosticSchema)
|
|
679
|
+
.optional()
|
|
680
|
+
.describe("Delegation diagnostics such as divergence or registrar-action requirements."),
|
|
681
|
+
});
|
|
682
|
+
export const domainNameserverUpdateSchema = z.object({
|
|
683
|
+
nameservers: z
|
|
684
|
+
.array(z.string().trim().min(1).max(253))
|
|
685
|
+
.min(2)
|
|
686
|
+
.max(13)
|
|
687
|
+
.describe("Nameserver hostnames to delegate to (2-13)."),
|
|
688
|
+
});
|
|
689
|
+
// Facet modes change only here, never via PATCH. The response is the
|
|
690
|
+
// post-mutation Domain plus an operation for callers watching the work item.
|
|
691
|
+
export const domainDnsModeRequestSchema = z.object({
|
|
692
|
+
mode: z
|
|
693
|
+
.enum(["stattic", "external"])
|
|
694
|
+
.describe("Target DNS hosting mode. external→stattic snapshots observed records first."),
|
|
695
|
+
});
|
|
696
|
+
// The transition receipt rides inside `data` (envelope law: `{data}` plus
|
|
697
|
+
// `operation` only): the post-mutation Domain plus the transition diagnostics
|
|
698
|
+
// (import visibility limits on external→stattic, exported records to re-create
|
|
699
|
+
// on stattic→external).
|
|
700
|
+
export const domainDnsModeResponseSchema = mutationEnvelope(z.object({
|
|
701
|
+
domain: DomainSchema.describe("The post-mutation Domain."),
|
|
702
|
+
diagnostics: z
|
|
703
|
+
.array(baseDiagnosticSchema)
|
|
704
|
+
.describe("Transition diagnostics: import visibility limits on external→stattic, exported records on stattic→external."),
|
|
705
|
+
requiredActions: requiredActionsSchema,
|
|
706
|
+
}));
|
|
707
|
+
// ---------------------------------------------------------------------------
|
|
708
|
+
// Managed DNS records (feature-flagged dev-only per charter D5).
|
|
709
|
+
// ---------------------------------------------------------------------------
|
|
710
|
+
export const domainDnsRecordSchema = z.object({
|
|
711
|
+
id: z.string().describe("DNS record id (`rec_...`)."),
|
|
712
|
+
domainId: z.string().describe("Zone-cut domain record this zone belongs to (`dom_...`)."),
|
|
713
|
+
type: z
|
|
714
|
+
.enum(["A", "AAAA", "ALIAS", "CAA", "CNAME", "MX", "NS", "SRV", "TXT"])
|
|
715
|
+
.describe("DNS record type."),
|
|
716
|
+
name: z.string().describe('Record name relative to the zone; "@" for the zone cut itself.'),
|
|
717
|
+
value: z.string().describe("Record value."),
|
|
718
|
+
ttl: z.number().int().positive().describe("Time to live in seconds."),
|
|
719
|
+
priority: z.number().int().nullable().describe("Record priority; required for MX and SRV."),
|
|
720
|
+
providerRecordId: z
|
|
721
|
+
.string()
|
|
722
|
+
.nullable()
|
|
723
|
+
.optional()
|
|
724
|
+
.describe("Provider-assigned DNS record id; read-only and present only for connected providers."),
|
|
725
|
+
proxied: z
|
|
726
|
+
.boolean()
|
|
727
|
+
.nullable()
|
|
728
|
+
.optional()
|
|
729
|
+
.describe("Provider proxying flag when supported; null when not applicable or unknown."),
|
|
730
|
+
comment: z
|
|
731
|
+
.string()
|
|
732
|
+
.nullable()
|
|
733
|
+
.optional()
|
|
734
|
+
.describe("Provider/user record comment when supported; null when unavailable."),
|
|
735
|
+
providerOptions: AnyObjectSchema.nullable()
|
|
736
|
+
.optional()
|
|
737
|
+
.describe("Provider-only record metadata when advertised by capabilities."),
|
|
738
|
+
managed: z
|
|
739
|
+
.boolean()
|
|
740
|
+
.describe("Spacefast-owned (verification/routing/ssl); read-only via the public API."),
|
|
741
|
+
protected: z
|
|
742
|
+
.boolean()
|
|
743
|
+
.default(false)
|
|
744
|
+
.describe("Read-only through public DNS record writes even when the record is not Spacefast-owned."),
|
|
745
|
+
purpose: z
|
|
746
|
+
.enum(["verification", "routing", "ssl"])
|
|
747
|
+
.nullable()
|
|
748
|
+
.describe("Why Spacefast manages this record; present on managed records."),
|
|
749
|
+
ownerResourceId: z
|
|
750
|
+
.string()
|
|
751
|
+
.nullable()
|
|
752
|
+
.describe("Owning resource: the domain (verification) or binding/assignment (routing/ssl)."),
|
|
753
|
+
createdAt: z.string().datetime(),
|
|
754
|
+
updatedAt: z.string().datetime(),
|
|
755
|
+
});
|
|
756
|
+
export const domainDnsCapabilityFieldSchema = z.object({
|
|
757
|
+
path: z
|
|
758
|
+
.string()
|
|
759
|
+
.describe("Stable record field path, for example `ttl` or `providerOptions.proxied`."),
|
|
760
|
+
type: z
|
|
761
|
+
.enum(["string", "number", "boolean", "enum", "object", "array"])
|
|
762
|
+
.describe("Machine-readable field type."),
|
|
763
|
+
readable: z.boolean().describe("Whether provider snapshots can return this field."),
|
|
764
|
+
creatable: z.boolean().describe("Whether create requests may set this field."),
|
|
765
|
+
patchable: z.boolean().describe("Whether patch requests may change this field."),
|
|
766
|
+
requiredWhen: z
|
|
767
|
+
.string()
|
|
768
|
+
.nullable()
|
|
769
|
+
.optional()
|
|
770
|
+
.describe("Stable condition expression, if conditional."),
|
|
771
|
+
limits: AnyObjectSchema.optional().describe("Provider-neutral field limits."),
|
|
772
|
+
default: z.unknown().optional().describe("Provider default when known."),
|
|
773
|
+
unsupportedReasonCode: z
|
|
774
|
+
.string()
|
|
775
|
+
.nullable()
|
|
776
|
+
.optional()
|
|
777
|
+
.describe("Why this field is unavailable."),
|
|
778
|
+
});
|
|
779
|
+
export const domainDnsExclusiveFieldGroupSchema = z.object({
|
|
780
|
+
key: z.string().describe("Stable exclusivity rule key."),
|
|
781
|
+
fields: z.array(z.string()).min(1).describe("Stable field paths included in this rule."),
|
|
782
|
+
mode: z.enum(["exactly_one", "at_most_one", "required_with", "forbidden_with"]),
|
|
783
|
+
appliesWhen: z
|
|
784
|
+
.object({
|
|
785
|
+
recordTypes: z.array(domainDnsRecordSchema.shape.type).optional(),
|
|
786
|
+
managementKinds: z
|
|
787
|
+
.array(z.enum([
|
|
788
|
+
"unmanaged_external",
|
|
789
|
+
"connected_provider",
|
|
790
|
+
"registrar_dns",
|
|
791
|
+
"stattic_nameservers",
|
|
792
|
+
]))
|
|
793
|
+
.optional(),
|
|
794
|
+
providerKeys: z.array(z.string()).optional(),
|
|
795
|
+
expression: z.string().optional(),
|
|
796
|
+
})
|
|
797
|
+
.optional(),
|
|
798
|
+
errorCode: z.string(),
|
|
799
|
+
message: z.string(),
|
|
800
|
+
fixSuggestion: z.string(),
|
|
801
|
+
});
|
|
802
|
+
export const domainDnsServiceTemplateSchema = z.object({
|
|
803
|
+
key: z.string().describe("Stable service-template key."),
|
|
804
|
+
subjectFacet: z.enum(["dns_zone", "email", "forwarding", "hosting_binding"]),
|
|
805
|
+
previewable: z.boolean().describe("Whether the template can be previewed before apply."),
|
|
806
|
+
applyMode: z
|
|
807
|
+
.enum(["automatic", "manual", "either"])
|
|
808
|
+
.describe("Whether Spacefast can apply the template automatically."),
|
|
809
|
+
variables: z
|
|
810
|
+
.array(domainDnsCapabilityFieldSchema)
|
|
811
|
+
.describe("Template variable descriptors rendered by clients."),
|
|
812
|
+
});
|
|
813
|
+
const domainFeatureCrudSchema = z.object({
|
|
814
|
+
readable: z.boolean(),
|
|
815
|
+
creatable: z.boolean(),
|
|
816
|
+
patchable: z.boolean(),
|
|
817
|
+
deletable: z.boolean().optional(),
|
|
818
|
+
verification: z.boolean().optional(),
|
|
819
|
+
});
|
|
820
|
+
export const domainDnsFeatureCapabilitiesSchema = z.object({
|
|
821
|
+
batch: z.object({
|
|
822
|
+
supported: z.boolean(),
|
|
823
|
+
atomic: z.boolean(),
|
|
824
|
+
maxChanges: z.number().int().positive().optional(),
|
|
825
|
+
}),
|
|
826
|
+
importBind: z.boolean(),
|
|
827
|
+
exportBind: z.boolean(),
|
|
828
|
+
providerImport: z.boolean(),
|
|
829
|
+
providerExport: z.boolean(),
|
|
830
|
+
migrationPreview: z.boolean(),
|
|
831
|
+
scanReview: z.boolean(),
|
|
832
|
+
serviceTemplates: z.array(domainDnsServiceTemplateSchema),
|
|
833
|
+
domainFeatures: z
|
|
834
|
+
.object({
|
|
835
|
+
forwarding: domainFeatureCrudSchema.optional(),
|
|
836
|
+
emailForwards: domainFeatureCrudSchema.optional(),
|
|
837
|
+
dnssec: z
|
|
838
|
+
.object({
|
|
839
|
+
readable: z.boolean(),
|
|
840
|
+
toggleable: z.boolean(),
|
|
841
|
+
dsData: z.boolean().optional(),
|
|
842
|
+
})
|
|
843
|
+
.optional(),
|
|
844
|
+
glueRecords: domainFeatureCrudSchema.optional(),
|
|
845
|
+
sslRetry: z.object({ supported: z.boolean() }).optional(),
|
|
846
|
+
contactVerification: z
|
|
847
|
+
.object({ supported: z.boolean(), uploadRequired: z.boolean().optional() })
|
|
848
|
+
.optional(),
|
|
849
|
+
privacy: z
|
|
850
|
+
.object({ supported: z.boolean(), discloseRedact: z.boolean().optional() })
|
|
851
|
+
.optional(),
|
|
852
|
+
transferLock: z.object({ supported: z.boolean() }).optional(),
|
|
853
|
+
authCodeCheck: z.object({ supported: z.boolean() }).optional(),
|
|
854
|
+
})
|
|
855
|
+
.describe("Capability-backed non-record domain features."),
|
|
856
|
+
proxied: z.object({
|
|
857
|
+
supported: z.boolean(),
|
|
858
|
+
editableTypes: z.array(domainDnsRecordSchema.shape.type),
|
|
859
|
+
ttlBehavior: z.enum(["automatic_when_enabled"]).optional(),
|
|
860
|
+
}),
|
|
861
|
+
comments: z.object({
|
|
862
|
+
supported: z.boolean(),
|
|
863
|
+
maxLength: z.number().int().positive().optional(),
|
|
864
|
+
}),
|
|
865
|
+
tags: z
|
|
866
|
+
.object({
|
|
867
|
+
supported: z.boolean(),
|
|
868
|
+
maxItems: z.number().int().positive().optional(),
|
|
869
|
+
maxLength: z.number().int().positive().optional(),
|
|
870
|
+
})
|
|
871
|
+
.optional(),
|
|
872
|
+
});
|
|
873
|
+
export const domainDnsProviderWriteStrategySchema = z.enum([
|
|
874
|
+
"per_record",
|
|
875
|
+
"batch_add_remove",
|
|
876
|
+
"batch_transaction",
|
|
877
|
+
"replace_all_record_sets",
|
|
878
|
+
]);
|
|
879
|
+
// Hybrid DNS mode (both available at launch). `authoritative` = Spacefast hosts
|
|
880
|
+
// the zone and answers queries (registrar_dns / stattic_nameservers backends);
|
|
881
|
+
// `adapter` = Spacefast manages records on the user's existing DNS host through
|
|
882
|
+
// that host's API (connected_provider) or only observes them and emits
|
|
883
|
+
// instructions (unmanaged_external). Additive read-model label derived from
|
|
884
|
+
// `managementKind`; it never changes how records are applied.
|
|
885
|
+
export const domainDnsHybridModeSchema = z
|
|
886
|
+
.enum(["authoritative", "adapter"])
|
|
887
|
+
.describe("Whether Spacefast hosts the authoritative zone (`authoritative`) or manages/observes records on the user's existing DNS host (`adapter`).");
|
|
888
|
+
const DNS_MANAGEMENT_KINDS = [
|
|
889
|
+
"unmanaged_external",
|
|
890
|
+
"connected_provider",
|
|
891
|
+
"registrar_dns",
|
|
892
|
+
"stattic_nameservers",
|
|
893
|
+
];
|
|
894
|
+
// Single source of truth mapping management kind -> hybrid mode. Spacefast-hosted
|
|
895
|
+
// zones (registrar_dns, stattic_nameservers) are authoritative; provider and
|
|
896
|
+
// external backends are adapter-mode.
|
|
897
|
+
export function deriveDnsHybridMode(managementKind) {
|
|
898
|
+
return managementKind === "registrar_dns" || managementKind === "stattic_nameservers"
|
|
899
|
+
? "authoritative"
|
|
900
|
+
: "adapter";
|
|
901
|
+
}
|
|
902
|
+
export const domainDnsCapabilitiesSchema = z
|
|
903
|
+
.object({
|
|
904
|
+
domainId: z.string().describe("Domain inventory record (`dom_...`)."),
|
|
905
|
+
managementKind: z.enum([
|
|
906
|
+
"unmanaged_external",
|
|
907
|
+
"connected_provider",
|
|
908
|
+
"registrar_dns",
|
|
909
|
+
"stattic_nameservers",
|
|
910
|
+
]),
|
|
911
|
+
dnsMode: domainDnsHybridModeSchema
|
|
912
|
+
.optional()
|
|
913
|
+
.describe("Hybrid DNS mode derived from `managementKind`: authoritative (Spacefast-hosted zone) or adapter (records on the user's existing host)."),
|
|
914
|
+
writeAuthority: z
|
|
915
|
+
.enum([
|
|
916
|
+
"none",
|
|
917
|
+
"provider_credentials",
|
|
918
|
+
"registrar_authority",
|
|
919
|
+
"delegated_nameservers",
|
|
920
|
+
"internal_service_key",
|
|
921
|
+
])
|
|
922
|
+
.describe("Why DNS writes are or are not available."),
|
|
923
|
+
provider: z.string().nullable().describe("Effective DNS provider key when known."),
|
|
924
|
+
recordTypes: z
|
|
925
|
+
.array(domainDnsRecordSchema.shape.type)
|
|
926
|
+
.describe("Record types accepted by this backend."),
|
|
927
|
+
fields: z
|
|
928
|
+
.array(domainDnsCapabilityFieldSchema)
|
|
929
|
+
.describe("Editable/readable field descriptors."),
|
|
930
|
+
exclusiveGroups: z
|
|
931
|
+
.array(domainDnsExclusiveFieldGroupSchema)
|
|
932
|
+
.describe("Cross-field rules shared by clients, validation, and provider planning."),
|
|
933
|
+
features: domainDnsFeatureCapabilitiesSchema.describe("Normalized service-template, import/export, and domain-feature capabilities."),
|
|
934
|
+
supportsBatch: z.boolean().describe("Whether normalized batch writes are supported."),
|
|
935
|
+
supportsAtomicBatch: z.boolean().describe("Whether batch writes are all-or-nothing."),
|
|
936
|
+
writeStrategy: domainDnsProviderWriteStrategySchema.describe("How normalized DNS changes are applied by the effective provider."),
|
|
937
|
+
supportsImport: z
|
|
938
|
+
.boolean()
|
|
939
|
+
.describe("Whether Spacefast can import zone content from this backend."),
|
|
940
|
+
supportsExport: z
|
|
941
|
+
.boolean()
|
|
942
|
+
.describe("Whether Spacefast can export zone content from this backend."),
|
|
943
|
+
ttlBehavior: z
|
|
944
|
+
.enum(["explicit", "automatic_when_proxied", "provider_default"])
|
|
945
|
+
.describe("How TTL is handled for editable records."),
|
|
946
|
+
snapshot: z
|
|
947
|
+
.object({
|
|
948
|
+
freshness: z.enum(["fresh", "stale", "refresh_running", "refresh_failed", "never_fetched"]),
|
|
949
|
+
lastFetchedAt: z.string().datetime().nullable(),
|
|
950
|
+
version: z.string().nullable(),
|
|
951
|
+
})
|
|
952
|
+
.describe("Provider snapshot freshness for connected-provider records."),
|
|
953
|
+
diagnostics: z.array(baseDiagnosticSchema).describe("Capability or authority diagnostics."),
|
|
954
|
+
})
|
|
955
|
+
.transform((capabilities) => ({
|
|
956
|
+
...capabilities,
|
|
957
|
+
dnsMode: capabilities.dnsMode ?? deriveDnsHybridMode(capabilities.managementKind),
|
|
958
|
+
}));
|
|
959
|
+
export const domainProviderConnectionStatusSchema = z.enum([
|
|
960
|
+
"active",
|
|
961
|
+
"degraded",
|
|
962
|
+
"auth_expired",
|
|
963
|
+
"revoked",
|
|
964
|
+
"owner_access_lost",
|
|
965
|
+
"disconnected",
|
|
966
|
+
"pending_relink",
|
|
967
|
+
"reconnect_required",
|
|
968
|
+
"disabled",
|
|
969
|
+
]);
|
|
970
|
+
export const domainDnsProviderSchema = z.enum([
|
|
971
|
+
"cloudflare",
|
|
972
|
+
"vercel",
|
|
973
|
+
"wpcom",
|
|
974
|
+
"pressable",
|
|
975
|
+
"route53",
|
|
976
|
+
"stattic",
|
|
977
|
+
"manual",
|
|
978
|
+
]);
|
|
979
|
+
export const domainProviderConnectionSchema = z.object({
|
|
980
|
+
id: z.string().describe("Provider connection id (`dpc_...`)."),
|
|
981
|
+
tenantId: z.string().describe("Tenant that owns this connection."),
|
|
982
|
+
teamId: z.string().describe("Team that owns this connection."),
|
|
983
|
+
provider: domainDnsProviderSchema.describe("DNS provider key."),
|
|
984
|
+
label: z.string().describe("Human-readable provider account label."),
|
|
985
|
+
authType: z.enum(["oauth", "api_token", "internal_service"]),
|
|
986
|
+
credentialConfigured: z.boolean().describe("Whether a write credential exists."),
|
|
987
|
+
scopes: z
|
|
988
|
+
.array(z.string())
|
|
989
|
+
.describe("Provider scopes or permissions observed for this connection."),
|
|
990
|
+
status: domainProviderConnectionStatusSchema,
|
|
991
|
+
lastVerifiedAt: z.string().datetime().nullable(),
|
|
992
|
+
lastErrorCode: z.string().nullable(),
|
|
993
|
+
createdAt: z.string().datetime(),
|
|
994
|
+
updatedAt: z.string().datetime(),
|
|
995
|
+
});
|
|
996
|
+
export const domainProviderConnectionListQuerySchema = cursorListQuerySchema.extend({
|
|
997
|
+
teamId: z.string().min(1).optional(),
|
|
998
|
+
provider: domainDnsProviderSchema.optional(),
|
|
999
|
+
});
|
|
1000
|
+
export const domainProviderConnectionListResponseSchema = z.object({
|
|
1001
|
+
data: z.array(domainProviderConnectionSchema),
|
|
1002
|
+
pagination: CursorPaginationSchema,
|
|
1003
|
+
});
|
|
1004
|
+
export const domainProviderConnectionCreateSchema = z
|
|
1005
|
+
.object({
|
|
1006
|
+
teamId: z.string().min(1).optional(),
|
|
1007
|
+
provider: domainDnsProviderSchema,
|
|
1008
|
+
label: z.string().trim().min(1).max(255),
|
|
1009
|
+
authType: z.enum(["api_token", "internal_service"]).default("api_token"),
|
|
1010
|
+
apiToken: z.string().trim().min(1).optional(),
|
|
1011
|
+
})
|
|
1012
|
+
.superRefine((value, ctx) => {
|
|
1013
|
+
if (value.authType === "api_token" && !value.apiToken) {
|
|
1014
|
+
ctx.addIssue({
|
|
1015
|
+
code: "custom",
|
|
1016
|
+
path: ["apiToken"],
|
|
1017
|
+
message: "`apiToken` is required for api_token provider connections.",
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
if (value.authType === "internal_service" && value.apiToken) {
|
|
1021
|
+
ctx.addIssue({
|
|
1022
|
+
code: "custom",
|
|
1023
|
+
path: ["apiToken"],
|
|
1024
|
+
message: "`apiToken` is not accepted for internal service provider connections.",
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
export const domainProviderConnectionOAuthCompleteSchema = z.object({
|
|
1029
|
+
teamId: z.string().min(1).optional(),
|
|
1030
|
+
provider: z.literal("cloudflare"),
|
|
1031
|
+
label: z.string().trim().min(1).max(255).optional(),
|
|
1032
|
+
});
|
|
1033
|
+
export const domainProviderZoneSchema = z.object({
|
|
1034
|
+
id: z.string().describe("Provider-native zone id."),
|
|
1035
|
+
name: z.string().describe("Provider-native zone name."),
|
|
1036
|
+
provider: domainDnsProviderSchema,
|
|
1037
|
+
connectionId: z.string(),
|
|
1038
|
+
status: z.enum(["active", "pending", "moved", "unknown"]).default("unknown"),
|
|
1039
|
+
observedNameservers: z.array(z.string()).default([]),
|
|
1040
|
+
});
|
|
1041
|
+
export const domainProviderZoneListResponseSchema = z.object({
|
|
1042
|
+
data: z.array(domainProviderZoneSchema),
|
|
1043
|
+
pagination: CursorPaginationSchema,
|
|
1044
|
+
});
|
|
1045
|
+
export const domainProviderZoneAttachSchema = z.object({
|
|
1046
|
+
connectionId: z.string().min(1),
|
|
1047
|
+
providerZoneId: z.string().min(1).max(255),
|
|
1048
|
+
providerZoneName: z.string().trim().min(1).max(255).optional(),
|
|
1049
|
+
});
|
|
1050
|
+
export const domainProviderSnapshotRefreshResponseSchema = mutationEnvelope(z.object({
|
|
1051
|
+
operationId: z.string(),
|
|
1052
|
+
snapshot: z.object({
|
|
1053
|
+
freshness: z.enum(["refresh_running", "fresh", "refresh_failed"]),
|
|
1054
|
+
lastFetchedAt: z.string().datetime().nullable(),
|
|
1055
|
+
version: z.string().nullable(),
|
|
1056
|
+
}),
|
|
1057
|
+
}));
|
|
1058
|
+
export const domainDnsOperationKindSchema = z.enum([
|
|
1059
|
+
"provider_snapshot_refresh",
|
|
1060
|
+
"provider_zone_detach",
|
|
1061
|
+
"dns_record_create",
|
|
1062
|
+
"dns_record_patch",
|
|
1063
|
+
"dns_record_delete",
|
|
1064
|
+
"dns_record_batch",
|
|
1065
|
+
"review_import",
|
|
1066
|
+
"apply_dns_plan",
|
|
1067
|
+
"apply_service_template",
|
|
1068
|
+
"change_nameservers",
|
|
1069
|
+
"observe_cutover",
|
|
1070
|
+
"repair_connection",
|
|
1071
|
+
"registration_action",
|
|
1072
|
+
"feature_action",
|
|
1073
|
+
]);
|
|
1074
|
+
export const domainDnsOperationSchema = z.object({
|
|
1075
|
+
id: z.string().describe("DNS operation id (`dop_...`)."),
|
|
1076
|
+
domainId: z.string(),
|
|
1077
|
+
zoneId: z.string().nullable(),
|
|
1078
|
+
operationKind: domainDnsOperationKindSchema,
|
|
1079
|
+
status: z.enum(["queued", "running", "observing", "succeeded", "failed", "canceled"]),
|
|
1080
|
+
provider: z.string(),
|
|
1081
|
+
providerConnectionId: z.string().nullable(),
|
|
1082
|
+
providerZoneId: z.string().nullable(),
|
|
1083
|
+
providerRequestId: z.string().nullable(),
|
|
1084
|
+
providerReceipt: AnyObjectSchema.nullable(),
|
|
1085
|
+
diagnostics: z.array(baseDiagnosticSchema),
|
|
1086
|
+
requiredActions: requiredActionsSchema,
|
|
1087
|
+
retryable: z.boolean(),
|
|
1088
|
+
retryAfterAt: z.string().datetime().nullable(),
|
|
1089
|
+
attemptCount: z.number().int().nonnegative(),
|
|
1090
|
+
lastSafeErrorCode: z.string().nullable(),
|
|
1091
|
+
lastSafeErrorMessage: z.string().nullable(),
|
|
1092
|
+
hints: z.array(domainHintSchema).optional(),
|
|
1093
|
+
createdAt: z.string().datetime(),
|
|
1094
|
+
updatedAt: z.string().datetime(),
|
|
1095
|
+
completedAt: z.string().datetime().nullable(),
|
|
1096
|
+
});
|
|
1097
|
+
export const domainDnsOperationListResponseSchema = z.object({
|
|
1098
|
+
data: z.array(domainDnsOperationSchema),
|
|
1099
|
+
pagination: CursorPaginationSchema,
|
|
1100
|
+
});
|
|
1101
|
+
export const domainActionKindSchema = z.enum([
|
|
1102
|
+
"setup_dns_records",
|
|
1103
|
+
"connect_provider",
|
|
1104
|
+
"review_import",
|
|
1105
|
+
"apply_dns_plan",
|
|
1106
|
+
"apply_service_template",
|
|
1107
|
+
"change_nameservers",
|
|
1108
|
+
"observe_cutover",
|
|
1109
|
+
"repair_connection",
|
|
1110
|
+
"registration_action",
|
|
1111
|
+
"feature_action",
|
|
1112
|
+
]);
|
|
1113
|
+
export const domainActionSubjectFacetSchema = z.enum([
|
|
1114
|
+
"dns_zone",
|
|
1115
|
+
"registrar",
|
|
1116
|
+
"hosting_binding",
|
|
1117
|
+
"email",
|
|
1118
|
+
"forwarding",
|
|
1119
|
+
"provider_connection",
|
|
1120
|
+
]);
|
|
1121
|
+
export const domainActionStatusSchema = z.enum([
|
|
1122
|
+
"open",
|
|
1123
|
+
"planned",
|
|
1124
|
+
"waiting_for_user",
|
|
1125
|
+
"ready_to_run",
|
|
1126
|
+
"running",
|
|
1127
|
+
"observing",
|
|
1128
|
+
"complete",
|
|
1129
|
+
"canceled",
|
|
1130
|
+
"dismissed",
|
|
1131
|
+
"expired",
|
|
1132
|
+
"failed",
|
|
1133
|
+
"blocked",
|
|
1134
|
+
"review_required",
|
|
1135
|
+
"reconnect_required",
|
|
1136
|
+
"retry_available",
|
|
1137
|
+
"rollback_available",
|
|
1138
|
+
]);
|
|
1139
|
+
export const domainActionOwnerSchema = z.enum([
|
|
1140
|
+
"stattic",
|
|
1141
|
+
"user",
|
|
1142
|
+
"team_admin",
|
|
1143
|
+
"provider",
|
|
1144
|
+
"registrar",
|
|
1145
|
+
"support",
|
|
1146
|
+
"waiting",
|
|
1147
|
+
]);
|
|
1148
|
+
export const domainActionSchema = z.object({
|
|
1149
|
+
id: z.string().describe("Domain action id (`dac_...`)."),
|
|
1150
|
+
tenantId: z.string(),
|
|
1151
|
+
teamId: z.string(),
|
|
1152
|
+
domainId: z.string(),
|
|
1153
|
+
kind: domainActionKindSchema,
|
|
1154
|
+
subjectFacet: domainActionSubjectFacetSchema,
|
|
1155
|
+
subject: AnyObjectSchema,
|
|
1156
|
+
status: domainActionStatusSchema,
|
|
1157
|
+
owner: domainActionOwnerSchema,
|
|
1158
|
+
desiredOutcome: AnyObjectSchema,
|
|
1159
|
+
plan: AnyObjectSchema,
|
|
1160
|
+
instructions: z.array(AnyObjectSchema),
|
|
1161
|
+
operationId: z.string().nullable(),
|
|
1162
|
+
observation: AnyObjectSchema,
|
|
1163
|
+
recovery: AnyObjectSchema,
|
|
1164
|
+
expiresAt: z.string().datetime().nullable(),
|
|
1165
|
+
createdAt: z.string().datetime(),
|
|
1166
|
+
updatedAt: z.string().datetime(),
|
|
1167
|
+
});
|
|
1168
|
+
export const domainActionListResponseSchema = z.object({
|
|
1169
|
+
data: z.array(domainActionSchema),
|
|
1170
|
+
pagination: CursorPaginationSchema,
|
|
1171
|
+
});
|
|
1172
|
+
export const domainActionListQuerySchema = cursorListQuerySchema.extend({
|
|
1173
|
+
status: domainActionStatusSchema.optional(),
|
|
1174
|
+
});
|
|
1175
|
+
const domainActionDraftSchema = z.object({
|
|
1176
|
+
kind: domainActionKindSchema,
|
|
1177
|
+
subjectFacet: domainActionSubjectFacetSchema,
|
|
1178
|
+
subject: AnyObjectSchema.default({}),
|
|
1179
|
+
owner: domainActionOwnerSchema,
|
|
1180
|
+
desiredOutcome: AnyObjectSchema,
|
|
1181
|
+
plan: AnyObjectSchema.default({}),
|
|
1182
|
+
instructions: z.array(AnyObjectSchema).default([]),
|
|
1183
|
+
observation: AnyObjectSchema.default({}),
|
|
1184
|
+
recovery: AnyObjectSchema.default({}),
|
|
1185
|
+
expiresAt: z.string().datetime().nullable().optional(),
|
|
1186
|
+
});
|
|
1187
|
+
export const domainActionPreviewRequestSchema = domainActionDraftSchema;
|
|
1188
|
+
export const domainActionCreateRequestSchema = domainActionDraftSchema.extend({
|
|
1189
|
+
dedupeKey: z
|
|
1190
|
+
.string()
|
|
1191
|
+
.trim()
|
|
1192
|
+
.min(1)
|
|
1193
|
+
.max(320)
|
|
1194
|
+
.optional()
|
|
1195
|
+
.describe("Stable caller-supplied dedupe key; server derives one when omitted."),
|
|
1196
|
+
});
|
|
1197
|
+
export const domainActionMutationResponseSchema = mutationEnvelope(domainActionSchema);
|
|
1198
|
+
export const domainDnsRecordSnapshotMetadataSchema = z.object({
|
|
1199
|
+
status: z.enum([
|
|
1200
|
+
"missing",
|
|
1201
|
+
"fresh",
|
|
1202
|
+
"stale",
|
|
1203
|
+
"refreshing",
|
|
1204
|
+
"refresh_failed",
|
|
1205
|
+
"provider_unavailable",
|
|
1206
|
+
]),
|
|
1207
|
+
fetchedAt: z.string().datetime().nullable(),
|
|
1208
|
+
expiresAt: z.string().datetime().nullable(),
|
|
1209
|
+
providerVersion: z.string().nullable().optional(),
|
|
1210
|
+
providerRequestId: z.string().nullable().optional(),
|
|
1211
|
+
recordCount: z.number().int().nonnegative(),
|
|
1212
|
+
});
|
|
1213
|
+
export const domainDnsRecordListResponseSchema = z.object({
|
|
1214
|
+
data: z.array(domainDnsRecordSchema),
|
|
1215
|
+
pagination: CursorPaginationSchema,
|
|
1216
|
+
managementKind: z.enum(DNS_MANAGEMENT_KINDS).optional(),
|
|
1217
|
+
provider: z.string().nullable().optional(),
|
|
1218
|
+
writeStrategy: domainDnsProviderWriteStrategySchema.optional(),
|
|
1219
|
+
snapshot: domainDnsRecordSnapshotMetadataSchema.optional(),
|
|
1220
|
+
capabilities: domainDnsCapabilitiesSchema.optional(),
|
|
1221
|
+
});
|
|
1222
|
+
export const domainDnsRecordListQuerySchema = cursorListQuerySchema;
|
|
1223
|
+
function requireMxSrvPriority(value, ctx) {
|
|
1224
|
+
if ((value.type === "MX" || value.type === "SRV") &&
|
|
1225
|
+
(value.priority === undefined || value.priority === null)) {
|
|
1226
|
+
ctx.addIssue({
|
|
1227
|
+
code: "custom",
|
|
1228
|
+
path: ["priority"],
|
|
1229
|
+
message: `\`priority\` is required for ${value.type} records.`,
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
if (value.type !== undefined &&
|
|
1233
|
+
value.type !== "MX" &&
|
|
1234
|
+
value.type !== "SRV" &&
|
|
1235
|
+
value.priority !== undefined &&
|
|
1236
|
+
value.priority !== null) {
|
|
1237
|
+
ctx.addIssue({
|
|
1238
|
+
code: "custom",
|
|
1239
|
+
path: ["priority"],
|
|
1240
|
+
message: "`priority` is only valid for MX and SRV records.",
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
export function validateDomainDnsFieldCapabilities(input, capabilities, operation) {
|
|
1245
|
+
const fields = new Map(capabilities.fields.map((field) => [field.path, field]));
|
|
1246
|
+
const issues = [];
|
|
1247
|
+
for (const [path, value] of Object.entries(input)) {
|
|
1248
|
+
if (value === undefined) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const field = fields.get(path);
|
|
1252
|
+
const writable = operation === "create" ? field?.creatable : field?.patchable;
|
|
1253
|
+
if (!field || !writable) {
|
|
1254
|
+
issues.push({
|
|
1255
|
+
path,
|
|
1256
|
+
code: "dns_field_not_applicable",
|
|
1257
|
+
message: `${path} is not writable for this DNS provider or management mode.`,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
for (const group of capabilities.exclusiveGroups) {
|
|
1262
|
+
const present = group.fields.filter((field) => input[field] !== undefined && input[field] !== null);
|
|
1263
|
+
const appliesToProvider = !group.appliesWhen?.providerKeys ||
|
|
1264
|
+
group.appliesWhen.providerKeys.includes(capabilities.provider ?? "");
|
|
1265
|
+
const appliesToManagementKind = !group.appliesWhen?.managementKinds ||
|
|
1266
|
+
group.appliesWhen.managementKinds.includes(capabilities.managementKind);
|
|
1267
|
+
if (!appliesToProvider || !appliesToManagementKind) {
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
let conflictPath = null;
|
|
1271
|
+
switch (group.mode) {
|
|
1272
|
+
case "forbidden_with":
|
|
1273
|
+
case "at_most_one":
|
|
1274
|
+
if (present.length > 1) {
|
|
1275
|
+
conflictPath = present[0] ?? group.fields[0] ?? "dns";
|
|
1276
|
+
}
|
|
1277
|
+
break;
|
|
1278
|
+
case "exactly_one":
|
|
1279
|
+
if (present.length !== 1) {
|
|
1280
|
+
conflictPath = group.fields[0] ?? "dns";
|
|
1281
|
+
}
|
|
1282
|
+
break;
|
|
1283
|
+
case "required_with":
|
|
1284
|
+
if (present.length > 0 && present.length < group.fields.length) {
|
|
1285
|
+
conflictPath =
|
|
1286
|
+
group.fields.find((field) => !present.includes(field)) ?? group.fields[0] ?? "dns";
|
|
1287
|
+
}
|
|
1288
|
+
break;
|
|
1289
|
+
}
|
|
1290
|
+
if (conflictPath !== null) {
|
|
1291
|
+
issues.push({
|
|
1292
|
+
path: conflictPath,
|
|
1293
|
+
code: "dns_field_conflict",
|
|
1294
|
+
message: group.message,
|
|
1295
|
+
fixSuggestion: group.fixSuggestion,
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return issues;
|
|
1300
|
+
}
|
|
1301
|
+
const domainDnsRecordPatchableFields = z.object({
|
|
1302
|
+
type: domainDnsRecordSchema.shape.type,
|
|
1303
|
+
name: z
|
|
1304
|
+
.string()
|
|
1305
|
+
.trim()
|
|
1306
|
+
.min(1)
|
|
1307
|
+
.max(255)
|
|
1308
|
+
.describe('Record name relative to the zone; defaults to "@" on create.'),
|
|
1309
|
+
value: z.string().trim().min(1).max(2048).describe("Record value."),
|
|
1310
|
+
ttl: z
|
|
1311
|
+
.number()
|
|
1312
|
+
.int()
|
|
1313
|
+
.min(60)
|
|
1314
|
+
.max(86400)
|
|
1315
|
+
.describe("Time to live in seconds (60-86400); defaults to 3600 on create."),
|
|
1316
|
+
priority: z
|
|
1317
|
+
.number()
|
|
1318
|
+
.int()
|
|
1319
|
+
.min(0)
|
|
1320
|
+
.max(65535)
|
|
1321
|
+
.nullable()
|
|
1322
|
+
.optional()
|
|
1323
|
+
.describe("Record priority; required for MX and SRV."),
|
|
1324
|
+
proxied: z
|
|
1325
|
+
.boolean()
|
|
1326
|
+
.nullable()
|
|
1327
|
+
.optional()
|
|
1328
|
+
.describe("Provider proxying flag; accepted only when capabilities advertise it."),
|
|
1329
|
+
comment: z.string().trim().max(1024).nullable().optional().describe("Record comment."),
|
|
1330
|
+
providerOptions: AnyObjectSchema.nullable()
|
|
1331
|
+
.optional()
|
|
1332
|
+
.describe("Provider-only record fields; accepted only when capabilities advertise them."),
|
|
1333
|
+
});
|
|
1334
|
+
const domainDnsRecordFields = domainDnsRecordPatchableFields.extend({
|
|
1335
|
+
name: domainDnsRecordPatchableFields.shape.name.default("@"),
|
|
1336
|
+
ttl: domainDnsRecordPatchableFields.shape.ttl.default(3600),
|
|
1337
|
+
});
|
|
1338
|
+
export const domainDnsRecordCreateSchema = domainDnsRecordFields.superRefine(requireMxSrvPriority);
|
|
1339
|
+
export const domainDnsRecordPatchSchema = domainDnsRecordPatchableFields
|
|
1340
|
+
.partial()
|
|
1341
|
+
.refine((value) => Object.keys(value).length > 0, {
|
|
1342
|
+
message: "At least one DNS record field is required.",
|
|
1343
|
+
})
|
|
1344
|
+
.superRefine(requireMxSrvPriority);
|
|
1345
|
+
// Executed in exactly this order — deletes, patches, puts, posts — as one
|
|
1346
|
+
// atomic transaction; any failure rejects the whole batch with the failing
|
|
1347
|
+
// entry in `error.param`.
|
|
1348
|
+
export const domainDnsRecordBatchSchema = z
|
|
1349
|
+
.object({
|
|
1350
|
+
deletes: z
|
|
1351
|
+
.array(z.object({ id: z.string().describe("Record id to delete.") }))
|
|
1352
|
+
.default([])
|
|
1353
|
+
.describe("Records to delete, applied first."),
|
|
1354
|
+
patches: z
|
|
1355
|
+
.array(domainDnsRecordPatchableFields
|
|
1356
|
+
.partial()
|
|
1357
|
+
.extend({ id: z.string() })
|
|
1358
|
+
.superRefine(requireMxSrvPriority))
|
|
1359
|
+
.default([])
|
|
1360
|
+
.describe("Partial record updates, applied second."),
|
|
1361
|
+
puts: z
|
|
1362
|
+
.array(domainDnsRecordFields.extend({ id: z.string() }).superRefine(requireMxSrvPriority))
|
|
1363
|
+
.default([])
|
|
1364
|
+
.describe("Full record upserts by id, applied third."),
|
|
1365
|
+
posts: z
|
|
1366
|
+
.array(domainDnsRecordFields.superRefine(requireMxSrvPriority))
|
|
1367
|
+
.default([])
|
|
1368
|
+
.describe("New records, applied last."),
|
|
1369
|
+
})
|
|
1370
|
+
.strict();
|
|
1371
|
+
// ---------------------------------------------------------------------------
|
|
1372
|
+
// Per-hostname connectivity diagnostics (dashboard/CLI/agent read model).
|
|
1373
|
+
// ---------------------------------------------------------------------------
|
|
1374
|
+
export const domainHostnameDnsSnapshotSchema = z.object({
|
|
1375
|
+
resolver: z.string().describe("Public resolver the snapshot was taken from."),
|
|
1376
|
+
a: z.array(z.string()).default([]),
|
|
1377
|
+
aaaa: z.array(z.string()).default([]),
|
|
1378
|
+
caa: z.array(z.string()).default([]),
|
|
1379
|
+
txt: z.array(z.string()).default([]),
|
|
1380
|
+
});
|
|
1381
|
+
export const domainHostnameSslBrokenRecordSchema = z.object({
|
|
1382
|
+
reason: z.string().nullable(),
|
|
1383
|
+
lastError: z.string().nullable(),
|
|
1384
|
+
retryDate: z.string().nullable(),
|
|
1385
|
+
failCount: z.number().int().nullable(),
|
|
1386
|
+
});
|
|
1387
|
+
export const domainHostnameDiagnosticsSchema = z.object({
|
|
1388
|
+
hostname: z.string().describe("Hostname this diagnostic block describes."),
|
|
1389
|
+
purpose: z
|
|
1390
|
+
.enum(["apex", "www", "configured"])
|
|
1391
|
+
.describe("Why this hostname is checked: the apex, its www twin, or the configured name."),
|
|
1392
|
+
isConfiguredHostname: z.boolean().describe("Whether this is the hostname on the domain record."),
|
|
1393
|
+
providerIps: z
|
|
1394
|
+
.array(z.string())
|
|
1395
|
+
.default([])
|
|
1396
|
+
.describe("Provider IPs the hostname should point at."),
|
|
1397
|
+
verificationCode: z.string().nullable().describe("Expected TXT verification value."),
|
|
1398
|
+
dnsInstructions: z
|
|
1399
|
+
.array(AnyObjectSchema)
|
|
1400
|
+
.default([])
|
|
1401
|
+
.describe("DNS records required for this hostname."),
|
|
1402
|
+
dns: z.object({
|
|
1403
|
+
expectedTxtValue: z.string().nullable(),
|
|
1404
|
+
pointsToProvider: z.boolean().describe("Whether observed A records point at the provider."),
|
|
1405
|
+
txtVerified: z.boolean().nullable().describe("Whether the verification TXT is observed."),
|
|
1406
|
+
hasAaaaRecords: z.boolean(),
|
|
1407
|
+
hasCaaRecords: z.boolean(),
|
|
1408
|
+
records: z.object({
|
|
1409
|
+
a: z.array(z.string()).default([]),
|
|
1410
|
+
aaaa: z.array(z.string()).default([]),
|
|
1411
|
+
caa: z.array(z.string()).default([]),
|
|
1412
|
+
txt: z.array(z.string()).default([]),
|
|
1413
|
+
}),
|
|
1414
|
+
byResolver: z.array(domainHostnameDnsSnapshotSchema).default([]),
|
|
1415
|
+
}),
|
|
1416
|
+
ssl: z.object({
|
|
1417
|
+
status: z.enum(["missing", "pending", "active", "broken"]).describe("Certificate state."),
|
|
1418
|
+
canRetry: z.boolean().describe("Whether SSL provisioning can be retried now."),
|
|
1419
|
+
certificateId: z.string().nullable(),
|
|
1420
|
+
validationExpiration: z.string().nullable(),
|
|
1421
|
+
authzUri: z.string().nullable(),
|
|
1422
|
+
brokenRecord: domainHostnameSslBrokenRecordSchema.nullable(),
|
|
1423
|
+
}),
|
|
1424
|
+
});
|
|
1425
|
+
export const domainDiagnosticsResponseSchema = z.object({
|
|
1426
|
+
domainId: z.string().describe("Domain inventory record (`dom_...`)."),
|
|
1427
|
+
hostname: z.string().describe("Configured hostname."),
|
|
1428
|
+
diagnostics: z
|
|
1429
|
+
.array(baseDiagnosticSchema)
|
|
1430
|
+
.optional()
|
|
1431
|
+
.describe('Domain-level setup nudges computed at read time, e.g. "companion_hostname_unbound" when the apex/www pair is half-bound.'),
|
|
1432
|
+
hostnameDiagnostics: z
|
|
1433
|
+
.array(domainHostnameDiagnosticsSchema)
|
|
1434
|
+
.describe("Per-hostname DNS and SSL connectivity diagnostics."),
|
|
1435
|
+
});
|