// Types for retire-site.js β€” WRITTEN rather than `@ts-expect-error`'d, on retry.d.ts's criterion: // this is our module and getting it wrong is customer-facing. // // πŸ”΄ What a declaration buys HERE specifically is that `site` is a RESOLVED RECORD and not a key. // Under `any`, `retireSite({ accountId, site: "sodaart" })` compiles, but the coordinator needs the // resolved project handles to perform and verify cleanup. The one caller resolves the site through // the tenancy boundary first (`this.resolve`); this type is what says that is not optional. /** * Tools the hosted Worker registers and never announces. See the .js header: the set lives with the * feature it hides, not with the registrations it affects. */ export const UNLISTED_TOOL_NAMES: ReadonlySet; export const RETIRE_REQUIRES_CONSOLE_SENTENCE: string; /** The plan line saying Site Storage is deleted after retirement (SPEC-site-media-upload Β§9.15). */ export const SITE_STORAGE_PLAN_LINE: string; /** Register tools, then drop `unlisted` from whatever the SDK's tools/list answers. */ export function withUnlistedTools( mcpServer: unknown, listToolsRequestSchema: unknown, unlisted: ReadonlySet, registerTools: () => T, ): T; /** The half of a resolved site record this module reads. Everything else on the record is ignored. */ export interface RetirableSite { key: string; /** Retired rows use the legacy idempotent re-retire path and do not create a leave. */ status?: string; /** `org/name`. Absent β‡’ nothing to archive, and the receipt says so. */ repo?: string | null; /** D1's recorded host; Cloudflare's project attachment list is authoritative during retirement. */ domain?: string | null; previewProject?: string | null; prodProject?: string | null; } export interface RetireSitePlan { site: string; wouldRetire: true; repo: string | null; previewProject: string | null; whatGoes: string[]; whatIsKept: string[]; /** * #1265 PR-A β€” `ready` is the verdict confirm would reach now; `refusal` (when not ready) carries * the confirm refusal's own code and sentence. `reason: "unreadable"` β‡’ the gate could not be read. */ exportPrerequisite: { required: true; evidence: string; leaveFlowStartedAt?: number; ready: boolean; refusal?: { code: string; reason: string | null; message: string; missing?: string[] }; }; closingEffect: { begins: string; effect: string }; /** #1242 第 5 點 β€” after confirm no personal data is kept and nothing can be undone. */ /** #1316 β€” nothing kept; `legalRecords` is the one line of ours the law requires (#1242 第 3 點). */ afterConfirm: { keeps: "named_exceptions_only"; legalRecords: string; undo: "none"; note: string; retainedException: string }; leaveStatus?: SiteLeaveStatus; confirmHint: string; permanence: string; } /** * #1368 第 5 點 β€” the plan of a site that is ALREADY retired: nothing a confirm could retire, so no * `wouldRetire:true`, no confirm hint and no export gate. `cleanup` is the retrier's live state (#1204). */ export interface RetireSiteRetiredPlan { site: string; wouldRetire: false; retired: true; alreadyRetired: true; repo: string | null; previewProject: string | null; whatIsKept: string[]; /** #1265 β€” a retired site is never gated on an export. */ exportPrerequisite: { ready: true }; cleanup?: unknown; leaveStatus?: SiteLeaveStatus; } export interface SiteLeaveStatus { phase: "preparing" | "accepted" | "completed"; leave_attempt_id: string; leave_confirmed_at: number | null; purge_after: number | null; fences?: Record; purge?: Record; completed_at: number | null; /** * RSP's named retained exceptions for an accepted or completed leave (always * `pre_attribution_members_history`); absent from older RSP builds. The bound is a Member * Group-wide upper bound, never this Site's count. */ retained_exceptions?: Array<{ code: "pre_attribution_members_history"; members_pre_attribution_rows_upper_bound: number | null; }>; } export interface RetireSiteReconcilingReceipt { site: string; retired: false; closing: true; reconciling: true; leave: SiteLeaveStatus | null; retirementComplete: false; forHuman: string; } export interface RetireSiteReceipt { retired: true; retirementComplete: boolean; /** True when it was already retired before this call β€” an idempotent success, never a failure. */ alreadyRetired: boolean; site: string; leave: SiteLeaveStatus | null; /** #1316 β€” the repo delete, LAST. An incomplete one is a reported FACT, not a thrown failure. */ /** `retry.notifyOnly` β€” held, but what it waits on has no retry of its own: it can only reach a person. */ repo: RepositoryDeletion & { retry?: { scheduled: false } | { scheduled: true; notifyOnly?: true; nextAttemptAt: string; maxAttempts: number }; /** #1385 β€” present (`reason: "leave_handled"`) when an earlier leave closed the site: what the repo retrier holds NOW. */ viaLeave?: true; retrier?: RetireRepoRetrierState; /** reef#1482 β€” that site's repository was READ at this confirm and is still there (so it is recorded as an unheld gap). */ stillThere?: true; }; /** #1316 β€” true only when no repository of this site is left (deleted, already gone, or none on record). */ repositoryDeleted: boolean; /** #1445 β€” true only when THIS confirm took the preview down and the provisioner answered, or the record says there is none. * γ€ŒNo handle, so nothing was done」 (a record that says R2 with no prefix reaching the retirer) is false. */ previewClosed: boolean; production: ProductionCleanup & { /** #1191 β€” present only when `complete` is false: whether the site's operations DO holds a retry. */ retry?: { scheduled: false } | { scheduled: true; nextAttemptAt: string; maxAttempts: number }; }; preview: { deleted: boolean; note: string; terminal?: boolean; retry?: { scheduled: boolean; nextAttemptAt?: string; maxAttempts?: number } }; previewAddress: { removed: boolean; note: string }; /** #1204 β€” the preview's Cloudflare Access app, when this deployment manages them. */ previewAccess?: { deleted: boolean; note: string }; whatIsKept: string[]; message: string; forHuman: string; /** #1389 β€” the receipt as one key per sentence: `lead` first, `kept` behind γ€ŒδΏη•™δΊ†δ»€ιΊΌγ€. */ forHumanKey: RetireReceiptKey; } /** #1385 β€” the repo retrier's live entry for a site an earlier leave closed (for-human.js `retireRepoRetrierState`). */ export interface RetireRepoRetrierState { state: "done" | "pending" | "notified" | "gave_up" | "none" | "unreadable"; maxAttempts?: number; } export interface RetireReceiptKey { lead: Array<{ key: string; params: Record }>; kept: Array<{ key: string; params: Record }>; } export const RETIRE_PERMANENT_SENTENCE: string; export const RETIRE_LEGAL_RECORDS_LINE: string; export const RETIRE_PERMANENT_SENTENCE_LEGACY: string; export const RETIRE_PERMANENT_SENTENCE_IMMEDIATE: string; export const RETIRE_LEGAL_RECORDS_LINE_NAMED: string; /** #245 D6 β€” the plan copy for the policy actually enforced for a leave (`immediate-v1` only when the status says so). */ export function retirePolicyCopy(policy?: string | null): { policy: 'legacy-7d' | 'immediate-v1'; permanentSentence: string; legalRecordsLine: string; }; /** #1316 β€” one repo delete's answer (retire_site step 3, and the retrier's run). */ export interface RepositoryDeletion { deleted: boolean; /** No repository of this site is left. */ complete: boolean; existed?: boolean; reason?: "repo_delete_forbidden" | "failed" | "unreachable" | "waiting_on_cleanup" | "legacy_retired" | "export_receipt_missing" | "leave_handled" | "leave_state_unreadable"; note: string; } /** #1316 β€” never throws. */ export function deleteRepository(p: { provisioner: { deleteRepo?(p: { name: string }): Promise } | null | undefined; key: string; repoName: string | null; log?: (m: string) => void }): Promise; export function assertTypedKey(key: string, typedKey: string | null | undefined, verb: string): void; /** #245 D3 β€” one managed resource's outcome from the internal retirer (facts for RSP, not owner copy). */ export interface RetireResourceOutcome { state: "complete" | "not_applicable" | "retrying" | "blocked"; reason?: string; /** #245 D5d β€” the provider gate a `handle_unverifiable` resource waits on (G2/G3). */ gate?: string; } export interface RetireResourcesResult { retired: true; retirementComplete: boolean; repositoryDeleted: boolean; resources: Record<"production" | "preview" | "repository", RetireResourceOutcome>; /** #245 D5d β€” present when the run was bound to exit handles. */ zoneTombstone?: RetireResourceOutcome; } export interface SiteRetirer { retireSite(p: { accountId: string; site: RetirableSite; confirm?: boolean; /** Must equal `site.key` EXACTLY when `confirm` is true. */ typedKey?: string | null; /** reef#1265 P2 β€” the owner opened the leave screen: a new attempt begins and the last one's export no longer counts. */ beginLeave?: boolean; }): Promise; /** * #245 D3 β€” the internal entry for a verified RSP-signed `retire_resources` command. Asserts the * registry row already reads retired for this Owner (throws `registry_not_retired` / * `registry_readback_unavailable` before any effect), then runs the same teardown. */ retireResources(p: { accountId: string; site: RetirableSite & { previewBucketPrefix?: string | null }; /** #245 D5d β€” the exit's first-command handle snapshot; with it every retry is a v2 entry. */ handles?: Record | null; siteIncarnationId?: string | null; exitAttemptId?: string | null; }): Promise; } /** #245 D3 β€” the public `retire_site` tool's only answer. */ export const SITE_EXIT_CONSOLE_ONLY: "site_exit_console_only"; export function publicRetireSiteRefusal(): Error & { code: "site_exit_console_only" }; export function makeSiteRetirer(deps: { registry: { confirmSiteLeave(p: { key: string; accountId: string }): Promise; siteLeaveStatus?(p: { key: string; accountId: string }): Promise; /** #1266 β€” the Site's still-renewing leg modules; absent or throwing β‡’ a confirm refuses `retire_subscriptions_unreadable` (fail-closed). */ siteSubscriptions?(p: { key: string; accountId: string }): Promise>; retireSite(p: { key: string; accountId: string }): Promise<{ retired: boolean; key: string }>; siteByKeyForAccount(p: { key: string; accountId: string }): Promise<{ held: boolean; status?: string; owner?: boolean; prodProject?: string|null; prodProjectAttempt?: string|null }>; }; /** #1316 β€” reef-provisioner `/delete-repo`. */ provisioner?: { deleteRepo(p: { name: string }): Promise } | null; cfPages?: { /** πŸ”΄ CALLED FIRST. The allowlist entry must go before the project β€” deleting a Pages project * releases its name globally, and a surviving entry pointed at a released name is a subdomain * takeover on our own zone. Optional: an adapter without Cloudflare reach simply has none. */ unregisterPreviewSite?(p: { name: string; siteIncarnationId?: string }): Promise; /** reef#1473 β€” the `/list-project-domains` / `/detach-domain` bodies (`listProjectDomainsRequest`, * `detachDomainRequest`): the Pages project travels as `name`, the route's own term. */ listDomains?(p: { name: string }): Promise<{ domains?: string[]; projectExists?: boolean }>; detachDomain?(p: { name: string; domain: string }): Promise; /** #1315 β€” reef-provisioner `/zone-tomb`, once per attached address BEFORE any detach. Required * whenever there is an address to detach: absent β‡’ the production cleanup stays incomplete. */ tombZone?(p: { domain: string; accountId: string }): Promise; deleteProject(p: { name: string }): Promise; /** #1204 β€” called LAST, only once the preview project is gone. Cloudflare 404 β‡’ `{deleted:false}`. */ deletePreviewAccess?(p: { siteKey: string; projectName: string }): Promise<{ deleted?: boolean; code?: string } | unknown>; /** #1210 β€” empties `sites//` in the preview bucket (reef-provisioner `/delete-preview-objects`). */ deletePreviewObjects?(p: { siteKey: string; bucketPrefix: string }): Promise<{ reclaimed?: boolean; deleted?: number; reason?: string; code?: string } | unknown>; /** #1527 β€” reef-provisioner `/delete-preview-dns`: the address's DNS record (cleanUpPreview step 5). */ deletePreviewDns?(p: { siteKey: string }): Promise<{ deleted?: boolean; existed?: boolean }>; } | null; /** #1242 β€” where the leave flow's start and the owner's export receipt live (the site's operations DO * on the hosted door). Absent β‡’ a confirm on a live site refuses `export_required` (fail-closed). */ leaveExport?: { readFlow(p: { key: string }): Promise<{ startedAt?: number } | null>; /** Absent on the public `/mcp` (#1242 review P2-1): the plan then begins nothing. Absent on the * RSP-signed exit's port (`exitCommandLeaveExport`) too: that attempt is RSP's. */ beginFlow?: (p: { key: string; accountId: string; startedAt: number }) => Promise; readReceipt(p: { key: string; accountId: string }): Promise<{ account_id: string; exported_at: number; layers: Record } | null>; /** reef#1265 P2 β€” ends this site's leave attempt: drops the flow and every export receipt taken under it. Console door only. */ endFlow?: (p: { key: string }) => Promise; } | null; now?: () => number; /** #1191 β€” hands an unfinished production cleanup to a retrier; absent β‡’ the receipt says none. */ productionRetry?: { schedule(p: { key: string; accountId: string; tombHosts?: string[]; heldHost?: string | null } & ExitRetryBinding): Promise } | null; /** #1204 β€” the same hand-off for an unfinished preview cleanup. */ previewRetry?: { schedule(p: { key: string; accountId: string; previewProject: string | null; previewBucketPrefix: string | null } & ExitRetryBinding): Promise } | null; /** #1316 β€” hands a repo delete that did not happen (or has to wait for `waitFor`) to the retrier. */ repoRetry?: { schedule(p: { key: string; accountId: string; repoName: string; waitFor: Array<"production" | "preview"> } & ExitRetryBinding & { settled?: string[] }): Promise } | null; /** #1204 β€” an already-retired site's plan carries this as `cleanup` (the retrier's live state). */ cleanupStatus?: ((p: { key: string }) => Promise) | null; /** reef#1482 β€” `true` only when the repository was read and answered; anything else proves nothing. */ repoPresence?: ((p: { key: string; repoName: string }) => Promise) | null; /** reef#1458 β€” the durable record of a cleanup nothing is retrying. `record` is true only when the receiver confirmed the row. */ abandoned?: { record(p: { key: string; kind: "production" | "preview" | "repository" }): Promise; resolve?(p: { key: string; kind: "production" | "preview" | "repository" }): Promise; } | null; /** #1527 β€” our own signal for a preview DNS record a retire left (or may have left). Best effort. */ previewDnsLeftover?: { report(p: { key: string; reason: "conflict" | "failed" | "timeout" }): Promise; close?(p: { key: string }): Promise } | null; config?: { log?: (m: string) => void }; }): SiteRetirer; /** #1204 β€” one retrier entry, as the owner's receipt reads it. `null` β‡’ nothing was ever handed over. */ export interface RetireCleanupEntryStatus { state: "pending" | "exhausted" | "done"; attempts: number; maxAttempts: number; nextAttemptAt: number | null; /** exhausted only: true once the signal to us was actually delivered (2xx). */ notified: boolean; /** reef#1513 β€” when this kind's cleanup first failed; absent on a status from an older Worker. */ firstFailedAt?: number | null; } /** #1385 β€” `GET /retire-status` β†’ the status; throws on a non-200 or a body that is not a JSON object. */ export function retireStatusFromResponse(response: Response): Promise; /** #1445 β€” the `/retire-status` envelope: all three kinds present, each `null` or an entry object. */ export function isRetireStatus(body: unknown): body is RetireCleanupStatus; /** #1445 β€” `GET /leave-flow` β†’ the flow; throws on a non-200 or anything but `{ flow: null | { startedAt } }`. */ export function leaveFlowFromResponse(response: Response): Promise<{ startedAt: number } | null>; export function readLeaveFlow(flow: unknown): { startedAt: number } | null; export interface RetireCleanupStatus { production: RetireCleanupEntryStatus | null; preview: RetireCleanupEntryStatus | null; repository?: RetireCleanupEntryStatus | null } export interface PreviewCleanup { previewAddress: { removed: boolean; note: string }; /** reef #1222 β€” `terminal` means no retry can ever succeed; `attemptPreviewCleanup` * (hosted/src/retire-production.ts) forwards it so the DO alarm exhausts immediately instead of * burning the full retry budget on a certainty. `terminalReason` says which certainty: * `preview_name_mismatch` (#1222) or, reef#1473, `preview_not_registered` β€” the registry never * heard of the key, so no tombstone can be written and the project may not be deleted. */ preview: { deleted: boolean; note: string; terminal?: boolean; terminalReason?: "preview_name_mismatch" | "preview_not_registered" }; previewAccess: { deleted: boolean; note: string } | null; /** #1210 β€” only for an R2 preview (`previewBucketPrefix`). */ storage?: { reclaimed: boolean; prefix: string; reason?: string; deletedObjects?: number }; registryClean: boolean; projectClean: boolean; accessClean: boolean; storageClean: boolean; complete: boolean; /** #1211 β€” budget spent after deleting something: the retrier does not count this run. */ progressed: boolean; /** #1527 β€” the address's DNS record, a best-effort SIDE step: present only when it ran, and read by * nothing that decides completeness. `previewDnsLeftover` β€” a record is (`conflict`) or may be * (`failed`) left; reported once through `reportDnsLeftover`. */ previewDns?: { deleted: boolean; note: string }; previewDnsLeftover?: "conflict" | "failed" | "timeout"; retry?: { scheduled: boolean; nextAttemptAt?: string; maxAttempts?: number }; } /** #1210 β€” the receipt's `previewStorage`: reclaimed / retrying / kept. */ export interface PreviewStorageReceipt { reclaimed: boolean; state: "reclaimed" | "retrying" | "kept"; prefix: string; deletedObjects?: number; note: string } export function previewStorageReceipt(cleanup: PreviewCleanup): PreviewStorageReceipt | null; /** #1204 β€” never throws; each step idempotent; tombstone β†’ project β†’ Access app, in that order. */ /** #1527 β€” the preview DNS side step's bound per attempt and for its leftover report (ms). */ export const PREVIEW_DNS_ATTEMPT_TIMEOUT_MS: number; export function cleanUpPreview(p: { cfPages: any; key: string; /** #1216 β€” travels in the `/delete-project` body; the provisioner checks it against the registry. */ accountId?: string | null; previewProject?: string | null; previewBucketPrefix?: string | null; /** #245 D5d β€” a Site exit's tombstone names the incarnation it retired (X11). */ siteIncarnationId?: string | null; log?: (m: string) => void; /** #1527 β€” called once when step 5 leaves (or may leave) a preview DNS record. Awaited (bounded at * PREVIEW_DNS_ATTEMPT_TIMEOUT_MS); only `recorded === false` in its answer is read β€” it is logged. */ reportDnsLeftover?: ((p: { key: string; reason: "conflict" | "failed" | "timeout" }) => Promise) | null; /** #1527 round 3 β€” when step 5 finds nothing of ours left (removed, or none), closes the site's * `preview_dns` leftover row an earlier run may have opened. `false` β‡’ logged. */ closeDnsLeftover?: ((p: { key: string }) => Promise) | null; }): Promise; /** `tombHosts` (#1315) β€” the snapshot of hosts whose zones are tombstoned; the retrier keeps it. */ export interface ProductionCleanup { deleted: boolean; detached: string[]; complete: boolean; note: string; tombHosts?: string[] } /** #1191 β€” waits before retry 1, 2, …; its length is the cap. */ export const PRODUCTION_RETRY_DELAYS_MS: readonly number[]; export const PRODUCTION_RETRY_MAX_ATTEMPTS: number; /** Never throws; idempotently re-runnable (the D1 pointer is cleared last). */ export function cleanUpProduction(p: { registry: any; cfPages: any; key: string; accountId: string; /** #1315 β€” hosts to tombstone even if Cloudflare no longer lists them. */ tombHosts?: string[]; /** #1315 β€” the host the registry said this site held; its tombstone may supersede an older one. */ heldHost?: string | null; /** #245 D5d β€” a Site exit's incarnation: the zone tombstones carry it and the receipt lists each answer. */ siteIncarnationId?: string | null; log?: (m: string) => void; }): Promise; /** #1216 β€” the `/delete-project` request body (protocol v2). */ export function deleteProjectRequest(p: { name: string; key: string; accountId: string | null; kind: "preview" | "production" }): { name: string; siteKey: string; accountId: string | null; kind: "preview" | "production" }; /** reef#1473 β€” the `/list-project-domains` and `/detach-domain` request bodies (`name` = the Pages project). */ export function listProjectDomainsRequest(project: string): { name: string }; export function detachDomainRequest(project: string, domain: string): { name: string; domain: string }; /** #245 D5d */ export function handleUnverifiable(need: { repo?: boolean; pages?: string | null }, handles: any): RetireResourceOutcome | null; export function zoneTombstoneOutcome(production: any, productionOutcome: RetireResourceOutcome): RetireResourceOutcome; /** #245 D5d β€” a Site exit's binding, forwarded with every retry hand-off (absent outside a Site exit). */ export type ExitRetryBinding = { siteIncarnationId?: string | null; exitAttemptId?: string | null; handles?: unknown; productionProject?: string | null; }; /** A retry hand-off's answer: scheduled, or (#245 D5d) written `blocked` with no retry. */ export type RetryScheduled = | { nextAttemptAt: number; maxAttempts: number; state?: undefined } | { nextAttemptAt: null; maxAttempts: number; state: "blocked"; reason?: string; gate?: string };