Research preview · 0.2.0-draft

No compatibility guarantee. Not for consequential production decisions.

Informative View current source

Static site deployment

On this page

This document is a deployment guide only. It does not create a Firebase project, authenticate to Firebase, upload files, change DNS, or deploy anything. The committed GitHub Actions workflow owns the production project and site bindings; the manual examples below remain fictional safeguards.

The site generator writes a provider-neutral static artifact to public/. Build and inspect that artifact before using any hosting command:

python3 web/build.py --environment preview
python3 -m http.server 8000 --bind 127.0.0.1 --directory public

Open http://localhost:8000, check the generated pages and links, and stop the local server with Ctrl+C. Re-run the build after source changes. Treat public/ as disposable generated output; do not edit it in place.

Published URLs are kept resolvable

A versioned URL that has been published may already have been cited, so the generator keeps it resolvable rather than letting it 404, and it does so with generated pages rather than host redirects — firebase.json's spec target deliberately declares no rewrites and no redirects, and the site tests assert that. Two cases exist today:

  • each schema is copied to the path of its own $id, including both superseded 0.1.0-draft schemas — the Core structural schema and the document-conformance manifest schema — so every $id the previous draft published still resolves for a document that keeps the older exact specVersion; and
  • /spec/<previous version>/ keeps a short signpost page saying the draft is superseded and linking to the tagged source, that draft's release notes, and the current specification. It publishes no normative text: the normative text of a superseded draft is its tag, never a page on a mutable site.

When the current version bumps, SITE_VERSION and PREVIOUS_SITE_VERSION in web/build.py move together, and the signpost follows the previous version.

Firebase Hosting is a good fit for this site because the output is static HTML, CSS, and other assets. Hosting provides managed HTTPS, CDN delivery, temporary preview channels, release history, and optional custom domains without requiring an application server. Preview channels make it practical to review a generated specification site before changing its live channel.

This recommendation is operational convenience, not a JPS requirement or an endorsement embedded in the specification. The public/ artifact remains portable to other static hosting providers.

One-time Firebase setup

The commands below use jps-spec-preview-example as an illustrative project ID. It is not known to exist and is not reserved for this repository. Replace every occurrence with the exact ID of a dedicated Firebase project that you are authorized to use.

  1. Create or select a dedicated Firebase project in the Firebase console. A separate project reduces the chance that a documentation preview affects unrelated Firebase resources. For a strict no-billing preview, leave it on the Spark plan and do not attach a billing account.
  2. Install or update the Firebase CLI using the official CLI instructions. If the npm installation reports that the installed Node.js is unsupported, upgrade to a currently supported Node.js or use the official standalone Firebase CLI binary. Then verify the CLI, authenticate, and list the visible projects:

bash firebase --version firebase login firebase projects:list

  1. In the Firebase console, open Hosting, choose Get started, and provision the project's default classic Hosting site. Do not upload sample content. Then record the actual Hosting site ID returned by:

bash firebase hosting:sites:list --project jps-spec-preview-example

The default site ID is often the project ID, but do not assume they match. The clone example below uses jps-spec-site-example as a separate fictional site ID. 4. From the repository root, build the site and inspect public/. Confirm it contains no secrets, credentials, private drafts, internal URLs, customer data, or untracked files that should not be public. 5. Review the repository's existing firebase.json. It points classic Firebase Hosting at public/, enables clean static URLs, supplies conservative cache and security headers, and has no backend or single-page-app rewrite. Do not run firebase init over it during normal setup. 6. Either keep passing the exact --project jps-spec-preview-example argument shown below or copy .firebaserc.example to the ignored .firebaserc file and replace its placeholder with the verified project ID. A real project binding is deliberately not committed.

The Firebase Hosting quickstart is the source of truth if the CLI prompts differ from this guide.

Deploy a preview first

Rebuild from a clean, reviewed revision immediately before previewing:

python3 web/build.py --environment preview
firebase hosting:channel:deploy spec-review --expires 7d --no-authorized-domains --project jps-spec-preview-example

Before confirming the command, verify the project ID shown by the CLI. This command is an external write: it uploads the Hosting configuration and current public/ contents to the spec-review preview channel. Preview output deliberately carries noindex, nofollow, has no canonical URLs or sitemap, and blocks crawling in robots.txt. --no-authorized-domains prevents this static documentation deployment from changing Firebase Authentication's authorized-domain list. The returned preview URL is temporary but public to anyone who has it; it is not an access-control boundary.

