Skip to content

chore: delete and rebuild the harness - #676

Merged
thomasluizon merged 8 commits into
mainfrom
chore/harness-rebuild
Aug 4, 2026
Merged

chore: delete and rebuild the harness#676
thomasluizon merged 8 commits into
mainfrom
chore/harness-rebuild

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Executes the plan approved on 2026-08-04. The harness measured 67,809 lines across three layers and had stopped being able to certify its own repair.

Why, in one number

PR #672 ran 9 local /pr-review rounds over 38 hours. Verdict every time: NEEDS_WORK. 19 findings, 19 unique, zero repeats. The fixer fixed everything and the reviewer found brand-new issues every round, on a 7,078-line diff against a stated 400-line cap. Termination required "the reviewer finds nothing", which on that diff has probability near zero. Correctly implemented and mathematically unable to stop.

Nearly every finding concerned the trust model of unattended merge: forged approval evidence, receipts trusting attacker-selected keys, replayable approvals. That adversary exists only under --sleep, where a machine acts on a receipt with no human present. --sleep is deleted. All 19 die by deletion.

What changed

Layer Before After
Global skills (~/.claude/skills/) 38,212 579
Project harness (tools/, .claude/, AGENTS.md, CLAUDE.md) 44,659 ~13,700
guards.yml 397 139
Always-loaded context 25,229 B 14,509 B
  • 86 files deleted: 29 tools, 25 test files, 10 skills, 4 review subagents, 1 hook, 6 CI workflows, 7 guards.yml jobs.
  • /feature merged into /ticket. Cardinality is now an output of the analysis, so one message listing several problems yields N tickets. It no longer tells you to quit and retype a different command.
  • launch-worker.mjs 1,477 to 263, a port not a rewrite. The 54-line Windows block is byte-identical: it encodes CVE-2024-27980, the ORB-88 mangled-prompt class, and codex exec "<prompt>" hangs indefinitely when stdin is a non-TTY pipe with no writer openai/codex#20919.
  • verify-delivery.mjs is new and is the sole authority for the word "delivered", derived only from git and gh.
  • Review is capped at 2 rounds with the finding list frozen after round 1, one mechanical carve-out for lines the fixer's own round-2 diff touched.
  • A machine never merges. Thomas merges. That single gate is what makes the whole simplification safe.

What this fixes, with evidence

The product-merge freeze. 9 of 44 required checks were harness self-checks. A red Harness Execution blocked every product merge. Branch protection is now 18 / 13 / 4.

A live hole in the admin-merge ban. httpie takes its method positionally, so http PUT .../pulls/{n}/merge was allowed while the byte-identical curl call was blocked. Found while rewriting the hook suite, which refused to write a passing assertion for something it had not verified.

A guardrail that blocked real work. grep -rnE 'claude|codex' was refused, because the splitter broke on the | inside the quoted pattern and read codex as an invocation. So was codex --version. Both are red-before, green-after in the suite.

A self-test that could not load. test-hooks.mjs imported a hook deleted minutes earlier. It now asserts settings.json and the hooks directory agree in both directions, which is exactly the check that would have caught it.

Where the plan was wrong, and what I did instead

  • orbit-api/mutation.yml: the plan said "0 runs. Ever." It has 284, ran four times today, all green. benchmark.yml is 20/20 green nightly. Neither was deleted. Verified against the live API.
  • forbid-raw-linear-mutation and forbid-ef-migration-raw-index were lumped in as harness self-checks. They guard Linear data integrity and EF migration idempotency. Kept.
  • design-reviewer was counted among the subagents absorbed into the rubric, but visual.yml invokes it as a CI job. Kept.
  • AGENTS.md cannot reach its 4,000-byte target. The "Never assume an external interface" section it must keep intact is itself exactly 4,000 bytes. It survives byte-identical, both measured failure cases included.

Verification

  • node tools/test-tools.mjs : 158 assertions, exit 0, hermetic
  • node .claude/hooks/test-hooks.mjs : 118 assertions, exit 0
  • Both suites proven able to go red, not just green
  • Dash Ban baseline and Copy Register pass locally

Not proven yet

The new harness has not completed a full end-to-end run. One attempt on ORB-87 reached delivery verification and correctly returned NO_COMMIT when codex exec exited 0 having written nothing, which is openai/codex#19945's exact shape. That is the invariant working, but steps 8 through 13 are unexercised. Re-prove on a real ticket after this merges.

Paired PRs

🤖 Generated with Claude Code

thomasluizon and others added 7 commits August 4, 2026 13:03
… /ticket)

Replaces the orchestration core with the design approved on 2026-08-04.

- launch-worker.mjs 1477 -> 263: headless only, stdin closed, foreground
  watchdog owning a 45 min hard ceiling and a 10 min no-progress timer.
  The Windows block (resolveOnPath, NPM_SHIM_SCRIPT, headlessInvocation)
  is a byte-identical port: it encodes CVE-2024-27980, the ORB-88 mangled
  prompt class, and openai/codex#20919.
- verify-delivery.mjs, new: the sole authority for "delivered", derived
  only from git and gh. A worker exit code proves nothing.
- orchestrate 770 -> 358: one ticket, no --sleep, no waves. Adds the D32
  always-loaded byte report and the D33 skill-collision assertion.
- pr-review 766 -> ~380: findings frozen after round 1, hard cap of two
  rounds, blocking/non-blocking severity floor, five review subagents
  absorbed into the rubric plus a new test-quality dimension.
- ticket: /feature merged in. Cardinality is now an output of the
  analysis, so one message listing several problems yields N tickets.
- orchestrator-guardrails: quote-aware segmentation. It no longer blocks
  a grep whose pattern contains "codex", nor "codex --version".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rubric.md 430 -> 257 while carrying strictly more coverage: the security,
contract-alignment, parity, i18n and design subagents (327 lines) fold in
as dimensions, plus a new test-quality dimension built from the vendored
tdd skill's tautological-test definition.

Drops the dimensions that Lint and Type Check already fail on. Re-flagging
a gate's own finding by hand is noise, and core.md calls that the
most-broken rule in the repo.

Scoped around what three gates ACTUALLY do, read from the workflow YAML
rather than inferred from their names: Cross-Platform Parity compares only
per-platform file counts, Contract Drift regenerates from orbit-api main
and is blind to a paired in-flight API PR, and Design Token Guard greps
three patterns. The judgement each one does not make stays reviewer-owned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 0 asserted "clean, on main, up to date" before step 1 had resolved
which repository the ticket targets, so the placeholder could only mean
the orchestrator's own checkout. That is the wrong tree.

D17 pins the session to orbit-ui-mobile no matter where the work lands,
and the worktree branches from the TARGET repo's main. Checking this
checkout would refuse a legitimate run (a repo:api ticket driven from any
non-main branch here) and pass a dangerous one (a repo:api ticket whose
own main is dirty or behind).

