From f2e778af62a5deaa38ca112e1693dd524dbd649a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 15:44:22 -0700 Subject: [PATCH 1/4] Correct the docs that stopped describing the repository The hub's first audit of this repo (ProjectTemplate#547) found three prose violations and, reading the docs alongside it, three claims that were true when written and are not now. A doc that describes a past state is worse than an absent one, because a reader cannot tell which half still holds. ## Claims that outlived their state - `reports/Blog/audit.md` said the release path was untested because the publisher had never been dispatched. It was dispatched on 2026-08-01 and cut release `1.0.11`, so the path is proven rather than untested. - The same file said the registry entry stays `publish: []`. The hub now carries an entry declaring the GitHub release, which is the only channel that ships today. The VPS target is still deferred, and that half is unchanged. - `TODO.md` still listed dispatching the publisher as the next task. Removed, and the State table gains the release-pipeline row, so the fact that it is proven is recorded rather than merely no longer denied. ## The current-time rule `HISTORY.md` described the 1.0 content as "having previously lived inside a hosted blogging platform". Release notes are written in current time, describing what the thing is: a reader of 1.0 never saw the platform it came from, so the delta is noise that only makes sense to whoever did the work. ## Prose Two of the three findings are fixed: a comment opening on a lowercase identifier in the merge-bot workflow, and a semicolon in `OPERATIONS.md` prose. The third is the COPYRIGHT SIGN in `layouts/rss.xml`, left alone deliberately. The character-set tiers classify it nowhere, so the linter reports `charset-unknown` rather than pass or fail, and transliterating it in a machine-read feed to satisfy a rule that does not exist yet is the wrong way round. It is raised as an escalation in the hub's audit report and is the hub's call. ## Verification markdownlint, editorconfig-checker, and the JSON gates are clean, and `prose_lint.py` over the repo excluding the imported archive now reports only the escalated `charset-unknown`. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/merge-bot-pull-request.yml | 2 +- HISTORY.md | 2 +- OPERATIONS.md | 2 +- TODO.md | 2 +- reports/Blog/audit.md | 5 ++--- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 5b08735..fb34126 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -17,7 +17,7 @@ on: # Concurrency keys on the pull request number rather than github.ref. # Under pull_request_target github.ref is the base branch, which would serialize every bot pull request. -# cancel-in-progress is false so a follow-up synchronize cannot cancel an in-flight opened run. +# The false cancel-in-progress keeps a follow-up synchronize from cancelling an in-flight opened run. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: false diff --git a/HISTORY.md b/HISTORY.md index f2f6537..5d16e86 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,7 +5,7 @@ Pieter Viljoen's blog, and the tooling that builds, verifies, and deploys it. ## Release History - Version 1.0: - - First public release. The blog's content, media, URL contract, and deploy tooling are published as a repository for the first time, having previously lived inside a hosted blogging platform. + - The blog's content, media, URL contract, and deploy tooling live in one repository, under version control and gated by CI. - 108 posts and 2 pages as Hugo content, in a tree that mirrors the URLs it serves, with 778 media files carried at their original bytes. - The URL contract as committed ground truth: 328 addresses that must render, 917 that must redirect, and 778 legacy image URLs that must resolve, each verified with a live request rather than predicted. - CI gates that contract on every pull request, alongside the doc, shell, and workflow linters, with the Hugo version pinned by checksum so a build is reproducible. diff --git a/OPERATIONS.md b/OPERATIONS.md index 1fc7094..d1a446b 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -139,7 +139,7 @@ A restart is needed whenever **any** Caddy config changes, not only this file. T ## Redirects -The site answers 917 addresses it does not render, satisfied by 13 `redir` directives reading 5 map files, all inside the bundle. [`deploy/README.md`](./deploy/README.md) carries the per-class breakdown and the counts; this section covers the operational shape only, so the two do not restate each other. +The site answers 917 addresses it does not render, satisfied by 13 `redir` directives reading 5 map files, all inside the bundle. [`deploy/README.md`](./deploy/README.md) carries the per-class breakdown and the counts. This section covers the operational shape only, so the two do not restate each other. Ordering is load-bearing, so every redirect lives in a single `route` block. Outside one, Caddy sorts directives by its own precedence rather than by file order, and the broad attachment rule claims the per-post comment feeds that the narrower rule must match first. diff --git a/TODO.md b/TODO.md index 8da733f..754b74d 100644 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,7 @@ The site is built and gated in CI. It is on GitHub, and it is not yet serving it | Deploy shape | done and proven against a running Caddy, on a local mirror | | CI workflows | green. Validation runs on every pull request and feeds the required check | | GitHub repo | public, both rulesets active, `configure.sh check` exits 0 | +| Release pipeline | proven end to end. Release `1.0.11` carries the tag, source archive, README, and LICENSE | | VPS | untouched | ## Blocked on the maintainer @@ -22,7 +23,6 @@ The site is built and gated in CI. It is on GitHub, and it is not yet serving it ## Next, in dependency order -- Dispatch `publish-release.yml` once to prove the release path, which exists but has never run. - Provision the VPS: an unprivileged `blogdeploy` user, the deploy root, and `unattended-upgrades` with automatic reboot. - Restrict the deploy key with `restrict,command=...`, no pty and no forwarding, so it can do nothing but rsync into `releases/` and swap the symlink. Generate per-environment keys so staging cannot reach production. - Choose the staging FQDN, add its DNS record, and expose it through Pangolin as a public resource with **no auth**, since CI's live-URL check has to reach it. Authentication defaults to on for a public resource and has to be turned off deliberately. diff --git a/reports/Blog/audit.md b/reports/Blog/audit.md index fb655b2..82e35a5 100644 --- a/reports/Blog/audit.md +++ b/reports/Blog/audit.md @@ -108,7 +108,7 @@ Eight carried files arrived CRLF and were normalized to LF to satisfy this repos This repository will deploy a built site to a VPS over SSH, which is a release surface the fleet spec has no type for. The measured shape will be reported to [ProjectTemplate#456][hub-issue] once CI has run a deploy, rather than predicted now. The VPS does not exist, so there is nothing to measure. -`publish-release.yml` exists and is dispatch-only, but has never been dispatched. The release path is therefore untested. +`publish-release.yml` is dispatch-only and proven end to end: release `1.0.11` on 2026-08-01 carries the tag, the source archive, the README, and the LICENSE. ## Deliberate Deviations @@ -122,8 +122,7 @@ Both are recorded in [AUDIT.md](../../AUDIT.md) and reported upstream, so neithe Carried forward rather than closed: - The redirect half of the contract is proven only against the local mirror, by hand, before a pull request. CI has no server to point at, so nothing enforces it automatically until staging exists. -- `publish-release.yml` has never been dispatched, so the release path is untested. -- No deploy exists, so the publish surface stays deferred and the registry entry stays `publish: []`. +- No deploy exists, so the publish surface stays deferred. The hub registry declares the GitHub release, which is the only channel that currently ships, and the VPS target is revisited when a deploy has actually run. - `checks/README.md` carries a small prose backlog of `dash` and `semicolon` findings, left for the next edit of that file per the correct-as-you-next-edit rule. From 20a919de0b1e0b03312ed642a9995ff0143580b1 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 15:48:40 -0700 Subject: [PATCH 2/4] Say which setting stops the cancellation, not which adjective The reworded comment opened "The false cancel-in-progress", which reads as an adjective modifying the key name rather than as the key's value. Naming the setting and its value says the same thing without the ambiguity, and it still opens on a capital, which is what the rewording was for. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/merge-bot-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index fb34126..381b974 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -17,7 +17,7 @@ on: # Concurrency keys on the pull request number rather than github.ref. # Under pull_request_target github.ref is the base branch, which would serialize every bot pull request. -# The false cancel-in-progress keeps a follow-up synchronize from cancelling an in-flight opened run. +# Setting cancel-in-progress to false means a follow-up synchronize cannot cancel an in-flight opened run. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: false From 91a3c0b41ef58bbd5c74741648f90a5794389d4a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 15:52:55 -0700 Subject: [PATCH 3/4] Separate the release, which passes, from the deploy, which is deferred The section read "Publish and Release: Deferred" over a paragraph saying the release is proven end to end, which is a heading contradicting its own body. A reader skimming headings got the opposite of the finding. They are two surfaces and only one is outstanding. The release is dispatch-only and proven by release `1.0.11`, and the hub registry declares it as such. The deploy to the VPS is the deferred half, and it is deferred because the VPS does not exist rather than because anything failed. Fixing the heading exposed two more places stating the merged claim: the verdict paragraph and the dimension table, which carried one "Publish and release: Deferred" row where there are two answers. Both now say which surface they mean, and the stale "`publish` is empty and `releaseTrigger` is `none`" line goes with them, since the registry entry landing in ProjectTemplate#547 declares the GitHub release and `dispatch-only`. Found by a suppressed low-confidence Copilot finding on the previous push, which was correct. Co-Authored-By: Claude Opus 5 (1M context) --- reports/Blog/audit.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/reports/Blog/audit.md b/reports/Blog/audit.md index 82e35a5..b1b5cb9 100644 --- a/reports/Blog/audit.md +++ b/reports/Blog/audit.md @@ -10,7 +10,7 @@ Self-audit of this repository against its own committed ground truth, per [AUDIT **Operational.** Every applicable check passes against the live repository. -The publish and release surface is **deferred**, not failed. That deferral is declared rather than hidden, and is tracked in [ProjectTemplate#456][hub-issue], which [`STANDUP.md` section 5][standup] permits. +The release surface passes. The **deploy** to the VPS is **deferred**, not failed, and that deferral is declared rather than hidden, tracked in [ProjectTemplate#456][hub-issue], which [`STANDUP.md` section 5][standup] permits. | Dimension | Result | | --- | --- | @@ -19,7 +19,8 @@ The publish and release surface is **deferred**, not failed. That deferral is de | 3. The URL contract | **Pass.** Enforced by CI, not only locally | | Baseline file presence | **Pass.** 23 of 23 | | Verbatim fidelity | **Pass.** 4 of 4 | -| Publish and release | **Deferred**, deliberately | +| Release | **Pass.** Dispatch-only, proven by release `1.0.11` | +| Deploy to the VPS | **Deferred**, deliberately | ## 1. Settings and Rulesets @@ -102,13 +103,13 @@ That is a local mirror, not CI and not production. CI cannot run it, because the Eight carried files arrived CRLF and were normalized to LF to satisfy this repository's declared `lineEndings`. That is governed drift rather than a fidelity deviation, and it is reported upstream as an onboarding trap, since nothing in the standup text says to normalize after carrying. -## Publish and Release: Deferred +## Release Proven, Deploy Deferred -`publish` is empty and `releaseTrigger` is `none`, deliberately. +The two are separate and only one of them is outstanding. -This repository will deploy a built site to a VPS over SSH, which is a release surface the fleet spec has no type for. The measured shape will be reported to [ProjectTemplate#456][hub-issue] once CI has run a deploy, rather than predicted now. The VPS does not exist, so there is nothing to measure. +**The release is proven.** `publish-release.yml` is dispatch-only, and release `1.0.11` on 2026-08-01 carries the tag, the source archive, the README, and the LICENSE. The hub registry declares it accordingly: `publish` names the GitHub release and `releaseTrigger` is `dispatch-only`. -`publish-release.yml` is dispatch-only and proven end to end: release `1.0.11` on 2026-08-01 carries the tag, the source archive, the README, and the LICENSE. +**The deploy is deferred.** This repository will deploy a built site to a VPS over SSH, which is a release surface the fleet spec has no type for. The measured shape will be reported to [ProjectTemplate#456][hub-issue] once CI has run a deploy, rather than predicted now. The VPS does not exist, so there is nothing to measure. ## Deliberate Deviations From c7f1155211cc549c2209973393c190807179957e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 3 Aug 2026 15:55:35 -0700 Subject: [PATCH 4/4] Name the deferred surface the VPS deploy, not the publish The residual-delta bullet still called it the publish surface, from before the section split release from deploy. That reading now includes the GitHub release, which ships and is proven, so the bullet said the proven half was deferred. Found by a suppressed low-confidence Copilot finding, the second in a row on this branch that was correct. Co-Authored-By: Claude Opus 5 (1M context) --- reports/Blog/audit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reports/Blog/audit.md b/reports/Blog/audit.md index b1b5cb9..2abf13e 100644 --- a/reports/Blog/audit.md +++ b/reports/Blog/audit.md @@ -123,7 +123,7 @@ Both are recorded in [AUDIT.md](../../AUDIT.md) and reported upstream, so neithe Carried forward rather than closed: - The redirect half of the contract is proven only against the local mirror, by hand, before a pull request. CI has no server to point at, so nothing enforces it automatically until staging exists. -- No deploy exists, so the publish surface stays deferred. The hub registry declares the GitHub release, which is the only channel that currently ships, and the VPS target is revisited when a deploy has actually run. +- No deploy exists, so the VPS deploy surface stays deferred. The GitHub release is the only channel that currently ships, which is what the hub registry declares, and the VPS target is revisited when a deploy has actually run. - `checks/README.md` carries a small prose backlog of `dash` and `semicolon` findings, left for the next edit of that file per the correct-as-you-next-edit rule.