Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Evidence

Collected 2026-09-11 on the reporting Windows machine. Read-only except for
`bun install`, which populated `node_modules` so the verifier could run.

## 1. The clamp diagnostic outlives the binary it describes

`~/.opencodex/codex-runtime-clamp.json`:

```json
{
"version": 1,
"updatedAt": "2026-09-10T12:01:09.525Z",
"runtimePath": "C:\\Users\\<user>\\AppData\\Local\\Programs\\OpenAI\\Codex\\bin\\codex.exe",
"runtimeVersion": "0.135.0",
"removedEfforts": ["max", "ultra"],
"affectedModels": ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark",
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra",
"anthropic/claude-fable-5-1", "anthropic/claude-opus-4-6", "anthropic/claude-opus-5"]
}
```

The binary at that exact path now reports `codex-cli 0.154.0`, and its own
bundled catalog carries both rungs:

```
$ codex debug models --bundled
... "slug":"gpt-6-astra" ... "supported_reasoning_levels":[
{"effort":"low"...},{"effort":"medium"...},{"effort":"high"...},
{"effort":"xhigh"...},{"effort":"max"...},{"effort":"ultra"...}]
```

`ocx status` nevertheless reports:

```
Codex version: 0.154.0
Catalog clamp: active
Removed efforts: max, ultra
```

and `ocx doctor`:

```
ok Selected runtime: ...\codex.exe (0.154.0, source=configured)
!! max and ultra were removed during catalog sync.
Suggested: set CODEX_CLI_PATH to a newer Codex binary and run ocx sync.
```

The advice is impossible to follow — the selected binary is already the newer one.

## 2. Why: path equality short-circuits the version check

`src/codex/runtime.ts:431`

```ts
export function effortClampAppliesToRuntime(diagnostic, runtime): boolean {
if (!diagnostic || diagnostic.removedEfforts.length === 0) return false;
if (sameRuntimeCommand(diagnostic.runtimePath, runtime.command)) return true; // <-- returns before version is read
return Boolean(diagnostic.runtimeVersion && runtime.version
&& diagnostic.runtimeVersion === runtime.version);
}
```

The version comparison on the last line is only reachable when the paths
**differ**. An in-place upgrade — which is how the Windows Codex install updates —
keeps the path identical, so the stale diagnostic is treated as current forever.
Consumers: `src/cli/status.ts:250`, `src/server/management/config-routes.ts:283`.

## 3. Where the effort ladder is derived

- `src/codex/catalog/effort.ts:331` `codexSupportedReasoningEfforts` → `loadBundledCodexCatalog`
- `src/codex/catalog/bundled.ts:225` runs `debug models --bundled`
- `src/codex/catalog/bundled.ts:261` passes `discoverAlternatives: deps.discoverAlternatives ?? false`
- `src/codex/runtime.ts:603` `if (deps.discoverAlternatives === false) break;` — candidate search stops after the persisted entry
- `src/codex/runtime.ts:582` the persisted command is pushed first with source `configured`
- Applied at `src/codex/catalog/sync.ts:1945` and `src/codex/convergence.ts:382`

So the machine-wide ladder is whatever the persisted binary reports, with no
notion of which client will render it.

## 4. Card fields: deleted everywhere, carried nowhere — OPEN QUESTION

Delete sites:

| Path | Row kind | Fields |
| --- | --- | --- |
| `src/codex/catalog/metadata.ts:567` | alias | `availability_nux` |
| `src/codex/catalog/sync.ts:354` | routed | `upgrade = null`, `availability_nux` |
| `src/codex/catalog/parsing.ts:613` | routed | `availability_nux`, `upgrade` |
| `src/codex/catalog/reserve.ts:36` | reserve projection | `availability_nux` |

Client side, for reference (openai/codex at submodule HEAD):

- `codex-rs/protocol/src/openai_models.rs:409,410` — `ModelInfo.availability_nux`, `ModelInfo.upgrade`
- `codex-rs/tui/src/app/startup_prompts.rs:203,211` — NUX selection and a four-show cap
- `codex-rs/app-server/src/models.rs:31` — `upgrade` / `upgrade_info` forwarded to the desktop app

**Unresolved.** `src/codex/data/upstream-models.json:928` has `availability_nux: null`
for `gpt-6-astra`, and so does the live `debug models --bundled` output above. Only
`gpt-5.6-sol` and `gpt-5.5` carry copy in the bundled catalog. That means the
bundled catalog may simply not be where Astra's announcement lives — the account
endpoint `backend-api/codex/models?client_version=...` is the other candidate, and
it has not been probed. **Phase 3 builds a carrier; whether there is anything to
carry for Astra is not yet established.** Probing it needs a live ChatGPT account
token and is a user decision, not an agent one.

Separately, the `workspace-messages` channel (`headline` / `announcement`,
`codex-rs/backend-client/src/client.rs:651`) has zero references in this
repository. It is out of scope here and is gated client-side on
`auth.uses_codex_backend()` (`account_processor.rs:1307`), which is false for
`AuthMode::ApiKey` (`codex-rs/protocol/src/auth.rs:61`) — the mode produced by
`env_key` injection at `src/codex/inject.ts:327`.

## 5. Verifier, actually run

```
$ bun install
103 packages installed

$ bun test tests/codex-integration/codex-runtime.test.ts tests/codex-integration/catalog-go-exact-efforts.test.ts
42 pass, 0 fail, 160 expect() calls # exit 0
```

Reads the change target: `tests/codex-integration/codex-runtime.test.ts:37` imports
`../../src/codex/runtime`; line 612 calls `effortClampAppliesToRuntime` directly.

A first attempt failed with `Cannot find module 'zod/v4'` before `bun install` —
recorded because "the verifier passed" would otherwise be unverifiable.

## 6. Not established

- Whether Astra carries announcement copy on the account catalog endpoint (§4).
- Which consumer should own the shared catalog when Desktop and CLI disagree.
- Whether any non-Windows install reproduces §1; the in-place-upgrade shape was only observed here.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Architecture consultation and main dispositions

Architect: `xai/grok-4.6` via the connected hub, read-only, dispatched 2026-09-11.
Two explorers ran alongside it on disjoint questions (test impact; presentation-field
flow). Verdict on the first draft of `000_plan.md`: **MISALIGNED**. Main accepts most
of it; the dispositions below are what actually governs B.

## Decisions

| ID | Proposal | Main disposition |
| --- | --- | --- |
| CLAMP-01 | Keep the clamp; a rung survives if it is in `supported` **or** it is `max`/`ultra`. Apply the same predicate to the default-repair block (`effort.ts:378`), which today would still rewrite `default_reasoning_level: "ultra"` even on a ladder that kept `ultra`. Do not post-patch with `ensureUltraReasoningLevel` — it is a construction helper, no-ops on empty ladders, and would make `removedEfforts` report rungs that were put back. | **Accepted.** This is the mechanism. The default-repair catch is a real defect the plan missed. |
| CLAMP-02 | Do **not** union `max`/`ultra` into `supportedCodexReasoningEffortsFromObservedCatalog` (`effort.ts:313`), and do not touch `catalogEffortCompatibility` (`effort.ts:409`, `src/client/catalog-compatibility.ts:47`). Emission and hub admission are different questions; hub download stays fail-closed per #4207. | **Accepted, and promoted to a scope boundary.** A hub client on a leftover 0.135 CLI must keep refusing rather than writing a catalog it cannot parse. |
| CLAMP-03 | One predicate change in `clampEntryToCodexSupportedEfforts` (`effort.ts:348`). No new module, no signature change at `sync.ts:1945` or `convergence.ts:382`, no consumer binding in `runtime.ts`, no `bundled.ts` discovery change. | **Accepted.** Strictly smaller than the plan's Phase 2. |
| CLAMP-04 | Keep `codex-runtime-clamp.json`. After CLAMP-01 a sync whose only removals were `max`/`ultra` persists `null` and unlinks. Until that sync, filter the two rungs out of "active clamp" in a single helper that **both** `effortClampAppliesToRuntime` and `doctor.ts` call — today `ocx doctor` (`doctor.ts:1180`) never calls the helper `ocx status` uses (`status.ts:249`), so the two can disagree about the same file. | **Accepted.** This explains the observed contradiction in `010_evidence.md` §1 and is a second, independent defect. Phase 1 stays: it is necessary for non-`max`/`ultra` rungs and insufficient alone, because a same-version leftover listing only those two would still warn. |
| CLAMP-05 | Keep reserve exactness. `{xhigh}` vs `{medium}` still deletes the row; `{low,high}` vs `{medium,high}` still yields `{high}`. Only the case where `max`/`ultra` are the sole survivors changes: the row is **kept** instead of spliced out (`effort.ts:466,472`). | **Accepted.** This closes the escalation the plan left open. Add the focused reserve test; do not weaken the existing xhigh-vs-medium omission test. |
| CLAMP-06 | Drop the per-consumer projection. One shared `$CODEX_HOME/opencodex-catalog.json`, no consumer key in the diagnostic, no second catalog. Desktop/CLI disagreement is resolved by always offering the two rungs. | **Accepted.** The "who owns the shared file" blocker in the first draft is obsolete. |

Residual risk accepted with CLAMP-06: a leftover pre-0.14x CLI reading the shared
file locally can fail to parse it. Hub clients still fail closed. Local `ocx sync`
does not, and that is the stated cost of the ruling.

## Correction to Phase 3 — the premise was wrong

