feat(docs): v0 static host (Vite, nginx, crawl files, stub routes) #3

Closed
opened 2026-09-13 05:39:01 +00:00 by PlasticDigits · 2 comments

Summary

Gated v0 implement for https://docs.cl8y.com in this repo: one Coolify-deployable TypeScript static site (Vite + React + TS → nginx non-root) with honest crawl files, unique prerendered titles/canonicals, stub indexable routes, and DEX CTAs.

Do not start until #1 architecture is on main and #2 has RECOMMEND: ACCEPT (or FIX items landed and re-reviewed). Do not apply ready / agent:implement until those gates pass.

Parent spec: PlasticDigits/cl8y-marketing#3. Follow docs/ARCHITECTURE.md from #1; do not invent product policy.

Bundle (do not split):

  1. Scaffold — Node 24, Vite, React, TypeScript strict, Woodpecker, multi-stage image: build → nginx non-root. Public VITE_* only.
  2. Honest crawl files — GET /robots.txt is text/plain robots syntax; GET /sitemap.xml is application/xml urlset. No SPA catch-all for those paths (missing file → 404, not index.html).
  3. Indexable stub routes with unique <title> and canonical: /, /methodology + one child, /markets, /contracts, /guides + one child.
  4. DEX CTAs deep-link to https://dex.cl8y.com with campaign query params via URLSearchParams. No unverified fee, volume, or “best DEX” claims.
  5. Image + hostname — Coolify can run this image at docs.cl8y.com. Do not pick SKU/snapshot/hypervisor. Rollback = previous image tag. DEX Sitemap: pointer is not this PR (marketing#2).

Current codebase

Until #1/#2 complete, this tree is README + architecture docs only. Implement against the merged architecture, not this paragraph alone.

This repo (expected at gate open)

  • README.md, docs/ARCHITECTURE.md, AGENTS.md from #1.
  • No shipped Vite app, Dockerfile, or nginx until this issue. If those already exist and contradict #1, stop and comment on #2.

Parent / siblings (do not merge into this PR)

  • marketing#3 — full v0 AC (this issue is the code/cl8y-docs half; marketing AC12 strategy file stays in marketing.git).
  • marketing#6 — repo create (done).
  • marketing#2 / DEX nginx — SPA serving HTML as robots/sitemap. Copy the exact-location 404 idea from docker/frontend/nginx.conf /og-image.png; do not copy location / { try_files ... /index.html; } for unknown docs paths or crawl files.
  • DEX Dockerfile — Node 24 builder pattern; runtime today is root nginx — this image must be non-root.
  • CL8Y-web — prerender + SEO helper pattern (vite.config.ts, src/blog/SEO.tsx, src/lib/siteOrigin.ts). Do not copy wagmi, WalletConnect, or three.js. src/content/invariants.ts DEX origin https://dex.cl8y.com. Token addresses only from src/data/tokenDirectory.ts / DEX VITE_CL8Y_TOKEN_ADDRESS if /contracts lists any.
  • cl8y-pm .woodpecker.yaml — gitleaks + tests; this repo has no Postgres.

Duplicates / already implemented

Work Action
Empty-repo implement card (old #1 body) Replaced by #1 architecture; do not re-scaffold without #2
marketing#3 Parent; implement here, not in marketing.git
CL8Y-web#6 cl8y.com deep-links; not this host
Live https://docs.cl8y.com Not a valid first-party static site yet

If a PR already shipped v0 and AC1–AC12 below pass on production, close this as implemented — do not duplicate.

Why the new implementation is needed

Phase 1 needs a crawlable content host. Architecture (#1) and program review (#2) only decide; they do not emit HTML, robots, or an image. Without this ship:

  • Crawlers have no honest docs origin (DEX remains one SPA shell).
  • marketing#2 cannot safely advertise Sitemap: https://docs.cl8y.com/sitemap.xml.
  • Guide/methodology drafts have no approved public surface.

v0 may stub copy. The platform (repo app, image, TLS hostname, robots/sitemap, unique titles, CTA helper, CI) must be real.

Constraints and guardrails

Follow docs/ARCHITECTURE.md. In addition:

  1. Gate. No implement, no ready, until #2 ACCEPT.
  2. One deployable = this repo. Do not commit a deployable into cl8y-marketing. Do not add docs.cl8y.com routes to CL8Y-web or the DEX SPA.
  3. Static only. No Node app server, SSR runtime, Postgres, or marketing-core API.
  4. Node 24 builder. nginx non-root runtime (unprivileged user and port).
  5. No SPA fallback for /robots.txt or /sitemap.xml. Exact location = + try_files $uri =404. Sibling paths (/robots.txt/, /sitemap_index.xml unless shipped) must not return HTML 200.
  6. MIME: robots text/plain; sitemap application/xml.
  7. Public VITE_* only. Fail the image build if VITE_SITE_ORIGIN / VITE_DEX_ORIGIN missing or not https://. Optional indexer origin must be https://indexer.dex.cl8y.com if present — no http://, no internal hostnames.
  8. Prerender. Every shipped path’s built HTML has a distinct title and link rel=canonical for that path. Unknown paths 404 (not home HTML 200).
  9. CTAs. dexHref uses URL + URLSearchParams. Default pair may use uluna + checksummed Terra Classic CL8Y CW20 already documented first-party. Campaign utm_source=docs&utm_medium=web&utm_campaign=<page-or-stub-id>. Never concatenate visitor query; never javascript: / data: / protocol-relative / ?url= redirectors; do not read window.location to build product origins.
  10. Claims. No unverified fee, TVL, volume, ranking, or “best DEX”. No CoinGecko/CMC. No unapproved content/guides/ from marketing.git.
  11. Headers. X-Frame-Options: DENY, frame-ancestors 'none', X-Content-Type-Options: nosniff on HTML and crawl files.
  12. Hashed assets missing → 404, not HTML shell.
  13. DEX Sitemap: must not be changed in this PR.
  14. Do not pick SKU, snapshot, or hypervisor. Hostname attach is existing Coolify; document rollback as image/tag swap.
  15. Clickjacking and nosniff as in #1. Canonical origin https://docs.cl8y.com (no trailing slash).

Relevant files

Path Role
docs/ARCHITECTURE.md Binding design from #1
AGENTS.md Invariants
To create package.json, lockfile, vite.config.ts, tsconfig.json, Dockerfile, nginx.conf
To create public/robots.txt, public/sitemap.xml
To create route modules, SEO helper, dexHref + unit tests
To create .woodpecker.yaml (gitleaks, typecheck, unit, Playwright 5 workers)
Reference cl8y-dex-terraclassic/docker/frontend/nginx.conf (exact-location 404; not SPA /)
Reference CL8Y-web/vite.config.ts, src/blog/SEO.tsx, src/lib/siteOrigin.ts
Reference CL8Y-web/src/content/invariants.ts, src/data/tokenDirectory.ts
Reference cl8y-dex-terraclassic/frontend-dapp/src/utils/swapQueryParams.ts
Out of this PR DEX public/robots.txt Sitemap line; marketing strategy/static-content-architecture.md
  1. Scaffold npm Vite + React + TypeScript (strict). Small dependency set: no wagmi, WalletConnect, three.js.
  2. Prerender the route list at build. VITE_SITE_ORIGIN in absolute canonicals/OG URLs.
  3. public/robots.txt: User-agent: * + Allow: /. Sitemap: https://docs.cl8y.com/sitemap.xml. No marketing claims in comments.
  4. public/sitemap.xml: urlset of only prerendered https://docs.cl8y.com/... URLs. No DEX/bridge/third-party loc. No unbounded parameters.
  5. nginx: unprivileged image or USER + writable pid/temp; exact robots/sitemap locations; HTML may try_files prerendered files — never fall through to a single root index.html for unknown URLs. Prefer 404. Security headers as in Constraints.
  6. Dockerfile: FROM node:24-*-slim AS builder → npm ci → npm run build with public ARG/ENV → copy dist into nginx html. Healthcheck GET / as non-root.
  7. Stub pages (honest placeholders; no invented stats):
    • / — what this host is; links to methodology, markets, contracts, guides; CTA to DEX.
    • /methodology and one child.
    • /markets — no fabricated protocol-fee numbers; “see DEX / public indexer” without inventing numbers.
    • /contracts — first-party addresses only if already in CL8Y-web token directory / DEX env; else “see DEX” stub.
    • /guides and one child.
  8. Pure unit-tested dexHref({ from, to, campaign }).
  9. Woodpecker: gitleaks; npm test; Playwright 5 workers (content-type + body sniff, titles/canonicals, CTA hrefs). Add a verify-* script that curls a local nginx container.
  10. Platform: attach docs.cl8y.com with TLS to this service. Rollback = previous tag. After live, a separate DEX PR may set Sitemap: only if marketing#2 files exist and production curl of this sitemap is application/xml + <urlset.

Acceptance criteria

  • AC0. #1 on main and #2 RECOMMEND: ACCEPT (or completed FIX cycle) before implement labels.
  • AC1. App lives in code/cl8y-docs; marketing.git has no app server.
  • AC2. GET https://docs.cl8y.com/ → 200, text/html, TLS valid, documentation chrome (not empty nginx default, not DEX SPA, not cl8y.com homepage).
  • AC3. GET https://docs.cl8y.com/robots.txt → text/plain, contains User-agent, not <!doctype / Vite shell.
  • AC4. GET https://docs.cl8y.com/sitemap.xml → application/xml (or +xml), XML urlset of https://docs.cl8y.com URLs only.
  • AC5. Local/image: delete those two files → 404, not HTML 200.
  • AC6. /, /methodology (+ child), /markets, /contracts, /guides (+ child) each have a distinct title and canonical in prerendered HTML (curl the path).
  • AC7. Primary CTAs are https://dex.cl8y.com with campaign params from URLSearchParams; no secrets in the image or repo.
  • AC8. No unverified fee/TVL/volume/ranking claims in copy, robots comments, or sitemap extras.
  • AC9. Runtime does not run nginx as root; hashed/static assets 404 when missing.
  • AC10. Deploy + rollback documented as image/tag swap; hostname docs.cl8y.com serves this service.
  • AC11. This PR does not point DEX Sitemap: at docs.
  • AC12. Woodpecker green: gitleaks, unit tests, Playwright 5 workers.

Test plan (functional paths)

# Path Expect
T1 GET / 200 HTML; unique home title + canonical https://docs.cl8y.com/
T2 GET /robots.txt 200 text/plain; User-agent; Sitemap: to this host
T3 GET /sitemap.xml 200 application/xml; <urlset>; loc = docs origin only
T4 GET /methodology and a child 200 HTML; titles/canonicals differ from / and each other
T5 GET /markets 200 HTML; no invented protocol-fee numbers
T6 GET /contracts 200 HTML; addresses only if already first-party documented
T7 GET /guides and a child 200 HTML; unique canonicals
T8 GET /no-such-page 404 (not home HTML 200)
T9 GET /robots.txt/ and /sitemap_index.xml Not HTML 200 unless a real file is shipped
T10 CTA href https://dex.cl8y.com + utm_* (optional from/to ids)
T11 Image build without VITE_SITE_ORIGIN / DEX origin Build fails
T12 docker run healthcheck GET / 200 as non-root
T13 T2–T4 on deployed hostname Same types/bodies as local image
T14 Rollback to previous tag Previous HTML/image restored
T15 Unit: dexHref URLSearchParams; rejects javascript: / foreign hosts
T16 Playwright 5 workers Crawl fixtures sniff body, not status alone

Test plan (attack, hack, and abuse)

# Vector Expect
A1 SPA fallback serves HTML as robots/sitemap Fail AC3–AC5; exact-location 404 if files missing
A2 Content-Type lie (HTML labeled text/plain) Fail; sniff User-agent / <urlset
A3 Sitemap loc to phishing / DEX / javascript: Rejected; allowlist https://docs.cl8y.com
A4 CTA from window.location.search Forbidden
A5 utm_* or from/to with javascript:, data:, //evil Ignored / never emitted
A6 Secrets in VITE_* or git None; gitleaks + review
A7 Wallet/WC/trading UI copied from CL8Y-web or DEX Fail review
A8 Unverified fee/APR/volume in stub copy Fail AC8
A9 Clickjacking / missing nosniff on crawl files Headers present
A10 Container root + bind port 80 only as root Fail AC9
A11 Unknown path 200 with home canonical Fail T8
A12 Publishing unapproved marketing content/guides/ Forbidden
A13 DEX robots Sitemap: to docs in this PR Forbidden
A14 Indexer origin http:// or internal hostname Build reject
A15 Path traversal / alias abuse nginx root confined to dist; no autoindex

Verification criteria

  • curl -sI and body on local nginx image and on https://docs.cl8y.com for /, /robots.txt, /sitemap.xml, one methodology URL, one guide URL (status + Content-Type + sniff).
  • Negative: remove robots/sitemap from dist, confirm 404.
  • Process list: nginx worker not uid 0.
  • HTML fixtures: distinct titles/canonicals; no DEX index.html OG shell.
  • CTA unit tests + Playwright 5 workers green in CI.
  • marketing#2 remains the DEX crawl ticket; docs Sitemap: pointer only after production curl of this host’s sitemap succeeds.
  • #2 review comment is ACCEPT before merge to main if implement started from a gated label.

Out of scope

  • Wallet, swap, pool, mint, wrap, or Create Token UI.
  • Postgres / marketing-core / research-worker APIs.
  • CoinGecko, CMC, DeFiLlama, or paid media.
  • Fixing cl8y.com missing robots/sitemap (CL8Y-web).
  • Implementing marketing#2 DEX nginx files.
  • strategy/static-content-architecture.md in marketing.git.
  • Choosing hypervisor image, SKU, or a new host.
  • Full methodology/guide manuscripts (stubs only).

First-pass model recommendation

Recommendation: grok-high. New deployable, nginx crawl-file security, prerender canonicals, CTA URL construction, Docker non-root. Cross-cutting vs DEX/CL8Y-web. Not a three-file Composer edit.

Gate

Blocked on #1 and #2. Labels at filing: feature only. A human applies ready + agent:implement only after #2 ACCEPT.

## Summary **Gated v0 implement** for **https://docs.cl8y.com** in this repo: one Coolify-deployable TypeScript static site (Vite + React + TS → nginx non-root) with honest crawl files, unique prerendered titles/canonicals, stub indexable routes, and DEX CTAs. **Do not start until** [#1](https://git.cl8y.com/code/cl8y-docs/issues/1) architecture is on `main` **and** [#2](https://git.cl8y.com/code/cl8y-docs/issues/2) has **RECOMMEND: ACCEPT** (or FIX items landed and re-reviewed). Do **not** apply `ready` / `agent:implement` until those gates pass. Parent spec: [PlasticDigits/cl8y-marketing#3](https://git.cl8y.com/PlasticDigits/cl8y-marketing/issues/3). Follow `docs/ARCHITECTURE.md` from #1; do not invent product policy. Bundle (do not split): 1. **Scaffold** — Node 24, Vite, React, TypeScript strict, Woodpecker, multi-stage image: build → nginx **non-root**. Public `VITE_*` only. 2. **Honest crawl files** — `GET /robots.txt` is `text/plain` robots syntax; `GET /sitemap.xml` is `application/xml` urlset. **No SPA catch-all** for those paths (missing file → **404**, not `index.html`). 3. **Indexable stub routes** with **unique** `<title>` and canonical: `/`, `/methodology` + one child, `/markets`, `/contracts`, `/guides` + one child. 4. **DEX CTAs** deep-link to `https://dex.cl8y.com` with campaign query params via `URLSearchParams`. No unverified fee, volume, or “best DEX” claims. 5. **Image + hostname** — Coolify can run this image at `docs.cl8y.com`. Do not pick SKU/snapshot/hypervisor. Rollback = previous image tag. DEX `Sitemap:` pointer is **not** this PR ([marketing#2](https://git.cl8y.com/PlasticDigits/cl8y-marketing/issues/2)). ## Current codebase Until #1/#2 complete, this tree is README + architecture docs only. **Implement against the merged architecture**, not this paragraph alone. ### This repo (expected at gate open) - `README.md`, `docs/ARCHITECTURE.md`, `AGENTS.md` from #1. - **No** shipped Vite app, `Dockerfile`, or nginx until this issue. If those already exist and contradict #1, stop and comment on #2. ### Parent / siblings (do not merge into this PR) - **marketing#3** — full v0 AC (this issue is the `code/cl8y-docs` half; marketing AC12 strategy file stays in marketing.git). - **marketing#6** — repo create (done). - **marketing#2 / DEX nginx** — SPA serving HTML as robots/sitemap. Copy the **exact-location 404** idea from `docker/frontend/nginx.conf` `/og-image.png`; do **not** copy `location / { try_files ... /index.html; }` for unknown docs paths or crawl files. - **DEX Dockerfile** — Node 24 builder pattern; runtime today is root nginx — **this image must be non-root**. - **CL8Y-web** — prerender + SEO helper pattern (`vite.config.ts`, `src/blog/SEO.tsx`, `src/lib/siteOrigin.ts`). Do **not** copy wagmi, WalletConnect, or three.js. `src/content/invariants.ts` DEX origin `https://dex.cl8y.com`. Token addresses only from `src/data/tokenDirectory.ts` / DEX `VITE_CL8Y_TOKEN_ADDRESS` if `/contracts` lists any. - **cl8y-pm `.woodpecker.yaml`** — gitleaks + tests; this repo has no Postgres. ### Duplicates / already implemented | Work | Action | |------|--------| | Empty-repo implement card (old #1 body) | Replaced by #1 architecture; do not re-scaffold without #2 | | marketing#3 | Parent; implement **here**, not in marketing.git | | CL8Y-web#6 | cl8y.com deep-links; not this host | | Live `https://docs.cl8y.com` | Not a valid first-party static site yet | If a PR already shipped v0 and AC1–AC12 below pass on production, close this as implemented — do not duplicate. ## Why the new implementation is needed Phase 1 needs a crawlable content host. Architecture (#1) and program review (#2) only decide; they do not emit HTML, robots, or an image. Without this ship: - Crawlers have no honest docs origin (DEX remains one SPA shell). - marketing#2 cannot safely advertise `Sitemap: https://docs.cl8y.com/sitemap.xml`. - Guide/methodology drafts have no approved public surface. v0 may stub copy. The **platform** (repo app, image, TLS hostname, robots/sitemap, unique titles, CTA helper, CI) must be real. ## Constraints and guardrails Follow `docs/ARCHITECTURE.md`. In addition: 1. **Gate.** No implement, no `ready`, until #2 ACCEPT. 2. **One deployable = this repo.** Do not commit a deployable into `cl8y-marketing`. Do not add `docs.cl8y.com` routes to `CL8Y-web` or the DEX SPA. 3. **Static only.** No Node app server, SSR runtime, Postgres, or marketing-core API. 4. **Node 24** builder. **nginx non-root** runtime (unprivileged user and port). 5. **No SPA fallback** for `/robots.txt` or `/sitemap.xml`. Exact `location =` + `try_files $uri =404`. Sibling paths (`/robots.txt/`, `/sitemap_index.xml` unless shipped) must not return HTML 200. 6. **MIME:** robots `text/plain`; sitemap `application/xml`. 7. **Public `VITE_*` only.** Fail the image build if `VITE_SITE_ORIGIN` / `VITE_DEX_ORIGIN` missing or not `https://`. Optional indexer origin must be `https://indexer.dex.cl8y.com` if present — no `http://`, no internal hostnames. 8. **Prerender.** Every shipped path’s **built** HTML has a distinct title and `link rel=canonical` for that path. Unknown paths **404** (not home HTML 200). 9. **CTAs.** `dexHref` uses `URL` + `URLSearchParams`. Default pair may use `uluna` + checksummed Terra Classic CL8Y CW20 already documented first-party. Campaign `utm_source=docs&utm_medium=web&utm_campaign=<page-or-stub-id>`. Never concatenate visitor query; never `javascript:` / `data:` / protocol-relative / `?url=` redirectors; do not read `window.location` to build product origins. 10. **Claims.** No unverified fee, TVL, volume, ranking, or “best DEX”. No CoinGecko/CMC. No unapproved `content/guides/` from marketing.git. 11. **Headers.** `X-Frame-Options: DENY`, `frame-ancestors 'none'`, `X-Content-Type-Options: nosniff` on HTML and crawl files. 12. **Hashed assets** missing → 404, not HTML shell. 13. **DEX `Sitemap:`** must **not** be changed in this PR. 14. **Do not pick** SKU, snapshot, or hypervisor. Hostname attach is existing Coolify; document rollback as image/tag swap. 15. Clickjacking and nosniff as in #1. Canonical origin `https://docs.cl8y.com` (no trailing slash). ## Relevant files | Path | Role | |------|------| | `docs/ARCHITECTURE.md` | Binding design from #1 | | `AGENTS.md` | Invariants | | To create | `package.json`, lockfile, `vite.config.ts`, `tsconfig.json`, `Dockerfile`, `nginx.conf` | | To create | `public/robots.txt`, `public/sitemap.xml` | | To create | route modules, SEO helper, `dexHref` + unit tests | | To create | `.woodpecker.yaml` (gitleaks, typecheck, unit, Playwright 5 workers) | | Reference | `cl8y-dex-terraclassic/docker/frontend/nginx.conf` (exact-location 404; **not** SPA `/`) | | Reference | `CL8Y-web/vite.config.ts`, `src/blog/SEO.tsx`, `src/lib/siteOrigin.ts` | | Reference | `CL8Y-web/src/content/invariants.ts`, `src/data/tokenDirectory.ts` | | Reference | `cl8y-dex-terraclassic/frontend-dapp/src/utils/swapQueryParams.ts` | | Out of this PR | DEX `public/robots.txt` Sitemap line; marketing `strategy/static-content-architecture.md` | ## Recommended direction 1. Scaffold `npm` Vite + React + TypeScript (strict). Small dependency set: no wagmi, WalletConnect, three.js. 2. Prerender the route list at build. `VITE_SITE_ORIGIN` in absolute canonicals/OG URLs. 3. `public/robots.txt`: `User-agent: *` + `Allow: /`. `Sitemap: https://docs.cl8y.com/sitemap.xml`. No marketing claims in comments. 4. `public/sitemap.xml`: urlset of **only** prerendered `https://docs.cl8y.com/...` URLs. No DEX/bridge/third-party loc. No unbounded parameters. 5. nginx: unprivileged image or `USER` + writable pid/temp; exact robots/sitemap locations; HTML may `try_files` **prerendered** files — **never** fall through to a single root `index.html` for unknown URLs. Prefer **404**. Security headers as in Constraints. 6. Dockerfile: `FROM node:24-*-slim AS builder` → `npm ci` → `npm run build` with public `ARG`/`ENV` → copy `dist` into nginx html. Healthcheck `GET /` as non-root. 7. Stub pages (honest placeholders; no invented stats): - `/` — what this host is; links to methodology, markets, contracts, guides; CTA to DEX. - `/methodology` and one child. - `/markets` — no fabricated protocol-fee numbers; “see DEX / public indexer” without inventing numbers. - `/contracts` — first-party addresses only if already in CL8Y-web token directory / DEX env; else “see DEX” stub. - `/guides` and one child. 8. Pure unit-tested `dexHref({ from, to, campaign })`. 9. Woodpecker: gitleaks; `npm test`; Playwright **5 workers** (content-type + body sniff, titles/canonicals, CTA hrefs). Add a `verify-*` script that curls a local nginx container. 10. Platform: attach `docs.cl8y.com` with TLS to this service. Rollback = previous tag. **After live**, a **separate** DEX PR may set `Sitemap:` only if marketing#2 files exist **and** production curl of this sitemap is `application/xml` + `<urlset`. ## Acceptance criteria - [ ] **AC0.** #1 on `main` and #2 `RECOMMEND: ACCEPT` (or completed FIX cycle) before implement labels. - [ ] **AC1.** App lives in `code/cl8y-docs`; marketing.git has no app server. - [ ] **AC2.** `GET https://docs.cl8y.com/` → **200**, `text/html`, TLS valid, documentation chrome (not empty nginx default, not DEX SPA, not `cl8y.com` homepage). - [ ] **AC3.** `GET https://docs.cl8y.com/robots.txt` → `text/plain`, contains `User-agent`, not `<!doctype` / Vite shell. - [ ] **AC4.** `GET https://docs.cl8y.com/sitemap.xml` → `application/xml` (or `+xml`), XML `urlset` of `https://docs.cl8y.com` URLs only. - [ ] **AC5.** Local/image: delete those two files → **404**, not HTML 200. - [ ] **AC6.** `/`, `/methodology` (+ child), `/markets`, `/contracts`, `/guides` (+ child) each have a **distinct** title and canonical in **prerendered** HTML (curl the path). - [ ] **AC7.** Primary CTAs are `https://dex.cl8y.com` with campaign params from `URLSearchParams`; no secrets in the image or repo. - [ ] **AC8.** No unverified fee/TVL/volume/ranking claims in copy, robots comments, or sitemap extras. - [ ] **AC9.** Runtime does not run nginx as root; hashed/static assets 404 when missing. - [ ] **AC10.** Deploy + rollback documented as image/tag swap; hostname `docs.cl8y.com` serves this service. - [ ] **AC11.** This PR does **not** point DEX `Sitemap:` at docs. - [ ] **AC12.** Woodpecker green: gitleaks, unit tests, Playwright 5 workers. ## Test plan (functional paths) | # | Path | Expect | |---|------|--------| | T1 | `GET /` | 200 HTML; unique home title + canonical `https://docs.cl8y.com/` | | T2 | `GET /robots.txt` | 200 `text/plain`; `User-agent`; `Sitemap:` to this host | | T3 | `GET /sitemap.xml` | 200 `application/xml`; `<urlset>`; loc = docs origin only | | T4 | `GET /methodology` and a child | 200 HTML; titles/canonicals differ from `/` and each other | | T5 | `GET /markets` | 200 HTML; no invented protocol-fee numbers | | T6 | `GET /contracts` | 200 HTML; addresses only if already first-party documented | | T7 | `GET /guides` and a child | 200 HTML; unique canonicals | | T8 | `GET /no-such-page` | **404** (not home HTML 200) | | T9 | `GET /robots.txt/` and `/sitemap_index.xml` | Not HTML 200 unless a real file is shipped | | T10 | CTA href | `https://dex.cl8y.com` + `utm_*` (optional `from`/`to` ids) | | T11 | Image build without `VITE_SITE_ORIGIN` / DEX origin | Build fails | | T12 | `docker run` healthcheck | `GET /` 200 as non-root | | T13 | T2–T4 on deployed hostname | Same types/bodies as local image | | T14 | Rollback to previous tag | Previous HTML/image restored | | T15 | Unit: `dexHref` | `URLSearchParams`; rejects `javascript:` / foreign hosts | | T16 | Playwright 5 workers | Crawl fixtures sniff body, not status alone | ## Test plan (attack, hack, and abuse) | # | Vector | Expect | |---|--------|--------| | A1 | SPA fallback serves HTML as robots/sitemap | Fail AC3–AC5; exact-location 404 if files missing | | A2 | Content-Type lie (HTML labeled `text/plain`) | Fail; sniff `User-agent` / `<urlset` | | A3 | Sitemap loc to phishing / DEX / `javascript:` | Rejected; allowlist `https://docs.cl8y.com` | | A4 | CTA from `window.location.search` | Forbidden | | A5 | `utm_*` or `from`/`to` with `javascript:`, `data:`, `//evil` | Ignored / never emitted | | A6 | Secrets in `VITE_*` or git | None; gitleaks + review | | A7 | Wallet/WC/trading UI copied from CL8Y-web or DEX | Fail review | | A8 | Unverified fee/APR/volume in stub copy | Fail AC8 | | A9 | Clickjacking / missing `nosniff` on crawl files | Headers present | | A10 | Container root + bind port 80 only as root | Fail AC9 | | A11 | Unknown path 200 with home canonical | Fail T8 | | A12 | Publishing unapproved marketing `content/guides/` | Forbidden | | A13 | DEX robots `Sitemap:` to docs in this PR | Forbidden | | A14 | Indexer origin `http://` or internal hostname | Build reject | | A15 | Path traversal / alias abuse | nginx root confined to dist; no autoindex | ## Verification criteria - curl `-sI` and body on local nginx image **and** on `https://docs.cl8y.com` for `/`, `/robots.txt`, `/sitemap.xml`, one methodology URL, one guide URL (status + `Content-Type` + sniff). - Negative: remove robots/sitemap from dist, confirm **404**. - Process list: nginx worker not uid 0. - HTML fixtures: distinct titles/canonicals; no DEX `index.html` OG shell. - CTA unit tests + Playwright 5 workers green in CI. - marketing#2 remains the DEX crawl ticket; docs `Sitemap:` pointer only after production curl of this host’s sitemap succeeds. - #2 review comment is ACCEPT before merge to `main` if implement started from a gated label. ## Out of scope - Wallet, swap, pool, mint, wrap, or Create Token UI. - Postgres / marketing-core / research-worker APIs. - CoinGecko, CMC, DeFiLlama, or paid media. - Fixing `cl8y.com` missing robots/sitemap (`CL8Y-web`). - Implementing marketing#2 DEX nginx files. - `strategy/static-content-architecture.md` in marketing.git. - Choosing hypervisor image, SKU, or a new host. - Full methodology/guide manuscripts (stubs only). ## First-pass model recommendation Recommendation: **grok-high**. New deployable, nginx crawl-file security, prerender canonicals, CTA URL construction, Docker non-root. Cross-cutting vs DEX/CL8Y-web. Not a three-file Composer edit. ## Gate Blocked on [#1](https://git.cl8y.com/code/cl8y-docs/issues/1) and [#2](https://git.cl8y.com/code/cl8y-docs/issues/2). Labels at filing: `feature` only. A human applies `ready` + `agent:implement` only after #2 ACCEPT.
Author
Owner

Gitleaks + nonempty-tree Woodpecker lands in #6 without waiting on this issue. AC12 Node 24 unit tests and Playwright 5 workers remain here after #2 ACCEPT.

Gitleaks + nonempty-tree Woodpecker lands in [#6](https://git.cl8y.com/code/cl8y-docs/pulls/6) without waiting on this issue. AC12 Node 24 unit tests and Playwright 5 workers remain here after #2 ACCEPT.
Author
Owner

#2 recorded RECOMMEND: ACCEPT. Implement is in #7 (issue/3-v0-static-host).

This work did not apply ready / agent:implement.

Local verify (re-run after nginx MIME + no-slash path fixes):

  • npm test 13 passed
  • npm run typecheck green
  • npm run build + npm run test:dist 4 passed
  • npm run verify: non-root uid, robots text/plain + User-agent, sitemap application/xml + <urlset, unknown path 404, missing crawl files 404, hashed asset 404, Playwright 5 workers 11 passed
  • scripts/check-origins.mjs without VITE_SITE_ORIGIN exits 1

Not in this PR: Coolify hostname attach / production curl of https://docs.cl8y.com (AC2/T13 live host; rollback remains image/tag). DEX Sitemap: still gated on marketing#2.

[#2](https://git.cl8y.com/code/cl8y-docs/issues/2) recorded **RECOMMEND: ACCEPT**. Implement is in [#7](https://git.cl8y.com/code/cl8y-docs/pulls/7) (`issue/3-v0-static-host`). This work did **not** apply `ready` / `agent:implement`. Local verify (re-run after nginx MIME + no-slash path fixes): - `npm test` 13 passed - `npm run typecheck` green - `npm run build` + `npm run test:dist` 4 passed - `npm run verify`: non-root uid, robots `text/plain` + `User-agent`, sitemap `application/xml` + `<urlset`, unknown path 404, missing crawl files 404, hashed asset 404, Playwright **5 workers** 11 passed - `scripts/check-origins.mjs` without `VITE_SITE_ORIGIN` exits 1 Not in this PR: Coolify hostname attach / production curl of `https://docs.cl8y.com` (AC2/T13 live host; rollback remains image/tag). DEX `Sitemap:` still gated on marketing#2.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-docs#3
No description provided.