Skip to content

fix(guards): scope the Dash Ban PR-body check to human-authored PRs - #598

Merged
thomasluizon merged 3 commits into
mainfrom
fix/dash-ban-bot-authored-prs
Jul 24, 2026
Merged

thomasluizon merged 3 commits into
mainfrom
fix/dash-ban-bot-authored-prs

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

The Dash Ban step fed the raw PR title and body to check-dashes.mjs with no author scoping. Dependabot copies the dependency's own changelog verbatim into the body, so any upstream release note containing an en or em dash fails the gate on text Orbit neither wrote nor can edit, and which Dependabot regenerates on every rebase.

Measured: orbit-api#425, a 21-package NuGet security bump, is blocked right now by en dashes inside a Hangfire release note. The identical defect exists in all three repos and is being fixed in all three.

The file-level dash check deliberately still runs on bot PRs: the files a Dependabot PR changes ARE Orbit's, so new dashes in them are still a real violation. Only the prose check, whose whole premise is that the text is Orbit-authored, is scoped.

Verified github.event.pull_request.user.type against live data: Bot for dependabot[bot], User for a human author.

The step fed the raw PR title and body to check-dashes.mjs with no author
scoping. Dependabot copies the dependency's own changelog verbatim into the
body, so any upstream release note containing an en or em dash fails the gate
on a PR whose text Orbit neither wrote nor can edit, and which Dependabot
regenerates on every rebase.

Measured: orbit-api#425 (a 21-package NuGet security bump) is blocked right
now by en dashes inside a Hangfire release note. The same latent defect exists
in all three repos, identically.

The file-level dash check is deliberately left running on bot PRs: the files a
Dependabot PR changes ARE Orbit's, so new dashes in them are still a real
violation. Only the prose check, whose whole premise is that the text is
Orbit-authored, is scoped.

Verified github.event.pull_request.user.type against live data: "Bot" for
dependabot[bot], "User" for a human author.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M2vH4pDvNnxUPecWWvwBc
@vercel

vercel Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
orbit-ui-mobile-web Ignored Ignored Jul 24, 2026 9:40pm

Request Review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #598 — fix(guards): scope the Dash Ban PR-body check to human-authored PRs

Scope: 1 file: .github/workflows/guards.yml (+7/-1)
Recommendation: APPROVE

Summary

Adds && github.event.pull_request.user.type != 'Bot' to the "PR title and body carry no dashes" step's if: condition, plus a WHY comment explaining that Dependabot copies upstream changelog text verbatim into PR bodies and shouldn't be held to Orbit's own dash-ban prose rule. The file-level (changed-files) dash check and the baseline ratchet check are untouched and still run on bot PRs — matches the PR's stated intent. Small, correctly scoped, low blast radius.

Findings

Critical / High / Medium

None.