Review the preview URL on desktop and mobile, follow internal and external links, and compare the displayed version with the intended source checkout. Normative tagged artifacts and their browsable views link to the immutable tag. Living overview, tooling, and boundary pages updated after that tag link to the clearly labelled current main source instead; they must not be presented as tagged content. A Firebase preview-channel URL is temporary and should be shared as a deployment candidate, not as the durable specification URL. The explicit seven-day expiration limits stale preview content; Firebase permits changing or deleting the channel sooner.

Publish the approved revision to live

Obtain explicit release approval before changing the live channel. Do not clone the preview channel to live: its artifact is intentionally noindex and uncanonical, and cloning preserves those bytes. The committed production workflow rebuilds the approved main revision with the live canonical origin. For a manual release from the exact clean, reviewed revision, use the same production build contract:

commit_sha="$(git rev-parse HEAD)"
build_time="$(git show -s --format=%cI "$commit_sha")"
python3 web/build.py \
  --environment production \
  --base-url https://judgmentpack.org \
  --commit-sha "$commit_sha" \
  --build-time "$build_time"
python3 -m unittest discover -s tests
mapfile -t noindex_pages < <(rg -lF '<meta name="robots" content="noindex, nofollow">' public --glob '*.html')
test "${#noindex_pages[@]}" -eq 1
test "${noindex_pages[0]}" = "public/404.html"
rg -q '^Allow: /$' public/robots.txt
test -f public/sitemap.xml
firebase target:apply hosting spec jps-spec-site-example --project jps-spec-preview-example
firebase deploy --only hosting:spec --project jps-spec-preview-example

Replace both fictional IDs with the values already verified for the intended project. Confirm the project, target, clean commit, canonical origin, and generated indexing checks before approving the external write. The --only hosting:spec scope prevents an accidental deployment of the redirect site or unrelated Firebase resources. Afterward, verify the live Firebase URL and custom domain, including the homepage canonical, robots.txt, sitemap.xml, and a nested specification page.

See Firebase's guide to testing, previewing, and deploying for current channel behavior.

Custom domain

First verify the Firebase-provided live URL. Then use the Hosting page in the Firebase console to add a custom domain and follow the displayed ownership, DNS, and certificate steps exactly. Do not copy DNS values from examples or from another project. Firebase Hosting provisions and renews the TLS certificate after domain verification and DNS propagation.

Coordinate any production DNS cutover with the domain owner. Use the console's advanced migration flow when an existing domain must remain available during a provider change. DNS and certificate provisioning can take time, so keep the Firebase-provided URL available for verification. Refer to the current custom-domain guide.

Rollback and cleanup

For a live rollback, open the intended site's Hosting release history in the Firebase console, identify a known-good previous version, and choose Roll back. Confirm the site and version; a rollback creates a new live release pointing to the selected earlier content. Do not try to repair a bad live release by editing generated files directly.

Delete a preview channel early when it is no longer needed:

firebase hosting:channel:delete spec-review --project jps-spec-preview-example

That command deactivates the preview URL and schedules its channel releases for cleanup. Verify the project and channel name before confirming deletion. Preview channels with an expiration also clean themselves up; the live channel does not expire and cannot be deleted as a preview channel.

Use the Firebase console to set release-retention limits or remove old, non-current releases when storage cleanup is needed. Deleting a previous release removes recoverable content, so retain at least one verified rollback candidate and confirm that a version is not needed by another channel. The current details are in Firebase's channel and release management guide.

Cost and quota caveat

Firebase plans, prices, and quotas can change. As checked on 2026-07-22, the Firebase pricing page lists a no-cost Hosting allowance of 10 GB stored and 360 MB/day transferred, including custom domain and SSL support. This small static site should fit comfortably for ordinary preview traffic, but that is an estimate rather than a spending guarantee. For a strict no-billing setup, remain on the Spark plan without a billing account and accept that service can be interrupted if a no-cost quota is exhausted. If the project is upgraded to Blaze to use credits, usage beyond no-cost quotas can incur charges and credits are not a spending cap. Hosting usage is measured at the project level, including all sites and channels in that project; preview releases consume storage too. Review the current Firebase pricing page and Hosting usage, quota, and pricing documentation before creating a project or enabling billing. Monitor storage and data transfer, set sensible release-retention limits, and configure budget alerts if billing is enabled. Budget alerts notify; they do not cap charges.

Provider-neutral alternative

Firebase is optional. Build production output with the explicit environment, canonical base URL, commit SHA, and build time shown above, then publish public/ with any static host that can serve an index.html, preserve paths and MIME types, and provide HTTPS. Never publish the default preview artifact: it is intentionally noindex. Configure that provider's build output or upload root as public/; no Python process, server-side rendering, database, or Firebase SDK is required at runtime.

Apply the same preview-before-live, public-content review, custom-domain, retention, cost, and rollback precautions with whichever provider is selected.