From 7ead03e4bb0049911ea5ee5e222b2052966a79e9 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 1 Aug 2026 09:43:55 -0700 Subject: [PATCH] Record the post-standup audit and prune the completed backlog The repository now exists on GitHub, so the two audit dimensions that could not be checked before are checkable, and both pass. Verdict moves from not operational to operational. Settings and rulesets pass with configure.sh check exiting 0 across 31 assertions. Secrets pass with both required names present in the Actions and Dependabot stores and the forbidden CODEGEN_APP_ID absent from both. The URL contract now passes in CI rather than only locally, on the first run, with the pinned Hugo build reporting 328/328 render, 778/778 media, and 1012/1012 assets. The ordering constraint held: the workflow was dispatched once and reported before either ruleset was applied, so the required check existed by the time it was bound by name. What the report does not claim: the live redirect gate still has not run, so 917 of the 1,245 contracted URLs remain asserted rather than proven, and publish-release.yml has never been dispatched, so the release path is untested. Both are recorded as residual deltas rather than glossed. TODO.md drops the four steps that are done and gains the two that standing the repo up newly exposed, proving the release path and deciding on the merge bot, whose App secrets are now in place. Co-Authored-By: Claude Opus 5 (1M context) --- TODO.md | 15 +++--- reports/Blog/audit.md | 116 ++++++++++++++++-------------------------- 2 files changed, 51 insertions(+), 80 deletions(-) diff --git a/TODO.md b/TODO.md index cee2399..d4df466 100644 --- a/TODO.md +++ b/TODO.md @@ -4,34 +4,31 @@ Running backlog for this repo, kept in a committed file so the work survives acr ## State -The site is built, gated, and proven against a real server. It is not yet on GitHub and not yet serving its public address. +The site is built and gated in CI. It is on GitHub, and it is not yet serving its public address. | Piece | State | | --- | --- | | Content and media | done. 514 pages, 778 media files hash-verified against the export tar | | URL contract | done. 328 render, 917 redirect, 778 legacy image URLs, all gated | | Deploy shape | done and proven against a running Caddy, on a local mirror | -| CI workflows | written and locally verified, never yet run on GitHub | -| GitHub repo | does not exist | +| 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 | | VPS | untouched | ## Blocked on the maintainer -- Create `ptr727/Blog` as a **public** repo, since an outward-facing write needs explicit per-repo permission. Everything below the first push depends on it, and nothing local can reveal the problems that only appear once CI runs for real. -- Install the GitHub App and set `CODEGEN_APP_CLIENT_ID` and `CODEGEN_APP_PRIVATE_KEY` in **both** the Actions and Dependabot stores. The App must be installed rather than only created, and `CODEGEN_APP_ID` must stay absent because it is a `forbids` and the deprecated input silently does nothing. - Install `shellcheck`, `shfmt`, `nodejs`, and `npm`, then `markdownlint-cli2` and `cspell`, so the lint gate can run locally instead of only in CI. Every one of them currently runs here through Docker, which works but is slower than it should be for an edit loop. +- Read the migration post before it ships, since it is written in the maintainer's voice and has not been reviewed. ## Next, in dependency order -- Push `main` and `develop`, then let the pull request workflow run **once** before any ruleset is applied. The `main` ruleset requires a check named `Check pull request workflow status job`, which binds by name and only reports after a run, so applying rulesets first deadlocks the first pull request. -- Run `repo-config/configure.sh apply ptr727/Blog release`, then `check` until it exits 0. Going public also enables Discussions, which `configure.sh` derives from visibility. -- Run `AUDIT.md` end to end and commit the result to `reports/Blog/audit.md`, recording the publish and release dimensions as deferred rather than passing. +- Dispatch `publish-release.yml` once to prove the release path, which exists but has never run. +- Decide on `merge-bot-pull-request.yml`. Dependabot is configured and its pull requests will otherwise sit open, and the App secrets it needs are now in place. - 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. - Write `deploy-site.yml` and prove it: a dry run that mutates nothing, then a real run, then a forced mid-deploy failure to confirm rollback keeps the site up. Report the measured deploy shape back to [ProjectTemplate#456][hub-issue], which is waiting on it before the publish type can be defined. - Deploy to a temporary production FQDN and validate there before touching the live record. Lower the `blog` A-record TTL to 60s a day ahead, then flip it to the VPS, unproxied. -- Read the migration post once more before it ships, since it is written in the maintainer's voice and has not been reviewed. - Watch server logs for 404s daily for the first week, because real traffic finds what the golden list missed. Append anything new to `checks/golden-urls.txt` and add a redirect. - 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. diff --git a/reports/Blog/audit.md b/reports/Blog/audit.md index ba5a091..86903f3 100644 --- a/reports/Blog/audit.md +++ b/reports/Blog/audit.md @@ -8,64 +8,71 @@ Self-audit of this repository against its own committed ground truth, per [AUDIT ## Verdict -**Not operational.** Two of the three dimensions cannot be checked at all, because the repository does not exist on GitHub yet. Nothing has been pushed, so there is no remote, no ruleset, no secret, and no environment. +**Operational.** Every applicable check passes against the live repository. -This is the expected state at this point rather than a failure, and it is recorded here rather than left in a session, per [`STANDUP.md` section 5][standup], which allows a repo to stand up with residual deltas tracked in a report plus an issue. The issue is [ProjectTemplate#456][hub-issue]. - -Every check that can run locally passes. The value of that is limited, and stating it plainly is the point of this report: local checks pass by construction because the same person wrote the checks and the thing being checked. Only a real CI run and a real deploy test the parts that matter, and neither has happened. +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. | Dimension | Result | | --- | --- | -| 1. Settings and rulesets | **Blocked.** No repository on GitHub | -| 2. Secrets | **Blocked.** No repository on GitHub | -| 3. The URL contract | **Pass** | +| 1. Settings and rulesets | **Pass.** `configure.sh check` exits 0 | +| 2. Secrets | **Pass.** Both required present in both stores, forbidden one absent | +| 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. See below | +| Publish and release | **Deferred**, deliberately | ## 1. Settings and Rulesets -Blocked. `repo-config/configure.sh check ptr727/Blog release` cannot run against a repository that does not exist. +**Pass.** + +```text +$ repo-config/configure.sh check ptr727/Blog release +... 31 assertions, all ok ... +Configuration matches on ptr727/Blog. +exit 0 +``` -What is verifiable locally is that the payloads are internally consistent and that the required check is bound by a name the workflow actually produces, which is the ordering trap that deadlocks a first pull request: +Both rulesets are active and carry every expected rule. `develop` allows squash only, `main` allows merge only, and both bind the required check by the same name the workflow produces: ```text -repo-config/main.json required_status_checks: ["Check pull request workflow status job"] -.github/workflows/test-pull-request.yml:26 name: Check pull request workflow status job +'develop' required checks = ["Check pull request workflow status job"] +'main' required checks = ["Check pull request workflow status job"] ``` -The two strings match. They are one string renamed together, never independently. +The ordering constraint was honored at standup: the workflow was dispatched once and reported before any ruleset was applied. Applying first would have deadlocked the first pull request, because the required check binds by name and only appears after a run. -The carried `develop` payload is the `release` variant (`repo-config/develop.json`), carrying `pull_request`, `required_status_checks`, `required_linear_history`, `copilot_code_review`, `deletion`, `non_fast_forward`, and `required_signatures`. The `operational/develop.json` variant is absent, which is correct for this workflow model. +`has_discussions = true`, derived by `configure.sh` from public visibility rather than from a committed setting. `default_branch = main`. Dependabot vulnerability alerts and automated security updates are enabled. ## 2. Secrets -Blocked. `gh secret list` cannot run against a repository that does not exist. +**Pass.** Names only. No secret value was read, printed, or logged. -The manifest in `spec/secrets.json` declares: +| Name | Actions | Dependabot | +| --- | --- | --- | +| `CODEGEN_APP_CLIENT_ID` | present | present | +| `CODEGEN_APP_PRIVATE_KEY` | present | present | +| `CODEGEN_APP_ID` (forbidden) | absent | absent | -- `requires`: `CODEGEN_APP_CLIENT_ID`, `CODEGEN_APP_PRIVATE_KEY`, in **both** the Actions and Dependabot stores -- `forbids`: `CODEGEN_APP_ID` +`CODEGEN_APP_ID` is forbidden because the App-token action takes `client-id`, and the deprecated `app-id` name silently does nothing. -No secret value has been read, printed, or logged. +The `staging` and `production` environments do not exist yet, which is correct: they hold deploy credentials for a VPS that has not been provisioned, and `AUDIT.md` places them outside the baseline audit. ## 3. The URL Contract -**Pass.** This is the only dimension with real evidence behind it, because it is the only one that does not need GitHub. +**Pass, and now enforced by CI rather than only locally**, which is the material change from the pre-standup state. -```text -$ hugo --gc --minify --panicOnWarning -Pages 514 | Total in 462 ms | zero warnings +From the first run on `main`: -$ checks/check-url-parity.py public +```text +hugo v0.164.0+extended (pinned by version and sha256) +Pages 514 | Total in 858 ms (zero warnings under --panicOnWarning) render : 328/328 golden URLs built - 207 additional URLs built (not a failure) media : 778/778 legacy image URLs resolve after the R8 rewrite assets : 1012/1012 local asset references resolve PASS - the built site honors the URL contract ``` -Contract sizes, matching the committed lists: 328 render, 917 redirect, 778 legacy media. +Every gate in the validation job passed on its first attempt: markdownlint, cspell, actionlint, `editorconfig-checker`, shellcheck, `shfmt -d`, config validation, the Hugo build, and the contract check. Floor assertions are present and below the real counts, so a truncated list fails rather than passing while covering nothing: @@ -73,61 +80,27 @@ Floor assertions are present and below the real counts, so a truncated list fail checks/check-live-urls.sh:18 FLOOR=(["golden-urls.txt"]=320 ["redirect-urls.txt"]=900) ``` -The 207 extra built URLs are the new post added this session plus its four new term archives and their pagination. An extra URL is reported and is not a failure. A missing one would be. - -**The live gate has not run this session.** `checks/check-live-urls.sh` is what proves the 917 redirects, and a redirect is the web server's job that no build can prove. It has previously passed against the local mirror for all 1,245 URLs, but that predates this session's changes and is not re-evidenced here. Treat the redirect half of the contract as asserted rather than currently proven. +**The live redirect gate has still not run.** `checks/check-live-urls.sh` is what proves the 917 redirects, and a redirect is the web server's job that no build can prove. It has passed against the local mirror for all 1,245 URLs, but not against this build and not from CI. The redirect half of the contract remains asserted rather than currently proven, and it stays that way until the VPS exists. ## Baseline File Presence **Pass, 23 of 23** applicable to `types: ["source-only"]` plus `workflowModel: release`. -`OPERATIONS.md` is retained although it left the required set when the workflow model changed from `operational` to `release`. It is accurate and useful, and carrying an extra file is not drift. +`OPERATIONS.md` is retained although it left the required set when the workflow model changed from `operational` to `release`. Carrying an extra file is not drift. ## Verbatim Fidelity -**Pass, 4 of 4**, compared after line-ending normalization as [`spec/fidelity-model.md`][fidelity] specifies: - -```text -.markdownlint-cli2.jsonc match -repo-config/configure.sh match -repo-config/main.json match -repo-config/develop.json match -``` - -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 to the hub as an onboarding trap, since nothing in the standup text says to normalize after carrying. - -## Lint and Prose - -Every gate the validation workflow runs was executed locally, through Docker, since the host lacks the tools: - -```text -actionlint clean -editorconfig-checker clean -markdownlint-cli2 (workflow globs) 0 issues in 14 files -cspell (README.md, HISTORY.md) 0 issues -shellcheck (default severity) clean -shfmt -d clean -``` - -Prose rules, using the hub's `scripts/prose_lint.py` against every file this repo authors: - -```text -charset, dupword, comment-wrap, comment-case 0 findings -dash, semicolon, charset-unknown 15 findings -``` - -The 15 remaining are backlog rules that report without gating, and the correct-as-you-next-edit rule leaves them alone until their file is touched: +**Pass, 4 of 4**, compared after line-ending normalization as [`spec/fidelity-model.md`][fidelity] specifies: `.markdownlint-cli2.jsonc`, `repo-config/configure.sh`, `repo-config/main.json`, `repo-config/develop.json`. -- `checks/README.md`, 14. Not edited this session. -- `layouts/rss.xml`, 1. **Kept deliberately.** The copyright sign is the argument to `strings.TrimPrefix "© "`, so it is a string literal that must match the character it strips, not typography. It is a functional deviation, not an unswept one. +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 -`publish` is empty and `releaseTrigger` is `none` in the proposed registry entry, deliberately. +`publish` is empty and `releaseTrigger` is `none`, deliberately. -This repository deploys a built site to a VPS over SSH, which is a release surface the fleet spec has no type for. The hub's position, which this repo follows, is to declare the deferral rather than hide it, and to report the measured shape after CI has run and a deploy has actually happened rather than predicting it. Tracked in [ProjectTemplate#456][hub-issue]. +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 VPS does not exist yet, 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. ## Deliberate Deviations @@ -140,10 +113,11 @@ Both are recorded in [AUDIT.md](../../AUDIT.md) and reported upstream, so neithe Carried forward rather than closed: -- The repository does not exist on GitHub. Dimensions 1 and 2 stay unverifiable until it does, and the first CI run is the first real test of the workflows. -- The live redirect gate has not run against this session's build. -- `merge-bot-pull-request.yml` is not carried. Dependabot is configured, so its pull requests will sit open until either the merge bot is added or they are merged by hand. The bot needs the App secrets, so it cannot be proven before those exist. -- `checks/README.md` carries 14 prose backlog findings. +- The live redirect gate has not run against this build, so 917 of the 1,245 contracted URLs are asserted rather than proven. +- `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: []`. +- `merge-bot-pull-request.yml` is not carried. Dependabot is configured and its pull requests will sit open until either the merge bot is added or they are merged by hand. The App secrets it needs are now in place, so this is no longer blocked, only undone. +- `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.