Conversation
Bumps the actions-deps group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [softprops/action-gh-release](https://github.com/softprops/action-gh-release) and [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action). Updates `actions/checkout` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@9c091bb...3d3c42e) Updates `softprops/action-gh-release` from 3.0.1 to 3.0.2 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](softprops/action-gh-release@718ea10...3d0d988) Updates `DavidAnson/markdownlint-cli2-action` from 24.0.0 to 24.1.0 - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](DavidAnson/markdownlint-cli2-action@8de2aa0...6bf21b0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: softprops/action-gh-release dependency-version: 3.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 24.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Re-vendor the eight verbatim carries the hub advanced past The hub's first audit of this repo (ProjectTemplate#547) found eight verbatim units behind the canonical at hub main `3a7cc64`. Six are stale copies and two never arrived at all, and the distinction matters, because a stale copy still states the rule in an older form while an absent one states nothing. ## Absent, so this is the first time the rule lands here - `AGENTS.md > Fleet Bootstrap` says where the canonical rules live and routes by the state this repository is actually in. Without it, an agent starting here has nothing telling it the hub exists. - `GOVERNANCE.md > Representative Data in Agent-Authored Text` is the rule that agent-authored text illustrates with constructed data rather than with data observed in the maintainer's environment. ## Stale, so the rule was here in an older form - `AGENTS.md > Context and Delegation Discipline` gains the rule that a wait separates three outcomes and says which one it reached. - `AGENTS.md > Where the Rules Live` gains the row routing to the representative-data rule above. - `GOVERNANCE.md > Git and Commit Rules` gains "Commit means commit and push". - `GOVERNANCE.md > Verification Discipline` gains "A launched process is not a result, and a cause nobody observed is not a diagnosis". - `GOVERNANCE.md > PR Review Etiquette` gains the whole "Every Finding Ends in an Action" subsection, so the review loop arrives with the five outcomes that close a finding rather than without them. - `repo-config/configure.sh` takes the payload-driven check mode. The copy here compared the merge methods and the required-status-check contexts by name; the canonical compares every parameterized rule's whole parameters object in both directions, so a rule added live that no payload declares is now caught. TODO.md says not to hand-edit `configure.sh` or the ruleset payloads. Re-vendoring from the hub canonical is the sanctioned exception and the reason that rule exists. The two ruleset payloads are deliberately not touched: ProjectTemplate#545 changed them an hour ago on the hub's `develop`, and ground truth is the hub's `main`, so that one converges when it promotes. The intro line is adjusted from two things to four, because the file now holds the bootstrap as well. ## Verification - Every verbatim unit byte-matches the canonical after LF normalization: the three `AGENTS.md` sections, the eighteen `GOVERNANCE.md` sections, `configure.sh`, and `.markdownlint-cli2.jsonc`. The only two that differ are `Devcontainer` and `Repository Layout`, both intent fidelity. - `repo-config/configure.sh check ptr727/Blog release` exits 0 under the new stricter check mode, so the live rulesets and settings still conform, which is the check that would have caught a bad re-vendor. - markdownlint, editorconfig-checker, and the JSON and prose gates are clean, and all 20 rows of the rules map resolve to a carried section except the one the table itself marks hub-only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * State the hub ref the carry now matches The report named `6501479`, the ref the baseline was carried from. The re-vendor in this branch moves every verbatim unit to `3a7cc64`, so the line named a ref the tree no longer matched the moment the rest of this change landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) The hub promoted `develop` to `main` as `7194f91` while the previous two convergence pull requests were open, so the ground truth this repo is measured against moved under them. ProjectTemplate#545 takes `bypass_actors` out of all three payloads, and rewrites `configure.sh` around that decision. The rewrite is the substantive half. Apply now reads the live bypass list and writes it back unchanged, because a PUT replaces the whole document and omitting the field would delete the list rather than leave it alone, and it aborts rather than proceeding if that read fails. Check reports the live list and asserts nothing about it. Who may bypass a ruleset decides who the rules do not apply to, which is a decision for the maintainer rather than a value the fleet config hands out. ## What this deliberately does not do Remove the live bypass. Both rulesets here carry the `RepositoryRole` admin entry the old payload granted every repo, and nothing in this change touches it. Removing it is a protected-branch settings write and a decision the hub just declared human, so it is the maintainer's. Worth knowing that the two tools now disagree about that state. `configure.sh check ptr727/Blog release` exits 0 and reports the bypass as unmanaged, while `spec/audit.py` still compares `bypass_actors` and calls the same state a DEFECT on both branches. Raised as ProjectTemplate#554, since it makes every fleet repo carrying a bypass permanently not operational on a field the fleet decided not to manage. ## Verification - All four verbatim units byte-match hub `main` `7194f91` after LF normalization: the three re-vendored here plus `.markdownlint-cli2.jsonc`. - `repo-config/configure.sh check ptr727/Blog release` exits 0 under the rewritten check mode, and reports "bypass list: RepositoryRole 5 always (not managed by this script)" on both rulesets rather than asserting it. - The JSON and editorconfig gates are clean. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Put the repo's own conventions where the section model sends them `AGENTS.md > Project Conventions` was a section the hub does not declare, which the audit flags as drift to reconcile. Reconciling it found that most of it was already reconciled elsewhere. Three of the four rules were near-verbatim duplicates of entries already in `TODO.md` Traps: never populating media over HTTP, `content/` being an imported archive, and a gate proving itself by failing. Duplicated rules are worse than absent ones, because the two copies drift and nothing says which is current, so those three are deleted here rather than moved. The fourth was unique and is the load-bearing one. The URL contract being ground truth and its lists being append-only appeared nowhere else, and in particular not in `OPERATIONS.md`, which operates that contract through the whole local-verification and deploy path without ever stating the invariant behind it. It moves there, ahead of the verification section that depends on it, and picks up the length-floor clause that `TODO.md` carried separately, since a floor is what makes append-only checkable rather than aspirational. `AGENTS.md` is left carrying only the three sections the hub declares, and its intro drops from four things to three, which is what the hub's own intro says. Note this does not resolve ProjectTemplate#552. That contradiction is about the byte-locked `Fleet Bootstrap` section naming the template repo, which is untouched here and still flagged. ## Verification - Every verbatim unit still byte-matches the canonical, so removing the local section disturbed none of the carried ones. - markdownlint, editorconfig-checker, and the prose gate are clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Point at the doc that owns a convention, now that one moved The preamble said this project's own conventions live "here", which was true while `Project Conventions` was in this file and stopped being true in the same change that moved it out. An agent reading the sentence would look for repo invariants in `AGENTS.md` and find none. The sentence's actual point is provider independence, that a rule a reviewer must honor cannot live in the Copilot-specific file, and that point survives intact. It now names the topical docs that own the content instead of claiming this file does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Record the hub conformance state and what is open against the hub This repo reconverged with the fleet hub on 2026-08-03, and four findings are open at the hub afterwards. None is work this repo can do, and each changes what a fleet audit of this repo means, so a reader who runs one and sees two DEFECTs needs the reason in the repo rather than only in an issue tracker. The State table gains a fleet-conformance row. A new section records where conformance stands, links the hub registry entry and the audit report the hub authored, and lists the four open issues with what each means here rather than restating the issue. It also records that the live ruleset bypass is deliberate and stays. The owner is automatically an admin and holds that capability anyway, so the entry grants nothing new, and the payloads stopped declaring it because code should not be in the business of granting a bypass at all. That reasoning existed only in conversation, which is exactly the kind of thing a later audit misreads as drift. ## Two traps The carried-file trap gains its second half: a reviewer finding a real defect in a verbatim file is answered by declining locally and filing it at the hub, never by editing the file. That happened this session on a correct jq portability finding, and editing would have broken the fleet audit that governs the file. The new one is that the hub's `main` can promote while a convergence pull request is open, so ground truth moves underneath work that was correct when it started. It happened twice in one session, and the second time added drift the branch could not have known about. The defense is to re-run the audit against the hub ref actually carried and name that ref in the change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Name the hub ref this record was measured against, and pin to it The section claimed a byte-match against the canonical, linked the hub by `main`, and then two entries below warned that `main` moves under work that was correct when it started. A record that cannot say which ref it describes is the failure its own trap names. It now states hub `main` `3b802b9`, and both hub links are pinned to that ref rather than to `main`, so the claim stays checkable after the hub moves again. Re-measuring for the ref turned up one exception worth recording rather than rounding away. Every verbatim section, both ruleset payloads, and `.markdownlint-cli2.jsonc` match. `repo-config/configure.sh` is one commit behind, on the change that fixes the jq portability defect this repo reported, so the re-vendor closing that loop is still owed. Found by a Copilot review that read the claim against the trap two entries below it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Pin the hub ref by full SHA, since a short one can go ambiguous GitHub resolves an abbreviated SHA only while it stays unique, and a `blob/<ref>/` link built on one breaks when the hub's history grows enough to collide. That is a record designed to expire, in the section whose whole point is staying checkable after the hub moves. All three references take the full 40 characters, the two link definitions and the ref named in the prose. Found by a suppressed low-confidence Copilot finding, the fourth this session that was correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) * Address two environments instead of one The release tooling could name a single deploy root, which was enough while there was one mirror and will not be once the VPS grows a staging site and a production site. Nothing here could address two at a time. The blocker was not the obvious one. make-release.sh sourced secrets/.env with `set -a`, which exports every assignment in the file and overwrites a DEPLOY_ROOT the caller exported first, so sourcing a different environment file silently deployed to the original root. On a host serving two sites that failure is not an error, it publishes to the other site. ENV_FILE now selects the file, the first argument still overrides the root because it is read afterwards, and a named file that does not exist is a hard failure rather than a fall-through to whatever the ambient environment holds. check-live-urls.sh could not authenticate, so it could not check a site that keeps its auth gate on. Staging keeps its gate on deliberately: it serves a byte-identical copy of the public site, and an open one is a duplicate handed to every crawler, which is the one thing a migration about preserving URLs should not do. A Pangolin resource access token opens it instead. The credential travels in a mode-600 curl config file rather than in -H arguments. A command line is readable in ps for the life of the process and this runs 1,245 of them, and the config file is also the only form that survives the `export -f` the parallel checks run under, since bash cannot export an array. It is sent to the base URL's own origin and nowhere else, so a rule that one day redirects off-site cannot carry it away. A preflight request runs first, because behind an auth gate a wrong token fails every URL and the output then reads as a site that has vanished rather than as a bad credential. Two decisions the docs asserted the opposite of, both now recorded with their reasoning rather than silently reversed. One deploy key covers both environments instead of one per environment: the split pays off only where the two keys never share a machine, and both sit on one workstation and in one secret store. The cost is that the rrsync forced command can no longer separate the environments, so the deploy roots move under a common parent and one pinned command covers both. And staging takes the VPS wildcard at blog.vps.insanegenius.net, which needs no new certificate and closes the open FQDN decision. Verified against both local mirrors, which are two containers in a sibling repository's new blog stack: 1,245 URLs honored on each, each baking its own base URL, neither disturbing the other's release history. Each new gate was demonstrated failing before being trusted, per the rule in TODO.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Deploy to an environment and prove the release is the one answering A release reaches a host by rsync and no restart, so nothing outside the verifier observes whether the rules that answer are the rules shipped. Content follows the current symlink per request and is live immediately, while rules wait on an in-process config reload. A container that never reloads therefore serves new content under a previous release's redirects, and the URL contract passes against a config that was never deployed. The bundle stamps its own version, substituted at install time, and the check compares it before requesting any of the 1,245 URLs. It waits rather than sampling once, because the reload is asynchronous and a check run straight after a deploy races it. The timeout still catches a container that never converges. Caddy also stops watching permanently after one failed config load, without logging that it has given up, so anything that breaks the symlink even briefly ends that container's ability to pick up releases until it is restarted. The failure reports as healthy, which is why the verifier names both causes. Two more headers identify the environment, since every environment serves one bundle on one port and only the container distinguishes them. Robots defaults to the value that is harmless on production, because the failures are asymmetric: an unset staging container is still behind its auth gate, where production inheriting noindex would deindex the site silently. trusted_proxies takes its ranges from the container, and excludes the bridge gateway. Trusting the subnet trusts the host, which was verified by forging a client address from it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Anchor ENV_FILE under the repo and correct the restart note A relative ENV_FILE resolved against the caller's working directory while the default resolved against the repo, so the same name meant different files depending on where the script ran. The container comment described a restart as the way a config change goes live, which the in-process reload replaced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Scope the staging-only secrets and correct the environment prose The manifest listed the access token beside a credential every environment carries, which reads as required in both. A name audit would then report it missing from production, where it is deliberately absent. The README described configuration as coming from one file, which the per-environment selection replaced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Assert the release placeholder and constrain the environment name sed reports success when it matches nothing, so a Caddyfile that lost the placeholder shipped unstamped and the live check then blamed a dead config watcher for a bundle that never carried a release id. The environment name selects a GitHub Environment and lands in a remote path, and a workflow_call caller is not bound by the dispatch choice list. It is asserted in its own job, because the environment binding resolves before any step in the deploying job runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Record the reviewer login trap that reads as a clean review Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Describe the accepted statuses, the env file, and the default name The redirect failure named one status while the check accepted two, and the usage text described a deploy root the environment file selection replaced. The setup instruction named only the per-environment form, so a single environment produced a file nothing reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Require an origin boundary before sending the access token The base URL has its trailing slash stripped, so a prefix test also matched a host that merely starts with it. A redirect to a lookalike registered as an attacker's subdomain would have received the credential. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Constrain the release version to a path-safe and substitution-safe form The value becomes a directory name, a symlink target, and a sed replacement, so a separator, a traversal, or a substitution metacharacter would place the release outside releases/ or corrupt the release stamp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Record the deploy scripts' Linux dependency ceiling The scripts use GNU and bash 4.4 constructs that a stock macOS lacks, and the platform rule requires a narrowed platform to be recorded so it reads as a dependency ceiling rather than an oversight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Report an unreachable host as transport rather than as a status A name that does not resolve produced a status code, which the preflight then explained as a bad credential or a broken symlink. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
* Point the hub debt at the issue carrying the proposed wording The intake thread holds the questions and their answers, and the change itself needs an issue that can be worked without reconstructing which framing stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Name the repository in the hub issue reference The reference sat beside a spelled-out one in the same sentence, so the bare form read as a local issue number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Record the review tooling the hub already authors The review loop was hand-rolled, which re-discovered two failures the hub script exists to prevent: findings suppressed into a review body reach no thread, and a request nothing picks up is indistinguishable from a slow reviewer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drop the counted cross-reference from the tooling note A count of the bullets that follow it is wrong as soon as one is added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Promotes the deploy pipeline and related governance/conformance updates from develop to main, making the site deploy runnable from the default branch while tightening release/deploy verification and updating carried workflow contract documentation.
Changes:
- Adds GitHub Actions deploy workflows (
deploy-site.ymldispatch + reusabledeploy-site-task.yml) that build, rsync, flipcurrent, then verify the live URL contract against the running host. - Strengthens deploy verification by stamping releases into the served config (
X-Blog-Release) and extendingchecks/check-live-urls.shwith preflight checks (auth-gate support, environment assertion, and release-id convergence wait). - Updates governance/docs/audit artifacts and ruleset tooling to reflect hub conformance state and “hands off” bypass actor management.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| WORKFLOW.md | Updates workflow contract prose for D2.2 smoke-build behavior. |
| TODO.md | Updates project status, hub-debt links, and conformance/traps documentation. |
| spec/secrets.json | Documents env secret expectations, including staging-only Pangolin token pair. |
| reports/Blog/audit.md | Updates self-audit to reflect proven release vs deferred VPS deploy. |
| repo-config/main.json | Removes committed bypass_actors payload content (now preserved from live). |
| repo-config/develop.json | Removes committed bypass_actors payload content (now preserved from live). |
| repo-config/configure.sh | Preserves live bypass list on apply; strengthens drift checks for rulesets/settings. |
| README.md | Refreshes repo description/links/badges and clarifies environment-file usage. |
| OPERATIONS.md | Expands environment model and deploy verification rationale (watch reload, auth gate, etc.). |
| HISTORY.md | Adjusts release-history wording for v1.0 description. |
| GOVERNANCE.md | Adds “Representative Data” rule section and strengthens PR-review loop contract language. |
| deploy/README.md | Documents Linux ceiling, environment-file selection, watch reload contract, and headers. |
| deploy/make-release.sh | Adds ENV_FILE selection, version validation, and stamps @@RELEASE@@ into shipped Caddyfile. |
| deploy/env.example | Updates env template for multi-environment usage and auth-gate verification inputs. |
| deploy/Caddyfile | Adds trusted_proxies handling and stamps env/robots/release headers. |
| checks/check-live-urls.sh | Adds auth-gate token support + preflight env/release assertions and reload wait. |
| AGENTS.md | Adds fleet bootstrap section and updates session-scope guidance. |
| .github/workflows/validate-task.yml | Bumps pinned action SHAs (checkout, markdownlint). |
| .github/workflows/publish-release.yml | Bumps pinned action SHAs (checkout, gh-release). |
| .github/workflows/merge-bot-pull-request.yml | Comment wording tweak for concurrency behavior. |
| .github/workflows/deploy-site.yml | Adds dispatch entrypoint workflow to deploy staging/production with gating. |
| .github/workflows/deploy-site-task.yml | Adds reusable deploy workflow: build bundle, rsync upload/flip, verify live contract. |
Uh oh!
There was an error while loading. Please reload this page.
* Separate a transport failure from a missing header while waiting The reload wait read an unreachable host as a reply carrying no release, so a connection lost mid-wait reported as a config that never reloaded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep a missing header from reporting as an unreachable host Pipefail carries grep's no-match status out of the read, so a reachable host serving no release header reported as a lost connection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
.github/workflows/deploy-site.yml:26
- The
assert-refjob is documented as running before Hugo is installed, butvalidate-task.ymlinstalls Hugo andvalidatecurrently runs in parallel. For a mis-dispatched production deploy (non-main ref), the workflow will still run the full validation (including Hugo install/build) before failing, which is slower and contradicts the comment.
Make validate depend on assert-ref so the branch/environment gate fails fast before any expensive validation runs.
# Staging deploys from any ref, since proving a branch before it merges is what staging is for.
# Asserted before Hugo is installed and before the key reaches the runner.
assert-ref:
.github/workflows/deploy-site-task.yml:121
- The deploy upload step forces directory permissions to
D755, butdeploy/make-release.shintentionally uses--chmod=D2755to preserve the setgid bit so the group propagates under the first directory rsync creates. UsingD755here can clear setgid on the remote release tree and make the on-host permission model diverge from what the bundle was built/tested with.
Use the same chmod policy as the bundle assembly (D2755,F644) and keep --no-g so the destination group is assigned by the setgid parent.
rsync -az --mkpath --chmod=D755,F644 \
--link-dest="/${ENVIRONMENT}/current/" \
-e "ssh -i ~/.ssh/deploy -o IdentitiesOnly=yes" \
"${RUNNER_TEMP}/bundle/releases/${RELEASE_ID}/" \
"${DEPLOY_SSH_USER}@${DEPLOY_SSH_HOST}:/${ENVIRONMENT}/releases/${RELEASE_ID}/"
Uh oh!
There was an error while loading. Please reload this page.
…ode (#21) The ref assertion ran beside validation rather than before it, so a mis-dispatched production deploy installed a toolchain and built a site before refusing. The upload cleared the setgid bit the bundle is assembled with, so the group would stop propagating below the first directory the transfer creates. A relative environment file is refused rather than resolved when it traverses, since a relative name is meant to reach the secrets directory. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
commented
Aug 4, 2026
The two suppressed findings from the last round are fixed on develop in 6b2c739, via #21, so this promotion carries them. Job ordering — Directory mode — the upload used Also fixed the thread finding on |
Uh oh!
There was an error while loading. Please reload this page.
Promotes nine commits, spanning two sessions. The merge method is a merge commit, per the
mainruleset.What lands
The deploy pipeline (#16). Two environments addressed by one artifact, shipped to a host by rsync with no restart. The bundle stamps its own release id, and the verifier asserts which release is answering before checking the URL contract, because a deploy is asynchronous and the transport succeeding proves nothing about what the host serves. Also
X-Blog-EnvandX-Robots-Tagfrom container variables,trusted_proxieswith the bridge gateway excluded, and auth-gate support for a non-public environment.The workflow contract (#17, #18). The carried
WORKFLOW.mdhad drifted one revision behind the hub and is re-vendored. The deploy has no destination in that contract, which is recorded as debt and filed upstream as ProjectTemplate#558 with proposed wording. It is a variant of the existing registry-push leaf rather than a new release surface, so the release and branching models are unchanged.Earlier session work (#10 through #15). Verbatim carries re-vendored, doc corrections, the conventions move, hub conformance state, and a Dependabot bump.
Why this matters beyond the diff
deploy-site.ymlisworkflow_dispatch, and GitHub only registers a dispatch workflow from the default branch. Until this promotion lands, the deploy pipeline exists ondevelopbut cannot be triggered at all.Verification
PASS - 1245 URLs honored, deploying with no restart.20260804-190007live behind the auth gate, pushed through the confined key, full contract verified.1.0.17-g4b2def3ee9cut fromdevelop, conforming to D4.1 through D4.3.main, and every changed file is LF, matching the repo's recordedlineEndingsdeviation.🤖 Generated with Claude Code