From 50cf6dcd443510fa3bc1f99abe39ad172c1302cd Mon Sep 17 00:00:00 2001 From: rogu3bear Date: Tue, 30 Jun 2026 17:02:04 -0500 Subject: [PATCH] Preview maildesk storage setup commands The maildesk-cf lifecycle could identify missing D1, R2, and Queue resources, but it emitted generic blocked rows even though cfctl already has a preview-gated Wrangler wrapper for those create operations. That made the live maildesk plan less actionable than it needed to be without crossing the protected apply boundary. Map missing maildesk storage resources to cfctl wrangler create commands with --plan: D1 databases, R2 buckets, and Queues. The composite maildesk apply path remains blocked; this only teaches the plan to expose component preview commands that still require their own ack-plan before mutation. Contract coverage now asserts the missing-resource fixture produces unblocked preview commands for both D1 databases, both R2 buckets, and the Queue. Verified with the focused maildesk contract, static contract, permission catalog check, git diff --check, and a private desired-state plan-only readback. --- scripts/cf_maildesk_cf_lifecycle.py | 13 +++++++++++++ scripts/verify_maildesk_cf_contract.sh | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/scripts/cf_maildesk_cf_lifecycle.py b/scripts/cf_maildesk_cf_lifecycle.py index 0309a76..6240063 100644 --- a/scripts/cf_maildesk_cf_lifecycle.py +++ b/scripts/cf_maildesk_cf_lifecycle.py @@ -5,6 +5,7 @@ import json import os from pathlib import Path +import shlex import subprocess import sys import time @@ -150,6 +151,17 @@ def expected_storage(spec: dict[str, Any]) -> dict[str, str]: return {key: str(storage.get(key) or "") for key in keys if storage.get(key)} +def storage_preview_command(key: str, name: str) -> str | None: + quoted_name = shlex.quote(name) + if key in {"d1_database", "d1_preview_database"}: + return f"cfctl wrangler d1 create {quoted_name} --plan" + if key in {"r2_raw_mail_bucket", "r2_raw_mail_preview_bucket"}: + return f"cfctl wrangler r2 bucket create {quoted_name} --plan" + if key == "queue": + return f"cfctl wrangler queues create {quoted_name} --plan" + return None + + def normalize_alias(alias: Any, domain: str) -> str: value = str(alias or "").strip().lower() if not value: @@ -538,6 +550,7 @@ def build_checks( "Storage resource is missing", expected, sorted(present_names), + storage_preview_command(key, expected), ) ) diff --git a/scripts/verify_maildesk_cf_contract.sh b/scripts/verify_maildesk_cf_contract.sh index 491ec68..a383e0c 100755 --- a/scripts/verify_maildesk_cf_contract.sh +++ b/scripts/verify_maildesk_cf_contract.sh @@ -109,6 +109,11 @@ jq -e ' and (.drift_classes | index("missing_resource")) != null and (.drift_classes | index("email_routing_alias_drift")) != null and (.drift_classes | index("dns_authentication_drift")) != null + and any(.plan.operations[]; .surface == "d1.database" and .preview_command == "cfctl wrangler d1 create maildesk-cf-db --plan" and .blocked == null) + and any(.plan.operations[]; .surface == "d1.database" and .preview_command == "cfctl wrangler d1 create maildesk-cf-preview-db --plan" and .blocked == null) + and any(.plan.operations[]; .surface == "r2.bucket" and .preview_command == "cfctl wrangler r2 bucket create maildesk-cf-raw-mail --plan" and .blocked == null) + and any(.plan.operations[]; .surface == "r2.bucket" and .preview_command == "cfctl wrangler r2 bucket create maildesk-cf-raw-mail-preview --plan" and .blocked == null) + and any(.plan.operations[]; .surface == "queue" and .preview_command == "cfctl wrangler queues create maildesk-cf-jobs --plan" and .blocked == null) ' "${missing_artifact_path}" >/dev/null || die "missing-resource drift classes did not match" cfctl_output="$(