Commit graph

280 commits

Author SHA1 Message Date
baf4a20571 feat(forge): port every reconciler call site onto the shim
Term 1 completed. All 52 runtime gh call sites in the three reconcilers and
lib/ruling.sh now go through forge_* verbs; the three remaining matches in
labels-reconcile are prose in comments. lib/facts.sh is deliberately
untouched — it is the release door, and the ruling keeps release.yml out of
this issue.

The CEREMONY_FORGE_CLIENT:-gh wrappers die here, in the same commit as the
sites they described, so the tree is never in a state where the declaration
lies. main() now runs forge_preflight then forge_select "".

Two sites needed judgment rather than substitution:

  - labels-scope's write is forge_labels_add, a genuine additive POST on
    both backends, NOT forge_issue_edit --add-label. ceremony#128 turns on
    that write not being a read-modify-PUT: the labeler action computed
    (labels-at-job-start union derived) and PUT the whole set, silently
    dropping a label applied while the job ran. Routing it through a generic
    edit verb would have quietly reopened that.

  - the human-review request is forge_request_reviewer. Contrary to my
    earlier reading, POST /pulls/{n}/requested_reviewers DOES exist on
    Forgejo — 422 naming the reviewer's access without it, 201 with it. The
    earlier 404 was a GET, which the endpoint does not serve, plus a
    username that did not exist.

Test churn, all of it the term-5 boundary move:

  - the suites select the github backend, so their existing gh() stubs stay
    the boundary and keep intercepting;
  - stubs strip the paging the shim injects, so fixtures stay keyed on the
    logical endpoint (inlined in the PATH stub, which is a standalone
    executable and cannot see a shell function);
  - fixtures renamed off the per_page suffix for the same reason;
  - recorded-mutation assertions now match the verb, not the raw gh line;
  - gh() stubs carry SC2317: they are reached through the backend now, so
    shellcheck can no longer see the call path.

Refs #188
2026-08-02 19:43:58 +00:00
dce12e0bb5 fix(test): the second curl stub needed the SC2317 disable too
a968e13 was pushed with shellcheck red. I chained the gates and the push in
one command, so a non-zero gate did not stop the push — the gate has to be a
condition, not a line of output I read afterwards.

Refs #188
2026-08-02 19:24:27 +00:00
a968e13ca4 fix(forge): parity gaps in the forgejo verbs — upsert, timestamps, typos
@codex-reviewer-andresmgsl's three findings (#4743), all real.

1. forge_label_create is now an UPSERT, matching gh label create --force.
   bootstrap_labels creates every declared label on EVERY workflow_dispatch,
   so a plain POST onto an existing name aborted the bootstrap under set -e
   from the second dispatch onward. Resolves name -> id and PATCHes when it
   exists.

2. forge_pr_view carries createdAt/completedAt. checks_state groups repeated
   contexts and selects the newest by [.startedAt, .createdAt, .completedAt];
   mapping only {context,state} left the winner to incidental array order, so
   a stale re-run could outrank the live verdict. The combined status carries
   created_at and updated_at — measured.

3. forge_issue_edit refuses unknown flags and missing values. The github
   backend hands them to gh, which fails; dropping them here turned a
   mis-typed port site into a mutation that silently did not happen — this
   issue's own failure class, inside the fix for it.

Also settles @grok-reviewer-andresmgsl's note 3 (#4741): Forgejo Actions DO
land as commit statuses on this instance, so the rollup is not empty.
rig main carries four — "ci / check (push)" and siblings, state success,
each with created_at. statusCheckRollup therefore populates, and NONE is not
silently substituted for SUCCESS.

Each fix mutation-verified: dropping the timestamps, forcing POST-always, and
restoring the silent flag skip each red exactly their own cases. The
newest-verdict case drives the real checks_state, not a copy.

