Development record
A status branch moves without a preflight receipt
Context
itd-2609151838312703 has sessions post announcements to the shared remote. They post from any account, any machine and hosted cloud sessions. Each announcement is an empty-tree commit on the person's own branch, abcd-status/<forge username in lower case>.
Three constraints were already locked:
- The push hook needs a receipt.
.githooks/pre-pushrefuses any push whose new commit carries no preflight receipt. - Cloud sessions have no receipt. A hosted cloud session cannot mint one, because it cannot pass preflight there (itd-2610120738075547).
- The cloud proxy accepts branches only. It refuses every ref that is not a branch, so a hidden ref namespace is out (itd-2609091034175565 decision 10).
The hook already admits one family of commits that change no file: reservations, under adr-2610031352212697. It does so with one row of its receipt-less table and the reservation_shape check.
Decision
The push hook's receipt-less table gains a second row: the prefix abcd-status/, checked by the same reservation_shape check that reservations pass. That check admits only a fast-forward of commits that change no file. Every other push under the prefix is gated as any branch is: one that changes a file, rewrites the branch or deletes it.
Rewrites and deletions are blocked on the forge too. A ruleset on refs/heads/abcd-status/** blocks deletion and non-fast-forward pushes, with no bypass. Its mirror is kept in .abcd/work/rulesets/, and the person applies it from that mirror, as every ruleset there is applied (brief invariant 10; corrected 2026-10-22, iss-2610222040438015: no apply verb exists for rulesets).
Announcements are fetched only by verbs whose documented meaning is that fetch: abcd announce, abcd status and the land step (brief invariant 7, adr-38). The prompt hook renders the last fetch from disk.
Invariants 7 and 10 gain clauses naming these verbs and the branch, written in the change that builds them.
Alternatives Considered
- A receipt for every announcement. Rejected: a cloud session cannot mint one, so it could never announce.
- A hidden ref namespace (
refs/abcd/status/*) outside the branch list. Rejected:- the cloud proxy refuses it;
- the forge's rulesets cannot protect it;
- the forge's activity record, which verifies senders, covers branches.
- A second, announcement-specific shape check that parses trailers in shell. Rejected: it would be a second parser beside the Go reader, which breaks one-canonical-primitive. The reader already flags an announcement it cannot parse (itd-2609151838312703 C11).
- The chosen option: a second row reusing
reservation_shape. It admits nothing the reservation row does not already admit: commits that change no file.
Consequences
- Easier: a cloud session, or any session without a receipt, can announce. The trust boundary is unchanged, because a push the row admits changes no file a gate would read.
- Harder: status branches can never be deleted. Their history is permanent and public, which the product thinker accepted (itd-2609151838312703 decision 19). They grow by one branch per person, never per session.
- The hook is a local-only check. The receipt-less row guards only a push
made through the committed hooks. A hosted cloud session runs no hook: its
clone has no
core.hooksPathand no.git/hooks/pre-push(the cloud probe of 2026-10-15). There the guards are the server's ruleset onrefs/heads/abcd-status/**, which refuses deletion and non-fast-forward pushes, and abcd's own reader, which shows a status commit whose tree is not the empty tree as doubtful, with that reason, and never renders it (itd-2609151838312703 C11). - Nothing on the forge refuses a status push that changes a file. GitHub's
push rulesets, which restrict file paths, are offered for private and
internal repositories only, and they target no branch: a push rule applies to
every push to the repository and its fork network, so none can be scoped to
abcd-status/. Branch rulesets govern creating, updating and deleting a branch, never which files a push changes (checked on 2026-10-15 against GitHub's ruleset documentation). So a cloud push that changes a file on a status branch lands, and the reader is the guard against it. - The ruleset is required, not optional. Without the hook in the cloud it
is the only refusal of a deletion or a rewrite there. Its mirror is
.abcd/work/rulesets/status-protection.json. - New obligations:
- apply the ruleset, and keep the mirror and the live ruleset in step;
- add the invariant clauses in the building change.
Frontmatter
| id | adr-2610150729333337 |
|---|---|
| type | adr |
| status | proposed |
| date | 2026-10-15 |
.abcd/development/decisions/adrs/2610150729333337-a-status-branch-moves-without-a.md
open on GitHub ↗ · commit history ↗
References4
- adr-38Implicit checks are disk-only — the network answers only an explicit askrelated
- adr-2610031352212697Claims are branches on the shared remote; nothing kept on one computer is the register; claim text is datarelated
- itd-2609091034175565Before an agent starts on an item, it reserves it on the shared remote, and everyone who works on the project sees the reservationrelated
- itd-2609151838312703Sessions under any account, on any machine or in the cloud announce what they are doing through short fixed-form announcements on the shared remote, a person reads everyone's status with one command, and what is really a hold stays a reservationrelated
mentions3
- iss-2610222040438015spc-2610150728396006's C15 says the status-branch ruleset (.abcd/work/rulesets/status-protection.json) is applied through the rulesets' own apply verb (invariant 10), but abcd has no such verb: the ruleset was applied by hand on 2026-10-17 with gh api (it is live as ruleset 25302453), the way the rulesets README tells a person to; so the criterion names a mechanism that does not exist and cannot be checked as written
- itd-2610120738075547Work handed to a cloud session comes back, or says why not
- spc-2610150728396006sessions-announce-what-they-are-doing