Low / Info

  • [Info] The condition excludes any user.type == 'Bot' author, whereas every other bot-scoping check in this repo (guards.yml's parity job, sonarcloud.yml, claude-review.yml) scopes to the literal github.actor != 'dependabot[bot]'. Checked whether this could exempt a non-Dependabot bot's LLM-authored prose from the dash ban — no evidence it does today: orchestrator workers run locally under the human's own gh auth, and no workflow auto-opens code PRs as a GitHub App bot. user.type != 'Bot' and actor != 'dependabot[bot]' are currently equivalent in this repo. Style/robustness note only, not a defect.
  • [Info] Top-of-file summary comment doesn't mention the new bot exclusion — cosmetic.
  • [Info] The WHY comment cites orbit-api#425 as bare text rather than a URL, which is what the local/no-comments WHY-comment allowance literally requires — but that rule doesn't lint .github/workflows/*.yml, so it's not gate-enforced here and not postable under the signal gate.

Not verifiable in this job

  • orbit-api#425 (the cited Hangfire release-note dash block motivating this fix) could not be independently checked — orbit-api is not checked out in this CI job. Supporting rationale only, doesn't affect the verdict.
  • Build / Unit Tests / SonarCloud run as separate required checks for this PR; skipped here per task scope.

What's good

  • Correctly scopes only the prose check; deliberately leaves the file-level dash check running on bot PRs.
  • WHY comment is thorough, explains the constraint and scope boundary, cites concrete motivating evidence.
  • PR body states the user.type field was verified against live GitHub API data rather than assumed.
  • Minimal, single-purpose diff, easy to review.

Recommendation

Approve as-is. No Critical/High findings. The Info-level notes are optional polish, not blockers.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #598 — fix(guards): scope the Dash Ban PR-body check to human-authored PRs

Scope: PR #598 in thomasluizon/orbit-ui-mobile
Recommendation: APPROVE

Summary

Single-file, 8-line change to .github/workflows/guards.yml that gates the "PR title and
body carry no dashes" step to human-authored PRs (github.event.pull_request.user.type != 'Bot'), so Dependabot's verbatim-copied upstream changelog text no longer trips the Dash
Ban gate. The file-level dash check (changed files) is correctly left unconditional for
bot PRs, since files a Dependabot PR touches are still Orbit's own. This is a root-cause
fix (narrows the check to the surface it was meant to police) rather than a workaround,
and the PR body documents the live-data verification of user.type (Bot vs User).

Findings

Critical

None.

High

None.

Medium

None.

Low / Info

None.

Subagents

Diff touches only .github/workflows/guards.yml — no apps/web/**, apps/mobile/**,
orbit-api, packages/shared/src/types/*, endpoints.ts, or i18n JSON changed, so every
gated subagent sits below its trigger threshold.

Agent Verdict
parity-checker N/A — no apps/web or apps/mobile file changed
i18n-syncer N/A — no user-facing strings or i18n JSON changed
contract-aligner N/A — no shared types / endpoints / orbit-api change (cross-repo dimension also not verifiable in CI, sibling repo not checked out)
security-reviewer N/A — no orbit-api code changed
design-reviewer N/A — no apps/*/orbit-landing-page UI file changed

Validation

Skipped per CI adaptation: this PR already runs Build / Unit Tests / SonarCloud as
separate required checks.

Check Result
Lint N/A (CI adaptation)
Type check N/A (CI adaptation)
Tests N/A (CI adaptation)
Build (api) N/A — orbit-api not touched

Deferred — N/A dimensions & files not verdicted

  • Dimension 8 (DESIGN.md/AI-slop): N/A — no apps/* UI files changed.
  • Dimension 9 (Parity): N/A — no apps/web/** or apps/mobile/** file changed.
  • Dimension 10 (i18n): N/A — no user-facing strings changed.
  • Dimension 11 (Contract drift + backward-compat): N/A — no packages/shared/src/types/*
    or DTO changed; the orbit-api side is not checked out in this CI job and would not be
    verifiable here regardless.
  • Dimension 12 (Security): reviewed inline — no injection/auth/secrets surface; the
    user.type gate only widens/narrows a lint-style CI check, not an access control.
  • Dimension 13 (Backend hard rules): N/A — orbit-api not touched.
  • Dimension 14 (FEATURES.md parity): N/A — CI/guards change, no user-facing feature
    surface change.
  • Dimension 4 (Comment policy): the new step comment is in a workflow YAML file, which
    local/no-comments (ESLint) never targets (root/apps/* eslint configs cover only
    .ts/.tsx/.js) — out of the dimension's lint-scoped surface, and consistent with
    the file's pre-existing header comment block (lines 3-11).
  • Changed files: .github/workflows/guards.yml — fully reviewed, verdict above (clean).

What's good

  • Root-cause fix: narrows the check to what it was actually meant to police (Orbit's own
    prose), rather than adding a suppression or exemption list.
  • Correctly leaves the file-level dash check unconditional for bot PRs — the files a
    Dependabot PR changes are still Orbit's, so that check still applies.
  • PR body states the user.type field was verified against live GitHub event data
    (Bot vs User), not assumed.
  • Explanatory comment on the workflow step gives the WHY and a concrete measured
    instance (orbit-api#425), matching the file's existing comment convention.

Recommendation

Approve as-is. No follow-up required in this repo; the PR body notes the identical fix is
being applied to orbit-api and orbit-landing-page separately (out of scope for this
review).

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #598

Recommendation: APPROVE

Single-file change to .github/workflows/guards.yml: the Dash Ban job's "PR title and body carry no dashes" step now also requires github.event.pull_request.user.type != 'Bot', so Dependabot (and other bot-authored) PRs skip the prose check on machine-copied changelog text, while the file-level dash check still runs unconditionally on files a bot PR actually changes. Small, correctly scoped, root-cause fix (not a workaround), with a compliant WHY comment naming the upstream constraint and citing orbit-api#425.

Findings

No Critical or High findings.

Info: the bot-detection style (user.type != 'Bot', any bot) differs from the rest of the file's github.actor != 'dependabot[bot]' idiom (parity job, claude-review.yml, sonarcloud.yml). Not a defect — the PR's own premise (any bot copies its changelog verbatim into the body) generalizes correctly to "any bot," so the broader primitive is arguably the more correct one here. No action needed.

Scope not touched by this diff (N/A)

Parity, i18n, contract drift/backward-compat, DESIGN.md/AI-slop, backend hard rules, and FEATURES.md parity are all N/A — this diff touches only .github/workflows/guards.yml, no apps/*, packages/shared, or orbit-api source. Comment-policy (no-comments.cjs) doesn't gate YAML, so the added comment was reviewed by judgment instead of the linter and satisfies the WHY-note bar independently.

Deferred

  • Live CI check status for this PR was not retrievable in this session (gh subcommand access beyond pr view/pr diff was gated); YAML well-formedness and Actions-expression syntax were verified by inspection instead.
  • The referenced orbit-api#425 (Hangfire release note blocked by en dashes) is in a sibling repo not checked out here — taken as author-asserted, not independently verified.

Automated review via /pr-review.

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit d467b38 into main Jul 24, 2026
27 checks passed
@thomasluizon
thomasluizon deleted the fix/dash-ban-bot-authored-prs branch July 24, 2026 21:51
thomasluizon added a commit that referenced this pull request Sep 18, 2026
The headless Claude engine inherited every user-scope MCP server, because
--permission-mode bypassPermissions removes the interactive approval and the
PreToolUse hooks match Bash and PowerShell only. --strict-mcp-config with no
--mcp-config beside it loads zero servers instead.

--output-format stream-json keeps the launcher's log-growth progress signal
live, so a worker that spends a long model turn without touching the tree is no
longer killed as KILLED_NO_PROGRESS. The installed binary refuses stream-json
without --verbose.

The config reader validates config.workers[config.worker] only, so a typo in the
fallback engine surfaced at the first real launch, after reserveWorkerLaunch had
spent one of the two launches the branch cap allows. The gate now resolves every
declared tier of every declared engine.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
core.md loads on every turn and read "Claude never edits code", full stop, so a
session that hit an exhausted allowance read that first and stopped the night.
That is the ending D104 exists to prevent. It now points at the fallback.

Five corrections inside the section the tree contradicted: the launcher's pid is
the wake source with the worker's pid beside it, a subagent does NOT end the
night silently because the Stop hook blocks, the --dry-run is an argv check
rather than a preflight, the calibration stamp goes stale for the duration of
the switch and nobody reseeds it, and the worker edit is uncommitted with
git checkout -- .claude/orchestrator.json as its revert. The staleness trap in
assertNotStale is named beside them, because it fires on the config edit itself.

The reviewer clause turns on the word quietly. On protected main no substitute
may stand in for pullfrog-approval. On unprotected redesign/main no such check
exists to forge, so a separate-agent review is acceptable when the pull request
comment names the substitution and lists checkable evidence.

The skill is calibrated, so its verdict is rewritten for the new text and the
stamp reseeded: 1 entry renewed, 31 carried forward, worker still codex.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
Round 3 on #598. The reviewer clause 5.4.1 gained in round 2 contradicted
three other sections of the same file, so a night run reached a different
merge answer depending on which section it read.

- Step 8, Hard prohibitions and 5.4.1 now state one branch-dependent rule:
  absolute on protected main, a named separate-agent review admissible on
  unprotected redesign/main, and a green check alone never an approval.
- 5.4.1 records that the clause came from a written instruction at entry,
  not from a contract this run granted itself.
- Pin the claude engine's command and both model ids by name, because
  resolveWorkerInvocation never reads command and reads the model from the
  block the loop compares against. claude-opus-4-8 is a real id, so that
  typo would run a whole night in silence.
- Name the tools-harness red beside the calibration red, by assertion, and
  say neither licenses editing a test.
- Correct the assertNotStale headline, the stream-json rationale, the
  launch-cap comment, the launcher line reference and the missing
  --tier mechanical dry run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
The claude engine assertion checked that `--strict-mcp-config` is present and
never that `--mcp-config` is absent, so adding the flag left every user-scope
MCP server loading into a worker at bypassPermissions with the test still
green. Assert the absence.

`record-readiness.mjs` supplies the `pullfrog-approval` axis itself when
protection names no required check, which is the whole reason an unprotected
base is not a free pass (#429). That rule now lives beside the reader it feeds
as `reviewChecksFor`, so a test drives the real rule, and the receipt verdict
it produces on `redesign/main` with no review is pinned as CI_STALE alone.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
Round 3 reconciled the four prose sections and left the tool saying something
else. `record-readiness.mjs` supplies `pullfrog-approval` itself when
protection names no required check, so on `redesign/main` a named substitute
reviewer still produces a blocked receipt, and step 9 said a blocked receipt
never permits a merge. One run merged and one run stopped from one tree.

Step 9 now carries the single exception and the reasoning for keeping the
recorder strict: it reads checks and reviews, never a claim in a comment, and
a recorder that accepts an unverifiable claim is worse than a written
exception a human reads (#429). Step 7 and the queue name the same
branch-conditional bar by reference instead of naming Pullfrog.

Hard prohibitions and §5.4.1 now say what they decide: the case where
Pullfrog produced neither the check nor an APPROVED review, so neither reads
as banning the #440 fallback that step 8 and the tool both allow on `main`.

Also: three progress signals rather than four, the live `worker` edit is
named in the handoff inventory and the step 11 report, and core.md names the
file its two pointers live in.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
…erdict

The merge bar moved out of prose alone and into step 9, where
`record-readiness.mjs` decides. The verdict says what that costs a reader: the
recorder cannot verify a substitution written in a comment, so it blocks on
the review axis by design, and the run has to tell that one expected verdict
apart from a real blocker reported under the same name.

One entry renewed, 31 carried forward.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
`reviewChecksFor` moved the rule, so step 9 names its real home and the line
the recorder calls it from. The exception paragraph now also says what
happens after it: a merge admitted there closes the pull request exactly as a
READY one does, and when it does not apply the run records the `blocker`
string that the Stop hook already accepts as a legitimate BLOCKED ending.

Reseeded after the edit: one entry renewed, 31 carried forward.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
…at ending

`merged` accepted any non-empty string, so copying the skill's own template and
leaving `<merge commit sha once it is merged, or absent>` unfilled reported an
UNMERGED pull request as a finished night, printing nothing. Driven against
`checkSleepStop`: that literal and the bare word "yes" both returned null.

Both predicates now test `/^[0-9a-f]{7,40}$/`, in the Stop hook and in the
recorder, and a new assertion pins the two copies to the same literal so the
lenient direction cannot drift back. A merged-only completion returns a terminal
MERGED banner naming each pull request and its sha, and `require-wake-source.mjs`
writes it beside the BLOCKED one, so the sha reaches a reader.

Step 11 now asks for the disposition per pull request, the readiness-receipt
assertion compares against the literal `pullfrog-approval` rather than the
constant it imports from the module under test, and both SKILL.md and NEXT.md
name the load-sensitive `launch-worker.mjs` CPU test so a fifth FAIL is checked
against that name first.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
…e sha

The skill gained a shape-checked `merged` sha, a MERGED terminal banner, the
disposition per pull request in step 11, and the note that its fifth FAIL may be
the load-sensitive CPU test rather than a defect. The verdict now names the call
that change asks for: which self-asserted fact stays honest is the one a reader
can check.

`worker` stays `codex`, so `workerMoved` is false and the other 31 verdicts carry
forward untouched: `stamped 32 file(s) ... 1 verdict(s) renewed, 31 carried
forward`.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
This round did not reproduce `ORBIT TOOLS GATE FAILED (1)` on the CPU test: the
full run passes it at 1824 assertions. The measurement belongs to the review at
head `4d590110`, so both documents now say so rather than reading as this run's
own observation. The mechanics stay confirmed from the tree.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Sep 18, 2026
…tract (#1023)

* chore: add a Claude headless worker engine and forbid subagent workers

An exhausted Codex allowance stopped every code round today. It should not:
the launcher already routes on the `worker` key, so a second engine is all
that was missing.

`.claude/orchestrator.json` now declares `claude` beside `codex`, running
Claude Code headless through the same launcher, order file, worktree,
timeouts and wake source. Flags were confirmed from `claude --help`, not
assumed, and `--dry-run` resolves both tiers.

`§5.4.1` of the orchestrate skill states the switch plainly and forbids
substituting a Claude subagent for a worker. Measured today: five subagents
ran as workers, registered no wake source so the Stop hook read the run as
having no continuation, and each had to be told to commit before a handoff
was possible, because a subagent dies with its session and leaves its edits
uncommitted in a worktree with no record of what they were.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep codex as the committed default worker

tools/test-tools.mjs pins the shipped default implementer to gpt-5.6-sol at
high reasoning effort, and committing worker="claude" broke both of those
assertions. The gate is right: the shipped default belongs in the file, and
running on Claude is an operational switch a session makes locally while the
Codex allowance is out, not a new default.

D95 forbids editing the gate a run is judged by, so the assertion stands and
the config yields.

Harness went from 61 failures to 1 with this change. The remaining one is
create-worktree.mjs hitting EPERM on a lock file under Temp in its
concurrency case, which touches nothing here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: isolate the claude worker engine and cover every declared engine

The headless Claude engine inherited every user-scope MCP server, because
--permission-mode bypassPermissions removes the interactive approval and the
PreToolUse hooks match Bash and PowerShell only. --strict-mcp-config with no
--mcp-config beside it loads zero servers instead.

--output-format stream-json keeps the launcher's log-growth progress signal
live, so a worker that spends a long model turn without touching the tree is no
longer killed as KILLED_NO_PROGRESS. The installed binary refuses stream-json
without --verbose.

The config reader validates config.workers[config.worker] only, so a typo in the
fallback engine surfaced at the first real launch, after reserveWorkerLaunch had
spent one of the two launches the branch cap allows. The gate now resolves every
declared tier of every declared engine.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: say what the claude worker engine means when an operator sets it

core.md loads on every turn and read "Claude never edits code", full stop, so a
session that hit an exhausted allowance read that first and stopped the night.
That is the ending D104 exists to prevent. It now points at the fallback.

Five corrections inside the section the tree contradicted: the launcher's pid is
the wake source with the worker's pid beside it, a subagent does NOT end the
night silently because the Stop hook blocks, the --dry-run is an argv check
rather than a preflight, the calibration stamp goes stale for the duration of
the switch and nobody reseeds it, and the worker edit is uncommitted with
git checkout -- .claude/orchestrator.json as its revert. The staleness trap in
assertNotStale is named beside them, because it fires on the config edit itself.

The reviewer clause turns on the word quietly. On protected main no substitute
may stand in for pullfrog-approval. On unprotected redesign/main no such check
exists to forge, so a separate-agent review is acceptable when the pull request
comment names the substitution and lists checkable evidence.

The skill is calibrated, so its verdict is rewritten for the new text and the
stamp reseeded: 1 entry renewed, 31 carried forward, worker still codex.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: make the merge bar say one thing in all four places

Round 3 on #598. The reviewer clause 5.4.1 gained in round 2 contradicted
three other sections of the same file, so a night run reached a different
merge answer depending on which section it read.

- Step 8, Hard prohibitions and 5.4.1 now state one branch-dependent rule:
  absolute on protected main, a named separate-agent review admissible on
  unprotected redesign/main, and a green check alone never an approval.
- 5.4.1 records that the clause came from a written instruction at entry,
  not from a contract this run granted itself.
- Pin the claude engine's command and both model ids by name, because
  resolveWorkerInvocation never reads command and reads the model from the
  block the loop compares against. claude-opus-4-8 is a real id, so that
  typo would run a whole night in silence.
- Name the tools-harness red beside the calibration red, by assertion, and
  say neither licenses editing a test.
- Correct the assertNotStale headline, the stream-json rationale, the
  launch-cap comment, the launcher line reference and the missing
  --tier mechanical dry run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(tools): pin the MCP absence and the unprotected review verdict

The claude engine assertion checked that `--strict-mcp-config` is present and
never that `--mcp-config` is absent, so adding the flag left every user-scope
MCP server loading into a worker at bypassPermissions with the test still
green. Assert the absence.

`record-readiness.mjs` supplies the `pullfrog-approval` axis itself when
protection names no required check, which is the whole reason an unprotected
base is not a free pass (#429). That rule now lives beside the reader it feeds
as `reviewChecksFor`, so a test drives the real rule, and the receipt verdict
it produces on `redesign/main` with no review is pinned as CI_STALE alone.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: make the tool and the prose agree on the merge bar

Round 3 reconciled the four prose sections and left the tool saying something
else. `record-readiness.mjs` supplies `pullfrog-approval` itself when
protection names no required check, so on `redesign/main` a named substitute
reviewer still produces a blocked receipt, and step 9 said a blocked receipt
never permits a merge. One run merged and one run stopped from one tree.

Step 9 now carries the single exception and the reasoning for keeping the
recorder strict: it reads checks and reviews, never a claim in a comment, and
a recorder that accepts an unverifiable claim is worse than a written
exception a human reads (#429). Step 7 and the queue name the same
branch-conditional bar by reference instead of naming Pullfrog.

Hard prohibitions and §5.4.1 now say what they decide: the case where
Pullfrog produced neither the check nor an APPROVED review, so neither reads
as banning the #440 fallback that step 8 and the tool both allow on `main`.

Also: three progress signals rather than four, the live `worker` edit is
named in the handoff inventory and the step 11 report, and core.md names the
file its two pointers live in.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(calibration): reseed the orchestrate stamp and reconsider its verdict

The merge bar moved out of prose alone and into step 9, where
`record-readiness.mjs` decides. The verdict says what that costs a reader: the
recorder cannot verify a substitution written in a comment, so it blocks on
the review axis by design, and the run has to tell that one expected verdict
apart from a real blocker reported under the same name.

One entry renewed, 31 carried forward.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: correct the new citations and say how the night ends

`reviewChecksFor` moved the rule, so step 9 names its real home and the line
the recorder calls it from. The exception paragraph now also says what
happens after it: a merge admitted there closes the pull request exactly as a
READY one does, and when it does not apply the run records the `blocker`
string that the Stop hook already accepts as a legitimate BLOCKED ending.

Reseeded after the edit: one entry renewed, 31 carried forward.

#598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(orchestrate): let a merged pull request end an unattended night

A pull request merged under the step 9 exception could never end the run that
merged it. The Stop hook reads the receipt and never the pull request state, and
that receipt stays CI_STALE forever because pullfrog-approval will not publish on
an unprotected base. Driven against checkSleepStop: a merged row with no blocker
returned block:true, the 2026-08-08 deadlock reached after a successful merge.

Adds `merged` as the third ledger disposition beside READY and BLOCKED. The bar is
the merge commit sha, a fact the run records and a reader can check on GitHub. It
is sticky where a blocker is not, because a merge cannot be undone.

Also corrects two things the tree contradicted. The expected-FAIL count for the
worker switch is four, not two: the earlier reading used --only orchestrator-config,
which cannot see the two launch-worker pins. And the MCP absence was asserted on
workers.claude.args, while the worker's real argv is the engine array concatenated
with the tier's, so --mcp-config in a tier's args passed the gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(calibration): reseed the orchestrate stamp for the third disposition

Sharpens the CI_STALE signature in step 9, then reseeds. The signature is
delivery's ci.checks.pass true beside the receipt's ci.green false, and only a
required check absent from the rollup entirely reads that way: one that ran and
failed lands in rollup.failing, and a running one in rollup.pending, so either
drops delivery's own pass.

The verdict text is rewritten before the stamp, so the reseed is not re-blessing
old reasoning about new prose. worker stays codex, so workerMoved is false and
the other 31 dates carry forward untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(orchestrate): require a real sha for a merged row and announce that ending

`merged` accepted any non-empty string, so copying the skill's own template and
leaving `<merge commit sha once it is merged, or absent>` unfilled reported an
UNMERGED pull request as a finished night, printing nothing. Driven against
`checkSleepStop`: that literal and the bare word "yes" both returned null.

Both predicates now test `/^[0-9a-f]{7,40}$/`, in the Stop hook and in the
recorder, and a new assertion pins the two copies to the same literal so the
lenient direction cannot drift back. A merged-only completion returns a terminal
MERGED banner naming each pull request and its sha, and `require-wake-source.mjs`
writes it beside the BLOCKED one, so the sha reaches a reader.

Step 11 now asks for the disposition per pull request, the readiness-receipt
assertion compares against the literal `pullfrog-approval` rather than the
constant it imports from the module under test, and both SKILL.md and NEXT.md
name the load-sensitive `launch-worker.mjs` CPU test so a fifth FAIL is checked
against that name first.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(calibration): reseed the orchestrate stamp for the checked merge sha

The skill gained a shape-checked `merged` sha, a MERGED terminal banner, the
disposition per pull request in step 11, and the note that its fifth FAIL may be
the load-sensitive CPU test rather than a defect. The verdict now names the call
that change asks for: which self-asserted fact stays honest is the one a reader
can check.

`worker` stays `codex`, so `workerMoved` is false and the other 31 verdicts carry
forward untouched: `stamped 32 file(s) ... 1 verdict(s) renewed, 31 carried
forward`.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: attribute the load-sensitive FAIL to the head it was measured at

This round did not reproduce `ORBIT TOOLS GATE FAILED (1)` on the CPU test: the
full run passes it at 1824 assertions. The measurement belongs to the review at
head `4d590110`, so both documents now say so rather than reading as this run's
own observation. The mechanics stay confirmed from the tree.

Refs #598

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant