[for #2153] parity: rerecord lanes against a v3 assembler + replay timing fixes - #2177
Merged
SawyerHood merged 1 commit intoAug 21, 2026
Conversation
Harness fixes found while stacking the codex bridge migration (#2164) on WS1a's v3-only assembler. None of them changes a recording; all are safe on this branch (self-suite 43/43, codex/claude/acp recorded conformance green). - A recording is never rewritten. `pnpm rerecord [--plan-with <checkout>]` writes the current bridge's side of the wire to bridge→runtime.current.ndjson beside the recorded lane (request ids kept by method and order so the recorded runtime responses still match; lines placed after the runtime entry sent last before them). The self-suite and recorded conformance pin and compare against the current lane when one exists; `pnpm parity` paces a pre-migration leg from the recorded lane and the current leg from the current lane (`planFromCurrentLane`), so each assembler plans from a lane it parses whole. `--plan-with` names a checkout whose assembler parses the recorded lane for the re-recording itself. - `pnpm parity --dump-dir <dir>` writes both legs' normalized event and row lists per cell, for allowlist entries that must name a list index (the CLI renders values truncated to 160 chars). - The replay drains the pre-request stream (300 ms quiet once the gates are met) before sending each runtime request, and the replay child leaves a 50 ms gap after a response line: a steer's ack follows an `await` on the bridge's own request, and on a loaded runner the next notification otherwise lands in the same read and the ack moves one position (two CI flakes on #2164). - The self-suite holds allowlist entries to their form (PR, reason, path) instead of requiring an empty list, which a migration PR cannot satisfy; a non-replayable provider whose recorded lane predates the grammar is reported, not failed, until re-recorded live. Co-Authored-By: Claude <noreply@anthropic.com>
SawyerHood
deleted the
bb/for-2153-parity-rerecord-and-timing-thr_xnid5ftd87
branch
August 21, 2026 09:31
SawyerHood
added a commit
that referenced
this pull request
Aug 21, 2026
…rdings, and the parity harness (#2153) Stacked on #2136 (WS1a, `bb/ws1a-assembler-testing-kit-stack-on-2124-thr_jkbj56vr97`), itself stacked on the contract #2124. Landing order: contract → WS1a → this → WS2a → #2121 → codex → claude. **Why stacked, not independent.** `@bb/provider-parity` and the three `bridge.recorded-conformance.test.ts` files import `createBridgeDeltaEventCollector`, which lives at `@bb/agent-runtime/test/bridge-delta-assembly` on `main` and at `@bb/provider-bridge-protocol/testing` on WS1a. No ordering of two independent PRs yields a buildable `main`, so this PR follows the move (and drops `provider-parity`'s `@bb/agent-runtime` dependency). The recorded `bridge→runtime` lanes are grammar v2, which WS1a's v3-only assembler refuses, so every replayable cell now also carries a `bridge→runtime.current.ndjson` written by `pnpm --filter @bb/provider-parity rerecord --plan-with <main checkout>` through this branch's bridges; they assemble to the **unchanged** pins in `row-counts.json`. The 10 pi cells (in-process SDK, not replayable) keep their pins and are reported, not failed. **Do not merge.** Coordinator reviews, Sawyer merges the stack. ## What was wrong The provider-plugin migration will break byte-equivalence with today's translators on purpose, which removes the regression oracle the goldens provide. Calibration sessions are scripted fakes and the integration suite runs a fake adapter, so nothing checks a bridge against what the provider CLIs really emit across the behavior matrix. Design: "Regression confidence" (A2, A3) and "Corpus" in the ideal-state provider API spec. No bridge may migrate before this lands. ## What changed **Bridge record mode** (`BB_PROVIDER_BRIDGE_RECORD_DIR`, off by default, additive) - `bridge-worker-entry.ts` tees both sides of the runtime wire for every bridge, first- or third-party, before the bridge module loads. - Bridges tee their provider child with `experimental_recordProviderChildIo(child, { threadId })` (codex `app-server-connection.ts`, acp `agent-connection.ts`). Claude's pipe belongs to the Agent SDK, so `sdk-session.ts` takes the SDK's `spawnClaudeCodeProcess` seam over when `experimental_isProviderBridgeRecording()`. Pi runs in-process and records its SDK event boundary (`AgentSessionEvent` in, prompt/abort/compact out). - Layout `<dir>/<providerId>/<threadId>/<direction>.ndjson` (`_process` for lines that belong to no thread); one `{ts, run, seq, dir, line}` per line, appended per line, never buffered. Responses land in the scope of the request they answer. `run` identifies the bridge process so a thread served by several processes merges back in order. - The daemon forwards the variable from its own env (not the shell env, which doubles as the agent's shell environment); the runtime appends the provider id. `withoutBridgeRuntimeEnv` and the existing `BB_*` allowlist both strip it from provider children. - Docs: `docs/provider-bridge-protocol.md` ("Record mode"), `docs/debugging-and-qa.md`, `docs/configuration.md`, `docs/api_to_audit.md` (both new `experimental_` SDK members). **Recordings** — 52 redacted cells under `packages/provider-bridge-protocol/recordings/<provider>/<cell>/` with a `manifest.json` each (provider, cell, CLI version, date, description, lane line counts). `scripts/provider-recordings/redact.mjs` rewrites paths under `$HOME`, emails, `ls -l` owner columns, token shapes (`bbde_`, `ghp_`, `github_pat_`, `sk-`, `sk-ant-`, `xox?-`, `bbcm_`, JWTs, bearer values, `Authorization` headers), secret-shaped env keys, collapses Claude's `system/init` and `control_response` catalogs to names, and trims strings over 2,000 chars to head/tail with a marker. It is idempotent and exits 3 if any pattern survives. `package-cells.mjs` cuts the raw per-thread recordings into cells. Raw recordings stay in `~/.bb/provider-recordings/raw` (gitignored). **Parity harness (A2)** - `@bb/provider-bridge-protocol/testing/parity`: `replayRecording` drives the recorded `runtime→bridge` lane into a bridge process and `replay-provider-child.mjs` plays the provider lanes back as the bridge's child — the recording *is* the script. The child gates each recorded provider line on the live bridge's own writes (method/subtype for requests, id for responses), maps bridge-minted ids to recorded ones, cuts the lanes into one segment per spawned child, paces spontaneous lines behind the harness's cursor so steers and interrupts land between the same two provider lines they did live, and never hangs a divergent bridge (stall → generic answer, logged). Serves JSON-RPC (codex, ACP) and the Claude CLI control protocol. Each replay runs in its own temp workspace and (for Claude) its own `CLAUDE_CONFIG_DIR`, with a seeded source transcript per recorded `thread/fork`, so recordings replay on any machine. - `compareParity(old, new, allowlist)` diffs normalized events (the resurrected `diffCalibrationStreams`, `#2140` had removed it), normalized rows, and grammar drops. Allowlist entries are `{provider|"*", cell|"*", layer, path (JSON pointer with `*`/`**`), pr, reason}`; an entry that masks nothing is reported stale and fails. - `@bb/provider-parity` wires the real delta assembler and `@bb/thread-view` projection (mirroring `timeline.ts`), owns `pnpm parity --old <checkout> --new . [--provider] [--cell] [--dump-dir]`, and loads each leg's assembler and projector from that leg's own checkout (its `@bb/provider-parity`, else the collector at its pre/post-WS1a home), so a `main` `--old` leg assembles with `main`'s assembler. - `parity.self.test.ts` (CI): every cell assembles to the counts pinned in `recordings/row-counts.json` (events, rows; `provider/unhandled` and grammar drops may only go down — G11), every allowlist entry names a PR, reason, and pointer (staleness is judged by `pnpm parity --old <main>`, which an old==new replay cannot), and every replayable cell replays through the current bridge with zero event/row/grammar diffs and zero stalls. `UPDATE_PARITY_ROW_COUNTS=1` rewrites the pins deliberately. **Current bridge lanes (merged from #2177, reviewed as harness owner)** — a recording is never rewritten. When a bridge change alters what the bridge emits, `pnpm --filter @bb/provider-parity rerecord [--plan-with <recording-time checkout>]` writes `bridge→runtime.current.ndjson` beside the recorded lane; the self-suite and recorded conformance pin/compare against it when present, and `pnpm parity` paces the old leg from the recorded lane and the new leg from the current one. Also from #2177: `pnpm parity --dump-dir`, a 300 ms drain before each replayed runtime request and a 50 ms gap after a replayed response (the steer-ack position race seen twice in CI). Two follow-ups on top: the harness's own `initialize` response is kept out of re-recorded lanes, and re-recorded lanes pass through `redact.mjs` before they are written (a bridge error can quote the replay child's command line). 39 current lanes are committed (every replayable cell) — see "Why stacked" above. **Conformance** — `checkRecordedCellReplay` + `replayRecordedCells` add the recorded-traffic scenario set (`recorded/<cell>/{replays, events-schema-valid, grammar, turn-lifecycle, not-empty}`); each first-party bridge gets `bridge.recorded-conformance.test.ts` over turn, steer, stop, approval allow/deny, question, resume, fork. No translation behavior change. No wire change; `HOST_DAEMON_PROTOCOL_VERSION` is untouched. ## How you verified On the stacked head `39066e19b` (WS1a base), one Turbo invocation at a time at `--concurrency 4`: typecheck 12/12 tasks (protocol, parity, agent-runtime, host-daemon, plugin-sdk, codex, claude-code, acp); tests — protocol 218, codex 173, claude-code 262, acp 182 (each `bridge.recorded-conformance.test.ts` green), parity self-suite 43/43 with `row-counts.json` untouched; `pnpm parity --old . --new .` 39 passed / 0 failed / 13 skipped; `pnpm rerecord --plan-with /home/sawyer/projects/bb` 39 OK / 0 STALL; redaction sweep over recordings + current lanes `0 survivors`, idempotent. Earlier, on the `main`-based head `0923c1c59` (after merging #2177): CI green (Checks, Package Smoke ×2, Tests app-1/2/3, integration, packages, server); 21/21 Turbo tasks, 1,848 tests (protocol 101, parity 43, agent-runtime 418, host-daemon 552, plugin-sdk 117, codex 173, claude-code 262, acp 182); the steer/stop-interrupt cells stable across 3 repeated runs. New tests in this PR: recorder routing/tee/oversize, worker-entry record tee, `withoutBridgeRuntimeEnv` strips the knob, daemon forwards it to bridge processes but not the shell env, the 43-test parity self-suite, three recorded-conformance suites. An earlier CI run on `main` failed in `packages` because ACP and Claude replays depended on the recording machine (`cwd`, `~/.claude` transcripts for `forkSession`, `PATH`); reproduced locally with the directory moved away and a bare `HOME`, fixed as described above. **Matrix** (`codex-cli 0.149.0`, `Claude Code 2.1.238` / Agent SDK 0.3.197, `cursor-agent 2026.08.11`, pi `@earendil-works/pi-coding-agent 0.84.0`, recorded 2026-08-21 through `scripts/bb-dev-app` + the `bb` CLI): | cell | codex | claude-code | acp-cursor | pi | | --- | --- | --- | --- | --- | | 1 turn (shell + edit + read) | ✅ | ✅ | ✅ | ✅ | | 2 steer mid-turn | ✅ | ✅ | ✅ | ✅ | | 3 stop mid-turn, new turn | ✅ | ✅ | ✅ | ✅ | | 4 approval allow | ✅ | ✅ | ✅ | ⛔ pi has only `full` mode; nothing asks | | 4 approval deny | ✅ | ✅ | ✅ | ⛔ same | | 5 user question | ✅ (bb tool) | ✅ (`AskUserQuestion`) | ✅ (bb tool) | ✅ (bb tool) | | 6 subagent / delegation | ✅ (native `subAgentActivity`) | ✅ (`Agent`) | ✅ (bb delegation) | ✅ (bb delegation) | | 7 resume after `thread stop` | ✅ | ✅ | ✅ | ✅ | | 8 fork | ✅ | ✅ | ✅ recorded as the agent's refusal: `cursor-agent` advertises no `session/fork` (0 events, pinned) | ✅ | | 9 plan mode | ✅ (`/plan` command mention) | ✅ (`claudeCodePermissionMode: plan`, plan approval interaction) | ⛔ no plan command | ⛔ no plan command | | 10 model list | ✅ (`_process` scope) | ✅ | ✅ | ✅ | | 11 web search / fetch | ✅ (`webSearch` item) | ✅ (`WebSearch` + `WebFetch`) | ✅ (Web Fetch tool) | ✅ (curl fallback; pi has no web tool) | | 12 compaction | ✅ | ✅ | ⛔ server 409: provider does not support manual compaction | ✅ | | 13 archived session resume | ✅ (archived natively via app-server `thread/archive`; bridge unarchives and retries) | n/a | n/a | n/a | | 13 empty rollout | ✅ (0-byte rollout → `failed to read session metadata`) + bonus `missing-rollout` | n/a | n/a | n/a | | 13 auth failure | ✅ real 401 ×11 via empty `CODEX_HOME` | ✅ "Not logged in" via empty `CLAUDE_CONFIG_DIR` | ⛔ not attempted: no config-dir knob to point at an empty store without touching the real login | ⛔ not attempted: pi's keys live in its own config; no safe override | | 13 429 | none occurred naturally (not forced) | — | — | — | **Redaction sweep** (`node scripts/provider-recordings/redact.mjs packages/provider-bridge-protocol/recordings /tmp/redact-stack`): ``` redacted 241 recording files into /tmp/redact-stack (home=/home/sawyer → /home/user); 0 survivors ``` `diff -rq` between the committed fixtures (recordings + current lanes) and that output is empty (idempotent). The only remaining occurrence of the username is the public skill name `bb-global-skills:sawyer-voice` in Claude's advertised skill catalog. **Parity self-run** (`pnpm parity --old . --new .`): `39 passed, 0 failed, 13 skipped (52 cells)` — the 13 skips are the 3 process-scoped `model-list` cells (no thread events) and the 10 pi cells (in-process SDK, no child to replay; still pinned and assembled by the self-suite). Every PASS line reports identical old/new counts, e.g. `codex/steer: old 87 events/3 rows, new 87 events/3 rows, unhandled 0→0, grammar drops 0→0`. The cross-checkout path was exercised too: `--old /home/sawyer/projects/bb` (on `main`, no `provider-parity` package) loaded that checkout's `@bb/agent-runtime` collector and bridge and matched on all 16 codex thread cells. **Sizes**: recordings 6.9 MB total including the 39 current lanes (6.2 MB without) — codex 1.6 MB, claude-code 1.2 MB, acp-cursor 932 KB, pi 2.5 MB recorded; largest cell `pi/turn-tools` 828 KB. Fixes none (step-0 PR of the provider-plugin migration). > AGENT GENERATED: by Claude Opus 5 --------- Co-authored-by: Claude <noreply@anthropic.com>
SawyerHood
added a commit
that referenced
this pull request
Aug 21, 2026
…bridge to grammar v3 with presentation (#2164) Stacked on #2121 (corpus harness + permission matrix, `bb/provider-baselines`) ← #2148 (WS2a) ← #2153 (recordings) ← #2136 (WS1a) ← #2124 (contract). WS1b-codex of the provider-plugin migration: the codex bridge speaks grammar v3 with a presentation on every item, its natives map to the core kinds, bb-injected tools carry their presentation, goals and the macOS permission profile are codex extension kinds, and the codex v2 path is deleted. **Do not merge.** Coordinator reviews, Sawyer merges the stack. ## Stack contract #2124 → WS1a #2136 → recordings #2153 (incl. #2177 and the `PARITY_INITIALIZE_ID` follow-up) → WS2a #2148 → corpus harness #2121 → **this PR** → WS1b-claude #2178. Six commits: the five codex layers below plus one `parity:` commit carrying this PR's allowlist entries and the codex `bridge→runtime.current.ndjson` lanes re-recorded against this bridge with #2153's `pnpm rerecord --provider codex --plan-with <main checkout>`. No vendored harness copies remain (they were needed only while #2153 was outside the stack). The A4 corpus check was run from a throwaway worktree with #2121's harness cherry-picked on top, since #2121 is not in the stack. ## What was wrong The codex bridge emitted v2-shaped items: no presentation (core thread-view kept codex's tool-name tables), native sub-agents as `tool` items named `spawnAgent`, `update_plan` as a turn-level event the UI discards, goals as core `thread/goal/*` events, bb-injected tools without `server`, and a macOS permission profile on a command approval failed the whole approval. Open work rode an out-of-band `thread/openWork` notification. ## What changed (one commit per layer; the last deletes) 1. **Presentation on every item.open/close** — `plugins/provider-codex/src/presentation.ts` is the one place codex tool-name knowledge lives: shell commands (wrapper stripped from the headline), file edits, the bundled `node_repl` server ("Ran JavaScript" with the call's title), other MCP servers by tool name, dynamic tools, collab verbs, web search/fetch, image views, reasoning, messages, plans, compactions, the synthesized sub-agent spawn. An invariant suite drives one item per codex native through the real translator and asserts every lifecycle delta carries one. 2. **Delegation + planSteps** — the synthesized native sub-agent is a foreground `delegation` (`childRef` = agent thread id, `label` = agentPath); a follow-up to a settled agent re-opens the same item and the row closes only when the agent owes nothing more (the exact open-work predicate); a dead app-server child settles its open delegations as failed on the wire. A collab call that names its receiver is a delegation to it; a bare `wait` stays a tool item with its collab presentation. `turn/plan/updated` becomes a settled `planSteps` snapshot per update. `RuntimeBackgroundWorkState` counts a pending delegation as open work. **Thread-view projects a `delegation` item to the existing delegation row with the child content nested** — without this the row and every child message under it vanish before the projection workstream lands (the projection suppresses orphans with a `parentToolCallId`). `planSteps` items are not projected yet (status quo: codex plans were discarded). 3. **bb-injected tools carry their presentation** (Q31) — `bb.agents.registerTool({ experimental_presentation })` (+ `docs/api_to_audit.md`); the server resolves one presentation per tool at its boundary (declaration → status labels → generic label + the plugin's branding glyph / `Toolbox`) onto `DynamicTool.presentation`; the codex bridge emits calls to injected tools as `{ server: "bb", tool }` with that presentation. ask-user-question and workflows declare theirs (AskUserQuestion and `bb_workflow_result` collapse by default). `HOST_DAEMON_PROTOCOL_VERSION` 149 → 150 (149 is WS2a's #2148, this PR's base; the history reads 147 → 148 → 149 → 150). The field is optional and `dynamicToolSchema` is not strict, so an older daemon strips it and keeps working; the bump follows the repository rule for a widened server↔daemon wire. Optional per A1: every committed recording's runtime lane predates the field and must keep replaying; the stabilization pass makes it required. 4. **Goals + macOS profile as codex extension kinds** — `provider-codex/goal` (state; `null` once cleared) and `provider-codex/macos-permission` (item) declared on the registration with plugin-owned zod schemas (`extension-kinds.ts`). The bridge emits goals as extension state. A command approval asking for macOS capabilities now reaches the user for the command; the profile rides the timeline as its own row saying bb cannot grant it (the plugin-rendered approval is WS5's). **Read-time conversion in core:** `parseStoredThreadEvent` decodes persisted `thread/goal/updated|cleared` rows into the extension state (the one path every stored-event read takes); thread-view goal extraction and the runtime's goal-clear wait read that state. The sidebar's latest-goal query becomes a latest-thread-state-by-kind query (partial index widened to `thread/extensionState/updated`, migration **0106** `thread_state_index` — regenerated with Drizzle so its snapshot chains from WS2a's 0105 `provider_settings_to_plugins`; `json_extract` kind filter). Verified against the corpus: the 721-row goal thread renders its goal unchanged. 5. **Delete the codex v2 path** — `thread.goal`/`thread.goalCleared` leave the grammar and assembler (G3 snapshot updated; `PROVIDER_BRIDGE_PROTOCOL_VERSION` stays 2 under the grammar range, as WS1a's v2 deletion did); the `thread/openWork` notification leaves the protocol, adapter and reaper (an unknown notification is ignored); the bridge drops its open-work reporting and the hard-coded AskUserQuestion presentation. Kept: the bridge's knowledge of its spawn/resume collab verbs for a receiver-less call — tool-name knowledge in the bridge is the point, not a remnant. The `thread/goal/*` domain event types remain as read-only legacy vocabulary. Naming note: the spec says `codex/goal`; the extension-kind namespace is the **plugin id**, which is `provider-codex`, and that is how the registry resolves the schema. ## Regression oracle All turbo invocations with `--concurrency 4`; perf suite ignored (known-noisy, flagged on #2121). - **Conformance**: codex scripted suite + recorded conformance over all 17 recorded cells (incl. archived-resume, auth-failure, empty-rollout, missing-rollout) green. - **Parity (A2)**, `pnpm parity --old <origin/main worktree at f6fb434> --new . --provider codex`: **16 passed, 0 failed, 1 skipped** (process-scoped `model-list`). Event and row counts equal in every cell. `recordings/parity-allowlist.json` names every intended byte-inequivalence with `#2164` and a reason — 26 entries, four classes: `presentation` on items (15 cells), `server: "bb"` on the AskUserQuestion call (user-question), the sub-agent spawn as a `delegation` item + its row (`subagent`, events `/6` `/28`, rows `/0/children/0/{toolName,output}`), and `thread/goal/cleared` → `provider-codex/goal` extension state (one event index in each of 6 goal-bearing cells). Zero unlisted diffs, zero stale entries. claude-code **13/13** and acp-cursor **10/10** replay against main with **zero diffs** (no entries). - **Corpus (A4)**, 307 threads / 93,262 rows: **zero diffs**, claude-code and codex alike; no corpus allowlist entry was needed (the only read-time change, goals, projects identically by design). - **G11**: `provider/unhandled` flat on every codex cell (0→0; auth-failure 1→1) and on the corpus. - **G1**: 209 → 209 (no provider-id literal added or removed in core; `"provider-codex/goal"` is not a provider-id literal by the ratchet's regex). - **Parity self-suite** (#2153's, in the stack): 43/43 with the codex current lanes re-recorded against this bridge; row-count pins **unchanged** (every current lane assembles to exactly the pinned counts); the claude/acp current lanes #2153 produced on WS1a's bridges hold as they are. Tests on the final stack (all forced, `--concurrency 4`, one turbo at a time): db 406 (migration chain 0104 → 0105 → 0106 incl. replay-on-existing-DB cases), agent-runtime 337, codex 193 (scripted + recorded conformance), host-daemon-contract 52, provider-parity 43; typecheck green for codex, agent-runtime, provider-parity, server, db. Earlier full sweep before the re-stacks: domain 150, thread-view 382, provider-bridge-protocol 218, plugin-sdk 127, host-daemon 552, integration 55, claude-code 262, acp 182, workflows 223, ask-user-question 36, scripted echo 1; server 1828/1829 (the one failure is the known local umask case in `internal-skill-trees`, passes in CI); typecheck green across 25 packages incl. app, mobile, cli. ## Not done - **Live QA cells via bb-dev-app** (turn/steer/stop/approve/deny/question/subagent/resume/fork/plan with screenshots) — not run; the coordinator schedules them separately. - `planSteps` rows are not projected by thread-view (no regression: codex plans were discarded before); the projection workstream owns it. - The plugin-rendered macOS approval (the profile round-trips only as a visible row today) — WS5. > AGENT GENERATED: by Claude Opus 5 --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For #2153 (
bb/provider-recordings), so the harness fixes the codex bridge migration (#2164) needed land with the recordings and #2164 can drop its copies on rebase. Do not merge; coordinator routes it.What was wrong
Stacked on WS1a's v3-only assembler, the recordings' v2
bridge→runtimelanes no longer assemble: 38/43 self-suite tests and the claude/acp recorded-conformance suites go red before any bridge changes. A migration PR also cannot satisfy "the allowlist is empty", the CLI's 160-char rendering cannot show the list index a type-changing diff needs, and two timing races moved a steer'sturn/input/acceptedone position under CI load.What changed
pnpm --filter @bb/provider-parity rerecord [--plan-with <checkout>]writesbridge→runtime.current.ndjsonbeside the recorded lane; the self-suite and recorded conformance pin/compare against it when present;pnpm paritypaces the old leg from the recorded lane and the new leg from the current lane. No current lanes are committed here — on this branch the recorded lanes are the bridges' output.pnpm parity --dump-dir <dir>writes both legs' normalized lists per cell.Verification
On this branch (no current lanes):
@bb/provider-parity43/43,@bb/provider-bridge-protocol101/101, codex 173, claude-code 262, acp 182 — all forced. On #2164 (the consumer): self-suite 43/43 in CI after the two timing fixes;pnpm parity --old <main> --new .codex 16/0/1, claude-code 13/0/1, acp-cursor 10/0/1.