Skip to content

Promote develop to main: CI, public readiness, and the 1.0 release notes - #7

Merged
ptr727 merged 7 commits into
mainfrom
develop
Aug 1, 2026
Merged

Promote develop to main: CI, public readiness, and the 1.0 release notes#7
ptr727 merged 7 commits into
mainfrom
develop

Conversation

@ptr727

@ptr727 ptr727 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

First promotion. Carries everything from standing the repo up on GitHub through to a verified local deploy loop.

What lands on main

PR What
#2 The post-standup audit. Verdict moved to operational once the repo existed and the ruleset and secret dimensions became checkable
#3 The 1.0 release notes in README.md and HISTORY.md
#4 merge-bot-pull-request.yml, so Dependabot pull requests can merge. dependabot.yml was baseline without it
#5 The local verification loop, because CI proves the render half of the URL contract and cannot prove the other 917
#6 CADDY_APPDATA and CADDY_CONTAINER, and the redirect table re-derived against the config

The finding worth reading

#6 falsified a claim this repo had been operating on. Caddy expands import at config-parse time, for the site config and the map files, and does not watch them. Swapping the current symlink reverts content per request but not rules.

Proven on the mirror twice: a map entry present in the live release on disk answered 404 until the container restarted, then 301; and a rollback to that release did the same. So the rollback procedure, which said "no restart and no reload", would have reverted content while leaving it served by the current release's redirects, which is the exact mismatch that shipping config inside the bundle exists to prevent.

The verification loop was unsound for the same reason and now restarts before checking. The blog post carried the same wrong claim and is corrected.

Merge mechanics

  • Merge commit, not squash. The main ruleset allows only merge, and the merge commit preserves develop's commit list as a real second parent for release attribution.
  • Do not use --delete-branch. The head of this pull request is develop.

After this

publish-release.yml has never run. Dispatching it from main cuts the 1.0 tag via NBGV and is the last untested path in the pipeline.

ptr727 and others added 5 commits August 1, 2026 09:50
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) <noreply@anthropic.com>
* Add the 1.0 release notes to README and HISTORY

The repo is public and CI is green, so 1.0 is the first public release
and both files now say what it contains. `version.json` already declared
1.0, and NBGV computes the tag from it on a dispatch from `main`.

HISTORY.md replaces its placeholder with a real entry, keeping the fleet
shape of a version heading and a nested list. README carries the short
summary the README structure asks for, three bullets, with the full list
left to HISTORY.

Both state the thing a reader would otherwise assume wrongly: the site is
not yet serving its public address, so 1.0 is the source and its
pipeline, not the cutover. Releasing without saying that would read as
the migration being finished.

Adds "cutover" to cspell.json, since it now appears in the two files the
spell gate actually covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Move the version label's colon outside the bold text

The line read `**Version: 1.0**:`, with a colon inside the bold and
another after it, which renders as a double colon. The sibling line
`**Summary**:` already puts the colon outside, so the two were
inconsistent with each other as well.

Now `**Version**: 1.0`, matching the sibling and the plain `Version 1.0:`
form used in HISTORY.md.

Raised by Copilot on this pull request. The construction was inherited
from the hub's own README, which carries the same double colon, and
spec/readme-structure.md does not mandate that literal form, asking only
for the current version and a short summary. README.md is repo-owned, so
this is fixed here and reported upstream rather than worked around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`.github/dependabot.yml` is a baseline file for every fleet repo, and
`merge-bot-pull-request.yml` is what services what it opens. This repo
carried the first without the second, so bot pull requests had no way to
merge and one was already sitting open.

Two jobs are vendored: auto-merge on opened or reopened, and disable
auto-merge when a maintainer pushes to the bot's branch. The merge method
follows the base, squash for `develop` and a merge commit for `main`,
matching what each ruleset allows.

The codegen and upstream-version jobs are dropped. This repo has neither,
and a job keyed on a bot login that never opens a pull request here would
be dead code that reads as coverage.

Every tier auto-merges, semver-major included, because the required
checks are the gate rather than the size of the bump.

Comments are rewritten to one sentence per line. The upstream copy is
block-prose, which is the shape the comment rule forbids, and carrying it
verbatim would import a defect into a file this repo owns.

Clears the corresponding residual delta in `reports/Blog/audit.md` and
the open decision in `TODO.md`.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Document the local verification loop for content and server changes

