Files
atheos-blog-lxc/docs/writer-branch-workflow.md
hermes c57aff3c33 docs: branch protection is a recommendation, not a blocker (operator decision)
The operator has decided not to enable branch protection on armistace/blog while the editor
is LAN-only:

  "leave it as a recommendation - I won't do that unless I take this out to the wider internet"

That is a reasonable call, so this records it as an ACCEPTED RISK rather than an open task.

Test behaviour changed to match. The suite still attempts a direct push to master with the
deploy key on EVERY run - the check is not removed, because that is what would catch the
setting being lost or never applied. But when protection is off it now reports
  ACCEPTED-RISK  master is NOT protected - the deploy key CAN push master directly
instead of FAIL, so the suite is green and CI-usable (it previously exited 1, meaning a
genuine regression would have been buried in expected noise).

The run flips back to "PASS master is protected" by itself the moment protection is enabled,
so the recommendation self-cancels with nobody having to remember it. Summary line now reads
  RESULT: 44 passed, 0 failed, 1 accepted-risk

Docs record WHY the risk is acceptable (the compensating control is reachability, not
immutability - nothing off-LAN can reach the editor, and the only LAN user is the operator)
and the two triggers that turn it back into a real finding:
  1. the editor becomes reachable off-LAN (NPM proxy, port-forward, VPN route)
  2. anyone other than the operator gets access
Written down because an accepted risk is only acceptable if it is explicit and has a
revisit trigger - otherwise it just looks like an oversight later.
2026-09-29 16:40:49 +10:00

153 lines
7.2 KiB
Markdown

