From efe8f74ffd6df4b22b51bc7d9e3dd82e225ed62a Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 24 Oct 2026 20:06:55 +0000 Subject: [PATCH] fix(model-policy-freshness): accept OpenAI deprecated cells with a display name before the backticked ids (#3155) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- lib/model-policy-freshness-sources.ts | 1 - .../model-policy-freshness/openai-lifecycle.md | 1 + test/model-policy-freshness.test.ts | 12 +++++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/model-policy-freshness-sources.ts b/lib/model-policy-freshness-sources.ts index b90bbecb4..e0dfee345 100644 --- a/lib/model-policy-freshness-sources.ts +++ b/lib/model-policy-freshness-sources.ts @@ -178,7 +178,6 @@ function openaiLifecycle(text: string): ParsedSource { if (!upcoming || !modelTables.length) throw new Error('missing-deprecation-tables'); const lifecycle = modelTables.flatMap(table => table.slice(1).flatMap(row => { if (/^(?:Videos API|Assistants API|OpenAI-Beta: |New fine-tuning training on )/.test(row[1]) || row[1].startsWith('`/')) return []; - if (!row[1].startsWith('`')) throw new Error('unrecognized-deprecated-model-cell'); const floor = row[0].startsWith('at earliest '); const date = lifecycleDate(row[0].replace(/^at earliest /, '')); if (!date) throw new Error('missing-shutdown-date'); diff --git a/test/fixtures/model-policy-freshness/openai-lifecycle.md b/test/fixtures/model-policy-freshness/openai-lifecycle.md index 4cffbb87f..e57d5e36b 100644 --- a/test/fixtures/model-policy-freshness/openai-lifecycle.md +++ b/test/fixtures/model-policy-freshness/openai-lifecycle.md @@ -9,6 +9,7 @@ This page lists all API deprecations, along with recommended replacements. | Shutdown date | Model / system | Recommended replacement | | --- | --- | --- | | Apr 1, 2027 | `gpt-5.3-codex` | `gpt-6-sol` | +| Feb 16, 2027 | GPT-4o (`gpt-4o-2024-08-06`, `gpt-4o-2024-11-20`) | `gpt-6-sol` | ## Past deprecations diff --git a/test/model-policy-freshness.test.ts b/test/model-policy-freshness.test.ts index ac88d2a02..5f51b04bf 100644 --- a/test/model-policy-freshness.test.ts +++ b/test/model-policy-freshness.test.ts @@ -11,7 +11,7 @@ describe('official recommendation and lifecycle structures', () => { expect(anthropic.recommendations).toEqual({ frontier: 'claude-fable-5-1', smart: 'claude-opus-5-5' }); expect(anthropic.lineup).not.toContain('claude-mythos-5-1'); const openai = parseOfficialSource('openai-lifecycle', freshnessFixture('openai-lifecycle')); - expect(openai.lifecycle?.map(row => row.modelId)).toEqual(['gpt-5.3-codex', 'gpt-old', 'gpt-unicode-date', 'gpt-iso-date', 'gpt-date-floor']); + expect(openai.lifecycle?.map(row => row.modelId)).toEqual(['gpt-5.3-codex', 'gpt-4o-2024-08-06', 'gpt-4o-2024-11-20', 'gpt-old', 'gpt-unicode-date', 'gpt-iso-date', 'gpt-date-floor']); expect(openai.lifecycle?.find(row => row.modelId === 'gpt-date-floor')).toMatchObject({ deadline: null, supportFloor: '2024-06-13' }); }); @@ -57,6 +57,16 @@ describe('official recommendation and lifecycle structures', () => { expect(report.state.lifecycle[0]).toMatchObject({ modelId: 'gpt-6-astra', deadline: '2027-04-01' }); }); + test('OpenAI deprecated cells with a display name before the backticked IDs yield every ID', () => { + const openai = parseOfficialSource('openai-lifecycle', freshnessFixture('openai-lifecycle')); + expect(openai.lifecycle?.filter(row => row.modelId.startsWith('gpt-4o-'))).toEqual([ + { modelId: 'gpt-4o-2024-08-06', state: 'deprecated', deadline: '2027-02-16', supportFloor: null }, + { modelId: 'gpt-4o-2024-11-20', state: 'deprecated', deadline: '2027-02-16', supportFloor: null }, + ]); + const unticked = freshnessFixture('openai-lifecycle').replace('GPT-4o (`gpt-4o-2024-08-06`, `gpt-4o-2024-11-20`)', 'GPT-4o (gpt-4o-2024-08-06, gpt-4o-2024-11-20)'); + expect(() => parseOfficialSource('openai-lifecycle', unticked)).toThrow('unrecognized-deprecated-model-cell'); + }); + test('missing required structures and semantic drift fail closed', () => { expect(() => parseOfficialSource('anthropic-models', freshnessFixture('anthropic-models').replace('Claude API ID', 'Google Cloud ID'))).toThrow('missing-api-id-row'); expect(() => parseOfficialSource('anthropic-models', freshnessFixture('anthropic-models').replace('for demanding reasoning', 'for unexplained goals'))).toThrow('unrecognized-recommendation');