CI proves the render half of the URL contract and cannot prove the other
917. A redirect is the web server's job, and the validation workflow has
no server to point at, so a change to the Caddy config or a generated map
is invisible to it. The workflow goes green while the redirect it broke
stays broken until someone follows an old link.

OPERATIONS.md now states which paths require a local release and live
check before a pull request is opened, and why each one does: the
Caddyfile because rule order is load-bearing and an over-matching regex
is silent, the maps because a regenerated one can lose entries and still
parse, content and static because a moved page turns a redirect
destination into a 404 the build gate does not follow, and hugo.yaml and
layouts because permalink changes move URLs underneath the redirects
pointing at them.

The loop was run rather than written from memory. Against the current
build it reports "PASS - 1245 URLs honored", which is the output quoted
in the new section.

That also settles a residual delta in the audit, which recorded the
redirect half as asserted rather than proven. It is now proven against a
running server, though only the local mirror and only by hand. CI still
cannot enforce it, and that stays true until staging exists, so the
report says so rather than claiming the gap is closed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Attribute the release and the check to the right scripts

Two corrections from review, both cases of the prose contradicting the
commands directly beneath it.

The audit said `check-live-urls.sh` releases to the local mirror. It does
not. `make-release.sh` installs the release and the check script only
verifies against a server that is already running, which is the whole
reason the two are separate steps.

OPERATIONS.md said neither command needs an argument, while the block
below it passed `"$HUGO_BASEURL"` to one of them. `make-release.sh` takes
no arguments because it reads `secrets/.env` itself. `check-live-urls.sh`
does take a base URL. The block now sources the file first, so the value
comes from the environment rather than being typed, which is what the
sentence was reaching for.

The corrected three-line block was run verbatim in one shell and reports
"PASS - 1245 URLs honored".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop the hard-coded rule counts from the verification table

The table said the Caddyfile holds "11 redirect rules" and the maps are
"5 lookup tables". The file carries 13 `redir` directives, so the count
was wrong, and it was incidental anyway: that cell exists to say why the
Caddyfile needs a running server, not to enumerate it.

Both counts are removed rather than corrected, since a number in prose
that has to track a config file drifts the moment either changes.

The same imprecision exists in `deploy/README.md`, which claims "11 regex
rules plus 5 map files". That file is not in this change, so it is
recorded in `TODO.md` for a proper re-derivation instead of being fixed
by guess.

Raised as a suppressed comment inside the collapsed review body, which
carries no thread. Worth noting because a loop that polls only unresolved
threads reports a clean pass while a finding like this stands, exactly as
the merge gate warns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Record the container appdata path and re-derive the redirect table

Two gaps, both surfaced by asking why `secrets/.env` carried only two
values.

**The appdata path was recorded nowhere.** The container reads three host
paths and a release writes only one. `$DEPLOY_ROOT` holds the bundle,
while the bootstrap Caddyfile and Caddy's persistent state live under a
separate appdata root that no script touches and nothing documented. The
bootstrap is outside the bundle deliberately, since rules held there
would leave a rolled-back site served by the current release's redirects,
but that also means no release refreshes it and a rebuild depended on
someone remembering where it goes. `CADDY_APPDATA` now records it in
`secrets/.env`, and OPERATIONS.md carries the install command and the
mount table. The value stays out of committed files, because this repo
names no host path.

The deployed bootstrap had drifted from the repo's copy, carrying the
pre-rewrite comments and a stale rule count. Functionally identical, the
`import` line matched exactly, so nothing was broken. Now byte-identical,
and the site was re-verified after the restart at 1245 URLs honored.

**The redirect table was wrong in more than its count.** It claimed 11
regex rules where the Caddyfile carries 13 `redir` directives reading 5
map files through 3 `map` blocks. Three destinations were also stale: the
date archives, their pagination, and the Blogger monthly archives were
all documented as redirecting to `/` when the config sends them to
`/all/`. `labels.map` and `terms.map` appeared nowhere.

The table is rebuilt against the config and keyed on the Caddy matcher
names rather than invented R numbers, so a reader can grep the Caddyfile
instead of trusting the doc. Every row was checked: the classes sum to
917 exactly, matching `redirect-urls.txt`, with the 778 legacy image URLs
called out as gated separately. `@label` is documented as deliberately
outside the contract, since that class is a soft 404 rather than a
redirect.

Clears the backlog item added when #5 dropped the count rather than
guessing at it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Require a restart after any config change, and name the container