# Writer-Branch Workflow
How writing and publishing work from the Atheos editor, and why the branch is structured this way.
## The model
The editor **never touches `master`**. It writes to a long-lived branch (`atheos-writer`), and
merging into `master` is done by the operator in Gitea.
```
┌──────────────────────────────────────────┐
Atheos editor ────┤ atheos-writer (long-lived) │
(macros) │ • write here │
│ • commit + push here │
│ • can be reset --hard back to master │
└───────────────┬──────────────────────────┘
│ operator opens a PR in Gitea
│ operator reviews
▼
master (protected: RECOMMENDED, currently OFF - see §2)
│
▼
existing Pelican build pipeline
```
**Why:** the editor is a web app with shell-exec reach (`codegit` + `macro` both run commands as the
php-fpm user). Bounding what it can reach is the whole point of design decision D6. Keeping `master`
behind a PR means a compromised or fat-fingered editor **cannot** publish directly — it can only
propose.
## Setup — do these once
### 1. Set your git identity (required, RUNBOOK STEP 1)
```sh
ssh root@192.168.178.57
su -s /bin/sh atheos
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
```
This is the *same* global gitconfig php-fpm uses (`HOME=/home/atheos` — verified), so macros commit
as **you**. If it is not set, macro **2** refuses with an actionable error rather than
committing under a bogus identity.
### 2. Protect `master` on the blog repo — RECOMMENDATION, deliberately not enabled
**Status: accepted risk. Branch protection is OFF, and that is a decision, not an oversight.**
> *"leave it as a recommendation — I won't do that unless I take this out to the wider internet"*
> — operator
The deploy key **can** push `master` directly, which means the writer-branch model is **advisory, not
enforced**. The operator has weighed this and chosen not to add the gate while the editor is LAN-only:
- **The compensating control is reachability, not immutability.** The editor is LAN-only (FR-10). The
threat this setting defends against is a compromised editor publishing straight to `master`; while
nothing off-LAN can reach the editor, and the only user on the LAN is the operator, that is largely
theoretical.
- **The cost is real now, the benefit is not.** Protection adds a merge gate for the sole user, who is
also the repo owner — friction with no realistic risk reduction today.
#### ⚠️ Enable it BEFORE either of these happens
1. **The editor becomes reachable off-LAN** — an NPM proxy entry, a port-forward, or a
Tailscale/WireGuard route to `blog-ide.aridgwayweb.com`.
2. **Anyone other than the operator gets access to the editor.**
At that point a deploy key that can force-push `master` stops being theoretical. The setting
(`armistace/blog` → **Settings** → **Branches** → rule for `master`):
| Setting | Value |
|---|---|
| **Enable push** | **off** ← direct pushes blocked, including the deploy key |
| **Enable merge whitelist** | **on** → add `armistace` |
| Require approvals | optional (you're the only human here) |
Verify:
```bash
curl -s -H "Authorization: token $GITEA_TOKEN" \
https://git.aridgwayweb.com/api/v1/repos/armistace/blog/branch_protections
```
#### It is still checked on every test run
`build-and-test.sh` attempts a direct push to `master` with the deploy key and requires it to be
**rejected**. While protection is off that reports as `ACCEPTED-RISK`, not `FAIL` — so the suite stays
green and CI-usable — but **the check runs every time**. It flips to
`PASS master is protected` by itself the moment protection is enabled, and this recommendation
becomes moot without anyone having to remember it.
### 3. The macros
Installed directly into Atheos's macro store (`data/macros.db.php`); a copy is kept in this repo at
`files/macros.db.php` so a rebuild reproduces them. They appear when you **right-click the `blog`
folder** in the file tree → **Macros**.
| # | Macro | Command | Notes |
|---|---|---|---|
| 1 | **Start/Join Writer Branch** | `git checkout atheos-writer \|\| git checkout -b atheos-writer` then `push -u` | idempotent — safe to run repeatedly |
| 2 | **Commit + Push Writer Branch** | identity guard → `git add -A` → commit (only if staged changes) → `git push origin atheos-writer` | the everyday button |
| 3 | **DESTRUCTIVE: Reset Writer Branch to Master** | `git fetch` → `git reset --hard origin/master` → `git push --force-with-lease` | wipes the branch's commits, keeps your files |
| 4 | **Status + Recent Log** | `git status --short --branch`, `git log --oneline -8`, `git branch` | read-only |
| 5 | **Refresh Master (ff-only)** | `git checkout master` → `git pull --ff-only` → back to `atheos-writer` | brings master's merges down |
| 6 | **Merge Master Into Writer Branch** | `git merge origin/master` → push | for when master has moved ahead |
Output is returned into the UI — you see git's actual stdout/stderr, not a silent success.
## Day-to-day
1. Right-click `blog` → Macros → **1. Start/Join Writer Branch** (first time; also safe later)
2. Write your post, save
3. Right-click `blog` → Macros → **2. Commit + Push Writer Branch**
4. Open a PR: `atheos-writer` → `master`
5. Review the diff in Gitea, merge
### ⚠️ Important: macro 2 uses `git add -A`
`git add -A` stages **everything** in the repo, including files you may not intend to publish
(drafts, scratch files, `.orig`/`.bak` leftovers). A `reset --hard` will also **delete any untracked
file that has been staged**.
Practical consequences:
- `atheos-writer` is a **publishing** branch — everything you leave in the tree gets committed and
pushed to Gitea.
- If you want a scratch area, keep it **outside** the repo (e.g. `workspace/scratch/`, not
`workspace/blog/`).
- To unstage before a reset: `git reset` (clears the index without touching your files).
## Recovering a deleted file
`reset --hard` does not touch the object database, so a file that was ever staged or committed is
still recoverable:
```sh
su -s /bin/sh atheos
cd /var/www/atheos/workspace/blog
git fsck --lost-found | grep commit # find dangling commits
git ls-tree <commit> path/to/file # confirm it is there
git cat-file -p <blob-sha> > path/to/file # restore it
```
`git reflog --all` also shows recently discarded commits. This is how the empty
`src/content/please_no_facebook.md` was recovered after a `reset --hard` in testing.
## Changing the branch name
Edit macros 1, 2, 3, 5 and 6 in **Settings → Macro** and replace `atheos-writer`. The branch name is
literal in each command — there is no variable. (Macros have no input prompt, so a
branch name that changes per task would need edit-then-run.)