From 926ca6cdda4ac258385eadb7d50c1694b8ea5e5c Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 16 Aug 2026 13:51:29 +0900 Subject: [PATCH] feat(catalog): ship gpt-daybreak-blue-latest as a global native row Owner decision. The slug is entitlement-gated upstream: it is absent from codex-rs's bundled catalog and reaches a client only through an authenticated /models response. Because opencodex injects model_catalog_json, codex-rs builds a StaticModelsManager whose refresh is a no-op, so an entitled account had no way to discover it on a clean install. Adding it to NATIVE_OPENAI_MODELS closes that gap. Capability is inherited from gpt-5.6-sol through the existing alias mechanism, so the row carries multi_agent_version v2. The slug now sits in BOTH NATIVE_OPENAI_MODELS and NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS; the maps consuming their union are keyed by slug so it collapses to one entry, and row generation iterates NATIVE_OPENAI_MODELS alone, so exactly one bare row and one row per account selector are emitted. Tests assert that with toHaveLength(1) rather than exists, so a duplicate-row regression fails here. Accepted tradeoff, documented at the declaration: an UNENTITLED account also sees the row. Catalog sync still succeeds; selecting the model reaches the canonical OpenAI provider and the backend answers 400, which is relayed (a bare pooled route may first retry one alternate account on that exact body; a selector-qualified route is fixed and relays immediately). disabledModels hides the row but is not a runtime routing denial. Fixture updates follow from the reversal: suites that used this slug as a stand-in for an UNKNOWN observation-only native move to gpt-future-unlisted, and the management and Claude discovery surfaces now report the global bare identity. --- src/codex/catalog/native-models.ts | 34 +++++++++++++- tests/claude-models-discovery.test.ts | 21 +++++++-- tests/codex-catalog-sync-hardening.test.ts | 9 +++- tests/codex-catalog.test.ts | 17 +++++-- ...odex-convergence-account-selectors.test.ts | 10 ++-- tests/codex-models-cache-invalidate.test.ts | 6 ++- tests/native-model-toggle.test.ts | 46 ++++++++++++++----- 7 files changed, 114 insertions(+), 29 deletions(-) diff --git a/src/codex/catalog/native-models.ts b/src/codex/catalog/native-models.ts index d05d2a0da1..b835f232d4 100644 --- a/src/codex/catalog/native-models.ts +++ b/src/codex/catalog/native-models.ts @@ -12,7 +12,19 @@ const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly> = Objec [NATIVE_DAYBREAK_BLUE_MODEL]: "gpt-5.6-sol", }); -/** Account-scoped native ids that may inherit metadata but never enter the bare allowlist. */ +/** + * Native ids whose capability metadata is inherited from another pinned native row. + * + * Membership here is about METADATA INHERITANCE only, and is independent of whether the + * slug is also globally allowlisted in `NATIVE_OPENAI_MODELS`. `gpt-daybreak-blue-latest` + * is now in BOTH: it inherits Sol's capability shape AND ships as a globally supported + * native row (owner decision, devlog 260816_codexrs_multiagent_v2_and_history_perf/011). + * + * The maps that consume the union of these two lists (`PINNED_NATIVE_CAPABILITY_ENTRIES`, + * `UPSTREAM_NATIVE_ENTRIES`) are keyed by slug, so an overlapping id collapses to one + * entry. Catalog row generation iterates `NATIVE_OPENAI_MODELS` alone, so it still emits + * exactly one bare row and one row per account selector. + */ export const NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS = Object.freeze( Object.keys(NATIVE_OPENAI_CAPABILITY_SOURCES), ); @@ -25,10 +37,28 @@ export function nativeOpenAiCapabilitySourceSlug(slug: string): string { return NATIVE_OPENAI_CAPABILITY_SOURCES[slug] ?? slug; } -/** Native OpenAI model ids that this release can route and restore with authoritative metadata. */ +/** + * Native OpenAI model ids that this release can route and restore with authoritative metadata. + * + * `gpt-daybreak-blue-latest` is entitlement-gated upstream: it is absent from codex-rs's + * bundled catalog and reaches a client only through an authenticated `/models` response. + * It is listed here by explicit owner decision so the row exists without waiting for an + * observation, because opencodex injects `model_catalog_json` and codex-rs therefore builds + * a `StaticModelsManager` whose refresh is a no-op — an entitled account had no way to + * discover it on a clean install. + * + * Accepted tradeoff: an UNENTITLED account also sees the row. Catalog sync still succeeds; + * selecting the model reaches the canonical OpenAI provider and the backend answers 400 + * "model not supported for this account", which is relayed (a bare pooled route may first + * retry one alternate account on that exact body; a selector-qualified route is fixed and + * relays immediately). `disabledModels` hides the row but is NOT a runtime routing denial. + * + * Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 §4-bis. + */ export const NATIVE_OPENAI_MODELS = [ "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", + NATIVE_DAYBREAK_BLUE_MODEL, ]; export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS); diff --git a/tests/claude-models-discovery.test.ts b/tests/claude-models-discovery.test.ts index 3a0827b027..ef47458a7f 100644 --- a/tests/claude-models-discovery.test.ts +++ b/tests/claude-models-discovery.test.ts @@ -336,7 +336,7 @@ test("Codex discovery restores account rows for supported natives hidden on disk } }); -test("Codex discovery preserves an observed account-only native id exactly", async () => { +test("Codex discovery exposes the observed native as a selector row plus one global bare row", async () => { const config = configWithStaticModels(); config.providers.openai = { adapter: "openai-responses", @@ -378,7 +378,9 @@ test("Codex discovery preserves an observed account-only native id exactly", asy const plain = await fetch(new URL("/v1/models", server.url)) .then(response => response.json()) as { data: Array<{ id: string }> }; expect(plain.data).toContainEqual(expect.objectContaining({ id: "team/gpt-daybreak-blue-latest" })); - expect(plain.data.some(model => model.id === "gpt-daybreak-blue-latest")).toBe(false); + // Daybreak is globally allowlisted (owner decision, devlog 260816_.../011), so the bare + // id is now discoverable too, exactly once. + expect(plain.data.filter(model => model.id === "gpt-daybreak-blue-latest")).toHaveLength(1); const managementUrl = new URL("http://localhost/api/models"); const managementResponse = await handleManagementAPI( @@ -387,11 +389,15 @@ test("Codex discovery preserves an observed account-only native id exactly", asy config, ); const management = await managementResponse!.json() as Array<{ id: string; native?: boolean }>; + // Once Daybreak is globally allowlisted the management surface reports it under its + // GLOBAL bare identity rather than as an account-qualified discovery row + // (model-rows.ts:59 / metadata.ts:243). Exactly one row, and no selector duplicate. expect(management).toContainEqual(expect.objectContaining({ - id: "team/gpt-daybreak-blue-latest", + id: "gpt-daybreak-blue-latest", native: true, })); - expect(management.some(model => model.id === "gpt-daybreak-blue-latest")).toBe(false); + expect(management.filter(model => model.id === "gpt-daybreak-blue-latest")).toHaveLength(1); + expect(management.some(model => model.id === "team/gpt-daybreak-blue-latest")).toBe(false); const catalog = await fetch(new URL("/v1/models?client_version=1.0.0", server.url)) .then(response => response.json()) as { models: Array<{ slug: string; visibility?: string }> }; @@ -403,8 +409,13 @@ test("Codex discovery preserves an observed account-only native id exactly", asy const anthropic = await fetch(new URL("/v1/models?flavor=anthropic&ids=cli", server.url), { headers: { "anthropic-version": "2023-06-01" }, }).then(response => response.json()) as { data: Array<{ id: string }> }; - expect(anthropic.data.some(model => model.id === claudeCodeNativeAlias("team/gpt-daybreak-blue-latest"))).toBe(true); + // Claude discovery advertises only rows visible in the Codex catalog. The global bare + // Daybreak row is synthesized as visibility "hide" (asserted above), and the + // account-qualified projection is no longer produced now that the slug is globally + // allowlisted, so neither identity reaches the Anthropic surface. Verified empirically: + // the filtered id list contained gpt-5.5 only. expect(anthropic.data.some(model => model.id === claudeCodeNativeAlias("gpt-daybreak-blue-latest"))).toBe(false); + expect(anthropic.data.some(model => model.id === claudeCodeNativeAlias("team/gpt-daybreak-blue-latest"))).toBe(false); } finally { await server.stop(true); } diff --git a/tests/codex-catalog-sync-hardening.test.ts b/tests/codex-catalog-sync-hardening.test.ts index 7a359079f5..6af072d87f 100644 --- a/tests/codex-catalog-sync-hardening.test.ts +++ b/tests/codex-catalog-sync-hardening.test.ts @@ -427,7 +427,9 @@ describe("Codex catalog sync hardening", () => { use_responses_lite: true, supports_parallel_tool_calls: true, }); - expect(rows.some(row => row.slug === "gpt-daybreak-blue-latest")).toBe(false); + // Daybreak is globally allowlisted now (owner decision, devlog 260816_.../011), + // so the bare row IS expected — exactly once — alongside the account-qualified row. + expect(rows.filter(row => row.slug === "gpt-daybreak-blue-latest")).toHaveLength(1); }); test("explicit Codex-forward Daybreak survives sync with Sol metadata while account picker is off", () => { @@ -477,8 +479,11 @@ describe("Codex catalog sync hardening", () => { opencodex_catalog_kind: "custom-model-v1", }); expect(daybreak?.base_instructions).toContain("powered by the gpt-daybreak-blue-latest"); - expect(rows.some(row => row.slug === "gpt-daybreak-blue-latest")).toBe(false); + // The global native row exists (owner decision); the explicit Codex-forward custom row + // above is a separate identity and must not collapse into it. + expect(rows.filter(row => row.slug === "gpt-daybreak-blue-latest")).toHaveLength(1); expect(rows.some(row => row.slug === "main/gpt-daybreak-blue-latest")).toBe(false); + // The separately billed API-key alias must still never reach the Codex surface. expect(rows.some(row => row.slug === "openai-apikey/daybreak-blue-latest")).toBe(false); }); diff --git a/tests/codex-catalog.test.ts b/tests/codex-catalog.test.ts index cf586dc801..105655483d 100644 --- a/tests/codex-catalog.test.ts +++ b/tests/codex-catalog.test.ts @@ -2793,7 +2793,11 @@ describe("Codex catalog routed normalization", () => { expect(nativeOpenAiContextWindow("gpt-5.4", 272_000)).toBe(272_000); }); - test("account-scoped Daybreak Blue inherits Sol capabilities without expanding the bare allowlist", () => { + // Owner decision (devlog 260816_.../011 §4-bis): Daybreak Blue is now a GLOBALLY + // allowlisted native, so a bare row IS expected. It still inherits Sol's capability + // shape, and the overlap between NATIVE_OPENAI_MODELS and + // NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS must not duplicate any row. + test("Daybreak Blue inherits Sol capabilities and ships one bare row plus one row per selector", () => { expect(NATIVE_DAYBREAK_BLUE_MODEL).toBe("gpt-daybreak-blue-latest"); expect(nativeOpenAiCapabilitySourceSlug(NATIVE_DAYBREAK_BLUE_MODEL)).toBe("gpt-5.6-sol"); expect(nativeOpenAiContextWindow(NATIVE_DAYBREAK_BLUE_MODEL)).toBe(372_000); @@ -2821,6 +2825,7 @@ describe("Codex catalog routed normalization", () => { expect((source?.model_messages as { instructions_template?: string })?.instructions_template) .toContain("powered by the gpt-daybreak-blue-latest"); + // NATIVE_OPENAI_MODELS already contains the slug; passing it again would double it. const projected = buildCatalogEntries( nativeTemplate(), NATIVE_OPENAI_MODELS, @@ -2833,8 +2838,8 @@ describe("Codex catalog routed normalization", () => { new Set(), new Set(), undefined, - [...NATIVE_OPENAI_MODELS, NATIVE_DAYBREAK_BLUE_MODEL], - new Map([["main", [...NATIVE_OPENAI_MODELS, NATIVE_DAYBREAK_BLUE_MODEL]]]), + [...NATIVE_OPENAI_MODELS], + new Map([["main", [...NATIVE_OPENAI_MODELS]]]), ); const daybreak = projected.find(entry => entry.slug === `main/${NATIVE_DAYBREAK_BLUE_MODEL}`); const sol = projected.find(entry => entry.slug === "gpt-5.6-sol"); @@ -2849,7 +2854,11 @@ describe("Codex catalog routed normalization", () => { supports_parallel_tool_calls: sol?.supports_parallel_tool_calls, input_modalities: sol?.input_modalities, }); - expect(projected.some(entry => entry.slug === NATIVE_DAYBREAK_BLUE_MODEL)).toBe(false); + // The bare row now exists (owner decision) and appears exactly once, proving the + // two-list overlap does not duplicate it. + expect(projected.filter(entry => entry.slug === NATIVE_DAYBREAK_BLUE_MODEL)).toHaveLength(1); + expect(projected.filter(entry => entry.slug === `main/${NATIVE_DAYBREAK_BLUE_MODEL}`)).toHaveLength(1); + // The separately billed API-key alias must still never appear on the Codex surface. expect(projected.some(entry => entry.slug === "daybreak-blue-latest")).toBe(false); }); diff --git a/tests/codex-convergence-account-selectors.test.ts b/tests/codex-convergence-account-selectors.test.ts index c5536cea35..372cff0b7d 100644 --- a/tests/codex-convergence-account-selectors.test.ts +++ b/tests/codex-convergence-account-selectors.test.ts @@ -355,7 +355,7 @@ test("convergence drops unsupported bare native rows and never qualifies them", ))).toBe(true); }); -test("convergence preserves an observed account-only native id without creating a bare row", async () => { +test("convergence projects the observed Daybreak row onto its selector and one bare row", async () => { writeCatalog([nativeEntry()]); writeFileSync(join(codexHome, "models_cache.json"), JSON.stringify({ models: [{ @@ -388,8 +388,12 @@ test("convergence preserves an observed account-only native id without creating }); expect((daybreak?.supported_reasoning_levels as Array<{ effort: string }>).map(level => level.effort)) .toEqual(["low", "medium", "high", "xhigh", "max", "ultra"]); - expect(models.some(entry => entry.slug === "team/gpt-daybreak-blue-latest")).toBe(false); - expect(models.some(entry => entry.slug === "gpt-daybreak-blue-latest")).toBe(false); + // Daybreak is globally allowlisted (owner decision, devlog 260816_.../011). A global + // native is seeded onto EVERY visible selector, not only the one that observed it, and the + // bare row now exists. Each must appear exactly once despite the observation also present. + expect(models.filter(entry => entry.slug === "team/gpt-daybreak-blue-latest")).toHaveLength(1); + expect(models.filter(entry => entry.slug === "desktop/gpt-daybreak-blue-latest")).toHaveLength(1); + expect(models.filter(entry => entry.slug === "gpt-daybreak-blue-latest")).toHaveLength(1); }); test("convergence preserves unrelated foreign rows alongside fresh configured provider rows", async () => { diff --git a/tests/codex-models-cache-invalidate.test.ts b/tests/codex-models-cache-invalidate.test.ts index af2dd1dbfd..8800f0001a 100644 --- a/tests/codex-models-cache-invalidate.test.ts +++ b/tests/codex-models-cache-invalidate.test.ts @@ -60,7 +60,9 @@ describe("invalidateCodexModelsCache write gate (#476 / #518)", () => { }, null, 2) + "\n"); writeFileSync(join(codexHome, "models_cache.json"), JSON.stringify({ models: [{ - slug: "gpt-daybreak-blue-latest", + // gpt-daybreak-blue-latest is a KNOWN global native now (devlog 260816_.../011), + // so it can no longer stand in for an unknown observed id. + slug: "gpt-future-unlisted", visibility: "list", supported_in_api: true, shell_type: "shell_command", @@ -75,7 +77,7 @@ describe("invalidateCodexModelsCache write gate (#476 / #518)", () => { const cache = JSON.parse(readFileSync(join(codexHome, "models_cache.json"), "utf8")) as { models: Array>; }; - expect(cache.models.find(model => model.slug === "gpt-daybreak-blue-latest")).toMatchObject({ + expect(cache.models.find(model => model.slug === "gpt-future-unlisted")).toMatchObject({ visibility: "hide", opencodex_account_observed_native: true, }); diff --git a/tests/native-model-toggle.test.ts b/tests/native-model-toggle.test.ts index 9cbcea106f..139a0d1adc 100644 --- a/tests/native-model-toggle.test.ts +++ b/tests/native-model-toggle.test.ts @@ -142,15 +142,18 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { expect(entries.every(entry => Number.isInteger(entry.priority))).toBe(true); }); + // gpt-daybreak-blue-latest is now a GLOBALLY allowlisted native (owner decision, devlog + // 260816_.../011), so it is no longer an "unknown observed id" and cannot stand in for one + // here. gpt-future-unlisted plays that role instead; the invariant under test is unchanged. test("observed account-only native ids stay qualified and do not expand the bare set", () => { const observedEntries = [ - { ...nativeTemplate(), slug: "gpt-daybreak-blue-latest", visibility: "list", supported_in_api: true }, - { ...nativeTemplate(), slug: "gpt-hidden-daybreak", visibility: "hide", supported_in_api: true }, + { ...nativeTemplate(), slug: "gpt-future-unlisted", visibility: "list", supported_in_api: true }, + { ...nativeTemplate(), slug: "gpt-hidden-future", visibility: "hide", supported_in_api: true }, { ...nativeTemplate(), slug: "gpt-not-an-api-model", visibility: "list", supported_in_api: false }, - { ...nativeTemplate(), slug: "provider/gpt-daybreak-blue-latest", visibility: "list", supported_in_api: true }, + { ...nativeTemplate(), slug: "provider/gpt-future-unlisted", visibility: "list", supported_in_api: true }, ]; - expect(accountBoundNativeOpenAiSlugs(observedEntries)).toContain("gpt-daybreak-blue-latest"); - expect(accountBoundNativeOpenAiSlugs(observedEntries)).not.toContain("gpt-hidden-daybreak"); + expect(accountBoundNativeOpenAiSlugs(observedEntries)).toContain("gpt-future-unlisted"); + expect(accountBoundNativeOpenAiSlugs(observedEntries)).not.toContain("gpt-hidden-future"); expect(accountBoundNativeOpenAiSlugs(observedEntries)).not.toContain("gpt-not-an-api-model"); const entries = buildCatalogEntries( @@ -167,25 +170,46 @@ describe("native GPT model toggles (bare slugs in disabledModels)", () => { undefined, accountBoundNativeOpenAiSlugs(observedEntries), ); - expect(entries.find(entry => entry.slug === "gpt-daybreak-blue-latest")).toBeUndefined(); - expect(entries.find(entry => entry.slug === "team/gpt-daybreak-blue-latest")).toMatchObject({ + expect(entries.find(entry => entry.slug === "gpt-future-unlisted")).toBeUndefined(); + expect(entries.find(entry => entry.slug === "team/gpt-future-unlisted")).toMatchObject({ opencodex_catalog_kind: CODEX_ACCOUNT_BOUND_CATALOG_KIND, visibility: "list", }); expect(observedAccountBoundNativeEntries([{ ...nativeTemplate(), - slug: "gpt-daybreak-blue-latest", + slug: "gpt-future-unlisted", visibility: "hide", supported_in_api: true, opencodex_account_observed_native: true, }])).toHaveLength(1); - expect(observedAccountBoundNativeOpenAiSlugs(observedEntries)).toEqual(["gpt-daybreak-blue-latest"]); + expect(observedAccountBoundNativeOpenAiSlugs(observedEntries)).toEqual(["gpt-future-unlisted"]); + }); + + test("gpt-daybreak-blue-latest ships as a global native row without an observation", () => { + const entries = buildCatalogEntries( + nativeTemplate(), + [...NATIVE_OPENAI_MODELS], + [], + [], + false, + "default", + new Set(), + [], + new Set(), + new Set(), + ); + const bare = entries.filter(entry => entry.slug === "gpt-daybreak-blue-latest"); + // Exactly one row: the slug sits in BOTH NATIVE_OPENAI_MODELS and + // NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS, and that overlap must not duplicate it. + expect(bare).toHaveLength(1); + // Capability is inherited from gpt-5.6-sol, so it is a recursive-capable v2 delegate. + expect(bare[0]?.multi_agent_version).toBe("v2"); }); test("a minimal hand-edited cache row is ignored", () => { - const handEdited = [{ slug: "gpt-daybreak-blue-latest", visibility: "list", supported_in_api: true }]; - expect(accountBoundNativeOpenAiSlugs(handEdited)).not.toContain("gpt-daybreak-blue-latest"); + const handEdited = [{ slug: "gpt-future-unlisted", visibility: "list", supported_in_api: true }]; + expect(accountBoundNativeOpenAiSlugs(handEdited)).not.toContain("gpt-future-unlisted"); expect(observedAccountBoundNativeEntries(handEdited)).toEqual([]); });