Review caught a claim that was not just wrong but inverted, and testing
it against the running mirror found a second instance that was worse.

Caddy expands `import` at config-parse time, for the site config and for
the `map` blocks reading `maps/*.map`, and does not watch those files.
Only static file requests follow the `current` symlink per request.
Proven on the mirror: a map entry present in the live release on disk
answered 404 until the container restarted, then 301.

That makes the verification loop unsound as it shipped. Change a
redirect, release, check without reloading, and the check exercises the
previous rules, so a broken redirect reports PASS while the artifact is
broken. That is exactly the failure the loop exists to catch, so the
restart is now a step in it rather than a footnote.

The rollback procedure was the worse instance. It said "no restart and no
reload", which would revert the content while leaving the previous
release served by the current release's redirects, the precise mismatch
that shipping config inside the bundle exists to prevent. Also proven:
rolling back to a release containing the probe still answered 404 until
the restart, then 301.

The container name moves to `CADDY_CONTAINER` rather than being hardcoded
in the docs, since this repo names no host value in a committed file.

`deploy/env.example` now states the naming convention, which is that the
prefix names whatever owns the value rather than whatever reads it. HUGO_
is fixed by Hugo's own env mapping, DEPLOY_ is the release tooling that
writes the root and is also the CI variable for the SSH deploy, and
CADDY_ is container state a release never touches. The set was already
consistent under that rule and nothing said so.

Also clarifies the redirect table per review: the thirteen classes sum to
917 and `@uploads` is excluded deliberately, with each repeated `@mapped`
row naming its map file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Correct the stale rule count in the two places it survived

Re-deriving the count in deploy/README.md fixed one instance of it and
left two, because the number was never grepped for across the repo. Both
found by review.

OPERATIONS.md still opened its Redirects section with "eleven
regular-expression rules and five map files". It now states the verified
figures and points at deploy/README.md for the per-class breakdown, so
the two files stop restating each other and there is one place for a
count to go stale.

The blog post carried the same wrong figure, and a worse claim beside it:
that rolling back "cannot leave yesterday's site being served by today's
rules". That is exactly what a rollback does without a reload, as proven
on the mirror earlier in this branch. A published post giving that advice
would be actively misleading, so it now carries the catch, the way it was
found, and the general lesson that an atomic swap is only atomic for
whatever reads through it per request.

Verified after the change with the full documented loop, release then
restart then check: 1245 URLs honored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Qualify when a missing restart actually invalidates the check

The warning read as unconditional. It is not: a content-only release
leaves the Caddyfile and the maps untouched, so the rules Caddy already
holds are still the right ones and a check without a restart is honest.
The false pass is specific to a release that changed `deploy/Caddyfile`
or anything under `deploy/maps/`.

The step stays unconditional regardless, and now says why rather than
overstating the risk. Deciding correctly means knowing whether anything
reached the config, which is easy to get wrong when a change spans
several paths or a map was regenerated as a side effect, and getting it
wrong is silent because the wrong answer is a green check rather than an
error. A few seconds of restart is cheaper than that reasoning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Promotes the current develop state to main for the first public release, consolidating CI/workflow readiness, 1.0 release notes, and operational documentation around deploy/rollback verification for the blog’s URL contract.

Changes:

  • Document 1.0 release notes/history and update the audit/TODO status to reflect a now-live GitHub repo and green CI.
  • Update operational guidance to require container restarts when config/maps change (including rollback), and document the bootstrap/appdata layout.
  • Add a Dependabot auto-merge workflow and refresh redirect design documentation (matcher-based breakdown, map files, and counts).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
TODO.md Updates project state/backlog now that CI is green and the repo is public.
reports/Blog/audit.md Updates audit verdict to operational; records CI-backed URL contract evidence and residual deferrals.
README.md Adds 1.0 release notes summary to the repo README.
OPERATIONS.md Documents the pre-PR local verification loop and makes container restart part of deploy/rollback correctness.
HISTORY.md Replaces placeholder with a 1.0 release history entry.
deploy/README.md Updates redirect/table documentation to matcher-based classes and correct counts/maps.
deploy/env.example Expands env template with CADDY_APPDATA/CADDY_CONTAINER and clarifies variable intent.
cspell.json Adds cutover to the dictionary for release-note terminology.
content/posts/2026/08/01/moving-this-blog-from-wordpress-to-hugo.md Corrects the post to reflect redirect directive counts and the required restart behavior.
.github/workflows/merge-bot-pull-request.yml Adds a pull_request_target workflow to auto-merge Dependabot PRs and disable auto-merge on maintainer pushes.

