Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ jobs:
path: artifacts/candidate
merge-multiple: true
- run: npm run verify:candidate
- run: npx --no-install playwright install --with-deps chromium firefox webkit
- run: npm run deploy
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
Expand All @@ -206,6 +207,9 @@ jobs:
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cloudflare-evidence-${{ github.run_id }}-${{ github.run_attempt }}
path: artifacts/deployment.json
path: |
artifacts/deployment.json
playwright-live-report
test-results/live
if-no-files-found: warn
retention-days: 90
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules/
dist/
artifacts/
playwright-report/
playwright-live-report/
test-results/
coverage/
*.tsbuildinfo
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ node_modules
**/build
artifacts
playwright-report
playwright-live-report
test-results
package-lock.json
LICENSE
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ Baseline: TypeScript **7.0.2**, React **19.3.0**, React Router **8.4.0**, Vite *

## Delivery

PRs run source checks on Linux/Windows, dependency auditing/review, secret scanning, CodeQL, and three-browser tests of a single candidate. Every successful `main` push then deploys those exact candidate bytes to `arcforges-web.<account-subdomain>.workers.dev`, verifies them over HTTPS, and creates a GitHub prerelease with an automatically generated version. CI does not publish these private workspaces to npm.
PRs run source checks on Linux/Windows, dependency auditing/review, secret scanning, CodeQL, and three-browser tests of a single candidate. Every successful `main` push then deploys those exact candidate bytes to [arcforges.com](https://arcforges.com), verifies public files and browser behavior over HTTPS, and creates a GitHub prerelease with an automatically generated version. CI does not publish these private workspaces to npm. The default Workers subdomain and preview URLs are disabled by the deployment configuration.

**Deployment setup is currently deferred by the owner.** The repository's main-only `cloudflare` environment and account variable exist; its API token has not been configured. PR checks work without it. A main deployment without that secret fails with a setup message and creates no successful release. See [deployment setup and recovery](docs/deploying.md) before merging when a live release is wanted.
The main-only GitHub `cloudflare` environment contains the account variable and deployment secret. The custom-domain binding is managed in Cloudflare; CI verifies that it belongs to this Worker before deploying. PR checks remain credential-free. See [deployment setup and recovery](docs/deploying.md) and [evidence](docs/validation.md).

Workers Static Assets supports this static React build directly. Frameworks that need request-time server code require a Workers-compatible adapter/runtime. This setup does not host C# or provide an API proxy. See the [official React guide](https://developers.cloudflare.com/workers/framework-guides/web-apps/react/) and [static assets guide](https://developers.cloudflare.com/workers/static-assets/get-started/).

Expand Down
2 changes: 1 addition & 1 deletion docs/bootstrap-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- Cloudflare supports Workers Static Assets directly. This bootstrap needs no request-time application server, database, AI binding, domain change or Workers paid feature.
- The repository initially has no Cloudflare environment or credentials. Create a main-only `cloudflare` environment and configure the existing account ID; the owner must supply its deployment API token through GitHub's secret UI.

Owner decision during implementation: defer the deployment credential. Complete local/PR validation and the deployment pipeline, but do not perform a real Cloudflare deployment in this task. The environment and account variable have been configured; the token remains absent.
Historical owner decision during the bootstrap: defer the deployment credential while completing local/PR validation. The owner subsequently supplied the token and authorized real delivery; see [validation evidence](validation.md) and the subsequent [custom-domain plan](custom-domain-plan.md).

## Bounded implementation

Expand Down
29 changes: 29 additions & 0 deletions docs/custom-domain-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Custom domain delivery

## Scope and observed issues

The first main deployment, `485150f00cbe9491e02f22a9156f9ce667e9f6dd`, passed real delivery checks at the Workers subdomain. The owner then attached `arcforges.com` to `arcforges-web`. Both hosts serve version `0.1.0-ci.5.1`.

The remaining issues are bounded:

1. Delivery and live checks still depend on the public Workers subdomain. Disabling that route in the dashboard alone would break verification and the next deployment would re-enable it.
2. Browser requests to the custom domain receive an automatically injected Cloudflare Web Analytics script. The existing CSP correctly blocks it. Generic HTTP requests do not receive the same injection, so their hashes alone miss this browser-visible difference.
3. Operational documentation still says that the deployment credential is deferred.

## Implementation order

1. Keep the existing custom-domain binding managed in Cloudflare. Before deploying, read the account's Worker domain mappings and require `arcforges.com` to belong to this Worker's production environment. Do not create DNS records or alter other domain mappings.
2. Set `workers_dev: false`, retain `preview_urls: false`, and require this in candidate verification. Use only `https://arcforges.com` for live checks.
3. Add `no-transform` to public cache policies while preserving HTML revalidation and immutable asset caching. This prevents edge HTML injection without widening CSP. Fetch HTML as HTML in remote byte verification.
4. Run the existing browser suite against the real domain after delivery, as well as against the candidate before deployment. Create a release only after both remote byte and browser checks pass.
5. Update operational setup/recovery instructions and record actual evidence.

## Closure

- Local source checks, immutable candidate verification and three-browser candidate tests pass.
- PR CI passes without Cloudflare credentials.
- A main deployment validates the existing domain binding and disables the Workers subdomain.
- HTTPS at `arcforges.com` serves the candidate identity and exact public files, with the required headers and real 404 responses; production browser interaction passes without the injected beacon.
- Report real deployment evidence separately from local tests. Cloud/C# Containers and the future Hello API are a subsequent worktree, after this delivery step is verified.

References: [Workers subdomain configuration](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/), [Web Analytics and no-transform](https://developers.cloudflare.com/web-analytics/get-started/).
21 changes: 11 additions & 10 deletions docs/deploying.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

## Current setup

The public Hello site uses **Workers Static Assets**, with no Worker script, paid AI binding, request-time SSR or C# host. Wrangler deploys `artifacts/candidate/assets` using the configuration inside that same verified candidate. The worker name is `arcforges-web`; it does not alter the AI worker or any existing domain. `workers_dev` is enabled and preview URLs are disabled.
The public Hello site uses **Workers Static Assets**, with no Worker script, paid AI binding, request-time SSR or C# host. Wrangler deploys `artifacts/candidate/assets` using the configuration inside that same verified candidate. The Worker is `arcforges-web`, serving `https://arcforges.com`. `workers_dev` and preview URLs are explicitly disabled. The existing domain binding is managed in Cloudflare; the configuration intentionally omits `routes` and does not create or replace DNS/domain mappings.

The owner has deferred automatic deployment credentials. GitHub's `cloudflare` environment exists with a deployment branch rule for `main` and its account ID variable. No real deployment or live verification has been performed for this bootstrap. Local and PR tests remain usable.
GitHub's `cloudflare` environment contains the account variable and API token, with deployments restricted to `main`. The first real deployment passed on 2026-09-15, and the owner subsequently attached the custom domain. See [validation evidence](validation.md) for the distinction between that bootstrap deployment and this domain configuration.

## One-time owner setup

When ready to enable delivery:

1. In the selected Cloudflare account, ensure a `workers.dev` account subdomain exists. An existing Workers deployment normally means this is already configured.
1. In Cloudflare, select Worker `arcforges-web` > **Settings > Domains & Routes > Add > Custom Domain**, enter `arcforges.com`, and confirm. Cloudflare provisions the DNS record and HTTPS certificate. This binding is already configured for the current account. A fresh account needs the Worker and an active zone first; this established production pipeline refuses to disable the default address before the custom-domain mapping exists.
2. Create or reuse a Cloudflare API token restricted to that account with **Account / Workers Scripts / Edit**. Static assets need no Workers AI, DNS or Zone permissions. Save the token privately.
3. Open [Web environment settings](https://github.com/ArcForges/Web/settings/environments), enter **cloudflare**, and add **Environment secret** `CLOUDFLARE_API_TOKEN`. Do not put it in variables, code, chat or a `VITE_` value.
4. Check **Environment variable** `CLOUDFLARE_ACCOUNT_ID` contains the account's 32-character ID. This is already configured. Environments are repository-specific; the AI repository's environment is not automatically inherited.
4. Check **Environment variable** `CLOUDFLARE_ACCOUNT_ID` contains the account's 32-character ID. Both this and the deployment secret are configured. Environments are repository-specific; the AI repository's environment is not automatically inherited.
5. Keep deployments restricted to `main`. No enable switch or human approval is required by this workflow. Merge the tested PR when ready, or rerun the failed deployment after configuring the secret if the source is still current main.

The token stays in CI. The browser never receives Cloudflare management credentials. CI uses this API token independently of any developer's `wrangler login` browser session. Credential rotation consists of replacing the environment secret; do not commit an example credential.
Expand All @@ -24,28 +24,29 @@ The token stays in CI. The browser never receives Cloudflare management credenti
2. Linux builds one static candidate, generates CSP hashes from its actual inline scripts, adds upstream notices and full/runtime CycloneDX SBOMs, and seals the complete candidate file set with SHA-256.
3. Chromium, Firefox and WebKit test that candidate served through local Wrangler, including accessibility, no-JavaScript behavior, local greeting, CSP/cache behavior and missing-path 404s.
4. `Verify` requires all applicable checks. On a `push` to `main`, the deployment job downloads the exact candidate artifact by its ID and verifies source/hashes. It never rebuilds it. A skipped PR-only dependency review cannot skip deployment.
5. Missing account/token configuration is an explicit failure. The job checks that the source is still current main, serializes deployment, and deploys with Wrangler.
6. Bounded, read-only HTTPS polling waits for the expected `__build.json`; then every public candidate file is compared by hash, security/cache headers are checked, and missing routes must return the real 404. A failed check never automatically creates another deployment.
7. Only successful live verification creates a GitHub prerelease containing the original candidate archive and deployment evidence. Versions are `0.1.0-ci.<run_number>.<run_attempt>`; reruns are distinct. These are preview releases, not an assertion that the complete product is implemented.
5. Missing account/token configuration is an explicit failure. The job checks that the source is still current main and that `arcforges.com` belongs to `arcforges-web` in production, serializes deployment, and deploys with Wrangler. Checking the mapping only needs the existing Workers Scripts permission.
6. Bounded, read-only HTTPS polling waits for the expected `https://arcforges.com/__build.json`; then every public candidate file is compared by hash, security/cache headers are checked, and missing routes must return the real 404. HTML requests explicitly accept HTML. The existing Chromium, Firefox and WebKit suite then runs against the real domain to check browser-visible edge behavior. A failed check never automatically creates another deployment.
7. Only successful file and browser verification creates a GitHub prerelease containing the original candidate archive and deployment evidence. Versions are `0.1.0-ci.<run_number>.<run_attempt>`; reruns are distinct. These are preview releases, not an assertion that the complete product is implemented.

PR, schedule and manually dispatched workflows validate but do not deploy. Main pushes deploy automatically once the credential is present. No second Cloudflare Git integration is required; enabling one would create an independent deployment path that bypasses this candidate process.

## Serving and security

All content is static and public. HTML revalidates, hashed `/assets/*` files are immutable, and `__build.json` is not cached. The generated `_headers` file applies strict script hashes, self-only connections, `nosniff`, a restrictive permissions policy, framing protection and no-index headers. There is no generic SPA fallback and no API forwarding.
All content is static and public. HTML revalidates, hashed `/assets/*` files are immutable, and `__build.json` is not cached. Responses use `no-transform` to preserve the reviewed bytes and prevent automatic edge analytics injection. The generated `_headers` file applies strict script hashes, self-only connections, `nosniff`, a restrictive permissions policy, framing protection and no-index headers. There is no generic SPA fallback and no API forwarding.

The source link identifies the candidate's source revision. Public content contains no secret; open-source code does not grant deployment authority. Adding a backend/AI proxy in future requires its own authentication, authorization, quota and abuse controls. This static preview has no paid model invocation path.

Use the emitted workers.dev URL initially. Custom domains, CORS for C# Cloud, auth cookies, API origins and production profile separation require explicit configuration later; they are not silently provisioned here. Self-only CSP must be intentionally updated when real backend calls are introduced.
Use `https://arcforges.com`. The former Workers subdomain was another public entry to the same deployment, not a staging environment. Do not re-enable it merely to run CI. Disabling it in the dashboard alone is insufficient if a later Wrangler configuration enables it; the source configuration is authoritative. CORS for C# Cloud, auth cookies, API origins and production profile separation remain future integration work.

## Failure and recovery

- **Source or browser failure:** fix the failing check in a PR. Nothing deploys.
- **Missing/invalid token:** configure the environment, then rerun. No successful release is created merely because the build passed.
- **Missing custom-domain mapping:** restore `arcforges.com` on `arcforges-web` in Cloudflare before rerunning. CI checks ownership before deploying or disabling the default address.
- **Superseded run:** do not rerun an old main commit. Use the newest tested main run; the guard prevents stale queued runs from rolling back production.
- **Wrangler or live-check failure:** inspect `cloudflare-evidence-*`, the job log and the Workers dashboard. A deployment may have completed even when later validation failed. Retrying propagation only issues reads. Do not infer a successful release from upload completion.
- **Rollback:** prefer reverting the offending source in a PR so current main produces a newly verified release. For an urgent operator rollback, select the prior known-good deployment/version in Cloudflare's deployment history, verify its public `__build.json` and pages, and promptly reconcile main. The automatic workflow deliberately cannot deploy stale source. Keep the previous verified candidate archive for recovery; do not reconstruct it from mutable dependencies.

Candidate artifacts remain for 30 days, browser evidence for 14 days and deployment evidence for 90 days. Verified GitHub prereleases preserve their candidate archive beyond those artifact windows. The first real main deployment must validate API-token permissions, account subdomain, remote propagation and public HTTP behavior; local success cannot prove those account-specific conditions.
Candidate artifacts remain for 30 days, local browser evidence for 14 days and deployment evidence (including live browser reports) for 90 days. Verified GitHub prereleases preserve their candidate archive beyond those artifact windows. Each main deployment validates the actual domain mapping, remote propagation and public HTTP/browser behavior; local success cannot prove those account-specific conditions.

References: [static assets](https://developers.cloudflare.com/workers/static-assets/get-started/), [headers](https://developers.cloudflare.com/workers/static-assets/headers/), [routing](https://developers.cloudflare.com/workers/static-assets/routing/advanced/html-handling/).
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Open `win.slnx` with a Visual Studio release supporting the JavaScript project S
- React Router framework mode is configured with `ssr: false` and build-time prerendering of `/` and `/hello`. A temporary server build is used by the framework during prerendering and excluded from the candidate. Its generated SPA fallback is also excluded.
- Initial text and links work without JavaScript. Greeting controls remain disabled until hydration, cannot submit names as native form query parameters and are covered by `form-action 'none'`. Names are trimmed, limited to 80 Unicode code points and reject control characters. React renders the greeting as text.
- The example serializes real `@arcforges/proto` messages locally. `@arcforges/api-client` is tested with binary gRPC-Web success and failure fixtures. No Cloud endpoint or credential is invented. Real Cloud integration requires its published API, session and CORS decisions.
- No AI, database, analytics, service worker, custom domain, privileged proxy or user-data storage is included. React Router's scroll restoration may store scroll positions in session storage.
- No AI, database, analytics, service worker, privileged proxy or user-data storage is included. The production custom domain is `arcforges.com`. React Router's scroll restoration may store scroll positions in session storage.
- Future Account/Chat/operator/status delivery profiles remain separate work. Shared components live in `packages/ui`; profiles must not import business source from adjacent repositories.

## Dependencies
Expand Down
Loading