From fe187d180897917df7057088bb1ad3a59357b77e Mon Sep 17 00:00:00 2001 From: rogu3bear Date: Tue, 30 Jun 2026 10:05:16 -0500 Subject: [PATCH] Expose maildesk lifecycle in cfctl registry The maildesk lifecycle command already existed, but standards/classify/guide could not see it because the surface was absent from the catalogs. Add maildesk-cf as a non-apply lifecycle surface, teach classify and guide to point at the real composite preview path, and keep ack explicitly blocked until component writes are preview-gated. Also add file selector readiness so file-backed lifecycle operations can prove their required selector. --- catalog/standards.json | 34 ++++++ catalog/surfaces.json | 27 +++++ commands/cfctl.sh | 137 +++++++++++++++++++++++++ docs/capabilities.md | 1 + scripts/lib/cfctl.sh | 2 + scripts/verify_maildesk_cf_contract.sh | 37 +++++++ scripts/verify_static_contract.sh | 9 +- 7 files changed, 245 insertions(+), 2 deletions(-) diff --git a/catalog/standards.json b/catalog/standards.json index 222fcd4..cb74753 100644 --- a/catalog/standards.json +++ b/catalog/standards.json @@ -228,6 +228,40 @@ "cfctl hostname plan --file state/hostname/.yaml" ] }, + "maildesk-cf": { + "stance": "composite maildesk lifecycle evidence before provisioning", + "standards": [ + { + "id": "maildesk-cf.state-owns-complete-path", + "level": "required", + "title": "One spec owns the maildesk path", + "summary": "A maildesk lifecycle spec should declare domains, role aliases, personal aliases, Workers, D1, R2, Queues, sender authentication, and outbound policy together." + }, + { + "id": "maildesk-cf.verify-before-provision", + "level": "required", + "title": "Verify every dependency before planning provisioning", + "summary": "Maildesk readiness must prove template validation, instance policy, Worker/storage bindings, Email Routing aliases, sender authentication, provider status, and outbound proof posture with evidence artifacts." + }, + { + "id": "maildesk-cf.component-writes-preview-gated", + "level": "required", + "title": "Do not composite-apply before component writes are gated", + "summary": "Composite provisioning must remain plan-only until Worker, D1, R2, Queue, Email Routing, DNS authentication, and sender-provider operations are each available through preview-gated public cfctl surfaces." + }, + { + "id": "maildesk-cf.targeted-mail-proof-only", + "level": "recommended", + "title": "Avoid broad live mail smoke tests", + "summary": "Prefer configuration checks, provider state reads, and single targeted probes. Live delivery proof should be explicit, bounded, and never substitute for routing/audit readiness." + } + ], + "evidence": [ + "cfctl maildesk-cf verify --file state/maildesk-cf/.json", + "cfctl maildesk-cf diff --file state/maildesk-cf/.json", + "cfctl maildesk-cf provision --file state/maildesk-cf/.json --plan" + ] + }, "dns.record": { "stance": "desired-state preferred for repeated, platform, and routing-critical records", "standards": [ diff --git a/catalog/surfaces.json b/catalog/surfaces.json index 800216e..da59472 100644 --- a/catalog/surfaces.json +++ b/catalog/surfaces.json @@ -512,6 +512,33 @@ "./cfctl apply email.routing_rule upsert --zone example.com --name founders@example.com --service maildesk-cf-router --ack-plan " ] }, + "maildesk-cf": { + "description": "Composite maildesk-cf lifecycle readiness across Email Routing aliases, Workers, D1, R2, Queues, sender authentication, and outbound identity evidence.", + "standards_ref": "maildesk-cf", + "docs_topics": ["email-routing", "email-workers", "d1", "r2", "queues", "api-auth"], + "backend": "maildesk_cf_lifecycle", + "permission_family": "Maildesk lifecycle", + "selectors": ["file", "domain"], + "actions": { + "provision": { + "supported": true, + "risk": "write", + "preview_required": true, + "verification_required": true, + "required_selectors": ["file"], + "public_example": "cfctl maildesk-cf provision --file state/maildesk-cf/.json --plan", + "troubleshooting_hint": "Composite provision apply remains blocked until each component write is available through preview-gated public cfctl surfaces." + }, + "apply": { + "supported": false + } + }, + "examples": [ + "./cfctl maildesk-cf verify --file state/maildesk-cf/example.json", + "./cfctl maildesk-cf diff --file state/maildesk-cf/example.json", + "./cfctl maildesk-cf provision --file state/maildesk-cf/example.json --plan" + ] + }, "d1.database": { "description": "D1 databases visible to the current account token.", "backend": "inventory_script", diff --git a/commands/cfctl.sh b/commands/cfctl.sh index ad68b63..8cd6ed9 100644 --- a/commands/cfctl.sh +++ b/commands/cfctl.sh @@ -1959,6 +1959,71 @@ cfctl_handle_classify() { return fi + if [[ "${surface}" == "maildesk-cf" && "${requested_operation}" == "provision" ]]; then + cfctl_require_surface "${surface}" + policy_json="$(cfctl_operation_policy_json "${surface}" "provision" "")" + selector_requirements_json="$(cfctl_requirement_check_json "${surface}" "provision" "")" + public_example="$(jq -r '.public_example // "cfctl maildesk-cf provision --file state/maildesk-cf/.json --plan"' <<< "${policy_json}")" + troubleshooting_hint="$(jq -r '.troubleshooting_hint // "Composite provision apply remains blocked until component writes are preview-gated."' <<< "${policy_json}")" + lane_hint_json="$( + jq -n \ + --arg current_lane "${CF_ACTIVE_TOKEN_LANE:-}" \ + '{ + current_lane: (if $current_lane == "" then null else $current_lane end), + recommended_lane: null, + retry_on_recommended_lane: false + }' + )" + result_json="$( + jq -n \ + --argjson target "$(cfctl_target_json)" \ + --argjson policy "${policy_json}" \ + --argjson selector_readiness "${selector_requirements_json}" \ + --argjson lane_hint "${lane_hint_json}" \ + --arg public_example "${public_example}" \ + --arg troubleshooting_hint "${troubleshooting_hint}" \ + ' + { + surface: "maildesk-cf", + action: "maildesk-cf", + requested_operation: "provision", + target: $target, + policy: $policy, + lane_comparison: null, + current_permission_probe: null, + selector_readiness: $selector_readiness, + lane_hint: $lane_hint, + public_example: $public_example, + troubleshooting_hint: $troubleshooting_hint, + likely_failure_class: ( + if $selector_readiness.ready != true then + "invalid_arguments" + elif ($policy.preview_required // false) == true then + "preview_required" + else + null + end + ) + } + ' + )" + cfctl_emit_result \ + "true" \ + "classify" \ + "maildesk-cf" \ + "registry" \ + "true" \ + '{"state":"not_applicable","basis":"maildesk_cf_lifecycle","errors":[],"request":null,"status_code":null,"permission_family":"Maildesk lifecycle"}' \ + '{"state":"not_applicable"}' \ + "$(jq '{risk: .policy.risk, preview_required: .policy.preview_required, selector_ready: .selector_readiness.ready, likely_failure_class: .likely_failure_class}' <<< "${result_json}")" \ + "${result_json}" \ + "" \ + "" \ + "" \ + "${requested_operation}" + return + fi + cfctl_require_surface "${surface}" if cfctl_action_supported "${surface}" "${requested_operation}"; then @@ -2191,6 +2256,78 @@ cfctl_handle_guide() { return fi + if [[ "${surface}" == "maildesk-cf" && "${requested_operation}" == "provision" ]]; then + local maildesk_policy_json + local maildesk_args_shell + local maildesk_verify_args_shell + + maildesk_policy_json="$(cfctl_operation_policy_json "maildesk-cf" "provision" "")" + maildesk_args_shell="$(cfctl_current_args_shell)" + maildesk_verify_args_shell="${maildesk_args_shell}" + if [[ -z "${CFCTL_FILE}" ]]; then + maildesk_args_shell="${maildesk_args_shell} --file state/maildesk-cf/.json" + maildesk_verify_args_shell="${maildesk_verify_args_shell} --file state/maildesk-cf/.json" + fi + preview_command="cfctl maildesk-cf provision${maildesk_args_shell} --plan" + apply_command="cfctl maildesk-cf provision${maildesk_args_shell} --ack-plan " + discovery_command="cfctl maildesk-cf verify${maildesk_verify_args_shell}" + verify_command="cfctl maildesk-cf verify${maildesk_verify_args_shell}" + troubleshooting_hint="$(jq -r '.troubleshooting_hint // "Composite provision apply remains blocked until component writes are preview-gated."' <<< "${maildesk_policy_json}")" + public_example="$(jq -r '.public_example // "cfctl maildesk-cf provision --file state/maildesk-cf/.json --plan"' <<< "${maildesk_policy_json}")" + guide_json="$( + jq -n \ + --arg discovery_command "${discovery_command}" \ + --arg preview_command "${preview_command}" \ + --arg apply_command "${apply_command}" \ + --arg verify_command "${verify_command}" \ + --arg public_example "${public_example}" \ + --arg troubleshooting_hint "${troubleshooting_hint}" \ + --argjson policy "${maildesk_policy_json}" \ + --argjson selector_readiness "$(cfctl_requirement_check_json "maildesk-cf" "provision" "")" \ + ' + { + surface: "maildesk-cf", + operation: "provision", + module: null, + standards_ref: "maildesk-cf", + docs_topics: ["email-routing", "email-workers", "d1", "r2", "queues", "api-auth"], + policy: $policy, + selector_readiness: $selector_readiness, + lane_hint: {current_lane: null, recommended_lane: null}, + steps: [ + "Run the verification command first to read current template, instance, edge, and mail readiness.", + "Run the preview command and inspect its operation_id plus component operation list.", + "Do not run the ack command until every component write path is available through preview-gated public cfctl surfaces.", + "After component provisioning is complete, run the verification command again." + ], + public_example: $public_example, + troubleshooting_hint: $troubleshooting_hint, + commands: { + discovery: $discovery_command, + preview: $preview_command, + apply_blocked: $apply_command, + verify: $verify_command + } + } + ' + )" + cfctl_emit_result \ + "true" \ + "guide" \ + "maildesk-cf" \ + "registry" \ + "true" \ + '{"state":"not_applicable","basis":"maildesk_cf_lifecycle","errors":[],"request":null,"status_code":null,"permission_family":"Maildesk lifecycle"}' \ + '{"state":"not_applicable"}' \ + "$(jq '.commands' <<< "${guide_json}")" \ + "${guide_json}" \ + "" \ + "" \ + "" \ + "provision" + return + fi + cfctl_require_surface "${surface}" if [[ "${requested_operation}" == "sync" ]]; then if ! cfctl_surface_sync_supported "${surface}"; then diff --git a/docs/capabilities.md b/docs/capabilities.md index 7db413d..c960289 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -21,6 +21,7 @@ This table is the operable runtime surface. The standards layer and docs bank in | `edge.certificate` | yes | yes | yes | yes | no | `edge.certificate` | `advanced-certificates, api-auth` | `edge_certificate` | | `email.routing_rule` | yes | yes | yes | yes | no | `-` | `email-routing, api-auth` | `-` | | `logpush.job` | yes | yes | yes | yes | no | `-` | `-` | `-` | +| `maildesk-cf` | no | no | no | no | yes | `maildesk-cf` | `email-routing, email-workers, d1, r2, queues, api-auth` | `-` | | `pages.project` | yes | yes | no | yes | no | `-` | `-` | `-` | | `pages.secret` | yes | yes | yes | yes | no | `-` | `-` | `-` | | `queue` | yes | yes | no | yes | no | `-` | `-` | `-` | diff --git a/scripts/lib/cfctl.sh b/scripts/lib/cfctl.sh index 6ae351d..0a51197 100644 --- a/scripts/lib/cfctl.sh +++ b/scripts/lib/cfctl.sh @@ -961,6 +961,7 @@ cfctl_selector_presence_json() { --arg policy_id "${CFCTL_POLICY_ID}" \ --arg job_id "${CFCTL_JOB_ID}" \ --arg scope "${CFCTL_SCOPE}" \ + --arg file "${CFCTL_FILE}" \ --arg tunnel_id "${CFCTL_TUNNEL_ID}" \ --arg client_id "${CFCTL_CLIENT_ID}" \ --arg script "${CFCTL_SCRIPT}" \ @@ -993,6 +994,7 @@ cfctl_selector_presence_json() { policy_id: ($policy_id | length > 0), job_id: ($job_id | length > 0), scope: ($scope | length > 0), + file: ($file | length > 0), tunnel_id: ($tunnel_id | length > 0), client_id: ($client_id | length > 0), script: ($script | length > 0), diff --git a/scripts/verify_maildesk_cf_contract.sh b/scripts/verify_maildesk_cf_contract.sh index e196262..491ec68 100755 --- a/scripts/verify_maildesk_cf_contract.sh +++ b/scripts/verify_maildesk_cf_contract.sh @@ -126,6 +126,43 @@ jq -e ' and .summary.mail_ready == false ' <<< "${cfctl_output}" >/dev/null || die "cfctl provision --plan envelope did not match" +standards_output="$("${ROOT_DIR}/cfctl" standards maildesk-cf)" +jq -e ' + .ok == true + and .action == "standards" + and .surface == "maildesk-cf" + and .summary.standard_count >= 4 + and .summary.desired_state_supported == true + and .result.runtime.backend == "maildesk_cf_lifecycle" +' <<< "${standards_output}" >/dev/null || die "maildesk-cf standards envelope did not match" + +classify_output="$( + "${ROOT_DIR}/cfctl" classify maildesk-cf provision --file "${ROOT_DIR}/state/maildesk-cf/example.json" +)" +jq -e ' + .ok == true + and .action == "classify" + and .surface == "maildesk-cf" + and .operation == "provision" + and .summary.preview_required == true + and .summary.selector_ready == true + and .result.policy.public_example == "cfctl maildesk-cf provision --file state/maildesk-cf/.json --plan" +' <<< "${classify_output}" >/dev/null || die "maildesk-cf classify envelope did not match" + +guide_output="$( + "${ROOT_DIR}/cfctl" guide maildesk-cf provision --file "${ROOT_DIR}/state/maildesk-cf/example.json" +)" +jq -e ' + .ok == true + and .action == "guide" + and .surface == "maildesk-cf" + and .operation == "provision" + and (.result.commands.preview | contains("cfctl maildesk-cf provision")) + and (.result.commands.preview | contains("--plan")) + and (.result.commands.apply_blocked | contains("--ack-plan ")) + and any(.result.steps[]; contains("Do not run the ack command")) +' <<< "${guide_output}" >/dev/null || die "maildesk-cf guide envelope did not match" + set +e ack_output="$( MAILDESK_CF_EVIDENCE_FILE="${fixture_file}" \ diff --git a/scripts/verify_static_contract.sh b/scripts/verify_static_contract.sh index df18333..94225da 100755 --- a/scripts/verify_static_contract.sh +++ b/scripts/verify_static_contract.sh @@ -517,7 +517,7 @@ assert_cross_catalog_empty "desired-state surfaces resolve to public surface cat | [ ($runtime[0].desired_state // {}) | to_entries[] - | select((.key | IN("hostname", "maildesk-cf")) | not) + | select((.key | IN("hostname")) | not) | select($surface_catalog[.key] == null) | {desired_state_surface: .key, issue: "missing_surface_catalog_entry"} ] @@ -645,7 +645,12 @@ assert_jq_file "surface module bindings" ' and .surfaces["edge.certificate"].standards_ref == "edge.certificate" and (.surfaces["edge.certificate"].docs_topics | index("advanced-certificates")) != null and (.surfaces["hostname"] == null) - and (.surfaces["maildesk-cf"] == null) + and .surfaces["maildesk-cf"].backend == "maildesk_cf_lifecycle" + and .surfaces["maildesk-cf"].standards_ref == "maildesk-cf" + and .surfaces["maildesk-cf"].actions.provision.supported == true + and .surfaces["maildesk-cf"].actions.provision.required_selectors == ["file"] + and .surfaces["maildesk-cf"].actions.apply.supported == false + and (.surfaces["maildesk-cf"].docs_topics | index("email-routing")) != null and .surfaces["worker.route"].module == "worker_route" and .surfaces["worker.route"].standards_ref == "worker.route" and (.surfaces["worker.route"].docs_topics | index("workers-routes")) != null