Skip to main content

Module cloudflare

Module cloudflare 

Source
Expand description

Cloudflare management API client — accounts, tunnels, R2 buckets, DNS.

Shared by the desktop Tauri commands, the CLI, and the reconciler. Callers resolve the API token themselves (keychain, env, vault) and pass it to CloudflareClient::new; this module never reads credentials.

Two distinct API surfaces:

  • Management API (this module) — accounts, R2-bucket CRUD, tunnels, DNS, cache purge.
  • R2 object publish — S3 SigV4, lives in reconciler::r2_publish and reuses the existing s3_sign helper. Not part of this module.

@yah:ticket(R320-F12, “yah cloud: mint scoped Cloudflare API token from a policy template (one-command onboarding for a new CF account)”) @yah:assignee(agent:claude) @yah:at(2026-05-26T14:57:46Z) @yah:status(review) @yah:parent(R320) @arch:see(.yah/docs/working/W074-cloudflare-infra-provider.md) @yah:next(“Resolve permission-group UUIDs at runtime from GET /accounts/{id}/tokens/permission_groups — CF references groups by ID not name; the policy template stores names and resolves to IDs at create time”) @yah:next(“Build the minimal mesofact-static policy: account-scoped block (Account Settings:Read + Workers R2 Storage:Edit) + zone-scoped block (Zone:Read + Transform Rules:Edit + Cache Purge). Keep scope blocks separate — mixing account- and zone-scoped groups in one block fails token-create validation”) @yah:next(“POST /accounts/{id}/tokens to create an ACCOUNT-OWNED token (DECIDED by user: survives the creating user, correct for a shared tool credential). Print the secret once and offer to store it in the cloudflare-api-token keystore slot”) @yah:next(“Expose as ‘yah cloud cf token create –account –zone ’ so a fresh CF account is one command. The policy template is the checked-in artifact CF won’t let you save dashboard-side”) @yah:gotcha(“Bootstrap credential needs ONLY API Tokens:Edit — VALIDATED live: the created token is bounded by the account’s own access, not the bootstrap token’s perms, so a minimal bootstrap suffices. Brand-new account still needs one such token minted manually (or Global Key) once.”) @yah:gotcha(“TRAP — two Transform Rules groups: ‘Transform Rules Write’ (ae16e88b…) is ACCOUNT-scoped = WRONG. upsert_index_rewrite hits /zones/{id}/rulesets so it needs ZONE-scoped ‘Zone Transform Rules Write’. Zone Read + Cache Purge are also zone-scoped, not account-scoped.”) @yah:gotcha(“Permission-group IDs (global constants, validated 2026-05-26): acct-scoped Account Settings Read=c1fde68c7bcc44588cbb6ddbc16d6480, Workers R2 Storage Write=bf7481a1826f439697cb59a20b22293e; zone-scoped Zone Read=c8fed203ed3043cba015a93ad1616f1f, Zone Transform Rules Write=0ac90a90249747bca6b047d97f0803e9, Cache Purge=e17beae8b8cb423a99b1730f21238bed”) @yah:gotcha(“account-owned token verify = GET /accounts/{id}/tokens/verify; /user/tokens/verify returns success:false for account-owned tokens (NOT a failure — seen in live test)”) @yah:assumes(“VALIDATED live 2026-05-26: POST /accounts/{id}/tokens with the 2-block policy created ‘yah-mesofact-static-yahdev’ which lists R2 buckets successfully. R2 object upload still uses SEPARATE S3 keys (SigV4), not this management token.”) @yah:handoff(“Implemented + verified live end-to-end. CloudflareClient gained create_account_token (cloudflare.rs) + list_permission_group_ids; MESOFACT_STATIC_GRANTS const carries the 5 validated permission-group IDs (account: Account Settings Read, Workers R2 Storage Write; zone: Zone Read, Zone Transform Rules Write, Cache Purge) with group-name→id resolved against the live catalog at create time, baked-in IDs as fallback. Pure helpers build_token_body/resolve_grant_id split account- vs zone-scoped policy blocks (2 unit tests). CLI: ‘yah cloud cf token create –zone [–account ] [–store-slot ] [–name ] [–bootstrap-slot ]’ in cloud.rs handle_cf_token_create — resolves account from –account or .yah/infra/providers/cloudflare.toml, resolves zone name→id, mints account-owned token via POST /accounts/{id}/tokens, stores to keystore (fail-fast on occupied slot, BEFORE minting) or prints once. Bootstrap defaults to cloudflare-api-token slot / $CLOUDFLARE_API_TOKEN.”) @yah:next(“Follow-ups (not in scope): ‘yah cloud cf token revoke ’ (DELETE endpoint already proven), and broader grant presets beyond MESOFACT_STATIC_GRANTS (e.g. + Tunnel:Read/DNS:Read for the Infra panel)”) @yah:verify(“cargo test -p cloud –lib — 207 passed (incl. token_body_splits_scopes_and_resolves_ids, token_body_omits_empty_scope_block)”) @yah:verify(“Live E2E 2026-05-26: ‘yah cloud cf token create –zone yah.dev –store-slot cf-clitest’ minted account-owned token, minted token listed R2 buckets successfully, then DELETE /accounts/{id}/tokens/{id} revoked it cleanly”)