Comment thread TODO.md Outdated
Comment thread reports/Blog/audit.md
* Retire the R numbering that the matcher rename orphaned

Re-deriving the redirect table replaced the R1 to R11 numbering with the
Caddy matcher names, but only in the table. Two references to the old
scheme survived elsewhere, pointing at labels that are now defined
nowhere in the repo. Both found by review on the promotion.

`checks/check-url-parity.py` printed "resolve after the R8 rewrite" and
named "the R8 rewrite prefix" in its failure text. That script is the
source of the line quoted in the audit, so it was the actual origin of
the orphan rather than a second copy of it. Both strings now name
`@uploads`, and the quoted output in `reports/Blog/audit.md` is updated
from a real run rather than edited to match.

`TODO.md` described an open decision as "a one-line change to rule R3",
which is now the `@date_archive` rule.

Matcher names were chosen precisely because they can be grepped against
the Caddyfile, and that only holds if nothing still refers to the
numbering they replaced.

Verified: 514 pages, 328/328 render, 778/778 media, 1012/1012 assets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop the date-archive open decision, which is already implemented

The item asked whether the legacy date archives should redirect to `/all/`
rather than `/`. The Caddyfile already does exactly that, in
`redir @date_archive /all/ 301`, and the redirect table documents the
class as landing on `/all/`.

So the entry was not an open decision but a record of one already made,
which is worse than no entry: it sends a reader hunting for a change that
is in effect, and it contradicts the config and the table.

Raised by review, which noticed the config and the backlog disagreed.
Rewording it to name `@date_archive` in the previous commit made the
contradiction easier to see, since it now pointed at the exact rule that
already behaved the way the item was asking for.

The remaining decisions were checked against the config for the same
defect. The `/robots.txt/` and `/osd.xml/` entry is genuinely open, since
both still resolve to `/` in `slugs.map`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 19:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (1)

OPERATIONS.md:58

  • In this section it says make-release.sh “takes no arguments”, but later in the same document ("Deploying") the canonical invocation passes [deploy-root] [version]. This is true only when DEPLOY_ROOT is set (e.g., via secrets/.env) and can be confusing for someone following these steps without that context.
Sourcing `secrets/.env` first puts the deploy root and the base URL in the environment, so no literal value is typed. `make-release.sh` then takes no arguments, and it refuses to install a release that fails the build gate. `check-live-urls.sh` does take a base URL, which is where the sourced `$HUGO_BASEURL` goes. It follows all 1,245 URLs against the running mirror, checking each redirect's destination rather than trusting its status code.

@ptr727

ptr727 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Answering the suppressed comment on 407219c, which carries no thread.

Accepted. The verification loop said make-release.sh "takes no arguments" while the Deploying section below passes a deploy root and a version, and read together those contradict. The first was only true because the local loop sources secrets/.env a few lines earlier, which the document never said.

Fixed on develop in #9, verified against the script rather than asserted:

ROOT="${1:-${DEPLOY_ROOT:-}}"
VERSION="${2:-$(date -u +%Y%m%d-%H%M%S)}"

The deploy root falls back to $DEPLOY_ROOT, the version to a timestamp, the script accepts both regardless, and an argument wins over the environment. The text now also says why CI passes them explicitly, which is so a pipeline run names the commit it built rather than the clock.

Since this promotion's head is develop, it will pick the change up once #9 merges, and will need a fresh review on the new head before it can pass the gate.

The verification loop said `make-release.sh` "takes no arguments" while
the Deploying section below it passes a deploy root and a version. Read
together those contradict, and the first is only true because the local
loop sources `secrets/.env` a few lines earlier.

Both forms are correct and the document never said why. It now states the
mechanism: the deploy root falls back to `$DEPLOY_ROOT` and the version
to a timestamp, the script accepts both regardless, and an argument wins
over the environment. It also says why CI passes them explicitly, which
is so a pipeline run names the commit it built rather than the clock.

Verified against the script rather than asserted:

    ROOT="${1:-${DEPLOY_ROOT:-}}"
    VERSION="${2:-$(date -u +%Y%m%d-%H%M%S)}"

Raised as a suppressed comment on the promotion, which carries no thread.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 20:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 6855ddb into main Aug 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants