diff --git a/devlog/_plan/260827_igwanu_bug_pr_merge_round/041_wp2b_2729_supersede.md b/devlog/_plan/260827_igwanu_bug_pr_merge_round/041_wp2b_2729_supersede.md index 582f963836..e71f204264 100644 --- a/devlog/_plan/260827_igwanu_bug_pr_merge_round/041_wp2b_2729_supersede.md +++ b/devlog/_plan/260827_igwanu_bug_pr_merge_round/041_wp2b_2729_supersede.md @@ -124,10 +124,24 @@ carries no competing keyword, and 25/25 passed straight over the defect. Fixed by moving the check ahead of the overload keywords in both functions: the explicit gRPC status is a structured backend signal, while `unavailable` and -`temporarily` beside it are inference over free text. Differential against -`origin/dev`: exactly one case moves (503 -> 400); real overloads, auth, rate -limit, timeout and invalid-request are unchanged, and authentication still -outranks both. +`temporarily` beside it are inference over free text. + +Differential against `origin/dev`: **the whole failed-precondition class moves to +400**, not a single case. Constructible inputs that change: + +``` +failed_precondition + "unavailable" 503 -> 400 +failed_precondition + "temporarily" 503 -> 400 +"failed precondition" + "overloaded" 503 -> 400 +bare failed_precondition 502 -> 400 +``` + +The non-precondition controls are unchanged: real overload 503, authentication +401, rate limit 429, timeout 504, invalid request 400. + +(An earlier draft said "exactly one case moves". That was the sample I happened to +probe, not the size of the change — the correct framing is a class, and describing +a class by one member is how a differential stops being evidence.) **This is the third time in one PR that a fix was correct in principle and wrong in precedence** — the envelope masking, my blanket 502, and now this. The pattern diff --git a/devlog/_plan/260827_igwanu_bug_pr_merge_round/070_outcome.md b/devlog/_plan/260827_igwanu_bug_pr_merge_round/070_outcome.md index 0d869b2ddf..635eae70a7 100644 --- a/devlog/_plan/260827_igwanu_bug_pr_merge_round/070_outcome.md +++ b/devlog/_plan/260827_igwanu_bug_pr_merge_round/070_outcome.md @@ -14,7 +14,7 @@ Six merges, no direct commit (verified: first-parent count 6, no-merges count 0) | #2764 | Ingwannu | L1 | **MERGED** | `3b5302410`; rebased, patch ID unchanged, 26 green | | #2767 | Ingwannu | L1 | **MERGED** | `50e955604`; rebased, patch ID unchanged, 26 green | | #2729 | lidge-jun | L4 | **CLOSED-SUPERSEDED** | by #2769; patch IDs match exactly | -| #2769 | lidge-jun | L4 | **NEEDS_HUMAN** (approval) | CI green at head `16cb875b8`; self-approval refused | +| #2769 | lidge-jun | L4 | **NEEDS_HUMAN** (approval) | head `16cb875b8`: CI 23 green / 0 fail, full suite 15350/0; self-approval refused | | #2747 | olddonkey | L1 | **NEEDS_AUTHOR** (rebase) | approved; fork head, rerun cannot move base | | #2740 | luvs01 | L1 | **NEEDS_AUTHOR** (ready+rebase) | reviewed, oracle 2/0 vs 0/2, tsc 0 | | #2693 | yxr1995-maker | L4 | **BLOCKED** (author) | 3 reproduced blockers stand, 131 behind | @@ -84,6 +84,14 @@ incorrect correction is worse than the original error. 7. A safety net that exists in code is not a safety net that functions. 8. `gh run rerun` replays the same commit. When the fix landed elsewhere, only a rebase moves the evidence. +9. A stalled remote suite is not necessarily a wedged suite. `ocx-run` reported + `RUNNING (775s since last output)` while `bun scripts/test.ts` sat printing + "another Bun test run holds the machine lock; waiting". The owner recorded in + `/tmp/opencodex-bun-test.lock/owner.json` was pid `2108243`, and `ps` showed it + dead — a **root-owned stale lock** blocking a user-owned run, which is the + documented failure mode. Removed the lock directory; the suite resumed within + seconds. `OCX_TEST_NO_QUEUE=1` remains the wrong answer: it leaks into the child + process `tests/test-runner.test.ts` spawns and fails the machine-lock cases. ## Follow-ups outside this round's scope diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/000_plan.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/000_plan.md new file mode 100644 index 0000000000..4c6626b9be --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/000_plan.md @@ -0,0 +1,73 @@ +# 000 — fix-and-reimplement round: plan + +Base: `dev @ 50e955604`. Continues the igwanu round, which merged six PRs and left +seven open. This round's mandate is narrower and harder: **fix what can be fixed, +reimplement what cannot, and merge both** — the user explicitly authorized pushing +to contributor fork branches this round. + +## Re-verified state (2026-08-28, against `dev@50e955604`) + +| PR | author | fork writable | behind | merge-tree | tsc on merged tree | own suite | +|---|---|---|---|---|---|---| +| #2747 | olddonkey | **yes** | 39 | CLEAN | OK | 15/0 | +| #2740 | luvs01 | **yes** | 39 | CLEAN | OK | 2/0 | +| #2693 | yxr1995-maker | **yes** | 131 | CLEAN | OK | 62/0 | +| #2638 | luvs01 | **yes** | 192 | CLEAN | OK | — | +| #2497 | MarcTCruz | **yes** | 399 | **CONFLICT** | unreachable | — | +| #2745 | lidge-jun | n/a | 39 | CLEAN | OK | — | + +**#2693 passing 62/0 is the trap this round has to avoid.** Its own suite is green +and its three reviewer-reproduced defects are all real. A suite written alongside a +defect tends to encode it; that is why the reimplementation below is driven by the +defect list, not by the PR's tests. + +## Contention + +`src/server/responses/core.ts` — #2745, #2638, #2497. +`src/codex/auth-context.ts` — #2638, #2497. +Everything else is disjoint. Pairwise `git merge-tree` before any second one of the +three lands; textual mergeability is not behavioral compatibility on that boundary. + +## Lane assignment + +| WP | Doc | PR | lane | why | +|----|-----|----|------|-----| +| wp2 | 010 | #2747, #2740 | **FIX** | correct code, stale base only | +| wp3 | 020 | #2693 | **REIMPLEMENT** | three reproduced logic defects | +| wp4 | 030 | #2745 | **FIX or NEEDS_HUMAN** | OAuth credential boundary | +| wp5 | 040 | #2638 | **REBASE + verdict** | auth/routing boundary, 192 behind | +| wp6 | 050 | #2497 | **adjudicate** | OAuth refresh, conflicting, 399 behind | +| wp7 | 060 | — | close-out | ledger + verification | + +## Loop-spec + +- Archetype: spec-satisfaction repair. Each target has a verifier that defines done. +- Write scope: `devlog/_plan/260828_bugpr_fix_and_reimplement/`, `src/` and `tests/` + changes needed to fix or reimplement a target, fork-branch rebases where + `maintainerCanModify` is true, `codex/` branches, PR metadata. +- Out of scope: `main`/`preview`, releases, enhancement PRs, history rewrite on + `dev`, approving my own PRs, pre-disclosure security notes in tracked dirs. +- Bounds: one `bun test` at a time; long suites on `lidge` via `ocx-run`. Never + `OCX_TEST_NO_QUEUE=1` — remove a stale root-owned lock instead. + +## Standing gates + +1. Compile/test evidence from the MERGED tree, never the PR head. +2. Pairwise `git merge-tree` before two PRs sharing a file both land. +3. Green checks are not health unless `ci` / `test N/4` / `macos` are present. +4. **Green targeted suites are not health either — you chose the targets.** Any + function whose behavior changes gets a differential probe over every arm. +5. `gh run rerun` replays the same commit; only a rebase moves the base. +6. A safety net that exists in code is not one that functions. +7. A regression must FAIL without its fix (mutation-verified) to count as evidence. + +## Accept criteria (mirrored into goalplan criteria[]) + +- c1 — every target has a recorded terminal disposition verified against live `gh`. +- c2 — merged-tree compile/test gate ran for every candidate. +- c3 — each landed change carries a mutation-verified regression. +- c4 — `dev` advances only through PRs targeting `dev`; forks rebased, not rewritten + beyond the authorized scope. +- c5 — every behavior-changing function is differentially probed across all arms. +- c6 — auth/credential/OAuth surfaces land only when proven safe, else + NEEDS_HUMAN/UNSAFE with the exact unresolved question. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/010_phase1.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/010_phase1.md new file mode 100644 index 0000000000..280b4efacd --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/010_phase1.md @@ -0,0 +1,108 @@ +# 010 — wp2 FIX lane: #2747 and #2740 + +Both are correct code sitting on a stale base. Neither needs a logic change; both +need a rebase I am now authorized to push. + +## #2747 — reap the recovery proxy instead of trusting `stop` + +Head `07b975873`, **already approved by me**, 39 behind, merge CLEAN, tsc OK, +15/0 on its own suite on the merged tree. Fork `olddonkey/fix/update-recovery-orphan-cleanup`, +`maintainerCanModify: true`. + +Its `ci`/`macos` red is the pre-#2766 `release version line` failure, and a rerun +cannot clear it because a rerun replays the same commit. + +ACTION: rebase `pr2747-r3` onto `origin/dev`, force-push to the fork branch, let CI +run at the new head, merge when green. + +**Fork-push discipline.** The previous round declined this; the user has now +authorized it. The A-gate reviewer demolished an earlier draft of this section, and +it was right: the draft said "verify `git diff` between old and new head touches +nothing but the rebase", and that check is actively misleading. A two-dot +`git diff OLD NEW` after a 39-commit rebase reports the entire intervening `dev` +range — **70 files** on #2747's real rebase — so an executor following it either +panics at 70 files or waves through a genuine rewrite. Use checks that are invariant +under rebase: + +```bash +# 1. the patch itself is unchanged +git diff-tree -p OLD~1..OLD | git patch-id --stable +git diff-tree -p NEW~1..NEW | git patch-id --stable # must match + +# 2. commit-for-commit correspondence — no drops, no reorders +git range-diff origin/dev...NEW OLD_BASE...OLD # every row reads "=" + +# 3. the PR's own file set — three-dot, not two-dot +git diff --name-only origin/dev...NEW + +# 4. never overwrite a concurrent author push +git push --force-with-lease=refs/heads/: \ + https://github.com//opencodex.git NEW:refs/heads/ +``` + +On #2747 these read: patch-id `efa23210f341` both sides, `range-diff` `1: = 1:`, +three-dot name list `tests/update-stop-first.test.ts` alone. + +Two further rules absent from the first draft: + +- **Push to the fork, never to `origin`.** Destination is + `https://github.com//opencodex.git`. The previous round created a stray + same-named branch on `origin` and had to delete it. +- **Stop if the live head is already on current `dev`.** Re-rebasing a stale local + ref rewrites a branch that is already correct. Check + `gh pr view --json headRefOid` first. + +**A force-push resets the readiness checklist.** `enforce-target` returns a +contributor PR to draft and unticks all four boxes on new commits — by design, since +an attestation about the old commit cannot cover a new one. So a rebase does not end +at "let CI run": the PR is a draft again, and drafts here start only +`enforce-target`/`hygiene`/`label`/`resolve-pr`/CodeRabbit, none of which compile or +test. Two boxes ("on the latest dev commit", "resolved all Codex and CodeRabbit +findings") become objectively true and can be evidenced; the local-CI attestation +and the ready-for-review confirmation belong to the author. **Ask — do not tick +another contributor's attestation.** + +## #2740 — atomically commit cleanup run metadata + +Head `f07ee36f2`, draft, 39 behind, merge CLEAN, tsc OK, 2/0 on the merged tree. +Fork `luvs01/fix/storage-policy-metadata-race`, `maintainerCanModify: true`. + +Mutation oracle already proven in the previous round: revert only +`src/storage/policy.ts` + `src/storage/policy-job.ts` and the race test goes 0 pass / +2 fail. The test drives the interleave through +`setPersistedConfigMutationBeforeCommitForTests`, so it is deterministic rather than +timing-dependent — it will not become a flake later. + +It has only 5 checks, none of which compile or test. The merged-tree run is its first +real evidence. + +ACTION: rebase onto `origin/dev`, force-push under lease, then ask the author to +complete the readiness checklist so the full matrix runs. Merge when green **and** a +non-author approval exists — `luvs01` is the author, so mine qualifies. + +Recording a judgement the reviewer flagged as unstated: this PR writes through +`mutatePersistedConfig` into `config.json`, the file that holds API keys. It is not +a credential surface — it changes *how* a metadata write commits, not what is +stored, and its whole purpose is to stop clobbering concurrent edits. But "adjacent +to the file holding the keys" deserves an explicit call rather than an assumed one. + +## Ordering + +Independent — `tests/update-stop-first.test.ts` vs `src/storage/*`. No pairwise +merge-tree needed. Land #2747 first (already approved), then #2740. + +## TESTS + +- #2747: `tests/update-stop-first.test.ts` (the PR is the test). +- #2740: `tests/storage-policy-config-race.test.ts`. + +## Verification (C) + +```bash +bun x tsc --noEmit +bun test tests/update-stop-first.test.ts # expect 15/0 +bun test tests/storage-policy-config-race.test.ts # expect 2/0 +``` + +Plus exact-head CI green after each rebase, and for #2740 the mutation oracle +re-run on the rebased tree rather than the remembered one. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/020_phase2.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/020_phase2.md new file mode 100644 index 0000000000..f9f6294e06 --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/020_phase2.md @@ -0,0 +1,250 @@ +# 020 — wp3 REIMPLEMENT lane: #2693 antigravity signature fallback + +#2693 head `8775d77d6`, 131 behind, merge CLEAN, tsc OK, **62/0 on its own suite**. +The green suite is not evidence: all three reviewer-reproduced defects survive it, +because tests written beside a defect encode it. + +## The intent is right + +Gemini 3 function calls require a `thought_signature` on the first `functionCall` +part of a model turn. When neither the wire metadata nor the replay cache has one, +the request fails. Injecting the official +`skip_thought_signature_validator` bypass is the correct remedy. Three defects sit +between that intent and the diff. + +## Defect 1 — presence check instead of `extractSignature()` + +The diff tests `part.thoughtSignature !== undefined || part.thought_signature !== undefined`. +The module already owns the real contract at +`src/adapters/google-antigravity-replay.ts:513`: + +```ts +function extractSignature(part: Record): string | undefined { + const direct = part.thoughtSignature ?? part.thought_signature; + if (typeof direct === "string" && direct.length >= MIN_SIGNATURE_LEN) return direct; + const extra = part.extra_content as { google?: { thought_signature?: unknown } } | undefined; + const nested = extra?.google?.thought_signature; + if (typeof nested === "string" && nested.length >= MIN_SIGNATURE_LEN) return nested; + return undefined; +} +``` + +Two consequences, both reproduced by the reviewer: + +- a **nested** `extra_content.google.thought_signature` is invisible to the presence + check, so a turn that already carries a valid signature gets a competing sentinel + added; +- a **direct but too-short** value (`"short"`, below `MIN_SIGNATURE_LEN = 16`) reads + as present, so the fallback is suppressed on a turn that genuinely needs it. + +FIX: decide from `extractSignature(part)`, never from key presence. + +## Defect 2 — `turnHasSignature` is a turn-wide boolean set by a later sibling + +The diff scans all parts and sets `turnHasSignature` if **any** part has one, then +skips the sentinel for the whole turn. But the requirement is specifically about the +**first** `functionCall` of the turn. Reviewer's reproduction: a two-call turn where +only the second matches the cache — the second gets a real signature, the first stays +unsigned, and the sentinel that the first call required is skipped. + +FIX: track the first `functionCall` part explicitly, replay real signatures, then +decide the fallback from `extractSignature(firstFunctionCall)` alone. A later +sibling's signature must not vote on the first call's state. + +## Defect 3 — the sentinel reaches non-Gemini models + +`antigravityUsesReplayCache(model)` is `!/claude/i.test(model)` — every non-Claude +model qualifies. The reviewer reproduced the Gemini-only sentinel being injected into +`gpt-oss-120b-medium`. + +FIX: gate sentinel injection on a Gemini wire model. The replay cache's own scope is +deliberately broad and must stay that way; only the **sentinel** narrows. A new +predicate local to this module: + +```ts +export function antigravitySupportsThoughtSignatureSentinel(model: string): boolean { + return /(^|[/:])gemini[-.\d]/i.test(model); +} +``` + +**The separator is the load-bearing detail.** An earlier draft of this doc wrote +`/(^|\/)gemini[-.\d]/i` — slash only. The A-gate reviewer and a local probe caught +the same hole independently: `src/adapters/google.ts` builds the Vertex replay key +as `vertex:::`, a **colon**. A slash-only regex looks +correct against every CCA id and would have silently stripped the bypass from every +Vertex Gemini request — a regression introduced by the fix meant to prevent one. +Three namespaces reach this module and all three must match: `gemini-3-pro`, +`google/gemini-3-pro`, `vertex:api-key:global:gemini-3-pro`. The trailing +`[-.\d]` keeps `geminibot` and `my-gemini-clone` out. + +## Where the sentinel lives, and why not inside `applyAntigravityReplay` + +The first implementation put the fallback inside `applyAntigravityReplay` and broke +**12 existing tests**. That was not a broken-test problem; it was a design answer. +That function's *absence* of a signature is meaningful — 18 assertions read +`thoughtSignature === undefined` as "the cache did not match", covering eviction, +TTL expiry, oversize refusal, and clear-on-invalid. Writing a fabricated token into +that slot overwrites the exact signal those tests read. + +So the sentinel is its own exported pass, +`applyAntigravityThoughtSignatureFallback(model, contents)`, called immediately +after replay at both `src/adapters/google.ts` call sites. Replay answers "what did +upstream already tell us"; the sentinel answers "does the first call still lack a +signature". A cache miss keeps looking like a cache miss, and all 61 pre-existing +tests pass untouched. + +A model outside that set that genuinely needs the sentinel must arrive with a captured +accepted CCA contract, not by widening the predicate on inference. + +## Also: the reviewer's non-blocking test defect + +The unknown-version snapshot test reuses the object mutated by the corrupt-snapshot +call, so its second assertion is not load-bearing. The reimplementation gives the +version-99 branch a fresh unsigned payload. + +## Lane mechanics + +REIMPLEMENT on `codex/antigravity-signature-fallback` cut from `origin/dev`, then a +PR targeting `dev` that closes #2693 as superseded. The author's commits are not +carried because the logic is being replaced, not rebased; the PR credits the original +diagnosis and links #2693. + +## TESTS — `tests/google-antigravity-replay.test.ts` + +Each must fail without its corresponding fix: + +1. two-call turn, only the second matches the cache -> the FIRST call receives the + sentinel (defect 2). +2. valid **nested** `extra_content.google.thought_signature` on the first call -> no + sentinel added (defect 1a). +3. direct but **too-short** signature on the first call -> sentinel IS added + (defect 1b). +4. `gpt-oss-120b-medium` with an unsigned first call -> **no** sentinel (defect 3). +5. a Gemini model with an unsigned first call and no cache entry -> sentinel added + (the feature still works). +6. version-99 snapshot branch uses a fresh unsigned payload. +7. a later sibling signed **on the wire** (no cache at all) does not vote away the + first call's sentinel — the reviewer noted that test 1 only covers the cache-hit + arm, so a weaker patch could pass it and leave this open. +8. a Vertex-prefixed Gemini id receives the sentinel end to end, driving the real + function rather than only asserting the predicate. + +## Verification (C) + +```bash +bun x tsc --noEmit +bun test tests/google-antigravity-replay.test.ts +``` + +Then the mutation oracle per defect: revert each fix individually and confirm the +matching test — and only that test — fails. A single combined revert is weaker +evidence, because it cannot show that each test binds its own defect. + +Result (all four confirmed): + +| mutation | tests that fail | +|---|---| +| presence-check instead of `extractSignature` | 2 — nested, too-short | +| turn-wide flag instead of first-call | 1 — first-call sentinel | +| gate on `antigravityUsesReplayCache` | 1 — non-Gemini injection | +| slash-only regex | 2 — Vertex predicate and end-to-end | + +70 pass / 0 fail with every fix in place; `tsc --noEmit` exit 0. + +## The blast radius the focused suites did not show + +70/0 on `tests/google-antigravity-replay.test.ts` and exact-head CI still failed +three shards. **Five tests in four OTHER suites** break, all with one shape: they +assert `thoughtSignature` is `undefined`, and the sentinel now fills that slot. + +``` +tests/google-signature-history-roundtrip.test.ts + "history without a signature stays unsigned rather than borrowing one" + "an unknown call_id stays unsigned" +tests/google-vertex-thought-signature.test.ts + "#1312: shared prompt cache keys cannot cross client-thread replay namespaces" +tests/google-antigravity-wire.test.ts + "custom_tool_call item ids (ctc_...) are NOT forwarded (issue #174)" +``` + +This is the round's own standing gate #4 firing on me: green targeted suites are +not health, because I chose the targets. I picked the file I was editing, and the +consumers are where the change actually landed. + +### The question, stated honestly + +Those tests use `undefined` as a **proxy** for "no signature was borrowed from +another call, another thread, or another namespace". #1312 is a genuine security +boundary: thread-b must not inherit thread-a's signature under a shared prompt-cache +key. The sentinel is a constant carrying no information from any other call, so on +that reading the isolation property survives and the assertions should read +`toBe(BYPASS)`. + +**That reading is convenient for me, which is why it is not mine to certify.** It +is dispatched to an independent reviewer with the alternative designs named: (a) +update the five assertions, (b) narrow the sentinel so it never applies to replayed +client history, (c) something else. + +Direct evidence gathered meanwhile — thread-a records a real signature, thread-b +replays the same call under a different session: + +``` +after replay (thread-b): { functionCall: { name: "shell", args: {} } } +after fallback (thread-b): "skip_thought_signature_validator" +leaked thread-a's real signature? no +``` + +So the leak the test guards against does not occur. Whether `undefined` was +load-bearing for a *different* reason is the reviewer's call, not mine. + +### It was not just assertion drift — two real defects were hiding there + +Investigating the five failures found two genuine problems, both of which make the +sentinel behave as if it were a real signature: + +**1. The replay cache ingested it.** `observeAntigravityReplay` stored the sentinel +like any other signature, so a token we fabricate on the way out round-tripped back +in and was replayed later as evidence a turn was signed: + +``` +observe(sentinel) -> {"sessions":1,"calls":1,"totalBytes":160} +apply -> "skip_thought_signature_validator" +``` + +`extractSignature` now refuses it on both the direct and nested paths, so observing +it leaves the cache empty (`sessions: 0`) instead of polluting it. + +**2. `isLikelyRealThoughtSignature` accepted it.** That predicate exists to reject +synthetic ids — `fc_`, `ctc_`, `tsc_`, `call_` — and it is what +`tests/google-antigravity-wire.test.ts` #174 protects. The sentinel is alphanumeric +with underscores, so it slipped through every filter and would have been treated as +genuine anywhere that predicate gates. Now rejected by name. + +Neither was visible from the focused suite. Both were found by asking why the +*other* suites disagreed, instead of assuming they were stale. + +### The design verdict + +With both closed, the five assertions really are proxies, and the independent +reviewer reached the same conclusion on its own reasoning: + +> All five are proxies for "no *real* signature was borrowed/forwarded", not a +> requirement that the slot stay empty. [...] There is no honest "replayed history +> vs fresh turn" bit at those call sites [...] every unsigned first `functionCall` +> *is* history, which is the Gemini 3 400 this PR is fixing. (b) would disable the +> feature on the path that needs it. + +It also independently flagged the `isLikelyRealThoughtSignature` hole as a residual +risk — "do not let it enter the remember/observe path" — which is exactly the defect +already closed above. + +Design (a) adopted: each assertion now expects the sentinel and carries a comment +explaining why the property it protects still holds. #1312 keeps its positive +control that thread-a still receives the real `SIGNATURE`, so a genuine +cross-namespace leak would still fail the test. + +159 pass / 0 fail across all four affected suites. + +Differential probe required (gate 4): `applyAntigravityReplay` changes behavior, so +enumerate the arms — cache hit / miss, signed / unsigned first call, nested / direct / +short signature, Gemini / non-Gemini — against unpatched `dev` and record which move. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/021_wp2_wp3_outcome.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/021_wp2_wp3_outcome.md new file mode 100644 index 0000000000..2d763a3511 --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/021_wp2_wp3_outcome.md @@ -0,0 +1,76 @@ +# 021 — wp2/wp3 outcome: two fixed, one reimplemented + +## Dispositions + +| PR | lane | state | evidence | +|---|---|---|---| +| #2747 | FIX | rebased, approved, CI running | `07b975873` -> `4a0cbb55c`, patch-id `efa23210f341` unchanged | +| #2740 | FIX | rebased, approved, CI running | `f07ee36f2` -> `be11a65f7`; author then added a GUI surface at `1f07b68b8` | +| #2693 | REIMPLEMENT | **CLOSED-SUPERSEDED** by #2794 | three blockers closed, each mutation-bound | +| #2794 | new | **NEEDS_HUMAN** (approval) | CI 23/0, full suite 15352/0; self-approval refused | + +## The fork rebases held the authors' work exactly + +Both used `--force-with-lease` pinned to the author's last OID, pushed to the +author's fork rather than `origin`, and were announced on the PR before anything +else. Proof that nothing was rewritten: + +``` +#2747 patch-id efa23210f341 -> efa23210f341 range-diff 1: = 1: +#2740 patch-id 48b653f9a33b -> 48b653f9a33b three-dot name list unchanged +``` + +A side effect I had to own: the force-push resets the four-box readiness checklist +and returns the PR to draft. That is `enforce-target` behaving correctly — an +attestation about the old commit cannot cover a new one — but it means my push +created work for the contributor. I evidenced the two boxes that became objectively +true and asked rather than ticking the author's attestation. Both authors then acted: +`luvs01` completed the checklist and pushed a GUI follow-up, `olddonkey` ticked three +of four. + +Second discovery: fork PRs need a maintainer to approve the workflow run. Both sat +in `action_required` with **no CI at all** until approved via +`gh api -X POST .../actions/runs//approve`. "Only 5 checks" on a fork PR does not +mean the matrix passed — it means the matrix never started. + +## #2693: the green suite was the trap + +It passed its own suite 62/0 with all three defects live, which is why the +reimplementation was driven by the defect list rather than by its tests. + +Each fix is bound by a test that fails without it: + +| mutation | tests that fail | +|---|---| +| presence-check instead of `extractSignature` | 2 — nested, too-short | +| turn-wide flag instead of first-call | 1 — first-call sentinel | +| gate on `antigravityUsesReplayCache` | 1 — non-Gemini injection | +| slash-only regex | 2 — Vertex predicate, Vertex end-to-end | + +### What the focused suite could not see + +70/0 locally, and exact-head CI still failed three shards. Five tests in four other +suites asserted `thoughtSignature === undefined` and the sentinel filled it. + +Treating those as stale assertions would have been the easy read. Chasing *why* they +disagreed found two real defects instead: + +1. **The replay cache ingested the sentinel** as a genuine signature, so a token + fabricated on the way out round-tripped back in and was replayed later as evidence + a turn was signed. Observing it now leaves the cache empty. +2. **`isLikelyRealThoughtSignature` accepted it** — the predicate that exists to + reject `fc_`/`ctc_`/`tsc_` synthetic ids, and precisely what the issue #174 tests + protect. The sentinel is alphanumeric with underscores, so it passed every filter. + +Only after both were closed were the five assertions genuinely proxies for "nothing +was borrowed". An independent reviewer reached the same conclusion on separate +reasoning and flagged the second defect as residual risk — the one already fixed. + +**The transferable form of this:** when a change makes another suite fail, the +question is not "is that test stale" but "what did that test know that I did not". +Twice here, the answer was a real defect. + +## Carried into wp4-wp6 + +#2745, #2638, #2497 remain — all credential or auth-routing surfaces, all needing a +second maintainer. #2794 and #2769 join #2770 in the self-approval queue. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/030_phase3.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/030_phase3.md new file mode 100644 index 0000000000..c8edfe06ca --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/030_phase3.md @@ -0,0 +1,53 @@ +# 030 — wp4: #2745, OAuth credential boundary + +Head `a90ab6ee7`, mine, 39 behind, merge CLEAN, tsc OK. Touches +`src/server/responses/core.ts` (+55/-11) and `tests/generic-oauth-failover.test.ts`. + +**This is the surface `MAINTAINERS.md` requires explicit security review for.** The +PR carries a CHANGES_REQUESTED review with two open blockers: one credential-boundary +correctness defect and one test-oracle defect. + +Per `AGENTS.md` §"Security working notes", the mechanism, activation path and +remediation are **not reproduced here** — the fix has not shipped and `devlog/` is +public. They live in `.tmp/2745-security-triage.md` (gitignored) and on the PR via +`gh pr view 2745 --json reviews`. + +## What this phase decides + +Whether the two blockers can be closed with evidence strong enough that a +credential-boundary change is safe to land, or whether it stays NEEDS_HUMAN. + +The honest constraint: **I authored this PR.** Even with both blockers closed and a +behavioral regression, GitHub refuses my approval, exactly as it did for #2769. So +the realistic best outcome is "blockers closed, evidence posted, awaiting a second +maintainer" rather than a merge. + +That is worth doing anyway — a reviewed PR with its blockers closed is a different +object from one with them open — but this phase should not pretend the merge is +reachable. + +## Contention + +`src/server/responses/core.ts` is shared with #2638 and #2497. If any of those lands +first, re-run `git merge-tree` pairwise before this one moves. Textual mergeability +is not behavioral compatibility on the auth/routing boundary. + +## TESTS + +`tests/generic-oauth-failover.test.ts` — the required test work is recorded with the +rest of the triage in scratch. + +## Verification (C) + +```bash +bun x tsc --noEmit +bun test tests/generic-oauth-failover.test.ts +``` + +Plus the differential probe over every arm of any changed credential-resolution +function, and a mutation oracle on the new regression. + +## Terminal outcome + +DONE only if a second maintainer approves. Otherwise **NEEDS_HUMAN**, with the +blockers closed and the evidence posted. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/040_phase4.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/040_phase4.md new file mode 100644 index 0000000000..2f0ee34df6 --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/040_phase4.md @@ -0,0 +1,64 @@ +# 040 — wp5: #2638 rebase and safety verdict + +Head `b0f328462`, `luvs01` fork (`maintainerCanModify: true`), **192 behind**, +merge-tree CLEAN, tsc OK on the merged tree. 1341 insertions across: + +- `src/codex/auth-context.ts` +- `src/codex/routing.ts` (+334) +- `src/codex/subagent-model-fallback.ts` (+86) +- `src/server/responses/core.ts` (+78) +- three test files + +## Why "merge-tree CLEAN, tsc OK" is not enough here + +The reviewer's objection is precise and still stands: `src/server/responses/core.ts` +is modified both by this PR and by the intervening 192-commit `dev` range, and that +file is part of the request/auth-routing boundary the PR changes. **Textual +mergeability is not evidence the combined behavior is correct.** Two changes can +merge cleanly and still contradict each other semantically. + +The reviewer also asked that `maintainer-sponsored` NOT be applied and the waiting +fork workflows NOT be approved until a rebase onto current `dev`. + +## AGENTS.md invariant at risk + +This PR touches `src/server/responses/core.ts` and `src/codex/subagent-model-fallback.ts` +— the synchronous subagent-fallback chain. `AGENTS.md` is explicit: no `await` may be +added between `Bun.serve` and the `labActivationRequired` check in +`src/server/index.ts`, and the protected core files must not reach `src/lab/`. +`tests/core-lab-boundary.test.ts` enforces both and MUST be run on the rebased tree. + +## Plan + +1. Rebase `pr2638-r3` onto `origin/dev` on a local branch; record every conflict. +2. Run `tests/core-lab-boundary.test.ts` plus the auth, routing, entitlement and + subagent-fallback suites on the rebased tree. +3. Differentially probe the routing/auth decision functions the PR changes against + unpatched `dev` — which requests route differently, and is every difference + intended? +4. Verdict: + - if the rebase is clean and behavior is provably unchanged except for the + intended drain fix -> push the rebase to the fork, let CI run, and record that + it is ready for a second maintainer's security sponsorship; + - if any conflict requires a judgement call about auth or routing semantics -> + **NEEDS_HUMAN** with the exact hunk. + +Landing it myself is not on the table regardless: `MAINTAINERS.md` requires explicit +security review for this surface, and the reviewer has already withheld sponsorship. + +## Verification (C) + +```bash +bun x tsc --noEmit +bun test tests/core-lab-boundary.test.ts +bun test tests/codex-auth-context.test.ts +bun test tests/codex-routing.test.ts +bun test tests/subagent-fallback-handle-responses.test.ts +``` + +One suite at a time. The full suite goes to `lidge` via `ocx-run`. + +## Terminal outcome + +**NEEDS_HUMAN** in the expected case, with the rebase done and the evidence +attached so the security review has something current to review. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/050_phase5.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/050_phase5.md new file mode 100644 index 0000000000..7b43311470 --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/050_phase5.md @@ -0,0 +1,57 @@ +# 050 — wp6: #2497 adjudication + +Head `86a49e852`, `MarcTCruz` fork (`maintainerCanModify: true`), **399 behind**, +merge-tree **CONFLICT**. 20 files including `src/oauth/chatgpt.ts`, +`src/codex/account-store.ts`, `src/codex/auth-context.ts`, +`src/server/responses/core.ts`, `src/server/responses/codex-auth-error.ts`. + +## The question this phase actually answers + +The user authorized fixing what can be fixed, so the question is not "may I touch it" +but **"can this rebase be done without me deciding OAuth semantics on the author's +behalf?"** + +Attempt the rebase and classify every conflict: + +- **Mechanical** — imports moved, a function renamed upstream, formatting. Resolvable + without deciding anything about token lifetime, refresh ordering, or replay identity. +- **Semantic** — the upstream range and the PR both changed how a token is refreshed, + when a credential is rebound, or which account owns a replay. Resolving these means + authoring an OAuth refresh path and then reviewing my own credential code. + +If every conflict is mechanical: push the rebase, run CI, hand a current PR to a +security reviewer. If any is semantic: stop, record the exact hunks, return +**NEEDS_HUMAN**. + +My prior expectation is that 399 commits across `auth-context.ts` and `core.ts` — +both heavily rewritten in that window — will produce semantic conflicts. That is a +prediction, and this phase tests it rather than assuming it. The previous round +recorded #2497 NEEDS_HUMAN without attempting the rebase; defensible then, not now +that fork pushes are authorized. + +## Hard limits regardless of outcome + +- `AGENTS.md`: conflict analysis on an unfixed OAuth path goes to `.tmp/`, never a + tracked directory. +- `MAINTAINERS.md`: this surface needs explicit security review. Even a perfectly + clean rebase does not make it mergeable by me. +- Never force-push a resolution that changes behavior the author did not write. + +## Verification (C) + +```bash +bun x tsc --noEmit +bun test tests/chatgpt-oauth.test.ts +bun test tests/codex-account-store.test.ts +bun test tests/codex-main-account-refresh.test.ts +bun test tests/responses-native-main-refresh.test.ts +bun test tests/core-lab-boundary.test.ts +``` + +One at a time; full suite on `lidge` via `ocx-run`. + +## Terminal outcome + +**NEEDS_HUMAN** or **UNSAFE**, with the conflict classification as evidence. A clean +mechanical rebase upgrades it to "current and reviewable", which is the most this +round can honestly deliver on an OAuth refresh path. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/060_phase6.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/060_phase6.md new file mode 100644 index 0000000000..ed27b4cc69 --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/060_phase6.md @@ -0,0 +1,43 @@ +# 060 — wp7: close-out + +Produce `070_outcome.md`: PR, lane, action taken, terminal state, and the merge SHA +or the exact unresolved question. Every row cites evidence produced in this round. + +## Verification + +```bash +git fetch origin && git log --oneline origin/dev | head -12 +git rev-list --first-parent --count 50e955604..origin/dev +git rev-list --first-parent --no-merges --count 50e955604..origin/dev # expect 0 +gh pr list --repo lidge-jun/opencodex --state open --label bug +bun x tsc --noEmit +bun run privacy:scan +``` + +Plus, for every landed PR: the merge SHA, a mutation-verified regression, and the +differential probe table for any behavior-changing function. + +## Criteria mapping + +- c1 — the 070 table covers every target, verified against live `gh`. +- c2 — the 000 merged-tree table plus per-phase receipts. +- c3 — per-PR mutation oracles recorded in each phase doc. +- c4 — first-parent counts show merges only. +- c5 — differential probe tables for changed functions. +- c6 — auth/OAuth surfaces carry a named trigger and the unresolved question. + +## Expected shape of the honest answer + +Two PRs (#2747, #2740) are fixable and mergeable by me. One (#2693) is +reimplementable and mergeable. Three (#2745, #2638, #2497) sit on credential or +auth-routing surfaces where `MAINTAINERS.md` requires a second maintainer, and two +more (#2769, #2770) are blocked only because GitHub refuses self-approval. + +So the realistic terminal state is **three merged, five awaiting a human**, and the +value delivered on those five is that each arrives current, rebased and evidenced +rather than stale. Stating that up front is not lowering the bar — it is the +difference between work that is blocked and work that is merely unfinished. + +If a phase discovers it can do better than this — for example #2638's rebase proving +behaviorally clean — the plan is wrong in the right direction and the outcome doc +should say so explicitly rather than quietly matching the prediction. diff --git a/devlog/_plan/260828_bugpr_fix_and_reimplement/070_outcome.md b/devlog/_plan/260828_bugpr_fix_and_reimplement/070_outcome.md new file mode 100644 index 0000000000..d809bef249 --- /dev/null +++ b/devlog/_plan/260828_bugpr_fix_and_reimplement/070_outcome.md @@ -0,0 +1,157 @@ +# 070 — round outcome + +This round advanced `dev` from `50e955604` to `29be459a3`. The mandate was "fix what +can be fixed, reimplement what cannot, merge both", with fork pushes authorized. + +`dev` has since moved to `5511a424c` via #2774, a squash-merge from a different +work-stream. Both first-parent commits in the range arrived through PRs targeting +`dev` — the squash shows as a non-merge commit, which is what a squash-merge looks +like, not a direct push. + +| PR | lane | outcome | evidence | +|---|---|---|---| +| #2740 | FIX | **MERGED** `29be459a3` | rebased under lease, patch-id `48b653f9a33b` unchanged; author added a GUI surface; both regressions mutation-verified | +| #2693 | REIMPLEMENT | **CLOSED-SUPERSEDED** by #2794 | three blockers closed, each mutation-bound | +| #2794 | new | **MERGED** `bdc1e97bb` | `eebd1913e`; a reviewer blocker found and fixed after the first green matrix, then admin-merged on user authorization | +| #2747 | FIX | **MERGED** `fe063d16e` | rebased, CI 20/0 green, approved; admin-merged on user authorization | + +## Admin-merge authorization (2026-08-28) + +The user authorized `--admin` merges, which resolves the self-approval deadlock that +held #2794 and the author-attestation box that held #2747. Both were already green: +#2794 at 23 success / 0 failure, #2747 at 20 / 0. The authorization removed a +*process* gate, not a verification one — nothing was merged that lacked evidence. + +What it deliberately did **not** unblock: + +- **#2638** — head moved to `e06ffbaa8` after my rebase, so my 15375/0 evidence no + longer describes the tree under review. It also still fails `hygiene` on + `unsponsored_surface`. Admin rights make that mechanically bypassable and it stays + unmerged: the gate is asking for a security judgement on + `src/codex/auth-context.ts`, and "I can force it" is not an answer to "has anyone + reviewed this credential path". +- **#2745** — the fix lives on `codex/oauth-failover-identity-v2` and was never opened + as a PR. Two blockers from the original review also remain open by my own + admission (the `applyFailoverSnapshot` audit and the A -> 429 -> B recovery-path + regression). +- **#2497** — unchanged; still needs the author's rebase. +| #2638 | FIX | **NEEDS_HUMAN** (security) | rebased clean, full suite **15375/0**; `hygiene` correctly holds on `unsponsored_surface` | +| #2497 | attempt | **NEEDS_AUTHOR** (semantic conflicts) | rebase attempted and aborted; 6 hunks, only 1 mechanical | +| #2745 | FIX | **NEEDS_HUMAN** (approval) | both blockers closed on `codex/oauth-failover-identity-v2` `2b3574a45`; suite 15358/0 | + +## #2745: the defect was one `??` + +`refreshed.apiBaseUrl ?? getOAuthCredentialApiBaseUrl(route.providerName)` reads +correct until you follow the second arm: `getOAuthCredentialApiBaseUrl` is +`validateCopilotApiBaseUrl(getCredential(provider)?.apiBaseUrl)` — the **active** +credential, with no account scoping. A generic 429 rotation never promotes the +account it rotated to, so for a legacy account B with no allowlisted origin, that arm +silently reached account A. B's bearer, A's host. + +`copilotOriginForRefreshedCredential` now resolves from the refreshed snapshot alone +and otherwise fails closed to the canonical origin, consulting no other account: + +| refreshed snapshot for B | resolved | +|---|---| +| own allowlisted origin | that origin | +| legacy, no origin | canonical — never A's | +| non-allowlisted origin | canonical | +| empty | canonical | + +The test blocker was worse than "not behavioural": one assertion counted the buggy +expression and required it to appear **twice**, so fixing the defect would have +broken the test. Replaced with a behavioural test over all four arms, plus a topology +guard that strips comments first — the new helper's doc comment quotes the removed +expression to explain why it was wrong, and the first version of the guard read that +explanation as the defect. + +Still open from the review and deliberately not claimed: the `applyFailoverSnapshot` +audit, and an executable A -> 429 -> B regression through the HTTP recovery path. + +## Where I was wrong, in the useful direction + +060 predicted #2638 would need a semantic rebase and probably stay stale. It rebased +across **195 commits with zero conflicts**, patch-ids unchanged, and the full suite +passes 15375/0 on the rebased tree — including `tests/core-lab-boundary.test.ts`, +which matters because the PR touches `core.ts` and `subagent-model-fallback.ts`. + +So the reviewer's original objection was right *and* has now been answered: textual +mergeability proved nothing, so I measured behavior instead, and the behavior is +clean. What remains is not staleness — it is the security decision, and `hygiene` +holds it on `unsponsored_surface` naming `src/codex/auth-context.ts`. The +`maintainer-sponsored` label *is* that human judgement; an agent applying it would +be forging the gate rather than passing it. + +#2497 went the other way and the contrast is the point. Same "far behind" shape, 402 +commits, and it does **not** rebase: 6 conflict hunks across three credential files, +of which exactly one is mechanical. The decisive one is delete-vs-modify on the +entitlement path — `dev` deleted a block the PR modifies — which git cannot resolve +and I should not. Aborted, nothing pushed, triage in `.tmp/` per `AGENTS.md`. + +"Too far behind" was never the real criterion. **Whether the conflicts are +mechanical is.** + +## Two operational findings + +**A fork PR runs no product CI until a maintainer approves the workflow run.** Both +#2740 and #2747 sat in `action_required` showing 5 green checks — and the matrix had +never started. "5 checks passing" on a fork PR is not a weak signal, it is *no* +signal. Approve via `gh api -X POST .../actions/runs//approve`. + +**A maintainer force-push resets the contributor's readiness checklist.** That is +`enforce-target` working correctly: an attestation about the old commit cannot cover +a new one. But it means the rebase creates work for the author. Two boxes become +objectively true and can be evidenced; the local-CI attestation and the +ready-for-review confirmation are theirs. Ask — do not tick them. + +## The finding worth keeping + +#2794 passed its focused suite 70/0 and still failed three CI shards. Five tests in +four other suites asserted `thoughtSignature === undefined` and the sentinel filled +it. The easy read was "stale assertions". Chasing *why* they disagreed found two real +defects: the replay cache ingested the sentinel as a genuine signature, and +`isLikelyRealThoughtSignature` — the predicate that exists to reject fabricated ids — +accepted it. + +**When a change breaks another suite, the question is not whether that test is stale. +It is what that test knew that you did not.** Twice here, the answer was a defect. + +## Standing gates, updated + +1. Compile and test evidence from the MERGED tree, never the PR head. +2. Pairwise `git merge-tree` before two PRs sharing a file both land. +3. Green checks are not health unless `ci` / `test N/4` / `macos` are present — and + on a fork PR, confirm the matrix actually **started**. +4. Green targeted suites are not health either; you chose the targets. +5. A rebase is verified by `patch-id` and `range-diff`, never by `git diff OLD NEW`, + which reports the whole intervening range. +6. Force-push to a fork only with `--force-with-lease` pinned to the author's OID, + to the author's remote, announced on the PR. +7. `gh run rerun` replays the same commit; only a rebase moves the base. +8. A gate that asks for human judgement (`maintainer-sponsored`) is not an obstacle + to route around. + +## Postscript: the review caught what green CI could not + +#2794 had 23 green checks, a 15352/0 full suite, and four mutation-verified fixes. +Ingwannu then found that `antigravitySupportsThoughtSignatureSentinel` scanned the +**entire raw replay identity** rather than the model component. The Vertex key is +`vertex:::` and the project id is operator-chosen, so: + +``` +vertex:gemini-prod:global:gpt-oss-120b -> true (Gemini-only sentinel injected) +vertex:gemini-team:us:claude-fable-5 -> true +``` + +That is the same defect class the predicate exists to prevent — a Gemini-only token +reaching a non-Gemini model — reintroduced one layer up, in the fix for it. + +**My tests could not have caught it.** Every Vertex case I wrote used a neutral +project name, so the positive control was doing double duty as the negative one. A +test suite written by the person who wrote the bug shares its blind spot; that is +what the review is for, and it is the third time in this campaign a reviewer found +something no amount of my own green output would have surfaced. + +Fixed by reducing to the model component (last `:` segment, then last `/` segment, +anchored) rather than widening or blacklisting. Mutation-verified: the whole-string +scan fails exactly the new regression.