Refs #188
2026-08-02 19:22:28 +00:00
adf3299192 test(forge): assert the distinguishing text, not a surviving substring
@codex-reviewer-andresmgsl (#4727) and @grok-reviewer-andresmgsl (#4734):
"...and the refusal names both totals" searched only for "4", so it stayed
green if the later total vanished from the message. A case named "names
BOTH" must fail when one goes. Now asserts "4 then 9".

Auditing this file's siblings for the same shape found a second, older
instance: "the refusal names the client" searched for "gh", which also
occurs in the explanatory prose ("gh speaks GitHub's /api/v3..."), so it
would have passed even if the client name never reached the message. Now
asserts "the 'gh' client cannot speak it".

Both verified by mutation: removing the second total, and removing the
interpolated client name, each red exactly their own case.

Refs #188
2026-08-02 19:16:48 +00:00
714a2e0413 feat(forge): the reconciler verb surface on both backends
github is the existing gh invocation extracted 1:1 (term 5). forgejo is
/api/v1, and encodes three asymmetries measured against this instance on a
scratch repo — never a live board:

1. Adding labels takes NAMES; removing one takes a numeric ID.
     POST   /issues/1/labels {"labels":["probe:one"]}  -> 200
     DELETE /issues/1/labels/probe:one                 -> 422
     DELETE /issues/1/labels/149                       -> 204
   So a removal resolves name -> id first. gh hides this; the shim cannot.

2. Assignees are SET, not added and removed: PATCH /issues/{n} takes the
   whole list and {"assignees":[]} clears it. --remove-assignee is therefore
   a read-modify-write, not a delete.

3. There is no statusCheckRollup. The portable equivalent is the combined
   commit status, GET /commits/{sha}/status, mapped into the node shape
   checks_state already parses so the decision code is untouched.

gh pr list --limit 100 moves behind forge_pr_list: that page size lives in
gh's own flag namespace, so no URL-parameter strip could have caught it
(@grok-reviewer-andresmgsl's note 3).

Every verb driven live against a real Forgejo instance: label list/create/
delete, add and remove labels by name, a removal of a label the repo does
not have (no-op, as gh behaves), comment, assignee add and remove, pr_list.

Call sites are still unported, so this is not yet reachable on either forge.

Refs #188
2026-08-02 19:13:14 +00:00
66e20f12f0 fix(forge): validate the completeness bound itself, on every page
@codex-reviewer-andresmgsl's three findings (#4712), each a route by which
an unprovable read could still be reported as a whole one — the guard
leaking the failure class it exists to stop.

1. x-total-count was never validated. `X-Total-Count: not-a-number` returned
   rc=0 with that string as the bound the walk compared against, reproduced
   on ab23a3b. Now required to be a canonical non-negative integer.

2. The total was read once. A collection changing size under the walk was
   invisible: page 1 declaring 4 and page 2 declaring 9 stopped at 4
   believing itself whole. Now re-read per page; a moving total means the
   read was not atomic and is refused.

3. A 200 whose body is not an array counted as zero items, so an error
   object or scalar arriving where a list belongs read as a complete EMPTY
   collection whenever the declared total was 0. Now refused, quoting the
   body. A genuinely empty array is still fine — covered.

Each guard is mutation-verified: removing it reds exactly its own cases and
no others.

Refs #188
2026-08-02 19:07:32 +00:00
87b088114a fix(test): silence the two lint classes the new backend suite introduces
SC2016 on the deliberate single-quoted bash -c (the expansion belongs to
the isolated process, as the sibling case in issueflow-reconcile.test.sh
already documents), and SC2317 on the curl stub, which shellcheck cannot
see is invoked indirectly by forge_api.

Found only after committing, because .github/scripts/shellcheck-all.sh
derives its lint set from `git ls-files` — an UNTRACKED file is not linted
at all. "Gates clean" measured before `git add` was measuring a set that
excluded the file just written. Verified from a clean clone at the pushed
SHA, which is what caught it.

Refs #188
2026-08-02 19:03:40 +00:00
ab23a3b1b6 feat(forge): two backends behind one call surface, and the shim owns paging
Term 1's foundation. lib/forge.sh gains forge_select, which sources exactly
one of lib/forge-github.sh or lib/forge-forgejo.sh; both define the same
verbs, so no branching reaches the 61 call sites. The github backend is the
current gh invocation extracted 1:1 — term 5 is kept by making that path
boring.

The page size moves OUT of the call sites and into the backend, because it
is not portable and fails silently. Measured 2026-08-02:

  ?per_page=100   GitHub 100 items   Forgejo 30 items  (ignored)
  ?limit=100      GitHub  30 items   Forgejo 50 items  (capped)

Both answer HTTP 200 with valid JSON. Every call site here is GitHub-shaped,
so a verbatim port would have swept 30 of rig's 137 issues and printed
"reconciled." — criterion 2 failing green, the same failure class as the
blind sweep. Both page_url helpers strip a stray page-size parameter in
either dialect, so a call site cannot reintroduce it by accident.

Forgejo caps a page at 50 whatever is asked, so pagination is mandatory, not
an optimisation. The gather is then PROVEN complete against x-total-count
rather than assumed complete because a loop ended.

@kimi-reviewer-andresmgsl's hardening (#4699): a missing x-total-count is
itself a loud refusal. Header exposure is a server setting, and an assert
that cannot run must not silently pass — that is the failure class
re-entering through the guard built to stop it.

Call sites are not ported yet; that is the next commit.

Refs #188
2026-08-02 19:00:58 +00:00
3885437f02 test(forge): cover the open-pull REST gather at main() granularity
@codex-reviewer-andresmgsl's draft-stage finding: the closed/merged half of
the term-3 replacement had an executable-path case, the open half did not.
The 27 closes_references cases test the parser, not the
`.body | @base64` -> base64 -d -> closes_references wiring around it.

Both directions in one sweep so neither assertion passes vacuously: #50 is
closed by an open PR and keeps its claim, #51 is closed by nothing and is
reclaimed. `Closes #50` sits on the third line of the body, so the newline
protection is non-vacuous — an @tsv-shaped regression that keeps only the
first line reclaims #50 and reds the case.

Verified by mutation: replacing the decode with `base64 -d | head -1` fails
exactly "a claim closed by an open PR survives the base64 round trip" and
nothing else; reverting restores 148/148.

The clock is injected. INOW is a fixed 2033 epoch, so without ISSUEFLOW_NOW
the subprocess reads its own wall clock, dates both claims in the future and
keeps them on a negative age — green, and proving nothing. Caught while
writing this case.

Also renames the sibling assertion that still said "through GraphQL"; that
gather has been REST since 5797b41.

Refs #188
2026-08-02 18:49:28 +00:00
5797b418b9 feat(forge): replace both gh api graphql sites with REST + a body parser
Term 3 of #188. Forgejo has no GraphQL API, so these two gathers could not
be translated — there is no endpoint to translate them to. A real
forgejo-runner job says so from the other side: GITHUB_GRAPHQL_URL arrives
set to the empty string (probe task 278).

MERGED_REF_PR_RECORDS was already a body parse; GraphQL was buying
pagination, nothing semantic. OPEN_PR_ISSUES used GitHub's own parse of the
closing keywords, so it becomes lib/closes_references.sh — a sibling of
refs_references, sharing its LOCAL/CROSS classifier so rig#112 can still
never be read as local #112 (#61).

Both gathers now read /pulls, which /api/v3 and /api/v1 return in the same
shape (measured on both). merged_at replaces GraphQL's states: MERGED.
Bodies travel base64: jq's @tsv escapes a newline to a literal backslash-n,
which a line parser reads as one line and loses every declaration after the
first.

The accepted delta, written down rather than rediscovered: GitHub also
records closing links attached through the PR development sidebar, which
live in no body. This family declares links in the body, so the delta is
zero here.

Refs #188
2026-08-02 18:41:03 +00:00
7d52b2cd4a feat(forge): refuse loudly when the client cannot speak the forge
The preflight half of #188, landed first so it stands alone: the forge is
decided once, before any sweep, and a client that cannot speak it exits
non-zero with a named reason.

Measured against forgejo.heavyduty.builders at 84bb1a4 — two of the three
actions reported SUCCESS having read nothing:

  labels-scope         exit 0  "no .github/labeler.yml" (the file is HTTP 200)
  labels-reconcile     exit 0  "reconciled."            (zero PRs enumerated)
  issueflow-reconcile  exit 1  "unexpected end of JSON input"

labels-reconcile's blind-sweep warning (#96) could not fire: it counts
unreadable PRs against a list `gh pr list` never produced, and a process
substitution's failure does not trip set -e, so total stayed 0. Installing
gh makes it worse, silencing the one loud failure.

Detection is measured, not inferred from docs: a real forgejo-runner v6.3.1
job (probe task 278) shows Forgejo populating the whole GITHUB_* namespace,
so GITHUB_ACTIONS proves nothing. GITHUB_API_URL's shape, GITEA_ACTIONS and
GITHUB_SERVER_URL do. The same probe shows the runner image carries neither
gh nor stoke, which is what makes the forgejo backend REST.

Tests declare CEREMONY_FORGE at the forge boundary rather than stubbing gh
and staying silent about the forge — the boundary move term 5 asks for.

Refs #188
2026-08-02 18:29:08 +00:00
github-actions[bot]
84bb1a424d chore: bump main to 0.4.1-dev — a dev install must not impersonate 0.4.0 2026-07-29 12:27:50 +00:00
Daniel Marin
0a54e31972
Merge pull request #207 from codex-bot-andresmgsl/build/206-release-0-4-0
release: cut 0.4.0
2026-07-29 13:27:32 +01:00
codex-bot-andresmgsl
98c8dc2eb4 docs: record 0.4.0 live drill 2026-07-29 10:58:28 +00:00
codex-bot-andresmgsl
7c755bcd40 release: stamp 0.4.0 candidate 2026-07-29 10:51:51 +00:00
Daniel Marin
fa04d67033
Merge pull request #204 from codex-bot-andresmgsl/build/203-sweep-cadence-manual-sweep
docs: explain sweep cadence and manual dispatch
2026-07-29 11:40:36 +01:00
codex-bot-andresmgsl
4198597834 docs: correct labels maintenance cadence 2026-07-29 10:37:03 +00:00
codex-bot-andresmgsl
3eff28e624 docs: explain sweep cadence and manual dispatch 2026-07-29 09:27:29 +00:00
Daniel Marin
1de6b742f8
Merge pull request #202 from codex-bot-andresmgsl/build/198-contributing-flow-boundary
docs: make BUILDER the single PR flow contract
2026-07-29 10:23:37 +01:00
codex-bot-andresmgsl
6fe549cdb7 docs: make BUILDER the single PR flow contract 2026-07-28 22:31:55 +00:00
Daniel Marin
c4b49cd8d3
Merge pull request #200 from claude-bot-andresmgsl/build/199-labels-sweep-cadence
labels: reduce full-board sweep trigger frequency (#199)
2026-07-28 23:15:09 +01:00
claude-bot-andresmgsl
8cf3c335df labels: name what the hourly cron uniquely covers (#199 round)
The trigger comments, CONSUMERS stub, changelog, and reusable labels.yml
comment said "events carry every real state change in seconds; the cron
only backstops a forgotten handoff." That is backwards: no subscribed
event wakes this sweep for a review verdict landing (no pull_request_review
trigger), blocker:ci-red set/cleared, blocker:conflict when another PR
merges, or the time-based stale/48h-reclaim. The hourly cron is the sole
discovery path for those four classes, not a forgotten-handoff net —
so the comments now name them and warn against deleting the cron (AC5).

codex-2 / kimi-2 (both blocking, round 2 @0a812c4b).
2026-07-28 19:26:40 +00:00
claude-bot-andresmgsl
0a812c4b19 labels: keep edited/reopened on issues; correct 0.3.0 adoption prose (#199 round)
Round fixes on #200.

codex-1 (blocking): the issues narrowing dropped `edited`/`reopened`, but both
carry a queue-state change an event uniquely carries — `edited` a body rewrite
of the `Blocked by #N` declaration the sweep parses
(issueflow-reconcile.sh:179), `reopened` a closed issue re-entering the queue.
Dropping them tripped #199's must-fail. Narrow to
`[opened, closed, edited, reopened]`, dropping only the churn/validation
actions labeled/unlabeled/assigned/unassigned. Trigger tests now pin
edited/reopened present and the four dropped; labels.test.sh exact-list updated.

kimi (blocking): the "supersedes unreleased #144" prose was false — #144's
edited/reopened shipped in 0.3.0. Dissolved: we now keep them. CONSUMERS prose
rewritten to the real version history (0.2.0 #32 / 0.3.0 #144 / #199 narrows),
and the #137 review-request line corrected from "unreleased" to shipped-in-0.3.0.

kimi (non-blocking): reusable labels.yml comment no longer cites */15.

codex-2 (AC1 after-measurement / closing) escalated to triage on #199 — held,
not guessed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 19:04:27 +00:00
claude-bot-andresmgsl
155828a069 changelog: group the #199 fragment and split it under the 300-char bound
The tree is grouped (fragments carry ### headings) and changelog-armed
caps each '- ' entry at 300 chars; the single 574-char bullet failed
both. Rewrite as three '### Changed' bullets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:24:23 +00:00
claude-bot-andresmgsl
f8ad0b34c7 test: guard the #199 trigger surface; update #144 parity to [opened, closed]
Add test/labels-triggers.test.sh: reconcile stays cancel-in-progress:
false (the must-fail — true kills a sweep mid-board), the cron is hourly
not */15, each churn action is gone from the issues surface, and the PR
labeled handoff wake survives the issues narrowing. Update the #137/#144
parity block in labels.test.sh to the narrowed [opened, closed] contract
and replace its fragile inline pull_request_target scan (the #199 prose
comments name the trigger keys) with the anchored event_types reader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:21:48 +00:00
claude-bot-andresmgsl
12887cc9de docs: narrow CONSUMERS stub issues triggers, relax cron to hourly (#199)
Bring the published caller stub to the #199 shape in lockstep with
self-labels.yml (preserving the stub<->caller parity #144 established):
hourly cron, issues: [opened, closed], per-trigger why-comments. Rewrite
the adoption prose — 0.2.0 shipped the broad set; #199 narrows it at its
pin bump and supersedes the unreleased edited/reopened additions (#144).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:13:15 +00:00
Daniel Marin
0e3d8de2a6
Merge pull request #197 from codex-bot-andresmgsl/build/196-round-log-handoff
docs: make round replies the durable handoff record
2026-07-28 19:11:10 +01:00
claude-bot-andresmgsl
dfafeedeec labels: relax self caller cron to hourly, narrow issues triggers (#199)
The advisory */15 cron fired a full-board sweep four times an hour at
GitHub's 1-minute billing floor; events carry real state changes in
seconds, so the cron only backstops a forgotten handoff. Relax it to
hourly and narrow the issues: trigger to [opened, closed] — the two
actions with promptness-critical reconcile behavior — dropping the
labeled/unlabeled churn source. Per-trigger why-comments added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 18:10:23 +00:00
codex-bot-andresmgsl
0405d206ae docs: align mechanical handoff wording 2026-07-28 17:57:31 +00:00
codex-bot-andresmgsl
ab77fa93ac docs: relocate handoff summary to round log 2026-07-28 17:04:33 +00:00
Daniel Marin
0a9316f7e1
Merge pull request #195 from codex-bot-andresmgsl/build/193-fleet-roster-audit
docs(fleet): move roster ownership to operator config
2026-07-28 12:08:48 +01:00
codex-bot-andresmgsl
5757e24cd4 docs(fleet): move roster ownership to operator config 2026-07-28 10:48:03 +00:00
Daniel Marin
c96d2d8f4e
Merge pull request #194 from codex-bot-andresmgsl/build/192-reduce-fleet-registry
docs(fleet): reduce registry mechanism to a pinned pointer
2026-07-28 11:18:56 +01:00
codex-bot-andresmgsl
7fc0425184 docs(fleet): reduce registry mechanism to a pinned pointer 2026-07-28 09:58:49 +00:00
Daniel Marin
432dff0613
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
2026-07-27 23:04:12 +01:00
dan-claude-bot
4f3fc2a20b docs(fleet): reconcile the attention wake too — the stamp covers the whole file
Refs #189. Round 1 on #191 (codex blocking, kimi nit).

codex caught that advancing the stamp to crew@4da17c4 made the attention
section false. It did, and the error is mine in kind, not only in detail: a
reconciliation stamp is a claim about the WHOLE file against that tree, and I
audited only the ci-red surface across a 17-commit advance. That is the #187
failure mode, on the PR that closes #189.

What was false at the pinned SHA, all from the crew#66 ruling (danmt,
2026-07-27) landing in d578150e:

- "One wake is registry-independent, by design: attention" — no wake is
  exempt now. _attention_partition splits rows against the registry; OUT rows
  are reported and never acted on.
- "the assignment is what carries the authorization — there is nothing here
  for a repo list to scope" — this is the position the ruling REJECTED. The
  cost was argued first: a cross-repo handoff now waits on an operator adding
  the repo, which is why an out-of-scope demand also pings the operator over
  the boot-gate channel rather than only reaching duty.log.
- "a fix that bounds this wake to the registry would re-create the #16
  incident" — that fix landed, with the ping as its mitigation.
- The parenthetical calling crew's repos-default.txt header a contradiction
  "raised there as a discussion" — the discussion is crew#66, it was ruled,
  and crew's header now names the attention wake explicitly. This file was
  preserving the losing side of a settled question.

Also in range and owned by this section: d849f166 ledgered the wake, so
"a session that dies before acking is relaunched; that is the whole
crash-recovery story" no longer is. Dying relaunches; COMPLETING without
acking is a decline, and the ledger stops it re-firing until the issue
moves. Meaning, not mechanism, so it belongs here.

Re-audited the rest of the advance rather than spot-fixing: fleet.roster
still declares itself the TARGET environment (the roster paragraph holds),
and the roster/install commits touch role resolution inside crew, which this
file does not describe.

kimi's nit, which the earlier rewrite made mine: the paper inventory counted
one row while the triage-signals bullet marks a second. Both are needs-ruling
rows and both are now named — a number that has to be recounted every time a
wake lands is the thing that went wrong.

18/18 test files; changelog_fragment_problem OK; shellcheck, self-ref and
git diff --check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 21:36:10 +00:00
dan-claude-bot
45934b54a2 docs(fleet): ci-red is deployed engine — advance the stamp to crew@4da17c4
Refs #189 — the post-merge, triage-owned task, now reachable: heavy-duty/crew#64
merged at 4da17c49594c2d86bd3793fa3567846cbca38e90.

#190 wrote the ci-red wake into FLEET.md deliberately marked on paper, per this
file's convention for a spec that is not yet running, and left the reconciliation
stamp at crew@01fb49c because advancing it to a SHA that did not carry the wake
would be the drift #187 exists to remove. crew#64 has merged, so both halves flip:

- The stamp advances to crew@4da17c4, and every crew permalink in the file with
  it. A stamp and its evidence links naming different trees is worse than no
  stamp: the reader diffs the wrong engine and finds no drift because the tree
  they were pointed at is the one the prose was written against.
- The four on-paper markers go: the duty-order caveat, the ci-red bullet's
  parenthetical, the Build bullet's "once ci-red deploys", and the closing
  paper inventory — which now names one remaining paper wake, the notifier's
  needs-ruling queue, not two.

The Build bullet is not a pure marker removal. crew#64's last review round
changed what it has to say: the operator ruled the round gate a whitelist, so
the wake admits a green head OR one with no checks configured, and holds a red
head AND one whose check has not finished. Copying the old "never a round at a
red head" through the flip would have shipped a fresh inaccuracy on the same
commit that claims the file is reconciled. The ci-red bullet gains the matching
sentence from the other side: an unfinished check is not a red head and wakes
nothing, because nothing has failed yet.

Verified at the stamped SHA rather than assumed: duty.sh's header carries
attention → … → resume → ci-red → build (and says it is what this file is
reconciled against), shared/README.md's duty order matches, and notify.sh's
only label filter is still state:needs-human — which is what keeps the
remaining paper claim true.

18/18 test files pass; changelog_fragment_problem OK (it caught a 387-char
entry against the 300 bound, now split); shellcheck, actionlint self-ref and
git diff --check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 21:22:07 +00:00
Daniel Marin
e3820dbf9a
Merge pull request #190 from claude-bot-andresmgsl/build/189-ci-red-doctrine
docs: the check at the head — round precondition, CI-red recovery, and the ci-red wake on paper
2026-07-27 21:25:20 +01:00
claude-bot-andresmgsl
44ff7524af docs(builder): re-request by head, not by verdict — a push stales every approval
The round protocol told the author to re-request exactly the reviewers
who did not approve, but the handoff predicate counts an approval only
at the current head: any push while answering a round staled the
earlier approver's verdict, doctrine said not to re-request them, and
the PR could never converge — the silent-stall shape of #26/#39.
Step 2 now re-requests by head, not by verdict: every panelist after a
push, the non-approvers alone at an unchanged head. Shape 2's wording
is aligned so the two paragraphs agree.

Defect raised by dan-claude-bot on #190; folded in at the operator's
direction while the paragraph is open. Refs #189.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 19:53:59 +00:00
claude-bot-andresmgsl
e9cf461ed3 docs(builder): avoid 'ledger' for review-verdict state in the red-head rule
Refs #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:47:00 +00:00
claude-bot-andresmgsl
e85a7d42f3 docs(fleet): ci-red in the duty order and wake list, on paper; changelog fragment
FLEET.md gains the ci-red wake between resume and build in crew#64's
engine position, marked on paper per this file's existing convention (the
notifier queue, triage's past-24h wake); the Build bullet records the
red-head exclusion as reported-not-swallowed; the reconciliation stamp
stays at crew@01fb49c because crew#64 has not merged.

Refs #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:46:10 +00:00
claude-bot-andresmgsl
203907b0ea docs(builder): green check at the head gates both request points; red-head recovery in picking
The round precondition (crew#45) at step 1 and the re-request in step 2,
with the argued exception and its evidence requirement; the ruled
classification (cancelled/stale not green, skipped/neutral green,
operator 2026-07-27); ci-red pickup precedence and crew#17's recovery
path in Picking, with the explicit shape-2 carve-out so a red head never
reads as parked.

Refs #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:45:03 +00:00
Daniel Marin
a414f3105b
Merge pull request #188 from claude-bot-andresmgsl/build/187-fleet-split
docs: FLEET.md reconciled against crew@01fb49c — registry is the scope
2026-07-27 19:29:15 +01:00
claude-bot-andresmgsl
7a49de7efb docs: FLEET.md reconciled against crew@01fb49c — registry is the scope
The two scope passages said the opposite of merged behaviour: a reviewer's
registry was 'the org itself' and 'no repo filter may gate' a request.
duty-review.sh implements repos.txt as the queue's scope since the
2026-07-25 operator ruling (crew#16) — out-of-scope requests WARN, never
act. The attention wake survives as the one stated registry-independent
exception: the assignment is the authorization (duty-attention.sh).

Mechanism moved to crew: the duty-loop anatomy and resilience prose is now
a pointer to crew's shared/README.md, folding the two drifted facts (cron
runs tick.sh; hygiene self-schedules inside the duty tick). Wake lists
follow the engine's duty order; the roster keeps the as-built bench beside
fleet.roster's target with the delta stated; stamp updated to crew@01fb49c.

Closes #187

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 18:16:17 +00:00
Daniel Marin
d9c5b92dd1
Merge pull request #186 from claude-bot-andresmgsl/build/184-blocked-by-union
fix: blocked_reference_records unions every Blocked by clause (#184)
2026-07-25 16:40:25 +01:00
claude-bot-andresmgsl
0eea112d50 fix: blocked_reference_records unions every Blocked by clause
Binding to the first marker occurrence dropped every later sentence of a
repeated declaration and let earlier prose hijack the parse — the false
ready promotion on rig#154. Each occurrence now contributes its own
clause, terminated at its own first ./; (unterminated -> end of input),
and the union feeds the unchanged classification and decision table.

Closes #184

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:58:39 +00:00
Daniel Marin
80c0dca3b0
Merge pull request #183 from claude-bot-andresmgsl/build/182-shape-sentinel
lib/changelog.sh + changelog-armed — the shape anchor becomes declarable; ceremony flips to grouped
2026-07-25 14:43:53 +01:00
claude-bot-andresmgsl
0b158a6917 fix: the sentinel's one-line contract is checked on the file, not the $(cat) word
Command substitution strips every trailing newline, so 'grouped\n\n'
reached the case as a clean word and passed — the round's shared blocker
(codex, grok). A line count taken from the file itself now refuses any
physically multi-line sentinel before the word check runs, with the
existing diagnosis naming the file. Red rows: grouped/flat with a
trailing blank line in the unit suite, grouped in the armed suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:29:02 +00:00
claude-bot-andresmgsl
be666ebed6 feat: ceremony flips to grouped — changelog.d/shape = grouped, five fragments converted, docs per D6
167/175 gain '### Added', 173/178 '### Changed', 180 '### Fixed' — every
bullet byte-identical, headings only (the #158 bar, inverted). CONSUMERS.md
names the sentinel and the flip procedure; changelog.d/README.md names the
sentinel. 182.md is this PR's own fragment, grouped atop the sentinel it
ships.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:14:05 +00:00
claude-bot-andresmgsl
2fd9cb7ded test: sentinel rows across the shape predicate, the armed guard, and the assembler
Must-pass and must-fail rows from #182's test plan: the flip shape green
under the sentinel, drift and malformed sentinels red with file named, the
D3 fragment-list assertion, and D5 sentinel survival through consumption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 13:12:30 +00:00