Skip to content

[for #2153] parity: rerecord lanes against a v3 assembler + replay timing fixes - #2177

Merged
SawyerHood merged 1 commit into
bb/provider-recordingsfrom
bb/for-2153-parity-rerecord-and-timing-thr_xnid5ftd87
Aug 21, 2026
Merged

[for #2153] parity: rerecord lanes against a v3 assembler + replay timing fixes#2177
SawyerHood merged 1 commit into
bb/provider-recordingsfrom
bb/for-2153-parity-rerecord-and-timing-thr_xnid5ftd87

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

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→runtime lanes 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's turn/input/accepted one position under CI load.

What changed

  • Current bridge lanes. A recording is never rewritten. pnpm --filter @bb/provider-parity rerecord [--plan-with <checkout>] writes bridge→runtime.current.ndjson beside the recorded lane; the self-suite and recorded conformance pin/compare against it when present; pnpm parity paces 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.
  • Replay timing: a 300 ms drain before each runtime request; a 50 ms gap in the replay child after a response line.
  • Self-suite: allowlist entries are held to their form (PR, reason, path); non-replayable providers with a pre-grammar lane are reported, not failed.

Verification

On this branch (no current lanes): @bb/provider-parity 43/43, @bb/provider-bridge-protocol 101/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.

AGENT GENERATED: by Claude Opus 5

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
SawyerHood merged commit 9fccde0 into bb/provider-recordings Aug 21, 2026
13 checks passed
@SawyerHood
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>
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