diff --git a/AGENTS.md b/AGENTS.md index 13ee45b..4e511c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,12 +21,52 @@ These rules apply before the writing-style guidance below. - Blog and Pulse broker streams are display/projection contracts only. Do not treat them as public Fediverse delivery, and do not weaken broker hydration tests just because prerendered fallback still paints. - `static/cv` is synced from the private `spear_resumes` repository. Preserve that boundary and use the existing sync/test path instead of hand-editing generated resume artifacts. +## Cross-Repo Delivery Ownership + +- This repo owns blog source, the static build, source-image/digest publication + and protected dispatch, and the production Cloudflare Pages contract. +- `Jesssullivan/jesssullivan-infra` owns the private tailnet acceptance + environment: dispatch validation and digest validation, private image + mirroring, RustFS-backed OpenTofu state, the `jesssullivan-blog-shadow` + workload, protected apply workflow, and Tailscale route. +- `https://jesssullivan-blog-shadow.taila4c78d.ts.net` is the approved active + exact-head acceptance and interactive QA route. Despite its stale name, + `.github/workflows/cloudflare-pages-shadow.yml` publishes the production + `transscendsurvival-org` Pages project on eligible `main` or manual runs; + pull-request runs are build-only. `https://tss.ephemera.tinyland.dev` and + the separately deployed `https://tss.tinyland.dev` still serve legacy + compatibility content, but both are retired as QA routes. Neither proves the + digest-pinned private exact-head route. Cloudflare Pages remains production + serving. +- `tinyland-inc/GloriousFlywheel` supplies reusable runner/build, Nix/toolchain, + Bazel/cache/RBE, enrollment, and validation substrate. Passing GF checks or + running on GF substrate does not transfer application deployment ownership + to GF. +- `tinyland-inc/tinyland.dev` owns mothership content, broker, producer, and + federation contracts consumed by this spoke. +- `tinyland-inc/blahaj` is the bounded infrastructure receiver and owns + cluster-side admission, RBAC, placement, storage, + DNS/certificate/tunnel enforcement, and state contracts. Its app-specific + receivers and reapers are limited to the canonical adopted-live exception + register; none owns this blog's shadow workload, apply decision, or + application lifecycle. `tinyland-inc/lab` may bootstrap hosts, enforce + runtime policy and operator preflights, and project narrowly scoped + credentials, but it is not an application deployment owner. +- Never infer application ownership from the cluster hosting a pod, the repo + escrowing a credential, or the runner executing a build. Follow the + source-to-digest-to-overlay chain above. + ## Build, Test, And Deploy - Normal local development is npm/SvelteKit: `npm ci`, `npm run build`, `npm run lint`, and focused scripts from `package.json`. - Production behavior changes require `npm run test:production-health`. That check covers public DNS, apex/`www` HTTPS, canonical redirects, slash variants, Tinyland broker contract, and browser hydration. - CI has two lanes. `build-and-test` runs hosted checks such as gitleaks, production dependency audit, lint, npm build, bundle reporting, and Lighthouse. `bazel-remote-gates` is the check/test/e2e authority. -- Cloudflare Pages shadow builds come from `.github/workflows/cloudflare-pages-shadow.yml`; PRs are build-only unless the workflow is explicitly eligible to deploy. GitHub Pages deploys come from `.github/workflows/deploy-pages.yml` and are still needed for rollback parity. +- `.github/workflows/cloudflare-pages-shadow.yml` is the stale-named + Cloudflare Pages production build/deploy lane; pull requests build without + deploying. It does not make either legacy `tss` route an active QA surface + and is not the private exact-head acceptance authority. GitHub Pages deploys + come from `.github/workflows/deploy-pages.yml` and are still needed for + rollback parity. - `.github/workflows/production-health.yml` runs every 30 minutes and sends ntfy alerts on failure. Treat a red scheduled monitor as production evidence, not noise. ## Bazel And GloriousFlywheel diff --git a/docs/blog-shadow-preview.md b/docs/blog-shadow-preview.md index 5ae7cef..aed4386 100644 --- a/docs/blog-shadow-preview.md +++ b/docs/blog-shadow-preview.md @@ -1,31 +1,54 @@ # Blog Shadow Preview -The live shadow preview route is owned by `Jesssullivan/jesssullivan-infra`, -but this repo owns the branch build that feeds it. +The approved active development and QA surface is the private exact-head +tailnet route. `Jesssullivan/jesssullivan-infra` owns that acceptance +environment; this repo owns the source and static build that feeds it. -Current review route: +Approved development and QA route: ```text https://jesssullivan-blog-shadow.taila4c78d.ts.net ``` +## Ownership + +- `Jesssullivan/jesssullivan.github.io` owns blog source, the static build, + source-image/digest publication, protected dispatch, and the Cloudflare Pages + production contract. +- `Jesssullivan/jesssullivan-infra` validates the dispatched source evidence + and digest, mirrors that digest privately, and owns the RustFS-backed + OpenTofu state, workload, apply, and private Tailscale route. +- GloriousFlywheel supplies reusable runner/build, Nix/toolchain, + Bazel/cache/RBE, enrollment, and validation substrate. It does not own this + application's deployment. +- Blahaj and Lab do not own this application. Blahaj is the bounded + infrastructure receiver for cluster admission, RBAC, placement, storage, + DNS/certificate/tunnel enforcement, and state contracts; its canonical + adopted-live receiver/reaper exceptions do not transfer source, workload, + route, apply-decision, or lifecycle authority. Neither do Lab host bootstrap, + runtime policy, operator preflight, or scoped credential projection. + ## Automatic PR Flow -`.github/workflows/shadow-preview.yml` publishes the newest active same-repo PR -branch to the shared shadow route. +`.github/workflows/shadow-preview.yml` is the automatic exact-head lane for the +newest active same-repo PR branch on the shared shadow route. 1. A same-repo, non-draft PR against `main` is opened, marked ready, or updated. -2. The workflow builds `Dockerfile.shadow` on the configured source runner. - `BLOG_SHADOW_SOURCE_RUNNER` is currently `ubuntu-latest`; manual dispatch - retains `tinyland-dind` as its explicit default. +2. The workflow builds the static output through `Dockerfile.shadow` on the + configured source runner. `BLOG_SHADOW_SOURCE_RUNNER` is currently + `ubuntu-latest`; manual dispatch retains `tinyland-dind` as its explicit + default. 3. The workflow pushes the CI source artifact to - `ghcr.io/jesssullivan/jesssullivan-github-io-shadow-tailnet`. + `ghcr.io/jesssullivan/jesssullivan-github-io-shadow-tailnet` and publishes + the immutable OCI source digest as a job output. 4. A separate, no-checkout job enters the reviewer-gated `blog-shadow-dispatch` environment, mints a short-lived GitHub App token, and starts the named workflow in `Jesssullivan/jesssullivan-infra` with the - exact image digest and source metadata. -5. The private infra workflow mirrors that digest into the private operator - package and applies the RustFS-backed OpenTofu stack. + source repository, workflow run, commit SHA, correlation tag, and exact + source digest. +5. The private infra workflow validates that source workflow evidence and the + tag-to-digest binding, mirrors the exact digest into the private operator + package, and applies the RustFS-backed OpenTofu stack. 6. The public workflow follows the source-SHA/run-ID-correlated private run and fails unless that exact receiver run succeeds. @@ -35,6 +58,12 @@ private dispatches use separate `blog-shadow-preview-build` and true`. The private receiver also serializes applies, so the newest approved PR wins without draft resolve-only runs canceling an in-flight deploy. +The correlated receiver run proves the digest-pinned workload and canonical +service responses through a Kubernetes API port-forward, and requires the +Tailscale Ingress to report a MagicDNS hostname. Direct HTTPS against the +MagicDNS route is an independent tailnet-route QA check from a +tailnet-connected client. Neither proof substitutes for the other. + Fork PRs and draft PRs are ignored. Branch pushes are covered by the PR `synchronize` event so the workflow does not create duplicate push and PR check runs for the same commit. @@ -65,51 +94,58 @@ and is currently `ubuntu-latest`. Manual dispatch can select `tinyland-dind` when an ARC source-build proof is required. A hosted build does not prove the ARC source-build lane. The correlated receiver result still separately proves private GHCR mirroring, -RustFS-backed OpenTofu apply, and tailnet smoke. - -## Manual Shadow Image Build - -`.github/workflows/shadow-image.yml` still supports the older -`shadow-deploy/**` branch flow for explicit operator builds. That workflow only -builds the source image; the private mirror and apply are handled by infra. It -uses the `tinyland-dind` ARC runner by default and accepts the same -`BLOG_SHADOW_SOURCE_RUNNER=ubuntu-latest` fallback, or manual dispatch -`source_runner=ubuntu-latest`, when the ARC source-image lane is unavailable. - -## Cloudflare Pages Shadow - -`.github/workflows/cloudflare-pages-shadow.yml` builds the same static -SvelteKit output and can publish it to Cloudflare Pages by Direct Upload. - -This workflow exercises a separate Cloudflare Pages shadow without changing -production DNS. `https://transscendsurvival.org` is served by Cloudflare Pages; -`https://tss.tinyland.dev` remains the review shadow. The built site is still -static, but current `/blog`, -`/blog/[slug]`, and `/pulse` client code may hydrate from public -`hub.tinyland.dev` broker endpoints at runtime when those endpoints are -available. +RustFS-backed OpenTofu apply, exact-head runtime attestation, canonical service +smoke, and Tailscale Ingress status. Direct MagicDNS HTTPS remains the +independent tailnet-route QA proof described above. + +## Legacy Manual Shadow Image Build + +`.github/workflows/shadow-image.yml` is a legacy/manual, tag-correlated source +build for `shadow-deploy/**` branches or explicit workflow dispatch. It pushes +a tagged source image only. It does not dispatch +`Jesssullivan/jesssullivan-infra`, and it is not the automatic exact-head lane. + +An operator using this legacy lane must resolve the published tag to an +immutable source digest, mirror that exact digest into the private operator +package with infra-owned authority, and deliberately apply the infra-owned +stack with the digest, source SHA, and source-run correlation. The workflow +uses the `tinyland-dind` ARC runner by default and accepts +`BLOG_SHADOW_SOURCE_RUNNER=ubuntu-latest`, or manual dispatch with +`source_runner=ubuntu-latest`, when the ARC source-build lane is unavailable. + +## Cloudflare Pages Production And Legacy Routes + +Despite its stale name, `.github/workflows/cloudflare-pages-shadow.yml` builds +and, on eligible `main` or manual runs, deploys the production +`transscendsurvival-org` Pages project. Pull-request runs are build-only. +`https://tss.ephemera.tinyland.dev` still serves legacy compatibility content +from that project, while `https://tss.tinyland.dev` still serves the separate, +stale `tss-shadow` project. Both URLs are retired as QA routes. Do not +deliberately redeploy the separate `tss-shadow` project as part of blog QA. +None of these surfaces proves the private mirror, infra-owned state/apply, +digest-pinned workload, or current MagicDNS route, so do not cite them as +exact-head acceptance evidence. + +The workflow uses these repository credentials and configuration: -Required repository secrets: - -| Secret | Purpose | -|---|---| -| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account that owns the Pages project | -| `CLOUDFLARE_API_TOKEN` | Token with Cloudflare Pages edit/deploy access | - -Optional repository variable: - -| Variable | Default | Purpose | +| Kind | Name | Purpose | |---|---|---| -| `CLOUDFLARE_PAGES_PROJECT_NAME` | `transscendsurvival-org` | Cloudflare Pages project name | - -PRs and missing-credential runs build and validate the static site, then skip -the Cloudflare deploy with an explicit notice. Manual dispatch accepts -`require_deploy=true` when the operator wants missing credentials to fail -instead of skip. - -Cloudflare Pages is the production serving authority. GitHub Pages remains the -rollback publisher and parity path. Browser validation remains in GitHub -Actions or an approved remote lane. Do not run local Playwright for this slice. +| Secret | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account that owns the Pages project | +| Secret | `CLOUDFLARE_API_TOKEN` | Token with Cloudflare Pages edit/deploy access | +| Variable | `CLOUDFLARE_PAGES_PROJECT_NAME` | Pages project name; defaults to `transscendsurvival-org` | + +PRs and missing-credential runs build and validate the static artifact, then +skip the Cloudflare deploy with an explicit notice. Eligible `main` pushes and +manual runs can deploy production. Manual dispatch accepts +`require_deploy=true` when an operator deliberately wants missing credentials +to fail instead of skip. This is production-serving machinery, not the +interactive PR QA route. + +Cloudflare Pages remains the production serving authority for +`https://transscendsurvival.org` and `https://www.transscendsurvival.org`. +GitHub Pages remains the rollback publisher and parity path. The approved +exact-head acceptance and interactive QA target is the private tailnet route +documented above. ## Pulse Client Smoke @@ -130,5 +166,6 @@ curl -fsSIL "$SHADOW/pulse/client" curl -fsSL "$SHADOW/data/pulse/public-snapshot.v1.json" ``` -Browser validation for the client route stays in hosted GitHub Actions. Do not -run Playwright locally. +Browser validation for the client route stays in the GF-backed +`bazel-remote-gates` GitHub Actions job on `tinyland-dind`. Do not run +Playwright locally.