diff --git a/.github/lanes.json b/.github/lanes.json new file mode 100644 index 00000000..88337464 --- /dev/null +++ b/.github/lanes.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://raw.githubusercontent.com/tinyland-inc/site.scaffold/main/docs/schemas/lanes.schema.json", + "schema_version": 1, + "spoke": { + "name": "jesssullivan-github-io", + "domain": "transscendsurvival.org", + "image_repository": "ghcr.io/jesssullivan/jesssullivan-github-io-shadow-tailnet" + }, + "defaults": { + "runner_class": "tinyland-nix", + "ttl_hours": 72, + "flywheel_target_classes": ["sveltekit-app-build", "sveltekit-unit-tests"] + }, + "lanes": [ + { + "name": "check", + "trigger": "pull_request", + "theme": "pine", + "snapshot_source": "checked-in", + "e2e": false, + "extra": { + "npm_script": "remote:check", + "public_variant": "remote:check:public", + "note": "Type/build-smoke authority. spoke-ci does not read `extra`; this records the existing script this lane stands for so the mapping is reviewable in one place." + } + }, + { + "name": "test", + "trigger": "pull_request", + "theme": "pine", + "snapshot_source": "checked-in", + "e2e": false, + "extra": { + "npm_script": "remote:test", + "public_variant": "remote:test:public", + "note": "Unit, graph-hygiene, workflow-authority, and browser-smoke authority." + } + }, + { + "name": "e2e", + "trigger": "pull_request", + "theme": "pine", + "snapshot_source": "checked-in", + "e2e": true, + "extra": { + "npm_script": "remote:e2e", + "public_variant": "remote:e2e:public", + "note": "Playwright Chromium end-to-end authority. spoke-ci's own playwright job stays disabled until a KVM-capable class is anchored for this repo." + } + } + ] +} diff --git a/.github/workflows/spoke-ci.yml b/.github/workflows/spoke-ci.yml new file mode 100644 index 00000000..caad3c45 --- /dev/null +++ b/.github/workflows/spoke-ci.yml @@ -0,0 +1,73 @@ +# TIN-3914 spoke-ci adoption: wired here, not yet servable. +# +# Additive by design. `.github/workflows/ci.yml` keeps the substrate-boundary, +# bazel-remote-gates, and build-and-test jobs exactly as they are, so every +# required check and the exact-source production/rollback proofs that consume +# them are untouched while this lane is proven. +# +# This cannot go green until a `jesssullivan-blog-nix` ARC scale set is applied +# in Jesssullivan/jesssullivan-infra +# (tofu/stacks/arc-runners/jesssullivan.tfvars). `tinyland-nix` is the +# capability label those runners will carry; nothing serves it for this +# repository today, so every job below queues until that apply lands. +name: Spoke CI (TIN-3914) + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + statuses: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + # Pinned by the v3.1.0 tag's commit rather than by the tag name: + # scripts/test-workflow-authority.mjs requires every non-local `uses:` in + # this directory to resolve to a 40-hex commit, reusable workflows + # included. d8d178c is `git rev-parse v3.1.0^{commit}` in + # tinyland-inc/ci-templates. Note this freezes the workflow body only -- + # spoke-ci@v3.1.0 resolves its own composite actions at floating `@v3`. + uses: tinyland-inc/ci-templates/.github/workflows/spoke-ci.yml@d8d178c022a0f84853d53a2c8fe0fc90115f0949 # v3.1.0 + with: + node_version: '22' + flywheel_config: flywheel + cache_backed: true + lanes_path: .github/lanes.json + default_runner_class: tinyland-nix + # This account publishes one capability class. The template default sends + # bazel-graph to `tinyland-nix-heavy`, which serves no repository in this + # forge scope, so the base label is passed here too. + heavy_runner_class: tinyland-nix + # The template default names //:sveltekit_types and //:svelte_check_test. + # Neither target exists in this repo; these three are its real + # flywheel-eligible CAS surface (BUILD.bazel). + cache_backed_targets: '//:node_modules //:sveltekit_check //:sveltekit_vite_build_smoke' + # No `secrets: inherit`, deliberately diverging from the gftb-site + # exemplar. This workflow carries a pull_request trigger, and this repo's + # standing contract is that a PR-triggered lane reaches no credential. + # spoke-ci declares one optional secret (ATTIC_TOKEN); the cache-backed + # path reads the shared Bazel cache and does not upload, so it does not + # need one. Revisit only with a same-repo-only guard. + + merge-gate: + name: merge-gate + if: always() + needs: [ci] + # TIN-3914 retired the GitHub-hosted class. `tinyland-nix` is the label + # the not-yet-applied `jesssullivan-blog-nix` scale set will carry, so this + # job has nowhere to run until that apply lands -- which is exactly the + # DO-NOT-MERGE condition on this change. + runs-on: tinyland-nix + timeout-minutes: 5 + steps: + - name: Require complete reusable CI + env: + REUSABLE_CI_RESULT: ${{ needs.ci.result }} + run: test "$REUSABLE_CI_RESULT" = success diff --git a/AGENTS.md b/AGENTS.md index 65b10669..b7076757 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,6 +45,7 @@ re-read immediately before their credentialed mutation. | Shadow source publish (GHCR) | `.github/workflows/shadow-source-publish-v2.yml` | `workflow_run` consumer of `Build shadow source v2`; runs default-branch code only and independently revalidates provenance, never executing PR code | repo var `BLOG_SHADOW_SOURCE_PUBLISH_ENABLED` (default false), revalidated at package-write time | | Private CV consistency | `.github/workflows/private-cv-authority-v2.yml` | exact current-`main` push + typed dispatch (`private-cv-verify-v2`) | none; credentialed verify-only lane that never commits or publishes, and is itself a required proof for production publish | | Production health monitor | `.github/workflows/production-health-v2.yml` | cron health check every 30 minutes (ntfy alert on failure) + typed dispatch (`production-health-v2`, optional ntfy smoke) | none; notification credentials carry no serving-state mutation authority, and a red scheduled run is production evidence | +| Org spoke CI (TIN-3914, not yet servable) | `.github/workflows/spoke-ci.yml` | push to `main` + PR to `main`, calling `tinyland-inc/ci-templates/.github/workflows/spoke-ci.yml` pinned at the `v3.1.0` commit | none needed; passes no secrets, and every job targets the `jesssullivan-nix` capability class that no scale set serves for this repo yet | - This repo owns blog source, the static build, shadow source-image publication to `ghcr.io/jesssullivan/jesssullivan-github-io-shadow-tailnet`, @@ -57,6 +58,16 @@ re-read immediately before their credentialed mutation. - `tinyland-inc/GloriousFlywheel` supplies runner, Nix/toolchain, Bazel cache/RBE, and validation substrate. Passing GF checks or running on GF runners transfers no application deployment ownership. +- `tinyland-inc/ci-templates` owns the reusable spoke CI contract. TIN-3914 + exception, recorded 2026-08-28: this repo's hand-rolled workflows pin + `runs-on: ubuntu-latest` at 17 sites, and `.github/workflows/spoke-ci.yml` + is the wiring that retires them, not proof they are retired. It is inert + until `Jesssullivan/jesssullivan-infra` applies a `jesssullivan-blog-nix` + ARC scale set serving the `jesssullivan-nix` label + (`tofu/stacks/arc-runners/jesssullivan.tfvars`), so `ci.yml` stays the + required-check authority and nothing about merge protection moves. Do not + hand-migrate individual `runs-on` lines or retire any `ci.yml` job ahead of + that apply: the tfvars entry lands first, the label flip second. - `tinyland-inc/tinyland.dev` owns the mothership content, broker, and federation contracts this spoke consumes. - The `substrate-boundary` job in `.github/workflows/ci.yml` enforces that diff --git a/tinyland.repo.json b/tinyland.repo.json new file mode 100644 index 00000000..71054f1f --- /dev/null +++ b/tinyland.repo.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://raw.githubusercontent.com/tinyland-inc/site.scaffold/main/docs/schemas/tinyland-repo-manifest.schema.json", + "schema_version": 1, + "repo": { + "name": "jesssullivan.github.io", + "github": "Jesssullivan/jesssullivan.github.io", + "domain": "transscendsurvival.org", + "description": "Personal static SvelteKit blog and CV spoke, published to Cloudflare Pages at transscendsurvival.org.", + "linear": { + "issue": "TIN-3914" + } + }, + "taxonomy": { + "primary_role": "static-spoke", + "spawned_repo_role": "static-spoke", + "layers": ["org-wide-repo-contract", "bazel-package-cache-rbe", "static-spoke"] + }, + "enrollment": { + "forgeScope": "Jesssullivan", + "operatorOverlay": "jesssullivan-infra", + "executionPool": "tinyland-nix", + "substrateMode": "shared-cache-backed" + }, + "contracts": { + "agent_contract": "AGENTS.md", + "just": "Justfile", + "nix": "devshell-via-spoke-ci", + "github_actions": ".github/workflows", + "secrets_scan": "gitleaks", + "conformance": "just check" + }, + "boundaries": { + "owns_runtime_backend": false, + "owns_auth": false, + "owns_payments": false, + "owns_activitypub_delivery": false, + "owns_live_broker_fetch": false, + "owns_static_projection_ingest": true, + "owns_gitops_apply": false, + "owns_cloudflare_mutation": false, + "owns_bazel_module_authority": false + }, + "authorities": { + "content_authority": "tinyland-inc/tinyland.dev", + "ci_templates": "tinyland-inc/ci-templates", + "cache_rbe_authority": "tinyland-inc/GloriousFlywheel", + "package_registry": "tinyland-inc/bazel-registry" + }, + "supply_chain": { + "sbom": { + "status": "planned", + "formats": ["CycloneDX JSON", "SPDX JSON"], + "notes": "No SBOM recipe exists in this repo yet. Adopting one is downstream of the spoke-ci adoption tracked in TIN-3914; status moves off planned only when a recipe lands." + } + } +}