From 3be75cea732510f83f47f7cf587024c78b5fd0a5 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Tue, 4 Aug 2026 14:39:51 -0700 Subject: [PATCH] Re-vendor the drifted workflow contract and record the hub debt The carried CI/CD contract had fallen one revision behind the hub on the release-classification guarantee, where the hub describes the smoke-build case in terms the local copy had lost. The deploy this repo now performs has no destination in that contract, and the shape it needs is a variant of the existing registry-push leaf rather than a new release surface: the same dispatch-gated leaf publishing to its own destination, differing only in the build tool and the transport. Co-Authored-By: Claude Opus 5 (1M context) --- TODO.md | 15 +++++++++++++++ WORKFLOW.md | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 995989b..4db4e30 100644 --- a/TODO.md +++ b/TODO.md @@ -33,6 +33,21 @@ The site is built and gated in CI. It is on GitHub, and it is not yet serving it - Add the weekly non-blocking external-link-check workflow, which is the one gate that cannot be blocking because it fails on other people's outages. - Decommission WordPress.com only after **30 clean days**, and downgrade to free rather than deleting, which keeps the media reachable as a safety net and preserves the ability to re-export. Do not start sooner: the conversion fetched media over HTTP from the live site. +## Owed to the hub + +The hub is owed a spec update for this repo's publishing type, tracked in [ProjectTemplate#456][hub-issue]. The measured deploy shape is reported there. + +**Frame it as a variant of the existing registry-push leaf, not a new release surface.** A NuGet or PyPI leaf builds an artifact and pushes it to its own destination, contributing no `release-asset-*`. This repo does exactly that. Only two things differ, and neither changes the seam: + +| Same as NuGet and PyPI | Unique here | +| --- | --- | +| A leaf builds, then pushes to its own destination | The build is Hugo rather than a language toolchain | +| No `release-asset-*` contributed | The transport is rsync over SSH to a host the project owns | +| Publish is dispatch-gated, never a merge | The destination is a filesystem, so the artifact carries its own version | +| Credentials come from a GitHub Environment | Two environments serve the same artifact, so a deploy must prove which one answered | + +What the type genuinely needs is therefore small: a destination row in `Output Seam by Destination`, and one guarantee that a deploy is verified against the running host by release rather than by transport success. The release model, the branching model, and the never-publish-on-merge rule all hold unchanged. + ## Open decisions - `/robots.txt/` and `/osd.xml/` currently sit in `slugs.map` pointing at `/`. The first would be better pointing at the real `/robots.txt`. diff --git a/WORKFLOW.md b/WORKFLOW.md index 135daa9..6cd1e62 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -150,7 +150,7 @@ The required behaviors, organized by domain. Each is a **MUST**, stated as input ### D2 - Input/State Validation at Entry - **D2.1 Validate before expensive work.** Output: a dedicated entry job/step asserts each cross-input/derived-state invariant and fails fast before builds. Downstream jobs `needs:` it. -- **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and it is **skipped on smoke** (a detached PR head always versions as prerelease). *Prevents: a non-default leg published as stable; a build-metadata false-positive; the gate blocking every default-base promotion PR.* +- **D2.2 Release branch matches version classification.** Input: a real (non-smoke) release build. Output: the gate fails loudly if the default branch carries a prerelease suffix **or** a non-default branch carries none. It strips `+buildmetadata` before testing for the prerelease `-` (only a core/prerelease `-` counts), and on a smoke build the **check exits early while the job still reports success** (a detached PR head always versions as prerelease). Read that as the validation being skipped rather than the job, because a job-level `if:` would skip the job itself, and a dependent skips with it unless that dependent opts out with `if: always()` and reads the result explicitly, the way the PR aggregator does. `github-release` carries `validate-release` in `needs:` and does **not** opt out, so a job-level skip there would couple the release to smoke through a second path on top of the `if:` it already carries. *Prevents: a non-default leg published as stable; a build-metadata false-positive; the gate blocking every default-base promotion PR.* - **D2.3 Publish only from main or develop.** Input: a dispatch publish. Output: a dispatch from any ref other than `main` or `develop` fails fast. *Prevents: cutting a release from an unintended branch.* - **D2.4 Mutually-exclusive / paired inputs are validated.** Input: a workflow with either/or or must-pair inputs (e.g. the docker-readme task's `repositories` XOR `manifest`+`manifest-jq`). Output: a half-filled or conflicting combination fails fast. *Prevents: a silent fall-through.*