a release is a tag, and the lock that stops a second deploy is local
Three changes, all from the owner's call.
The trigger is a tag, deploy/<version>, and nothing else. The code word it replaces could be written by
accident — every document about deploying contains it, this repository included, and so does every
conversation planning one. Defending that needs a rule about where in the subject line it sits, and a
rule like that is one somebody eventually gets wrong. A tag cannot be typed into prose, it names its own
version, and git ls-remote answers "is there one" without fetching.
The record of a deploy is LOCAL — data/deploys/<version>.done — not the pushed report. This is the part
that matters: making the report the marker means a failed push leaves the release looking undeployed and
the next tick rolls it out again. Pushes here fail for reasons unrelated to the rollout — gitea's public
SSH is rate-limited because every connection arrives from one proxy container IP that bots keep
penalised, which cost several retries tonight. So the lock cannot fail for a network reason, and the
report is an artefact: valuable, not load-bearing. Under data/ so a pull cannot clobber it.
And the commander skips the host it runs on ENTIRELY, naming it in the report. Restarting only officer
and leaving its own sidecar behind would work and is worse — one host in a state nobody chose, a step
behind, found later. The owner restarts it deliberately as the last step.
Tonight's deploy is backfilled: the lock file exists and deploy/2026.08.21.01 is tagged at bd41dff5, so
the mechanism has a precedent rather than starting from nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+8
-2
@@ -11,7 +11,13 @@ deploy/
|
||||
The plan is written by hand before the release commit. The report is written by the deploy commander
|
||||
afterwards and pushed back here.
|
||||
|
||||
**The report's presence is what marks a release deployed.** That is the whole idempotence mechanism: a
|
||||
watcher that restarts checks for the report before acting, so it cannot roll the same release out twice.
|
||||
**A release is a tag**, `deploy/<version>`, and nothing else triggers a deploy — not a commit message,
|
||||
not a branch. A tag cannot be written into prose by accident, which a code word can and which every
|
||||
document about deploying does.
|
||||
|
||||
**What marks a release deployed is LOCAL**: `$OFFICER_ROOT/data/deploys/<version>.done`. Not the report.
|
||||
If the lock were the pushed report, a failed push would mean the release looked undeployed and the next
|
||||
tick would roll it out again — and pushes here fail for unrelated reasons. So the report is an artefact,
|
||||
valuable and not load-bearing.
|
||||
|
||||
See `prompts/deploy-commander.md`.
|
||||
|
||||
+57
-16
@@ -15,30 +15,52 @@ You are not a loop. Something fires you, and each time you fire you do exactly o
|
||||
there is a release to deploy that has not been deployed.
|
||||
|
||||
```bash
|
||||
cd ~/officer-dev/platform && git fetch --quiet origin HEAD
|
||||
git log --oneline HEAD..FETCH_HEAD | grep DEPLOY_NOW
|
||||
git ls-remote --tags origin 'refs/tags/deploy/*'
|
||||
```
|
||||
|
||||
- **Nothing matching** → say "no release pending" and stop. Do not pull, do not touch a server.
|
||||
- **A commit whose subject begins `DEPLOY_NOW`** → that is the release. Continue.
|
||||
A release is **a tag**, named `deploy/<version>` — `deploy/2026.08.21.01`. Nothing else triggers you: not
|
||||
a commit message, not a branch, not a push.
|
||||
|
||||
`DEPLOY_NOW` must be at the **start of the subject line**, not merely present. This document contains the
|
||||
word repeatedly and must never trigger a deploy; so must any commit that discusses one.
|
||||
**Why a tag and not a code word in a commit.** A code word can be written by accident. Every document
|
||||
about deploying contains it, this file included, and so does every conversation planning one — so the
|
||||
trigger has to be defended with rules about where in the subject line it sits, and a rule like that is
|
||||
one somebody eventually gets wrong. A tag cannot be typed into prose. It is created deliberately, it
|
||||
names its own version, and `git ls-remote` answers "is there one" in a single call without fetching.
|
||||
|
||||
**Already deployed?** A release is deployed when `deploy/<version>.report.md` exists on `origin`. Check
|
||||
before acting. A watcher that restarts must not redeploy, and this is the only thing standing between it
|
||||
and doing so.
|
||||
- **No untagged release** → say "no release pending" and stop. Do not pull, do not touch a server.
|
||||
- **A `deploy/*` tag with no local record** → that is the release. Continue.
|
||||
|
||||
---
|
||||
### The record is LOCAL, not in git
|
||||
|
||||
```
|
||||
$OFFICER_ROOT/data/deploys/<version>.done
|
||||
```
|
||||
|
||||
That file's existence means deployed. Written after the rollout, before the report is pushed.
|
||||
|
||||
**This is deliberate and it is the important part.** The obvious design makes the pushed report the
|
||||
marker — and then a failed `git push` means the release looks undeployed and the next tick **does it all
|
||||
again**. Pushes here are genuinely unreliable: gitea's public SSH is rate-limited because every
|
||||
connection arrives from one proxy container IP and internet bots get it penalised, which cost several
|
||||
retries on 2026-08-21.
|
||||
|
||||
So the lock is local and cannot fail for a network reason. The report is an ARTEFACT — valuable, and not
|
||||
load-bearing. A missing report is a missing record; a missing lock is a second deploy.
|
||||
|
||||
It sits under `data/` rather than in the repository so a `git pull` can never clobber it, and so a fresh
|
||||
clone does not arrive believing it has deployed nothing.
|
||||
|
||||
## The release
|
||||
|
||||
The triggering commit carries everything:
|
||||
The tag names the version. The commit it points at carries the rest:
|
||||
|
||||
- `package.json` — `version` bumped to `yyyy.mm.dd.NN` (`2026.08.21.01`), the mobile apps' scheme. `NN`
|
||||
increments for a second deploy on the same day.
|
||||
- `package.json` — `version` matching the tag, `yyyy.mm.dd.NN` (`2026.08.21.01`), the mobile apps'
|
||||
scheme. `NN` increments for a second deploy on the same day.
|
||||
- `deploy/<version>.md` — **the plan**. What to run, in order, and on which hosts. Written by hand.
|
||||
|
||||
If the tag and `package.json` disagree, **stop and say so**. One of them is a mistake and guessing which
|
||||
is how the wrong tree gets deployed.
|
||||
|
||||
If the plan file is missing, **stop and say so**. A release with no plan is not a release you can guess
|
||||
at, and guessing is how a database gets a schema it was not ready for.
|
||||
|
||||
@@ -96,13 +118,32 @@ assemble at the end from memory is a report that will be wrong about the host th
|
||||
If a host fails, **continue to the next one**. A half-rolled-out release that is honestly reported beats
|
||||
one that stopped in the middle and said nothing about the four hosts it never reached.
|
||||
|
||||
### Skip the host you are running on
|
||||
|
||||
Entirely. Do not pull it, do not restart anything on it, and **name it in the report as skipped**.
|
||||
|
||||
You are a session inside `officer-claude-code` on that machine. Restarting it ends you mid-rollout —
|
||||
which is not a hypothetical: on 2026-08-21 a manual deploy reached its own host last and had to leave
|
||||
that one sidecar alone to survive writing its own report.
|
||||
|
||||
Restarting only `officer` and leaving your own sidecar behind would work, and it is worse: it puts one
|
||||
host in a state nobody chose, a step behind the others, discovered later. So skip the whole host and say
|
||||
which one. The owner restarts it deliberately, as the last step, once they are happy with the rest.
|
||||
|
||||
Find it with `hostname` and match it against the list — do not assume you are on any particular one.
|
||||
|
||||
---
|
||||
|
||||
## The report
|
||||
|
||||
Commit `deploy/<version>.report.md` to the platform repository and push it. Subject line: the version and
|
||||
the outcome, **without the code word** — a report is not a release, and a report that triggers a deploy
|
||||
would loop forever.
|
||||
Write the local record FIRST — `$OFFICER_ROOT/data/deploys/<version>.done` — then commit
|
||||
`deploy/<version>.report.md` and push it.
|
||||
|
||||
That order matters. The record is what stops a second deploy, and the push is the part that can fail for
|
||||
reasons having nothing to do with the rollout. **If the push fails, say so and stop — do not retry into a
|
||||
loop and do not treat it as a failed deploy.** The deploy succeeded; its paperwork is pending.
|
||||
|
||||
Never tag the report. A tag is the trigger, and a report that tags itself deploys itself forever.
|
||||
|
||||
Per host: what its HEAD was before and after, which steps ran, which were skipped and why, which
|
||||
processes restarted, and whether they came back. Then one line at the top saying how many hosts
|
||||
|
||||
Reference in New Issue
Block a user