Page Publish
What happens from a user clicking Publish to the page being live at pages.fuzzycode.dev?
§1 The shape
Publish is a two-hop, browser-orchestrated flow. There is no server-to-server call from FuzzyCode to Pages. The browser first obtains an HMAC-signed attestation from FuzzyCode, then submits to Pages through a Cloudflare CORS-proxy Worker.
Pages makes two independent decisions after accepting a submit: how to persist the row (Branch A, controlled by duplication_strategy) and what status to return (Branch B, controlled by requested access_level). The two branches combine multiplicatively — any persistence outcome can pair with any status outcome.
fuzzycode.dev participant W as CF Worker
proxy-cors participant P as Pages
pages.fuzzycode.dev participant DB as Postgres participant S3 as S3
aws.fuzzycode.dev rect rgb(237, 217, 192) Note over U,F: HOP 1 — Attest clean HTML U->>+F: POST /api/pages/attest-publish
html · title · project_id F->>F: PII firewall · Bedrock scan F->>F: HMAC-sign attestation F-->>-U: { claims, signature, html_hash, clean_state } end rect rgb(230, 169, 94) Note over U,P: HOP 2 — Submit via CORS-proxy Worker U->>+W: POST fuzzycode.dev/@pages/submit
html · attestation · screenshot · access_level Note right of W: Worker strips /@pages,
rewrites host to pages.fuzzycode.dev,
forwards cookies intact W->>-P: POST pages.fuzzycode.dev/submit end activate P P->>P: require JWT · resolve username
verify HMAC attestation P->>DB: duplicate-check by html_hash Note over P,DB: BRANCH A — persistence (duplication_strategy) alt 'update' P->>DB: UPDATE row (same hash_key) P->>S3: overwrite screenshot.webp
⚠ no Cloudflare purge else 'duplicate' (default) P->>DB: INSERT new row P->>S3: write screenshot.webp
direct boto3 (not via CDNBuddy) end Note over P,DB: BRANCH B — response status (requested access_level) alt public or unlisted P->>DB: open page_visibility_approval_request
store access_level='private', link_status=false P-->>U: HTTP 202 · { url, pending_approval } else private P-->>U: HTTP 200 · { url, hash_key, access_level } end deactivate P Note over U,S3: Page later served at pages.fuzzycode.dev/page/<hash>.
HTML returned verbatim · no asset URL rewriting.
Assets resolve directly to images./sounds./sprites./uploads. subdomains.
§2 Five things the diagram tells you that earlier docs did not
fuzzycode.dev/@pages/* is a Cloudflare CORS-proxy Worker, not a FuzzyCode backend route. Prior docs framed this as FuzzyCode → Pages → CDN → UGC proxy; that pipeline does not exist.
/submit that can't present a valid HMAC over {html_hash, clean_state, clean_basis, title_hash}. The attestation is created by FuzzyCode, stored client-side briefly, and forwarded on the second hop.
/resolve and is not on the publish path at all.
access_level='private', link_status=false when the request is public or unlisted — an approval request is opened, HTTP 202 is returned, and the row is "promoted" only if a parent approves. There is no explicit status column; state is encoded across four fields plus the approval-request reference.
duplication_strategy='update' overwrites both the Postgres row and the S3 screenshot at the same keys. Nothing in code calls Cloudflare's cache purge API. Until TTL expires (~2h default), edges may serve the stale HTML.
§3 Error handling — server emits 15 paths, client handles one
The single biggest source of publish-related incidents (including the 2026-04-06 Fire HD issue) is that all non-401 errors are silently swallowed by the client. The diagram above hides this for clarity; the table below does not.
| # | Where | Failure | HTTP | Client handling | Code |
|---|---|---|---|---|---|
| 1 | Hop 1 | Missing html_content | 400 | silently swallowed | api.py:1666 |
| 2 | Hop 1 | PII detected in prompt or HTML | 400 | silently swallowed | api.py:1696 |
| 3 | Hop 1 | PII firewall unavailable | 503 | silently swallowed | api.py:1702 |
| 4 | Hop 1 | Post-cancellation archive window | 403 | silently swallowed | api.py:1673 |
| 5 | Hop 2 | Missing JWT | 401 | handled — toast "Missing User Token" | jwt_required |
| 6 | Hop 2 | Username missing from JWT (Fire HD bug) | 400 | silently swallowed | main.py:2602 |
| 7 | Hop 2 | Archive read-only | 403 | silently swallowed | main.py:2608 |
| 8 | Hop 2 | Attestation HMAC mismatch | 400 | silently swallowed | main.py:2209-2223 |
| 9 | Hop 2 | Duplicate hash (prevent) | 409 | handled — handleDuplicationWarning | main.py:2716 |
| 10 | Hop 2 | Postgres read timeout (5s) | 503 | silently swallowed | main.py:2700 |
| 11 | Hop 2 | Postgres insert timeout (5s) | 503 | silently swallowed | main.py:2874 |
| 12 | Hop 2 | Postgres verify timeout (3s) | 503 | silently swallowed | main.py:2868 |
| 13 | Hop 2 | DB insert failure | 500 | silently swallowed | main.py:2899 |
| 14 | Hop 2 | Unhandled submit exception | 500 | silently swallowed | main.py:3005 |
| 15 | async | Background screenshot failure | — | logged only; screenshot=NULL forever | main.py:2959-2976 |
§4 Environment state
Publish itself is identical UAT vs prod. Auth layer feeding into publish is not.
UAT · 2026-04-17
Active-context on. Sibling verifiers fetch JWKS from FuzzyCode. Parent-child context tokens in play. Attestation path identical.
PROD · 2026-04-17
Active-context off (ACTIVE_CONTEXT_ENABLED="false" in every service .replit). Legacy JWT-claims gating. Media siblings (Images/Sounds/Sprites) still block child-direct sessions that lack SUBSCRIPTION:ACTIVE / CREATE_IMAGE:FULL.
§5 What state does a page end up in?
There is no status column on the pages table. Lifecycle is encoded as the combination of four fields: access_level, link_status, deleted, and the presence of pending_approval_request_id. Two artifacts below: a minimal transitions diagram (what can move where), and a state-encoding matrix (what each state looks like in the database).
Transitions
visible to owner] Start -->|request public / unlisted| B[Awaiting
parent approval] B -->|parent approves| C[Public
or Unlisted] B -->|parent denies /
request cancelled| A A -->|re-submit requesting
public or unlisted| B C -->|visibility change
requested| B A -->|/delete_page| D[Soft-deleted] B -->|/delete_page| D C -->|/delete_page| D class A private class B pending class C public class D deleted
State encoding — what each state looks like in the DB
Reading this matrix is the only way to correctly query for a given state. A query like WHERE access_level='public' alone does not return public pages — it also returns pages awaiting approval that were originally public.
| Field | Private visible |
Awaiting approval |
Public / Unlisted |
Soft- deleted |
|---|---|---|---|---|
access_level |
private | private (forced) | public or unlisted | any (unchanged) |
link_status |
true | false | true | any |
deleted |
false | false | false | true |
pending_approval_request_id |
NULL | <uuid> | NULL | any |
active_approval_request_id |
NULL | NULL | <uuid> | any |
| HTTP at submit | 200 | 202 | n/a (approval path) | n/a |
Servable by /page/<hash> |
yes (owner) | yes (owner) | yes (per ACL) | no |
Transition and encoding anchors: db/migrations/0001_baseline_prod_schema.sql:320-331, 0004_add_pages_visibility_approval_columns.sql, handler transitions at FuzzycodePagesFlaskServer/main.py:2812-2828 (approval open), main.py:3231 (approval grant), main.py:2771 (cancel), main.py:4383 (delete).
§6 Verification pointers
Every claim in the diagram and on this page is anchored to code. Regenerate confidence by re-reading these:
- Publish trigger
FuzzyCode/static/script.js:4305-4332 - Two-hop flow (attest + submit)
FuzzyCode/static/script.js:3899, 3958 - Attest endpoint (server)
FuzzyCode/quart_server/blueprints/api.py:1655-1761 - HMAC signing
FuzzyCode/quart_server/services/html_cleanliness.py:267-289 - Cloudflare Worker hop
docs/cloudflare/workers/uat-proxy-cors.js:5-16, 85-89 - Worker route binding
docs/cloudflare/zone_fuzzycode.dev_workers_routes.json:26 - Pages submit handler
FuzzycodePagesFlaskServer/main.py:2558-3007 - Attestation verify
FuzzycodePagesFlaskServer/main.py:2202-2234 - Page render (no asset rewrite)
FuzzycodePagesFlaskServer/main.py:2135-2186 - Schema (state columns)
db/migrations/0001:320-331,0004 - Screenshot upload direct to S3
FuzzycodePagesFlaskServer/main.py:2303-2386 - No cache purge on update
main.py:2347, 2371(S3 overwrite without Cloudflare API) - Background screenshot task
main.py:2389, 2951-2976 - Client error handling
FuzzyCode/static/script.js:3966-3988 - UGC proxy scope (NOT publish)
docs/cloudflare/workers/usercontent-proxy.js:27-36 - CDNBuddy surface (NOT publish)
CDNBuddy/main.py:1157—/resolveonly
§7 How this page was built
Everything above comes from two files:
source.mmd— the Mermaid sequence. Agents hand-edit this.description.md— frontmatter with verification pointers + regen triggers. Agents keep this in sync with code.
This HTML page is the human-facing render. It's static — no build step, no tooling install required. Open it in any browser. Mermaid rendering happens client-side via CDN.
When the underlying code shifts (say, a new step in Hop 2 or a changed Worker route), the maintenance agent updates source.mmd and description.md, bumps last_verified, and this page re-renders automatically. No drift between agent-facing text and human-facing visual, by construction.