@yah:ticket(R324-F5, “Tunnel connection status + uptime in the Tunnels table”) @yah:assignee(agent:claude) @yah:at(2026-05-26T15:33:48Z) @yah:status(review) @yah:phase(P2) @yah:parent(R324) @yah:handoff(“Tunnel connection state fully wired end-to-end. Rust: TunnelConnState enum (Active/Inactive/Degraded/Unknown) added to cloud crate; CfTunnel wire struct extended with status + conns_active_at; TunnelMeta private struct carries enriched data; list_tunnels_meta() fetches status in one pass; TunnelDriftRow gained conn_state + conn_since (RFC3339 optional); tunnel_dns_drift() refactored to walk accounts→tunnels_meta→configs in a single list_accounts pass instead of calling tunnel_dns_records() separately (avoids duplicate API round-trip). Re-exported TunnelConnState through provider/mod.rs + cloud/src/lib.rs + desktop cloudflare.rs. TS: TunnelConnState type + connState/connSince on TunnelDriftRow in types.ts. UI: DriftPill now shows ‘connected · ’ (pulsing forest dot) for synced+active, ‘inactive’ neutral pill for synced+inactive, ‘degraded’ warn for synced+degraded. fmtUptime() formats ISO → ‘<1m’/‘45m’/‘12h’/‘3d’. TunnelsSection right label changed to ‘N active’ (or ‘N active · M drift’). collectCfSlots test drift() fixture updated with connState: ‘unknown’.”) @yah:verify(“cargo test -p cloud –lib cloudflare — 15 passed (incl. 8 drift unit tests)”) @yah:verify(“cargo check -p desktop — clean (TunnelConnState re-exported + TunnelDriftRow extended)”) @yah:verify(“cd packages/yah/ui && bun test src/components/infra/CloudflarePanel.collectCfSlots.test.ts — 8 pass”) @yah:verify(“cd packages/yah/ui && bun run typecheck — no errors in infra/ or env/ (pre-existing failures elsewhere unchanged)”)