Split into 0a, environment only, which needs no ticket, and 0b, the
target repo, which runs after step 1 and before the worktree is created.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two of the three 0b conditions are one safe idempotent command away, and
refusing a run because a repo is one commit behind is friction with no
safety in it. That pattern is what froze the product.

  behind origin/main  -> fetch + merge --ff-only
  not on main         -> git switch main
  dirty tree          -> STOP, unchanged

The dirty tree stays a hard stop and is never auto-repaired. Uncommitted
work is somebody's unsaved thinking, and stashing hides it somewhere they
will not look.

Carve-out: never switch the branch of the repository the session is
running from. Doing so swaps this skill file, orchestrator.json and the
tools out mid-run, and the orchestrator would finish reading a different
harness than it started with.

Every repair is printed with its command. A silent repair starts a run
from a state nobody chose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CLAUDE.md and core.md load on every turn, so a stale instruction there is
not a docs nit, it is a wrong order given to every session. All three were
false as of the harness rebuild:

  "after /next, run /orchestrate ORB-N --only"  -> /next and --only are gone
  "guards.yml + merge-sweep gate merges"       -> merge-sweep is deleted,
                                                  and only Thomas merges
  core.md naming /feature                      -> merged into /ticket

Not the full CLAUDE.md trim, which is a later step. These three would have
sent the next session after tooling that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes the layer the rebuild replaces. Every deletion stands on its own
evidence: it serves a deleted feature, is provably inert, was shadowed, or
checks the harness rather than the product.

  29 tools + 25 test files + 7 fixtures   unattended merge, wave planning,
                                          the automation budget, the strike
                                          ledger, TUI worker driving, and
                                          nine harness self-checks
  10 skills                               /next /watch /rollup /quota
                                          /make-tool /android-generate
                                          /dep-sweep /profile /commit-sweep
                                          /llm-council
   4 review subagents                     absorbed into pr-review/rubric.md
   1 hook                                 forbid-raw-repo-tool-surfacing
   6 CI workflows                         plus 7 jobs cut from guards.yml,
                                          397 -> 139 lines, leaving exactly
                                          the five surviving required checks

Three things the deletion lists got wrong, kept instead:

- forbid-raw-linear-mutation and forbid-ef-migration-raw-index were lumped
  in with the harness self-checks. They guard Linear data integrity and EF
  migration idempotency, which is product safety, not harness bookkeeping.
- design-reviewer was counted among the subagents absorbed into the rubric,
  but visual.yml invokes it as a CI job. Deleting it breaks that job.
- calibration.json and two fixture directories lost their only readers, so
  they go as orphans rather than being patched.

Closes a real hole found while rewriting the hook suite: httpie takes its
method positionally, so `http PUT .../pulls/{n}/merge` was ALLOWED while the
byte-identical curl call was blocked. The admin-merge ban is the prohibition
everything else rests on, and it had a documented bypass.

Also stops rules-linear.mjs handing out `node tools/new-ticket.mjs` in its
block message, a command that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two suites now prove the surviving harness rather than the deleted one.

  test-tools.mjs 261 -> 201, _harness.mjs 1158 -> 318, and three new test
  files for the tools that changed today. 158 assertions, ~53s, hermetic:
  no network, no Linear, no model invocation. The coverage contract now
  fails in BOTH directions, so a row naming a tool that no longer exists is
  a failure. That gap is how 29 rows outlived their tools.

  test-hooks.mjs 1538 -> 300, 118 assertions. It asserts settings.json and
  the hooks directory agree in both directions, which is the check that
  would have caught this morning's breakage: the suite imported a hook that
  had just been deleted and could not even parse.

Both suites are proven able to go RED, not just green.

Always-loaded context, 25,229 -> 14,509 bytes:

  CLAUDE.md  10,449 -> 6,654    core.md  3,015 -> 2,197
  AGENTS.md  11,765 -> 5,658    hot.md   8,668 -> 5,968 (brain vault)

AGENTS.md cannot reach its 4,000-byte target: the "Never assume an external
interface" section it must keep intact is itself exactly 4,000 bytes, so
that is the floor. It survives byte-identical, both measured failure cases
included. What went instead was the worker contract, now injected into every
prompt by compose-prompt.mjs, and the Code Review Rules, whose five rules
all live in rubric.md dimensions 7 and 8, core.md rule 4, and standard 8.

Adds three code standards Thomas approved: simplest implementation, layered
growth, and lean on existing dependencies. Not the fourth, which would have
inverted the append-only DTO contract that exists because mobile lags behind
the Play store.

launch-worker.mjs now exports ORCA_CLI_COMMAND to the worker. Measured on
ORB-87: the worker resolved bare `orca`, which is not on PATH, and correctly
refused rather than falling through. It delivered nothing in 54 seconds.

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

vercel Bot commented Aug 4, 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 Aug 4, 2026 5:35pm

