From ce10b22a8ef3ab1f79747b9fbf35606d97fea6e4 Mon Sep 17 00:00:00 2001 From: Jess Sullivan Date: Fri, 28 Aug 2026 12:36:44 -0400 Subject: [PATCH 1/2] ci(spoke): wire tinyland.repo.json, lanes.json, and pinned spoke-ci (TIN-3914) Adopts the org spoke CI contract this repo has had zero adoption of: no tinyland.repo.json, no lanes.json, and not one `uses: tinyland-inc/*` reference across the ten workflow files. Additive only. `.github/workflows/ci.yml` is untouched, so substrate-boundary, bazel-remote-gates, and build-and-test remain the required checks and the exact-source proofs that production and rollback consume are unchanged. - tinyland.repo.json: schema_version 1 (v1 is what spoke-ci@v3.1.0's repo-manifest-validate gate validates against), modeled on gftb-site's manifest. `contracts.nix: none` because this repo has no flake.nix, matching the account-controller precedent rather than claiming a file that is absent. `scaffold_tag` is omitted: this repo was not spawned from site.scaffold. Validated against ci-templates@v3.1.0 schemas/tinyland-repo-manifest.schema.json with that repo's own scripts/manifest-schema-validate.py. - .github/lanes.json: three lanes named for the check/test/e2e authority this repo already has, each recording the npm script it stands for under `extra` (spoke-ci does not read `extra`; the lane schema has no command field, so this is the reviewable mapping, not a dispatch). `defaults.runner_class` is jesssullivan-nix, which satisfies the vendored runnerClass pattern. Validated against ci-templates@v3.1.0 schemas/lanes.schema.json. - .github/workflows/spoke-ci.yml: calls the reusable workflow pinned at the v3.1.0 tag's commit d8d178c, not the tag name, because scripts/test-workflow-authority.mjs requires every non-local `uses:` in this directory to resolve to a 40-hex commit and applies that rule to reusable workflows too. That test needed no change: the new file passes its generic loop as written. heavy_runner_class and cache_backed_targets override template defaults that name a runner pool and two Bazel targets which do not exist here. No `secrets: inherit`, diverging from the gftb-site exemplar, because this lane carries a pull_request trigger. DO NOT MERGE before Jesssullivan/jesssullivan-infra applies a jesssullivan-blog-nix ARC scale set serving the jesssullivan-nix label. Until then every job in the new workflow queues with nowhere to run. --- .github/lanes.json | 52 ++++++++++++++++++++++++ .github/workflows/spoke-ci.yml | 73 ++++++++++++++++++++++++++++++++++ AGENTS.md | 11 +++++ tinyland.repo.json | 56 ++++++++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 .github/lanes.json create mode 100644 .github/workflows/spoke-ci.yml create mode 100644 tinyland.repo.json diff --git a/.github/lanes.json b/.github/lanes.json new file mode 100644 index 00000000..8c2317e7 --- /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": "jesssullivan-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..f0d869eb --- /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). `jesssullivan-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: jesssullivan-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: jesssullivan-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. `jesssullivan-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: jesssullivan-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..0fbed918 --- /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": "jesssullivan-nix", + "substrateMode": "shared-cache-backed" + }, + "contracts": { + "agent_contract": "AGENTS.md", + "just": "Justfile", + "nix": "none", + "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." + } + } +} From 528f8f4d02a54ba15766a4ada85944b04312dd84 Mon Sep 17 00:00:00 2001 From: Jess Sullivan Date: Fri, 28 Aug 2026 12:45:13 -0400 Subject: [PATCH 2/2] ci(spoke): runner class is tinyland-nix (the label infra #96 registers); honest nix contract The jesssullivan-blog-nix scale set registers runs-on label tinyland-nix -- the overlay runner-taxonomy validator rejects owner-namespaced labels, and the k8s ARS name is a separate field from the label. Pointing spoke-ci at a label no listener serves would queue forever. Also stop declaring contracts.nix none while the wired workflow runs nix develop. --- .github/lanes.json | 2 +- .github/workflows/spoke-ci.yml | 10 +++++----- tinyland.repo.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/lanes.json b/.github/lanes.json index 8c2317e7..88337464 100644 --- a/.github/lanes.json +++ b/.github/lanes.json @@ -7,7 +7,7 @@ "image_repository": "ghcr.io/jesssullivan/jesssullivan-github-io-shadow-tailnet" }, "defaults": { - "runner_class": "jesssullivan-nix", + "runner_class": "tinyland-nix", "ttl_hours": 72, "flywheel_target_classes": ["sveltekit-app-build", "sveltekit-unit-tests"] }, diff --git a/.github/workflows/spoke-ci.yml b/.github/workflows/spoke-ci.yml index f0d869eb..caad3c45 100644 --- a/.github/workflows/spoke-ci.yml +++ b/.github/workflows/spoke-ci.yml @@ -7,7 +7,7 @@ # # This cannot go green until a `jesssullivan-blog-nix` ARC scale set is applied # in Jesssullivan/jesssullivan-infra -# (tofu/stacks/arc-runners/jesssullivan.tfvars). `jesssullivan-nix` is the +# (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) @@ -40,11 +40,11 @@ jobs: flywheel_config: flywheel cache_backed: true lanes_path: .github/lanes.json - default_runner_class: jesssullivan-nix + 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: jesssullivan-nix + 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). @@ -60,11 +60,11 @@ jobs: name: merge-gate if: always() needs: [ci] - # TIN-3914 retired the GitHub-hosted class. `jesssullivan-nix` is the label + # 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: jesssullivan-nix + runs-on: tinyland-nix timeout-minutes: 5 steps: - name: Require complete reusable CI diff --git a/tinyland.repo.json b/tinyland.repo.json index 0fbed918..71054f1f 100644 --- a/tinyland.repo.json +++ b/tinyland.repo.json @@ -18,13 +18,13 @@ "enrollment": { "forgeScope": "Jesssullivan", "operatorOverlay": "jesssullivan-infra", - "executionPool": "jesssullivan-nix", + "executionPool": "tinyland-nix", "substrateMode": "shared-cache-backed" }, "contracts": { "agent_contract": "AGENTS.md", "just": "Justfile", - "nix": "none", + "nix": "devshell-via-spoke-ci", "github_actions": ".github/workflows", "secrets_scan": "gitleaks", "conformance": "just check"