The first draft assumed the four `delete` sites were why no card appears. The
explorer pass disproves it. **Pin-backed native rows already keep both fields end
to end** — `upstreamNativeEntry` deletes only `minimal_client_version`,
`finishUpstreamNativeEntry` (`sync.ts:257`) does not touch them, and
`ensureStrictCatalogFields` strips them only when `isRouted === true`
(`parsing.ts:609`). There is a test pinning exactly this:
`tests/codex-integration/codex-catalog.test.ts:7398` — "a native row keeps its own
eligibility metadata" — and `:3529` asserts Sol's `availability_nux` is defined.

So the carrier exists. Three native-looking kinds still lose the field, each for a
defensible reason: the Daybreak capability alias (`metadata.ts:567`), older natives
not in `UPSTREAM_NATIVE_ENTRIES` when OpenCodex has to synthesize the row
(`deriveEntry`), and Reserve.

**The actual gap is upstream of all four sites.** `src/codex/model-entitlements.ts`
does fetch `https://chatgpt.com/backend-api/codex/models`, but `parseAccountModels`
(`:536-546`) keeps nothing except the slug — every presentation field in that
response is discarded, and the set is used only as an allowlist for account-gated
natives (currently just Daybreak). Astra's row therefore comes from the pin or the
bundled catalog, both of which carry `availability_nux: null`.

That reframes the open question in `010_evidence.md` §4. It is no longer "does the
carrier exist" but "does the account roster carry copy the pin does not, and should
it override the pin". The probe still needs a live account token and is still a user
decision.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Test impact of the `max`/`ultra` exemption

From the explorer pass (`xai/grok-4.6`, read-only). This is the list B inverts, and
it is the reason the loop-spec verifier row now names three files the first draft
did not.

## Must invert — these encode the behaviour being removed

`tests/codex-integration/codex-catalog.test.ts`, `describe("Codex reasoning-effort capability clamp")` at `:7027`:

| Test | Line | Why it inverts |
| --- | --- | --- |
| the observed-state clamp is pure with respect to frozen runtime evidence | 7051 | expects `removedEfforts: ["max","ultra"]` and a default rewritten to `xhigh` |
| strips max and ultra when the installed Codex ladder stops at xhigh | 7077 | the exemption is precisely this case |
| falls back to the conservative universal ladder when every advertised effort is unsupported | 7095 | a `max`/`ultra`-only row must no longer collapse to `low/medium/high` |
| repairs an unsupported max default to the highest surviving xhigh rung | 7107 | `effort.ts:378`, the default-repair block CLAMP-01 also changes |

`tests/codex-integration/codex-runtime.test.ts`:

| Test | Line | Why it inverts |
| --- | --- | --- |
| clamp diagnostics include unsupported default_reasoning_level changes | 1005 (listed as 940 before the new Phase 1 tests shifted it) | runs the live clamp and expects `ultra` → `high` with `"ultra"` in `removedEfforts` |

## Must keep passing — assert these explicitly, they are the proof it is an exemption

- `preserves max and ultra when the installed Codex ladder includes them` — `codex-catalog.test.ts:7086`
- `is a no-op when the installed Codex binary cannot be probed` — `codex-catalog.test.ts:7115`
- `final clamp omits incompatible Reserve in-place without inventing efforts` — `reserve-catalog.test.ts:239`; `{xhigh}` vs `{medium}` still deletes the row. Fails only if the whole clamp is disabled, which is the mistake this plan is trying not to make.
- `partial effort intersection keeps only source efforts and a surviving default` — `reserve-catalog.test.ts:252`
- The four `#4207` cases in `tests/clients/client-catalog-compatibility.test.ts:37,51,76,97` — they do not mutate the catalog, and they fail **only** if CLAMP-02 is violated by also treating the two rungs as always compatible. They are the regression gate for the hub boundary.
- Runtime tests that seed `persistEffortClamp` themselves and therefore do not depend on live stripping: `codex-runtime.test.ts:601, 624, 815, 842` — line numbers verified stale by the A reviewer (the Phase 1 test insertions shifted them; the tests are found by name, not line).

## Out of scope — a different layer, do not touch

The wire clamp (`nativeEffortClamp`, `effort.ts:52`, consumed at
`src/server/responses/core.ts:2582`) still maps `max`/`ultra` down for natives that
only mock those rungs. Catalog advertisement and wire honesty are deliberately
split, as `structure/03_catalog-and-subagents.md:339` already records. Affected
suites that must stay green unchanged: `codex-v2-gate.test.ts:1821`,
`effort-policy.test.ts:434`, `reasoning-effort.test.ts:887`,
`openai-responses-passthrough.test.ts:559`, `claude-model-info.test.ts:63`,
`vision-reasoning-contract.test.ts:193`.