@yah:ticket(R324-F6, “R2 bucket size + object count + region in Accounts section”) @yah:assignee(agent:claude) @yah:at(2026-05-26T15:33:49Z) @yah:status(review) @yah:phase(P2) @yah:parent(R324) @yah:next(“Extend R2BucketInfo + list_r2_buckets with size/object-count/region; design AccountBlock shows ‘412 MB · 142 obj’. Needs extra R2 (or S3 list) calls per bucket.”) @yah:next(“Render the new fields in CloudflarePanel AccountBlock bucket rows.”) @yah:handoff(“Added location + creation_date to R2BucketInfo (Rust struct + TS interface). Both fields come from the existing GET /accounts/{id}/r2/buckets list call — no extra round-trips. fmtR2Location() maps CF location codes (WEUR/EEUR/WNAM/ENAM/APAC) to short labels. AccountBlock bucket cards now show the region badge on the right when present. Note: bucket size + object count are not available from the CF management API without per-bucket S3 calls (paginated ListObjectsV2 + S3 credentials); deferred to a future ticket.”) @yah:verify(“cargo check -p cloud -p desktop — clean (R2BucketInfo extended, deserialized from BucketEntry, re-exported unchanged)”) @yah:verify(“cd packages/yah/ui && bun run typecheck — no new errors in infra/ or env/”)

@yah:ticket(R419-F1, “Extend deploy_worker_script for r2_bucket bindings”) @yah:assignee(agent:claude) @yah:at(2026-06-03T08:02:38Z) @yah:status(review) @yah:parent(R419) @yah:handoff(“Widened deploy_worker_script + build_worker_multipart to typed bindings. New pub enum WorkerBinding<’a> { PlainText { name, text }, R2Bucket { name, bucket_name } } encodes both shapes; multipart metadata.bindings now emits the matching CF wire JSON. Single existing caller (mesofact_static.rs:505) maps its (String,String) plain_text vec into WorkerBinding::PlainText refs — runtime behavior unchanged. F2’s CloudflareWorkerReconciler now has the surface it needs: pass [WorkerBinding::R2Bucket { name: binding_name_from_workload_toml, bucket_name: from_mirror_providers_cache }, …].”) @yah:verify(“cargo check -p cloud –lib — clean”) @yah:verify(“cargo test -p cloud –lib provider::cloudflare — 12 passed, incl. multipart_includes_r2_bucket_binding_metadata + multipart_mixes_plain_text_and_r2_bindings”) @yah:next(“F2 pickup: bind via WorkerBinding::R2Bucket { name: <workload.toml [[bindings]].name>, bucket_name: } after fail-fast on workload<->mirror binding-name drift.”)

Structs§

CfAccountInfo
A Cloudflare account the API token can access.
CloudflareClient
Cloudflare management API client.
CreateR2BucketResult
Result of creating a Cloudflare R2 bucket.
CreateTokenResult
Result of minting an account-owned API token. value is the secret and is returned by Cloudflare exactly once — store it immediately.
CreateTunnelResult
Result of creating a Cloudflare Named Tunnel.
R2BucketInfo
R2 bucket information from the list endpoint.
R2CustomDomain
One R2 custom-domain binding from GET /accounts/{id}/r2/buckets/{bucket}/domains/custom.
TokenGrant
One permission to bake into a minted token: a Cloudflare permission-group display name, the scope it applies at, and a validated fallback ID.
TunnelDnsRecord
One CNAME record a user needs to create in their external DNS registrar to route a hostname through a Cloudflare Tunnel.
TunnelDriftRow
One row of the tunnel DNS-drift report: a tunnel ingress hostname paired with whether live Cloudflare DNS routes it to the tunnel, plus the live connector connection state.
WorkerDeployResult
Result of deploying a Cloudflare Worker script.

Enums§

GrantScope
Resource scope a permission group applies at when building a token policy.
TunnelConnState
Live connection state of a Cloudflare Tunnel connector.
TunnelDriftState
Drift verdict for one tunnel ingress hostname: does live Cloudflare DNS route it to the tunnel’s CNAME target?
WorkerBinding
One binding to inject into a Worker’s env at deploy time.

Constants§

MESOFACT_STATIC_GRANTS
Minimal permission set for a mesofact-static publish token: see the account, list/create R2 buckets, deploy Worker scripts, resolve the zone, manage Worker routes + the index-rewrite Transform Rule, and purge the CDN cache.