Request Review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1fad3b514a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/launch-worker.mjs
Comment on lines +210 to +213
try {
process.kill(-pid)
} catch {
/* a tree that is already gone still ends this launch; the exit handler reports what happened */

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Start a process group before signaling it

On POSIX, spawn() does not create a new process group unless detached: true is set, so process.kill(-pid) targets a nonexistent group and the catch silently ignores ESRCH. Running node tools/test-tools.mjs confirmed that the configured 0.02-minute timeout did not terminate the 60-second sleeper; it only reported success after the sleeper exited naturally. A genuinely hung worker therefore survives both watchdog clocks indefinitely.

AGENTS.md reference: AGENTS.md:L15-L24

Useful? React with 👍 / 👎.

Comment thread tools/verify-delivery.mjs Outdated
checks.pushed = { pass: localOnly === 0, observed: localOnly }
if (!checks.pushed.pass) emit("UNPUSHED")

const listed = run(GH, ["pr", "list", "--head", branch, "--json", "number,url,headRefOid,additions,deletions"], githubCwd)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Verify that the pull request links the ticket

When a worker opens one current, in-cap PR without mentioning ORB-N, this query still finds it and the script emits DELIVERED, even though the composed work order requires a PR that links the issue. gh pr list --help confirms that --head filters only the head branch and that the default state is open, so nothing in this invocation ties the PR to issue; the sole delivery authority must independently verify that association.

Useful? React with 👍 / 👎.

Comment thread tools/verify-delivery.mjs
Comment on lines +158 to +160
const size = pullRequest.additions + pullRequest.deletions
checks.diffSize = { pass: size <= DIFF_CAP, observed: size, cap: DIFF_CAP }
if (!checks.diffSize.pass) emit("OVERSIZE")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce the affected-file cap before declaring delivery

A worker can change 9 or more files while staying under 400 added/deleted lines, and this verifier will still return DELIVERED. The new configuration and composed prompt call caps.affectedFiles a hard cap, but the sole delivery authority checks only total line count, so the post-work scope gate does not enforce one of its two promised limits.

Useful? React with 👍 / 👎.

Comment on lines 364 to 366
```bash
bash tools/merge-sweep-cov.sh \
--issue <pr-number>=ORB-N \
--reviewed-through <pr-number>=<ISO-8601-timestamp> \
<owner/repo> <pr-number>
node tools/teardown-worktree.mjs --issue ORB-N --worktree <p>
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass exactly one selector to teardown

After a PR is merged, the documented step invokes teardown with both --issue and --worktree, but teardown-worktree.mjs explicitly requires exactly one selector and exits 2 when both are present. Following /orchestrate verbatim therefore makes every completed run fail at cleanup and leaves its Orca worktree and local branch behind.

Useful? React with 👍 / 👎.

Comment thread tools/check-ticket.mjs
@@ -1,477 +0,0 @@
#!/usr/bin/env node

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove stale audit calls before deleting the validator

Deleting this executable breaks the ticket-emission phase of five retained workflows: a commit-tree-wide git grep still finds mandatory node tools/check-ticket.mjs --file calls in audit-code-quality, audit-performance, audit-security, audit-tests, and prod-readiness. Each workflow now reaches that command and stops with MODULE_NOT_FOUND; update those callers to the rebuilt validation flow as part of the deletion.

Useful? React with 👍 / 👎.

@@ -1,208 +0,0 @@
#!/usr/bin/env node

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the registered redesign coverage command

The root package.json still registers redesign:coverage as node tools/redesign-coverage.mjs, and a repo-wide search finds no replacement implementation. After this deletion, npm run redesign:coverage fails immediately with MODULE_NOT_FOUND, removing the documented check for unclaimed redesign surfaces and orphaned UI files; either retain the tool or update the package command to its replacement.

Useful? React with 👍 / 👎.

Five were broken on first use. Verified each against the tree before fixing.

- teardown was documented with BOTH --issue and --worktree, and the tool
  exits 2 on both. Following /orchestrate verbatim made every completed run
  fail at cleanup and leave its worktree behind. My error, in two places.
- package.json still ran `npm run redesign:coverage` against a deleted tool.
- Five skills still called `tools/check-ticket.mjs`: audit-code-quality,
  audit-performance, audit-security, audit-tests, prod-readiness. Each would
  have stopped with MODULE_NOT_FOUND at its ticket-emission phase. They now
  re-read the draft against the 6.2 template instead.
- verify-delivery enforced only ONE of the two promised caps. A worker could
  touch 20 files while staying under 400 lines and still read DELIVERED.
- verify-delivery never checked that the pull request names the ticket:
  `--head` filters on the branch alone, so a PR that never mentions ORB-N
  passed the sole delivery authority. New UNLINKED_PR verdict.
- process.kill(-pid) signalled a process group that spawn never created, so
  the POSIX watchdog was a silent no-op. Fixed with detached on POSIX only;
  Windows keeps taskkill /T, where detached would spawn a console window.

The seventh finding, that the watchdog does not kill a hung worker, is NOT
reproducible: the suite runs a 60-second sleeper twice and finishes in 49
seconds asserting KILLED_NO_PROGRESS and KILLED_HARD_CEILING. A sleeper that
survived could not exit inside that window. The POSIX branch was genuinely
dead, which is the defect underneath it, and is fixed above.

The gh fields title, body and files were read off a real response before
being used. `files` truncates at 100 entries, so a length of 100 is reported
as "at least 100" rather than as a count that would be a lie.

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

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit 2da9ec4 into main Aug 4, 2026
25 checks passed
@thomasluizon
thomasluizon deleted the chore/harness-rebuild branch August 4, 2026 17:50
thomasluizon added a commit that referenced this pull request Aug 4, 2026
* chore: ignore agent scratch files by shape, not by name

Seven session-transcript parsers accumulated at the repo root on
2026-08-04 (.orca-extract, .sess-now, .session-durable and four more),
and eleven of the same class in iqpay-api. They are throwaway scripts an
agent wrote while analysing a session, and tools/README.md already says a
throwaway belongs in the scratchpad and never in a repo.

That rule was prose, so it decayed. The tell is the line this replaces:
.tmp_session_text.txt was added by exact filename after an earlier
instance, which fixed one file and left the shape open.

Ignores the shape instead: .tmp-*, .tmp_*, .sess-*, .session-*, .orca-*.

Verified no tracked file is newly ignored: the count of tracked-but-ignored
paths is 5 both before and after, all pre-existing ORB-116 screenshot
artifacts.

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

* chore: delete stale harness artifacts the rebuild left behind

Every deletion below is a file whose reader was removed by the harness rebuild
(#676) or a file no reader ever had. Each was grepped across all three Orbit
repos and the brain vault before it went.

Five baselines and snapshots whose reader no longer exists:

  tools/lockstep-declarations.json   read by tools/check-lockstep.mjs
  tools/context-budget.json          read by tools/check-context-budget.mjs
  tools/on-demand-budget.json        read by tools/check-context-budget.mjs
  tools/archaeology-baseline.json    read by tools/check-archaeology.mjs
  tools/required-gates.json          read by tools/check-required-gates.mjs
  .claude/linear-team-labels.json    read by the tier-labels guards job

None of those five tools is on disk. required-gates.json names its own missing
reader in its "note" field. Two of them had also drifted into nonsense:
context-budget.json claims CLAUDE.md is 10,504 bytes when it is 6,654, and
on-demand-budget.json claims orchestrate/SKILL.md is 53,533 bytes when it is
19,024. Both numbers survived only because nothing reads them.

skills-lock.json, whose entire content was {"version":1,"skills":{}}. The
calibration job that wrote it is gone.

Two mocks that are literally `export {}` and are absent from every alias map in
apps/mobile/vitest.config.ts:

  apps/mobile/test-mocks/shared-empty.ts
  apps/mobile/test-mocks/shared-types.ts

Five ORB-116 artifacts untracked, not deleted from disk. .gitignore:72 already
ignores .artifacts/, which capture-surfaces.mjs regenerates; these five were
committed once in #636 and have been both tracked and ignored ever since. The
ignore rule is right, so the tracking was the error.

Dead references in two files. guards.yml's header listed thirteen jobs when the
file defines five, naming context-budget, archaeology, frontmatter, harness,
lockstep, calibration and tier-labels, all deleted, plus three escape-hatch
labels with no job left to honour them. second-opinion/SKILL.md still described
an unattended run as a `--sleep` run; that flag no longer exists.

Also carries the unpushed .gitignore commit that ignores agent scratch by shape,
since it gates the same class of file this sweep found.

Kept, having tried to prove them dead and failed: mutation.yml (696 runs),
perf.yml (82), visual.yml (96), promote-prod.yml (5, all green, dispatch-only),
.claude/mcp.json.example (a template to copy), .agents/skills/* (Codex-side
pointers), api.generated.ts (a Contract Drift diff target, documented as never
imported), and mono-toggle.tsx on both platforms, which has no importer but is
the settings toggle the Figma handoff specifies.

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

* chore: delete MonoToggle, close the scratch ignore gap, unbreak the mobile lint hook

MonoToggle was implemented on both platforms and wired to nothing. No file in
either app imports it. Its only appearances anywhere were three coverage
exclusion lists, which record that it was never tested because it was never
used, not that anything uses it. Thomas confirmed it is not the intended
settings toggle, so it goes with everything its deletion orphaned:

  apps/web/components/ui/mono-toggle.tsx
  apps/mobile/components/ui/mono-toggle.tsx
  its entry in apps/web/vitest.config.ts and apps/mobile/vitest.config.ts
  both entries in sonar.coverage.exclusions, plus the prose naming it
  common.toggleOn and common.toggleOff in en.json and pt-BR.json

Those two locale keys had no other consumer. They land in both files in the
same edit, as the parity rule requires. The Figma handoff still defines
MonoToggle, so the design source is unchanged; only the unused code goes.

.gitignore gains .agent-scratch-*. The commit that added the scratch patterns
covers .tmp-*, .tmp_*, .sess-*, .session-* and .orca-*, and the untracked file
that motivated it was .agent-scratch-extract.mjs, which matches none of them.
The gate did not cover its own trigger case.

lefthook.yml loses --fix from mobile-eslint, because that hook could not pass.
Measured: `expo lint` exits 0, `expo lint --fix` exits 2. `expo lint` ignores
its file arguments and walks the whole app, so --fix repaired all 718 suppressed
local/spacing-scale violations across 65 unstaged files, which orphaned those
same 718 suppressions and tripped eslint's stale-suppression error. A hook that
mass-edits files nobody staged is a hazard even when it exits 0. Pruning the
suppressions does not help: nothing is stale until --fix creates the staleness.
stage_fixed went with it, since nothing is fixed now.

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

* chore: regenerate the architecture map after the toggle key removal

Architecture map drift went red on PR #679 (run 30943557070). architecture.json
and architecture.html are generated and committed, and arch-map.yml regenerates
then fails on any diff, so deleting common.toggleOn and common.toggleOff had to
land in the map in the same PR.

Regenerated with the workflow's own command, node tools/arch-map.mjs. The diff
is exactly those two i18n keys and nothing else.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
Wires the three tools from the parent PR into the contract. /orchestrate now
takes one ticket, several, a project, or --auto, and grows --sleep, --parallel
and --auto. One ticket with no flags behaves exactly as it did.

--sleep opens pull requests and never merges one. Every piece of signing,
provenance, ledger and merge-sweep machinery the OLD --sleep needed stays
deleted, because nothing acts on a receipt. What makes an unattended run
trustworthy now is verify-delivery.mjs, which reads only artifacts and never a
worker's self-report. That is what was missing the first time.

Stacking, because nothing merges overnight: a ticket whose blocker is in the
same queue branches from its blocker's branch and targets it, since main will
not contain the blocker's work until morning. --base is passed to
verify-delivery too, or the size caps read the parent's commits as this
ticket's. A stack lives in one repository, so cross-repo blockers stay separate
pull requests and the api one deploys first.

New step 12 clears the Codex reviewer's threads, which nothing has ever read:
8 threads opened across #676, #680 and #681, 8 still unresolved, all merged.
The verdict comes from the presence of a review, never the thread count, and
NO_REVIEW is never reported as clean. Every resolve replies first. One fix
round, then re-verify and post @codex review explicitly, because the bot
reviews on open and on request but NEVER on a push: #676's only review landed
at 17:23:33Z while commits ran to 17:35:14Z.

Two jump references changed MEANING, not just digits. "0 blocking -> step 12"
and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the
bot pass"; a mechanical renumber would have skipped the new step on the
cleanest and most common path. The OPEN branch reaches it too, report-only.

Preflight 0a now asserts the ACTIVE gh account owns the target remote, not
merely that some account is logged in. This run hit the exact failure it
guards: a push refused for thomas-luizon_iqpay after the work was done.

Preflight 0b runs once per distinct repo rather than once per ticket, so
concurrent fetch and merge --ff-only cannot race on .git/index.

The visual layer is deliberately absent. A visible-effect ticket runs, opens
its pull request, and is NOT moved to In Review: the run prints "visual check
owed" and Thomas looks with /dev-server. The screenshots existed to prove
correctness before a machine merged, and no machine merges.

Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or
the bot fix round reads as a forbidden third round. Amends the merge
prohibition to say what it means: never merges UNASKED. Thomas asking for a
merge in a later turn is the intended path; --admin, the raw REST and GraphQL
paths, force pushes and pushes to main stay blocked in every case.

caps.parallelTickets is 3, not 8: each worktree is a full install, build and
test run plus its own model session.

node tools/test-tools.mjs           ORBIT TOOLS GATE OK
node .claude/hooks/test-hooks.mjs   ORBIT HOOKS OK
node tools/check-dashes.mjs         clean

Refs ORB-179, ORB-180.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
…m it never happens

#676 reviewed once and never again while commits kept landing. #682 reviewed the
old head, took a push, and reviewed the new head seven minutes later. The blanket
'never on a push' claim was written from #676 alone and #682 falsified it the same
day.

Nothing about the code changes: comparing review.commit.oid to headRefOid is what
makes the verdict correct under either behaviour, which is the point. Only the
claim is corrected, because a run that trusts 'never' would skip the freshness
check as redundant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
Wires the three tools from the parent PR into the contract. /orchestrate now
takes one ticket, several, a project, or --auto, and grows --sleep, --parallel
and --auto. One ticket with no flags behaves exactly as it did.

--sleep opens pull requests and never merges one. Every piece of signing,
provenance, ledger and merge-sweep machinery the OLD --sleep needed stays
deleted, because nothing acts on a receipt. What makes an unattended run
trustworthy now is verify-delivery.mjs, which reads only artifacts and never a
worker's self-report. That is what was missing the first time.

Stacking, because nothing merges overnight: a ticket whose blocker is in the
same queue branches from its blocker's branch and targets it, since main will
not contain the blocker's work until morning. --base is passed to
verify-delivery too, or the size caps read the parent's commits as this
ticket's. A stack lives in one repository, so cross-repo blockers stay separate
pull requests and the api one deploys first.

New step 12 clears the Codex reviewer's threads, which nothing has ever read:
8 threads opened across #676, #680 and #681, 8 still unresolved, all merged.
The verdict comes from the presence of a review, never the thread count, and
NO_REVIEW is never reported as clean. Every resolve replies first. One fix
round, then re-verify and post @codex review explicitly, because the bot
reviews on open and on request but NEVER on a push: #676's only review landed
at 17:23:33Z while commits ran to 17:35:14Z.

Two jump references changed MEANING, not just digits. "0 blocking -> step 12"
and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the
bot pass"; a mechanical renumber would have skipped the new step on the
cleanest and most common path. The OPEN branch reaches it too, report-only.

Preflight 0a now asserts the ACTIVE gh account owns the target remote, not
merely that some account is logged in. This run hit the exact failure it
guards: a push refused for thomas-luizon_iqpay after the work was done.

Preflight 0b runs once per distinct repo rather than once per ticket, so
concurrent fetch and merge --ff-only cannot race on .git/index.

The visual layer is deliberately absent. A visible-effect ticket runs, opens
its pull request, and is NOT moved to In Review: the run prints "visual check
owed" and Thomas looks with /dev-server. The screenshots existed to prove
correctness before a machine merged, and no machine merges.

Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or
the bot fix round reads as a forbidden third round. Amends the merge
prohibition to say what it means: never merges UNASKED. Thomas asking for a
merge in a later turn is the intended path; --admin, the raw REST and GraphQL
paths, force pushes and pushes to main stay blocked in every case.

caps.parallelTickets is 3, not 8: each worktree is a full install, build and
test run plus its own model session.

node tools/test-tools.mjs           ORBIT TOOLS GATE OK
node .claude/hooks/test-hooks.mjs   ORBIT HOOKS OK
node tools/check-dashes.mjs         clean

Refs ORB-179, ORB-180.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
The step 12 text claimed the bot never reviews on a push, written from #676
alone. #682 falsified it the same day: it reviewed the old head, took a push,
and reviewed the new head seven minutes later.

Both shapes are now named, and the conclusion is stronger for it. A re-review is
luck, never the mechanism, so the explicit @codex review request stays and
list-bot-threads comparing review.commit.oid to the head is what makes the
verdict correct under either behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
Wires the three tools from the parent PR into the contract. /orchestrate now
takes one ticket, several, a project, or --auto, and grows --sleep, --parallel
and --auto. One ticket with no flags behaves exactly as it did.

--sleep opens pull requests and never merges one. Every piece of signing,
provenance, ledger and merge-sweep machinery the OLD --sleep needed stays
deleted, because nothing acts on a receipt. What makes an unattended run
trustworthy now is verify-delivery.mjs, which reads only artifacts and never a
worker's self-report. That is what was missing the first time.

Stacking, because nothing merges overnight: a ticket whose blocker is in the
same queue branches from its blocker's branch and targets it, since main will
not contain the blocker's work until morning. --base is passed to
verify-delivery too, or the size caps read the parent's commits as this
ticket's. A stack lives in one repository, so cross-repo blockers stay separate
pull requests and the api one deploys first.

New step 12 clears the Codex reviewer's threads, which nothing has ever read:
8 threads opened across #676, #680 and #681, 8 still unresolved, all merged.
The verdict comes from the presence of a review, never the thread count, and
NO_REVIEW is never reported as clean. Every resolve replies first. One fix
round, then re-verify and post @codex review explicitly, because the bot
reviews on open and on request but NEVER on a push: #676's only review landed
at 17:23:33Z while commits ran to 17:35:14Z.

Two jump references changed MEANING, not just digits. "0 blocking -> step 12"
and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the
bot pass"; a mechanical renumber would have skipped the new step on the
cleanest and most common path. The OPEN branch reaches it too, report-only.

Preflight 0a now asserts the ACTIVE gh account owns the target remote, not
merely that some account is logged in. This run hit the exact failure it
guards: a push refused for thomas-luizon_iqpay after the work was done.

Preflight 0b runs once per distinct repo rather than once per ticket, so
concurrent fetch and merge --ff-only cannot race on .git/index.

The visual layer is deliberately absent. A visible-effect ticket runs, opens
its pull request, and is NOT moved to In Review: the run prints "visual check
owed" and Thomas looks with /dev-server. The screenshots existed to prove
correctness before a machine merged, and no machine merges.

Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or
the bot fix round reads as a forbidden third round. Amends the merge
prohibition to say what it means: never merges UNASKED. Thomas asking for a
merge in a later turn is the intended path; --admin, the raw REST and GraphQL
paths, force pushes and pushes to main stay blocked in every case.

caps.parallelTickets is 3, not 8: each worktree is a full install, build and
test run plus its own model session.

node tools/test-tools.mjs           ORBIT TOOLS GATE OK
node .claude/hooks/test-hooks.mjs   ORBIT HOOKS OK
node tools/check-dashes.mjs         clean

Refs ORB-179, ORB-180.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
The step 12 text claimed the bot never reviews on a push, written from #676
alone. #682 falsified it the same day: it reviewed the old head, took a push,
and reviewed the new head seven minutes later.

Both shapes are now named, and the conclusion is stronger for it. A re-review is
luck, never the mechanism, so the explicit @codex review request stays and
list-bot-threads comparing review.commit.oid to the head is what makes the
verdict correct under either behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
* feat(tools): plan a queue, read the Codex reviewer, close its threads

Three tools the overnight /orchestrate queue needs. All new files, nothing
wired yet: the SKILL.md contract change is the PR stacked on this one.

plan-queue.mjs resolves a scope (explicit tickets, a Linear project, or the
whole board) into one ordered execution plan. The rule that matters: a blocker
INSIDE the queue is a stack edge, because the branch can carry it, while an
open blocker OUTSIDE it defers the ticket, because its branch would have to
contain work that does not exist. Admission runs to a fixed point, since
dropping one ticket can strand another behind it. Cross-repo blockers order the
waves but never stack, because GitHub requires every branch in a stack to live
in one repository.

list-bot-threads.mjs makes "the second reviewer never ran" a verdict instead of
a silence read as approval. Measured on 2026-08-05 across PRs #676, #680 and
#681: the Codex bot opened eight inline threads, eight are still unresolved,
and all three are merged. Two shapes make a naive reading wrong, and both are
handled rather than documented: an empty thread list is ambiguous between
"clean" and "not yet", so the verdict comes from the presence of a bot review;
and a body-level CHANGES_REQUESTED opens no thread at all, so zero unresolved
threads is not proof of a clean PR. A draft attracts no review ever, so draft
is checked before the wait clock starts.

resolve-bot-thread.mjs replies first and resolves only once the reply is
confirmed. A thread closed with no reason is indistinguishable from one nobody
read, so the ordering is a gate rather than a convention: a failed or empty
reply exits non-zero having mutated nothing. A failed resolve after a landed
reply says so, so a retry cannot double-post.

Verified against live data, not fixtures alone: list-bot-threads reads PR #681
and reports its real unresolved P2 on tools/launch-worker.mjs, and plan-queue
orders ORB-179 before ORB-180 and stacks the second on the first. Both GraphQL
mutations were checked against the live schema; pullRequestReviewId is optional
and names a pending review, so it is omitted.

node tools/test-tools.mjs      ORBIT TOOLS GATE OK (21/20/12 new assertions)
node .claude/hooks/test-hooks.mjs   ORBIT HOOKS OK

Refs ORB-179, ORB-180.

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

* fix(tools): reject a bot review pinned to an older head, refuse forked stacks

Both defects were raised by the Codex reviewer on this pull request and both
were right. The cross-vendor review earned its keep on the very PR that adds
the tool for reading it.

P1, list-bot-threads: a review is evidence about the commit it was given on and
nothing else. The tool's own usage says the bot never reviews on a push, then it
accepted the newest bot review regardless of which commit that review saw. After
any fixup push it would report REVIEWED for code the bot never looked at, which
is precisely the defect the tool exists to remove. It now compares
review.commit.oid against headRefOid and keeps waiting otherwise, and NO_REVIEW
distinguishes "never reviewed" from "reviewed a dead head" by naming the stale
commit so the caller knows to post @codex review.

P2, plan-queue: a branch has ONE parent, so a ticket blocked by two INDEPENDENT
same-repo tickets cannot stack on both. It picked the lexicographically last and
said nothing, planning a branch that would not contain the other blocker's work
while the plan claimed both were satisfied. It now picks the deepest blocker in
topological order and asserts every other same-repo blocker is one of its
ancestors; a shape that cannot be expressed is refused by name rather than
emitted wrong. Blockers that do form a chain are unaffected, and a cross-repo
blocker alongside a same-repo one is not a fork.

Seven regression cases added, including the fork refusal, the chained pair, the
mixed cross-repo parent, a stale review, a fresh review following a stale one,
and a review carrying no commit at all.

node tools/test-tools.mjs   ORBIT TOOLS GATE OK  (list-bot-threads 24, plan-queue 24)

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

* docs(tools): a re-review after a push is not reliable, so do not claim it never happens

#676 reviewed once and never again while commits kept landing. #682 reviewed the
old head, took a push, and reviewed the new head seven minutes later. The blanket
'never on a push' claim was written from #676 alone and #682 falsified it the same
day.

Nothing about the code changes: comparing review.commit.oid to headRefOid is what
makes the verdict correct under either behaviour, which is the point. Only the
claim is corrected, because a run that trusts 'never' would skip the freshness
check as redundant.

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

* feat(tools): a resolve-only retry, and the body of a review-level block

Two P2s from the Codex re-review of this pull request, both real.

resolve-bot-thread: the failed-resolve branch told the caller to retry the
resolve alone and the tool offered no way to do it, so the advice could not be
followed. --resolve-only closes that WITHOUT weakening the no-bare-resolve rule:
it asks GitHub whether a reply is actually on the thread and refuses when there
is none, so the invariant is enforced against the live thread rather than
trusted from a flag. An already-resolved thread is a no-op exit 0.

list-bot-threads: a body-level CHANGES_REQUESTED carries its whole complaint in
the review body and opens no thread, so the caller learned it was blocked and
nothing about why. The body now rides along for that verdict only; on a
COMMENTED review it is boilerplate and the threads hold the findings.

The third P2, splitting sibling blockers into separate stack chains, is answered
on the thread rather than implemented. Refusing is one of the two remedies the
reviewer itself proposed in the previous round, and chaining two tickets Linear
never ordered would invent a dependency.

node tools/test-tools.mjs   ORBIT TOOLS GATE OK  (list-bot-threads 26, resolve-bot-thread 16)

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 5, 2026
* feat(orchestrate): a queue of tickets, a stack of reviewed pull requests

Wires the three tools from the parent PR into the contract. /orchestrate now
takes one ticket, several, a project, or --auto, and grows --sleep, --parallel
and --auto. One ticket with no flags behaves exactly as it did.

--sleep opens pull requests and never merges one. Every piece of signing,
provenance, ledger and merge-sweep machinery the OLD --sleep needed stays
deleted, because nothing acts on a receipt. What makes an unattended run
trustworthy now is verify-delivery.mjs, which reads only artifacts and never a
worker's self-report. That is what was missing the first time.

Stacking, because nothing merges overnight: a ticket whose blocker is in the
same queue branches from its blocker's branch and targets it, since main will
not contain the blocker's work until morning. --base is passed to
verify-delivery too, or the size caps read the parent's commits as this
ticket's. A stack lives in one repository, so cross-repo blockers stay separate
pull requests and the api one deploys first.

New step 12 clears the Codex reviewer's threads, which nothing has ever read:
8 threads opened across #676, #680 and #681, 8 still unresolved, all merged.
The verdict comes from the presence of a review, never the thread count, and
NO_REVIEW is never reported as clean. Every resolve replies first. One fix
round, then re-verify and post @codex review explicitly, because the bot
reviews on open and on request but NEVER on a push: #676's only review landed
at 17:23:33Z while commits ran to 17:35:14Z.

Two jump references changed MEANING, not just digits. "0 blocking -> step 12"
and "all CLOSED -> step 12" used to mean "hand over" and now mean "go to the
bot pass"; a mechanical renumber would have skipped the new step on the
cleanest and most common path. The OPEN branch reaches it too, report-only.

Preflight 0a now asserts the ACTIVE gh account owns the target remote, not
merely that some account is logged in. This run hit the exact failure it
guards: a push refused for thomas-luizon_iqpay after the work was done.

Preflight 0b runs once per distinct repo rather than once per ticket, so
concurrent fetch and merge --ff-only cannot race on .git/index.

The visual layer is deliberately absent. A visible-effect ticket runs, opens
its pull request, and is NOT moved to In Review: the run prints "visual check
owed" and Thomas looks with /dev-server. The screenshots existed to prove
correctness before a machine merged, and no machine merges.

Amends the §5.3 round cap to scope "round" to the frozen cross-vendor list, or
the bot fix round reads as a forbidden third round. Amends the merge
prohibition to say what it means: never merges UNASKED. Thomas asking for a
merge in a later turn is the intended path; --admin, the raw REST and GraphQL
paths, force pushes and pushes to main stay blocked in every case.

caps.parallelTickets is 3, not 8: each worktree is a full install, build and
test run plus its own model session.

node tools/test-tools.mjs           ORBIT TOOLS GATE OK
node .claude/hooks/test-hooks.mjs   ORBIT HOOKS OK
node tools/check-dashes.mjs         clean

Refs ORB-179, ORB-180.

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

* docs(orchestrate): state both measured shapes of the bot re-review

The step 12 text claimed the bot never reviews on a push, written from #676
alone. #682 falsified it the same day: it reviewed the old head, took a push,
and reviewed the new head seven minutes later.

Both shapes are now named, and the conclusion is stronger for it. A re-review is
luck, never the mechanism, so the explicit @codex review request stays and
list-bot-threads comparing review.commit.oid to the head is what makes the
verdict correct under either behaviour.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 12, 2026
* fix: bump the three patchable vulnerable transitive deps

Dependabot has five open high alerts, all npm transitives reached only
through package-lock.json. Three have a published patch and are fixed
here with the overrides block the repo already uses for exactly this
(brace-expansion@1, and js-yaml@4 which this raises rather than adds).

  js-yaml@4  4.3.0 -> 4.3.1   GHSA-5p4m-2wfm-xmqj  (eslintrc, xcpretty, orval)
  js-yaml@3  3.15.0 -> 3.15.1 GHSA-5p4m-2wfm-xmqj  (@lhci/utils, dev)
  nanoid@3   3.3.16 -> 3.3.17 GHSA-2v37-7h3g-55p8  (expo-router, postcss)

Every dependent's declared range already admits the patched version, so
no dependent needed relaxing. npm reported the installed copies "invalid"
against the new overrides but would not re-resolve them from the existing
lock, so the lockfile was regenerated with `npm update js-yaml nanoid`.

The remaining two alerts are image-size (GHSA-w3rx-r6r6-pgpr,
GHSA-5p2g-fcmc-qvqq) and are NOT fixed, because no fixed release exists:
the advisory range is <= 2.0.2 and 2.0.2 is the latest published version.
npm's only proposed remedy is a major downgrade to expo 53 / react-native
0.72, which is a worse outcome than the bug. It is unreachable in
production regardless: image-size is pulled by metro and runs in the
bundler at build time, never shipped into the app.

Validation: type-check 3/3, and 670 test files pass across all four
workspaces. The full suite must be run with --concurrency=1; at turbo's
default concurrency the shared barrel-import test at types.test.ts:1943
exceeds its 15s timeout purely from machine contention, unrelated to
this change (shared imports neither package).

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

* chore(deps): sweep in-range dependency drift for August 2026

Monthly non-security freshness sweep. /dep-sweep and the reminder
workflow that files these tickets were both deleted by #676, so this
follows the contract stated in the ticket body instead: bump web and
shared minor/patch, defer mobile to expo install --check, report the
orbit-api NuGet drift without changing it.

package-lock.json only. Every bump was already inside an existing
semver range, so no package.json edit was needed. 263 packages moved,
including next 16.2.11 -> 16.3.0, @sentry/* 10.68.0 -> 10.70.0,
@supabase/supabase-js 2.110.8 -> 2.112.3, react-hook-form 7.82 -> 7.85,
next-intl 4.13.4 -> 4.13.6, axios 1.18.1 -> 1.19.0, and 23 @radix-ui
patch bumps.

#599 recorded that regenerating this lockfile refloats transitives, so
the pins that had to hold were checked against the base commit rather
than by eye. Every exactly-pinned root override is identical: the Expo
SDK 57 native module set did not move, orval and @orval/zod held at
8.20.0 so the committed Zod snapshot stays comparable, and react,
react-dom and react-test-renderer held. The only override-pinned
package that moved is postcss 8.5.23 -> 8.5.26, whose override is the
caret range ^8.5.18, so that is the pin working as designed.

Two things npm did that the invocation did not ask for. A hoisted
lockfile ignores --workspace scoping, so Expo build tooling moved with
the sweep (@expo/cli 57.0.10 -> 57.0.14, @react-native/* 0.86.0 ->
0.86.2); all in-range, all still SDK 57, none in the pinned native set.
And brace-expansion moved 1.1.18 -> 1.1.16, a downgrade, because the
lockfile had drifted above its own exact security pin and re-resolution
pulled it back.

Validation: lint 3/3 with 0 errors, type-check 3/3, and 670 test files
pass across all four workspaces. The suite needs --concurrency=1; at
turbo's default the shared barrel-import test at types.test.ts:1943
exceeds its 15s timeout from machine contention alone.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
thomasluizon added a commit that referenced this pull request Aug 12, 2026
…nd de-flake the suite (#715)

* fix: bump the three patchable vulnerable transitive deps

Dependabot has five open high alerts, all npm transitives reached only
through package-lock.json. Three have a published patch and are fixed
here with the overrides block the repo already uses for exactly this
(brace-expansion@1, and js-yaml@4 which this raises rather than adds).

  js-yaml@4  4.3.0 -> 4.3.1   GHSA-5p4m-2wfm-xmqj  (eslintrc, xcpretty, orval)
  js-yaml@3  3.15.0 -> 3.15.1 GHSA-5p4m-2wfm-xmqj  (@lhci/utils, dev)
  nanoid@3   3.3.16 -> 3.3.17 GHSA-2v37-7h3g-55p8  (expo-router, postcss)

Every dependent's declared range already admits the patched version, so
no dependent needed relaxing. npm reported the installed copies "invalid"
against the new overrides but would not re-resolve them from the existing
lock, so the lockfile was regenerated with `npm update js-yaml nanoid`.

The remaining two alerts are image-size (GHSA-w3rx-r6r6-pgpr,
GHSA-5p2g-fcmc-qvqq) and are NOT fixed, because no fixed release exists:
the advisory range is <= 2.0.2 and 2.0.2 is the latest published version.
npm's only proposed remedy is a major downgrade to expo 53 / react-native
0.72, which is a worse outcome than the bug. It is unreachable in
production regardless: image-size is pulled by metro and runs in the
bundler at build time, never shipped into the app.

Validation: type-check 3/3, and 670 test files pass across all four
workspaces. The full suite must be run with --concurrency=1; at turbo's
default concurrency the shared barrel-import test at types.test.ts:1943
exceeds its 15s timeout purely from machine contention, unrelated to
this change (shared imports neither package).

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

* chore(deps): sweep in-range dependency drift for August 2026

Monthly non-security freshness sweep. /dep-sweep and the reminder
workflow that files these tickets were both deleted by #676, so this
follows the contract stated in the ticket body instead: bump web and
shared minor/patch, defer mobile to expo install --check, report the
orbit-api NuGet drift without changing it.

package-lock.json only. Every bump was already inside an existing
semver range, so no package.json edit was needed. 263 packages moved,
including next 16.2.11 -> 16.3.0, @sentry/* 10.68.0 -> 10.70.0,
@supabase/supabase-js 2.110.8 -> 2.112.3, react-hook-form 7.82 -> 7.85,
next-intl 4.13.4 -> 4.13.6, axios 1.18.1 -> 1.19.0, and 23 @radix-ui
patch bumps.

#599 recorded that regenerating this lockfile refloats transitives, so
the pins that had to hold were checked against the base commit rather
than by eye. Every exactly-pinned root override is identical: the Expo
SDK 57 native module set did not move, orval and @orval/zod held at
8.20.0 so the committed Zod snapshot stays comparable, and react,
react-dom and react-test-renderer held. The only override-pinned
package that moved is postcss 8.5.23 -> 8.5.26, whose override is the
caret range ^8.5.18, so that is the pin working as designed.

Two things npm did that the invocation did not ask for. A hoisted
lockfile ignores --workspace scoping, so Expo build tooling moved with
the sweep (@expo/cli 57.0.10 -> 57.0.14, @react-native/* 0.86.0 ->
0.86.2); all in-range, all still SDK 57, none in the pinned native set.
And brace-expansion moved 1.1.18 -> 1.1.16, a downgrade, because the
lockfile had drifted above its own exact security pin and re-resolution
pulled it back.

Validation: lint 3/3 with 0 errors, type-check 3/3, and 670 test files
pass across all four workspaces. The suite needs --concurrency=1; at
turbo's default the shared barrel-import test at types.test.ts:1943
exceeds its 15s timeout from machine contention alone.

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

* fix: align mobile to Expo SDK 57 and repair the rotting override pins

Closes the residual risk flagged on the August sweep, plus two override
defects found while fixing it.

expo install --check now reports "Dependencies are up to date". It
previously wanted 26 packages moved, including react-native 0.86.0 ->
0.86.2 while six of its own tooling packages had already floated to
0.86.2, leaving core and tooling on different versions across the
Android build.

The 30 exact Expo pins in the root overrides block are deleted rather
than refreshed. They existed to stop transitives refloating, but they
are hand-maintained and silently rot, which is what produced the skew
above. Removing them and measuring instead shows the tree dedupes on
its own: duplicate expo/react-native module copies went from 1 to 0.

The single remaining duplicate was react-native-device-info, hoisted
10.3.0 against react-native-siren's nested 8.7.1. Two copies of one
native module cannot both autolink, so siren was running 8.x JS against
10.x native. It is now the one Expo-adjacent override that survives,
because it is the only one carrying its weight.

  overrides: 45 entries -> 16

brace-expansion@1 was pinned to exactly 1.1.16, which is no longer a
patched version. Two advisories now cover it, GHSA-mh99-v99m-4gvg
(<1.1.17) and GHSA-rgw5-rvv9-x895 (<1.1.18, which bypasses the first
mitigation). The tree had drifted to the safe 1.1.18 and the exact pin
was dragging it back into both. Every security override is now a caret
range so a pin cannot rot into the vulnerability it was added to fix.

apps/mobile/package.json carried a 21-entry overrides block that npm
never applied, since overrides are honoured only from the root
workspace. #599 hoisted 7 of these; the block is now deleted outright.

Not taken, each verified rather than assumed:

  eslint 10   eslint-config-next advertises eslint >=9.0.0, but its
              transitive eslint-plugin-react is at its last release
              (7.37.5), peers capped at ^9.7, and it throws
              "contextOrFilename.getFilename is not a function" on
              eslint 10. The brace-expansion override is the real fix
              regardless; eslint 10 was only npm's route to it.
  typescript 7  typescript-eslint@8.67.0 peers typescript <6.1.0.
  react 19.2.8  expo install --check rejects it and wants 19.2.3.

Validation: lint 3/3 with 0 errors, type-check 3/3, 670 test files pass,
and npm ci accepts the lockfile.

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

* fix: stop the suite flaking when turbo runs four vitest instances at once

`turbo run test` runs the four workspace suites concurrently and each
vitest sizes its worker pool to the whole machine, so they oversubscribe
the CPU. Tests that re-import a module graph then blow their timeout for
no reason connected to the code under test.

Reproduced before fixing: five consecutive default-concurrency runs, red
every time, and the failure moved between workspaces rather than sitting
on one test.

  shared  types.test.ts barrel import, 15087ms against a 15s limit while
          its sibling barrels finish in 224ms to 490ms
  mobile  use-push-notifications-state beforeEach, "Hook timed out in
          10000ms", on a vi.resetModules() that rebuilds the graph per test
  web     i18n/request and lib/server-fetch, the latter being the
          intermittent failure already filed as orbit-tickets #287

All four workspaces pass standalone, which is what identifies contention
rather than a product defect as the cause.

testTimeout and hookTimeout go to 30s in all three vitest configs. The
inline per-test timeouts are deleted, because that is what made the first
attempt at this fix fail: `it(..., 10000)` silently overrides the config,
so raising the config alone left web red. Those magic numbers were an
earlier pass at this same flake and each one resets the same clock as the
suite grows.

Verified with two further full default-concurrency runs, 4/4 tasks and
670 test files green.

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

* fix: cap vitest worker pools instead of leaning on long timeouts

Addresses the Codex review on #715, which is right about the cause: 30s
timeouts left four machine-sized worker pools oversubscribing the CPU and
merely waited longer, while tripling how long a genuine hang takes to
surface.

maxWorkers is now '25%' in all three configs, so four concurrent workspace
suites add up to about one machine's worth of workers instead of four.
Confirmed against the installed vitest types rather than assumed:
maxWorkers?: number | string, "Maximum number or percentage of workers".

The cap alone is not sufficient, which is why the timeouts do not go all
the way back to the defaults. Measured at default turbo concurrency:

  no fix                      5 of 5 runs red
  30s timeouts, no cap        2 of 2 green
  cap, default 5s timeouts    1 of 3 red, at 5000ms
  cap plus 15s margin         4 of 4 green

15s rather than the previous 30s, so a real hang reports in half the time
while the residual cold-import cost still fits.

One failure in the cap-only run was an AssertionError, not a timeout:
server-fetch expecting forceRefresh: true on the second call. That is the
pre-existing intermittent failure filed as orbit-tickets #287. It appears
to be mock state cascading from the timed-out test above it in the same
file, and it has not recurred in the four green runs, but this change does
not claim to fix #287 because the cascade is unproven.

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

* fix: cap the fourth vitest suite too

Addresses the second Codex review on #715. The cap landed on mobile, web
and shared but not eslint-rules, which kept vitest's default machine-sized
pool and added workers on top of the three capped ones. That also made the
comment wrong where it claimed four suites were capped.

Its two test files make the practical pool small, but the config is what
sets the ceiling, and on a 4-core CI runner the uncapped fourth is exactly
where the aggregate goes back over one machine's worth.

Verified with two more full default-concurrency runs, 4/4 tasks and 670
test files green.

Co-Authored-By: Claude Opus 5 (1M context) <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