Likewise the construction-side exactness suites — `catalog-go-exact-efforts.test.ts`,
`codex-v2-gate.test.ts:111-126`, the none-only and combo ladder pins in
`codex-catalog.test.ts` — fail only if "unconditional" is misread as "always **add**
`max`/`ultra`". It is not: Go rows, Luna, combo rows, and none-only custom ladders
keep their exact ladders. Anything that grows Muse to include `max` or Luna to
include `ultra` is a defect, not the feature.

## New test file placement

`tests/test-layout.test.ts:20` forbids a root-level file that resolves to a migrated
domain. A new file needs matching entries in `scripts/test-layout/layout.json`
`explicit` and `tests/fixtures/test-layout-expected.json`; the `codex-integration`
regex seed already matches an `effort-*.test.ts` name until those exist
(`layout.json:34`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Unresolved after the reflection check

The architect reflection returned MISALIGNED with five findings. Two were document
coherence and are fixed in `000_plan.md`. These three are real and are **not**
closed. B does not start Phase 3, and does not implement CLAMP-04, until they are.

## GAP-1 — CLAMP-04 contradicts tests that `030_test_impact.md` promises to keep green

CLAMP-04 says a leftover diagnostic listing only `max`/`ultra` should stop counting
as an active clamp. But `tests/codex-integration/codex-runtime.test.ts:601` and
`:624` seed `persistEffortClamp` with exactly `removedEfforts: ["max","ultra"]` and
`["max"]` and then assert the diagnostic **is** active. `030_test_impact.md` lists
both as must-keep-passing. Both cannot be true.

Two more files assert the same leftover shape live and are missing from `030`
entirely: `tests/cli/cli-status-json.test.ts:376` and
`tests/config/settings-stream-mode.test.ts:145`.

Phase 1's accept criteria also require same-path-same-version to still return
`true` — which is precisely the leftover file on the reporting machine.

**Disposition: RESOLVED 2026-09-11 — CLAMP-04 ships.** The user authorised the full
cycle with the working tree already carrying the CLAMP-04 implementation
(`liveRemovedEfforts` in `src/codex/runtime.ts`, doctor/status/config-routes aligned
to it, and the four seed-test rows inverted to `["xhigh"]`). That is the recorded
decision; see `050_revalidation.md`.

Independent of that choice: `src/cli/doctor.ts:1180` did not call
`effortClampAppliesToRuntime`, so doctor and status could disagree about one file.
**Historical as of 2026-09-11** — `doctor.ts:1182-1189` now calls the shared
predicate; recorded in `050_revalidation.md`.

## GAP-2 — Phase 3 names a consumer that cannot consume

The reflection is right that the field chain skips a stage. The roster result is a
`ReadonlySet<string>` on `CodexModelEntitlementSnapshot.modelsByAccount`; there is no
presentation payload anywhere until that cache shape changes.
`finishUpstreamNativeEntry` (`sync.ts:257`) clones the pin and takes no roster data,
so naming it as the consumer describes a path that does not exist. The missing
stages are fetch → snapshot shape → sync plumbing, and the plan names none of them.

Worse for the stated goal: **Astra is not account-gated.**
`ACCOUNT_GATED_NATIVE_OPENAI_MODELS` (`src/codex/catalog/native-models.ts:50`) is
Daybreak alone, and `availableAccountGatedNativeModels`
(`model-entitlements.ts:1074`) filters only that set. So "a native slug the roster
already authorises" excludes the one model this whole thread is about. Overlaying
roster copy onto Astra is a new use of `/models`, not a descriptor on an existing
allowlist.

And Daybreak — the one slug the roster does authorise — is a capability alias whose
`availability_nux` is deleted at `metadata.ts:567`. Phase 3 asserts the alias still
loses the field while also asserting the roster wins over the pin. Merge order is
unspecified, so those two accept rows can contradict each other.

**Disposition: Phase 3 is withdrawn from the executable plan** and reduced to a
question: does `backend-api/codex/models?client_version=0.154.0` return
`availability_nux` for `gpt-6-astra` under a real account? If no, the whole phase
dies and the answer to "why is there no Astra card" is simply that upstream has not
shipped copy for it. If yes, Phase 3 is re-planned from the snapshot shape up, not
patched into `finishUpstreamNativeEntry`.

## GAP-3 — citation nits

- `020` cites `codex-catalog.test.ts:7398` for the native-keeps-eligibility pin; `:7398` is the comment, the test is `:7400`.
- `020` cites Sol's `availability_nux` assertion at `:3529`; it is `:3530`.
- `030` lists `client-catalog-compatibility.test.ts:97` as a fourth `#4207` case; it is an assertion inside the test at `:83`.

Left uncorrected in place deliberately — the reflection is the record, and rewriting
the numbers without re-reading the files would be the same class of error.
Loading
Loading