Merge pull request #191 from dan-claude-bot/build/189-ci-red-deployed

docs(fleet): ci-red is deployed engine — advance the stamp to crew@4da17c4
This commit is contained in:
Daniel Marin 2026-07-27 23:04:12 +01:00 committed by GitHub
commit 432dff0613
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 78 additions and 45 deletions

108
FLEET.md
View file

@ -10,7 +10,7 @@
> into [heavy-duty/crew](https://github.com/heavy-duty/crew) (private to the > into [heavy-duty/crew](https://github.com/heavy-duty/crew) (private to the
> org; the fleet can read it), the *mechanism* lives there and this file only > org; the fleet can read it), the *mechanism* lives there and this file only
> points at it. Last reconciled against the merged engine at > points at it. Last reconciled against the merged engine at
> [`heavy-duty/crew@01fb49c`](https://github.com/heavy-duty/crew/tree/01fb49cd717a0f4c83df286af86411c69e3c2363), > [`heavy-duty/crew@4da17c4`](https://github.com/heavy-duty/crew/tree/4da17c49594c2d86bd3793fa3567846cbca38e90),
> 2026-07-27 — a descriptive file with no reconciliation stamp gives the next > 2026-07-27 — a descriptive file with no reconciliation stamp gives the next
> reader nothing to diff, which is exactly how the #149 drift went unnoticed. > reader nothing to diff, which is exactly how the #149 drift went unnoticed.
@ -30,7 +30,7 @@ network path — GitHub is the only queue.
This table is the **as-built** bench — five boxes, two of them dual-role. It This table is the **as-built** bench — five boxes, two of them dual-role. It
is not the same thing as crew's is not the same thing as crew's
[`fleet.roster`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/fleet.roster), [`fleet.roster`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/fleet.roster),
whose own header declares it the **target** environment: seven single-role whose own header declares it the **target** environment: seven single-role
boxes, the dual-role claude and codex boxes each split into a builder and a boxes, the dual-role claude and codex boxes each split into a builder and a
reviewer member. The delta is exactly that split (plus each new box needing reviewer member. The delta is exactly that split (plus each new box needing
@ -49,13 +49,13 @@ second repo is a second thing to keep true — this one drifted (it said cron
ran `duty.sh` directly and gave the hygiene sweep its own cron line; crew's ran `duty.sh` directly and gave the hygiene sweep its own cron line; crew's
`duty.sh` records that separate line as the bug it fixed, sharing `duty.sh` records that separate line as the bug it fixed, sharing
`~/duty/work` unlocked). How a tick actually works — cron fires `~/duty/work` unlocked). How a tick actually works — cron fires
[`bin/tick.sh`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/shared/bin/tick.sh), [`bin/tick.sh`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/shared/bin/tick.sh),
the only cron target, which wraps the only cron target, which wraps
[`bin/duty.sh`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/shared/bin/duty.sh) [`bin/duty.sh`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/shared/bin/duty.sh)
in a non-blocking `flock` with one evidence line per boundary; the boot gate in a non-blocking `flock` with one evidence line per boundary; the boot gate
and crash recovery; the session runner; backlog hygiene self-scheduling and crash recovery; the session runner; backlog hygiene self-scheduling
inside the duty tick under the same lock — lives with the code: inside the duty tick under the same lock — lives with the code:
[`shared/README.md`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/shared/README.md) [`shared/README.md`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/shared/README.md)
is the map, provenance table included. Sessions stay stateless and is the map, provenance table included. Sessions stay stateless and
disposable — all state lives on the board (issues, PRs, labels) and in git disposable — all state lives on the board (issues, PRs, labels) and in git
branches; detection is the engine's, judgment is the session's. branches; detection is the engine's, judgment is the session's.
@ -73,11 +73,13 @@ What belongs here is what a wake *means*:
write surface the whole org, which no registry could bound: the drill's write surface the whole org, which no registry could bound: the drill's
containment interlock narrowed `repos.txt` and so confined triage and containment interlock narrowed `repos.txt` and so confined triage and
hygiene, but not review, the one module that submits verdicts. hygiene, but not review, the one module that submits verdicts.
[`lib/duty-review.sh`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/shared/lib/duty-review.sh) [`lib/duty-review.sh`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/shared/lib/duty-review.sh)
states the rule and implements the WARN. states the rule and implements the WARN.
- **One wake is registry-independent, by design: attention** (next section). - **No wake is exempt, including attention** (next section). The attention
The registry bounds what a box goes *looking for*, not what is handed to *query* is cross-repo by construction — it is one call to the
this identity *by name*. authenticated-user endpoint, which has no repo filter — but the *action*
it authorizes is bounded like every other. A demand parked on this box in
a repo nobody listed is reported, never worked.
### Wake conditions ### Wake conditions
@ -92,20 +94,26 @@ very thing that unparks the work resume would otherwise pick up. The query is
the authenticated-user endpoint — the authenticated-user endpoint —
`gh api "/issues?filter=assigned&state=open&labels=attention"` — one call, no `gh api "/issues?filter=assigned&state=open&labels=attention"` — one call, no
search index (the review queue below already records that the index lags) — search index (the review queue below already records that the index lags) —
and, **alone among the wakes, it reaches repos `~/duty/repos.txt` does not and it **sees** repos `~/duty/repos.txt` does not name, because that endpoint
name.** takes no repo filter.
That reach is deliberate, and it survives the registry rule above as its one **Seeing is not acting, and that is a ruling** (crew#66, danmt, 2026-07-27).
stated exception. An `attention` assignment is work handed to this identity The wake used to work every row it saw, which for a builder meant a clone and
by name, and **the assignment is what carries the authorization** — there is the full worktree and round rule set against a repo no operator had listed —
nothing here for a repo list to scope, because the box is not choosing where write authority outside the registry, and the one hole left in the
to look. containment story. Rows are now partitioned against the registry: inside it,
[`lib/duty-attention.sh`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/shared/lib/duty-attention.sh) a session as before; outside it, reported and never acted on, exactly like an
queries the cross-repo endpoint on purpose and says so in its header; a fix out-of-scope review request or authored PR.
that bounds this wake to the registry would re-create the #16 incident below. [`lib/duty-attention.sh`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/shared/lib/duty-attention.sh)
(Crew's `conf/repos-default.txt` header currently claims *every* duty module implements the partition and states the ruling in its header.
is registry-bounded — that contradiction is crew's, raised there as a
discussion; this file records the exception as it is implemented today.) The cost was argued before the ruling rather than discovered after it: an
assignment plus a label **is** a targeted authorization, so a cross-repo
handoff now waits on an operator adding the repo, and the box most likely to
be handed work outside its beat is the one that goes quiet. That is why an
out-of-scope demand does not only reach `duty.log` — it pings the operator
over the same channel the boot gate uses. A bounded wake that failed silently
would trade an unbounded write surface for a broken channel to the human.
Each demand gets **exactly one session, and the ack bounds it**: the Each demand gets **exactly one session, and the ack bounds it**: the
session's first act, before any of the demanded work, is the pickup comment session's first act, before any of the demanded work, is the pickup comment
@ -113,9 +121,13 @@ plus removing the label — [the `attention`
contract's](https://github.com/heavy-duty/ceremony/blob/bce09aa7648dbd74b8e91b1d4fbc2fa8d145f705/LABELS.md#L143-L149) contract's](https://github.com/heavy-duty/ceremony/blob/bce09aa7648dbd74b8e91b1d4fbc2fa8d145f705/LABELS.md#L143-L149)
ack (#85), which here becomes the session's ack-then-act ordering. ack (#85), which here becomes the session's ack-then-act ordering.
Then it acts on the thread and exits — short by construction. Until the label Then it acts on the thread and exits — short by construction. Until the label
is removed the flag is still up, so a session that dies before acking is is removed the flag is still up, so a session that **dies** before acking is
simply relaunched at the next tick; that is the whole crash-recovery story, simply relaunched at the next tick — the same crash-only shape as resume
and it is the same crash-only shape as resume below. below. A session that **completes** without acking is a different fact: that
is a decline, and a seen-ledger stops it re-firing until the issue moves.
Dying and declining used to look identical to the engine, which meant a
demand a session had considered and correctly left alone woke a new one every
tick forever.
The design this replaces was built and rejected: polling notifications for The design this replaces was built and rejected: polling notifications for
`reason: mention` re-arms a thread on every comment, so ordinary round `reason: mention` re-arms a thread on every comment, so ordinary round
@ -131,14 +143,13 @@ wake is no longer on paper: `duty-attention.sh` is deployed engine, and
`duty.sh` runs it first on every box, whatever its roles. `duty.sh` runs it first on every box, whatever its roles.
The engine's duty order is fleet-standard The engine's duty order is fleet-standard
([`bin/duty.sh`](https://github.com/heavy-duty/crew/blob/01fb49cd717a0f4c83df286af86411c69e3c2363/shared/bin/duty.sh)): ([`bin/duty.sh`](https://github.com/heavy-duty/crew/blob/4da17c49594c2d86bd3793fa3567846cbca38e90/shared/bin/duty.sh)):
**attention → triage signals → review queue → resume → ci-red → build → **attention → triage signals → review queue → resume → ci-red → build →
handoff → rebase → worktree hygiene → backlog hygiene (hourly)** — attention handoff → rebase → worktree hygiene → backlog hygiene (hourly)** — attention
role-independent and first, then each duty family the box's roles enable. role-independent and first, then each duty family the box's roles enable.
One position in that order is **on paper**: ci-red is Every position in that order is deployed engine at the stamped SHA:
[crew#64](https://github.com/heavy-duty/crew/pull/64)'s, unmerged at the [crew#64](https://github.com/heavy-duty/crew/pull/64) merged ci-red between
stamped SHA, where `duty.sh` still runs resume straight into build — it resume and build, and `duty.sh`'s own header carries the same order.
reads as deployed engine only once that PR merges.
The earlier form of this file folded handoff and rebase into the other The earlier form of this file folded handoff and rebase into the other
builder wakes; they are duties of their own. builder wakes; they are duties of their own.
@ -167,23 +178,30 @@ builder wakes; they are duties of their own.
— a session died between first push and PR creation. A branch whose PR — a session died between first push and PR creation. A branch whose PR
already **merged** is a post-merge wait, never resumed (#172, already **merged** is a post-merge wait, never resumed (#172,
incubator#55/#64). incubator#55/#64).
- **ci-red** (builders; **on paper** — crew#64's spec, unmerged at the - **ci-red** (builders): a non-draft PR of mine whose check at the current
stamped SHA): a non-draft PR of mine whose check at the current head is head is failing. Evaluated before the build wake, so a red PR of mine
failing. Evaluated before the build wake, so a red PR of mine outranks a outranks a new claim — repairing my own red head comes ahead of new work
new claim — repairing my own red head comes ahead of new work
(ceremony#163: full-panel approvals at the head, mergeable, stranded on (ceremony#163: full-panel approvals at the head, mergeable, stranded on
a transient failure no wake covered). A round owed at a red head is a transient failure no wake covered). A round owed at a red head is
excluded from the build wake below but reported rather than silent, and excluded from the build wake below but reported rather than silent, and
an unchanged red head goes quiet after one attempt, through the an unchanged red head goes quiet after one attempt, through the
`report_suppressed` path — suppressed, still said. How a red head is `report_suppressed` path — suppressed, still said. A check that has not
detected and kept quiet is the engine's mechanism, described in crew's finished is **not** a red head and wakes nothing here: nothing has failed
yet, so there is no investigation to launch. How a red head is detected
and kept quiet is the engine's mechanism, described in crew's
`shared/README.md`, not here. `shared/README.md`, not here.
- **Build**: a `ready` **unclaimed** issue (an assignee means mid-claim, not - **Build**: a `ready` **unclaimed** issue (an assignee means mid-claim, not
pickable), or a completed review round on my PR — a changes-request with pickable), or a completed review round on my PR — a changes-request with
no panel review request still outstanding; whole rounds, never single no panel review request still outstanding; whole rounds, never single
verdicts, and — once ci-red deploys — never a round at a red head: that verdicts, and never a round the check at its head does not support. The
head has already woken ci-red above, and the excluded round is reported, wake admits a **green** head, and a head with **no checks configured**
not swallowed. terminal, not transient, so holding there would retire the round rather
than delay it. It holds a **red** head (already woken ci-red above) and a
head whose check has **not finished** (opening the round there spends the
panel on a head that may go red — crew#45's measured cost — and it admits
itself a tick later once the check settles). Both holds are reported, not
swallowed, and they are reported *differently*: only one of them is the
author's own work to do.
- **Handoff**: a round of mine that converged — every panelist's latest - **Handoff**: a round of mine that converged — every panelist's latest
opinionated review approves the current head, no panel request opinionated review approves the current head, no panel request
outstanding, mergeable right now, `state:needs-human` not already set. outstanding, mergeable right now, `state:needs-human` not already set.
@ -248,10 +266,14 @@ The duty engine is crew's shared tree, one source deployed to every box;
makes the registry rule an operator decision rather than a sweep's. Specs makes the registry rule an operator decision rather than a sweep's. Specs
written in this file have a record of becoming engine: the attention wake written in this file have a record of becoming engine: the attention wake
and the reviewers' request sweep both started here as paper (the sweep's and the reviewers' request sweep both started here as paper (the sweep's
org-wide form was then retired by the 2026-07-25 scope ruling). Two are org-wide form was then retired by the 2026-07-25 scope ruling), and the
still on paper: the notifier's `needs-ruling` queue — at the stamped crew builders' ci-red wake above is the latest: written here as paper while
SHA, `notify.sh`'s only label filter is `state:needs-human` — and the crew#64 was open, engine at the stamped SHA. Two rows are still on paper, and
builders' ci-red wake above, engine the moment crew#64 merges. both are `needs-ruling`: the notifier's queue — at that SHA, `notify.sh`'s
only label filter is `state:needs-human` — and triage's **past 24h**
detection row above, which the triage-signals bullet already marks. Earlier
counts here said "two" while silently excluding the second; naming them is
cheaper than a number that has to be recounted every time a wake lands.
### Conventions on the board ### Conventions on the board

View file

@ -8,8 +8,19 @@
of your own PR is picked up before claiming another issue, is never a of your own PR is picked up before claiming another issue, is never a
parked claim, and follows crew#17's recovery path (#189). parked claim, and follows crew#17's recovery path (#189).
- `FLEET.md` writes the ci-red wake into the duty order between resume - `FLEET.md` writes the ci-red wake into the duty order between resume
and build, marked on paper until crew#64 merges, with the and build, now as deployed engine rather than on paper: the
reconciliation stamp unchanged (#189). reconciliation stamp advances to the crew SHA carrying crew#64 (#189).
- `FLEET.md` describes the build wake's check gate as the engine
implements it: a green head, or one with no checks configured, opens a
round; a red head and an unfinished one are held and reported
separately (#189).
- `FLEET.md` corrects the attention wake to the crew#66 ruling: the query
is cross-repo, the action is registry-bounded, and an out-of-scope
demand is reported and escalated to the operator rather than worked. It
no longer claims attention is exempt from the registry (#189).
- `FLEET.md` distinguishes an attention session that dies before acking,
which relaunches, from one that completes without acking, which is a
decline a ledger keeps from re-firing (#189).
- `BUILDER.md` re-requests by head, not by verdict: a push while - `BUILDER.md` re-requests by head, not by verdict: a push while
answering a round stales every approval, so every panelist is answering a round stales every approval, so every panelist is
re-requested; only an unchanged head re-requests the non-approvers re-requested; only an unchanged head re-requests the non-approvers