model-eol 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/SPEC.md +134 -0
- package/action.yml +71 -0
- package/check.mjs +263 -0
- package/feeds/amazon.json +64 -0
- package/feeds/anthropic.json +252 -0
- package/feeds/google.json +542 -0
- package/feeds/openai.json +1158 -0
- package/lib/apply.mjs +409 -0
- package/lib/cli.mjs +25 -0
- package/lib/color.mjs +16 -0
- package/lib/feeds.mjs +169 -0
- package/lib/plan.mjs +159 -0
- package/lib/reports.mjs +322 -0
- package/lib/scanner.mjs +478 -0
- package/lib/validate-feed.mjs +246 -0
- package/package.json +45 -0
- package/schema/model-eol.alert.schema.json +28 -0
- package/schema/model-eol.bot-config.schema.json +106 -0
- package/schema/model-eol.inventory.schema.json +130 -0
- package/schema/model-eol.plan.schema.json +82 -0
- package/schema/model-eol.schedule.schema.json +31 -0
- package/schema/model-eol.schema.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tom Sullivan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# model-eol
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
**A machine-readable deprecation feed format for AI models, plus the reference
|
|
10
|
+
tooling that turns it into a Dependabot for models.** v0.2.1.
|
|
11
|
+
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
## The problem
|
|
15
|
+
|
|
16
|
+
On July 23, 2026, OpenAI shut down 18 models on schedule. Teams that pinned dated
|
|
17
|
+
snapshots - the provider's own recommended practice - got production 404s, because a
|
|
18
|
+
model ID is the one dependency your toolchain can't see. When an npm package is
|
|
19
|
+
deprecated, the warning prints at install, Dependabot opens the PR, the advisory
|
|
20
|
+
fails CI. A model's retirement date lives in an HTML docs page and an email.
|
|
21
|
+
|
|
22
|
+
Ordinary software solved this layer: endoflife.date for EOL data, OSV for
|
|
23
|
+
vulnerability feeds. This is the model version - a shared schema first, tooling
|
|
24
|
+
second, so the existing trackers can converge instead of each scraping alone.
|
|
25
|
+
|
|
26
|
+
## What's here
|
|
27
|
+
|
|
28
|
+
- **`SPEC.md`** - the feed format (model-eol/0.1): id, aliases, announced, shutdown,
|
|
29
|
+
replacement, `distributions` for per-channel lifecycles, and publisher `policy`
|
|
30
|
+
floors. Small enough that a provider could serve it at
|
|
31
|
+
`/.well-known/model-eol.json` in an afternoon.
|
|
32
|
+
- **`feeds/`** - <!-- feeds-status -->Amazon (4 entries), Anthropic (29 entries), Google (64 entries) and OpenAI (194 entries), generated from the providers' live deprecation pages plus the AWS Bedrock lifecycle page, last verified 2026-08-03<!-- /feeds-status -->. Every
|
|
33
|
+
dated entry carries a source URL.
|
|
34
|
+
- **`check.mjs`** - zero-dependency CLI: CI gate, PR diff gate, inventory, CycloneDX
|
|
35
|
+
ML-BOM export, retirement schedule, alerts and badges, migration plan/apply.
|
|
36
|
+
- **`refresh/`** - regenerates the feeds from provider pages, models endpoints, and
|
|
37
|
+
distributor lifecycle pages, with a semantic diff for human review.
|
|
38
|
+
- **`bot/`** - the Dependabot part: a cron GitHub workflow that maintains one
|
|
39
|
+
migration PR or issue per retiring model.
|
|
40
|
+
- **`scripts/feed-changelog.mjs`** - the feeds' git history as Atom/markdown, so
|
|
41
|
+
"model retirements as they are announced" is a feed you can subscribe to.
|
|
42
|
+
|
|
43
|
+
## Try it
|
|
44
|
+
|
|
45
|
+
Two commands, no install, no dependencies:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
git clone https://github.com/thossullivan/model-eol && cd model-eol
|
|
49
|
+
node check.mjs path/to/your/repo --days 90
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
That is the whole CI gate. (`npx model-eol` lands with the npm publish.)
|
|
53
|
+
|
|
54
|
+
<details>
|
|
55
|
+
<summary><b>Every command</b> - PR gate, distributor clocks, SBOM, badges, migration plans</summary>
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
# PR gate: flag only lines THIS change adds relative to the base ref
|
|
59
|
+
node check.mjs check . --changed origin/main --days 90
|
|
60
|
+
# the same repo judged by a distributor's clock instead of the publisher's
|
|
61
|
+
node check.mjs path/to/your/repo --via aws-bedrock
|
|
62
|
+
# inventory, and a CycloneDX 1.6 ML-BOM for your SBOM pipeline
|
|
63
|
+
node check.mjs inventory path/to/your/repo
|
|
64
|
+
node check.mjs inventory path/to/your/repo --format cyclonedx > model-bom.json
|
|
65
|
+
# retirement schedule with the repo-level worst case
|
|
66
|
+
node check.mjs schedule path/to/your/repo
|
|
67
|
+
# GitHub Actions annotations, Markdown, or a shields.io badge
|
|
68
|
+
node check.mjs alert path/to/your/repo --format github
|
|
69
|
+
node check.mjs alert path/to/your/repo --format badge > model-eol-badge.json
|
|
70
|
+
# migration plan (only high-confidence direct API refs are patchable) and safe apply
|
|
71
|
+
node check.mjs plan path/to/your/repo --days 90 > plan.json
|
|
72
|
+
node check.mjs apply --plan plan.json --dry-run
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
</details>
|
|
76
|
+
|
|
77
|
+
Sample output against the test fixture (2026-08-01):
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
✗ app.py:2 o3-deep-research RETIRED 2026-07-23 (9 days ago) -> gpt-5.6-sol
|
|
81
|
+
! app.py:3 claude-opus-4-1-20250805 RETIRES 2026-08-05 (4 days) -> claude-opus-4-8
|
|
82
|
+
· app.py:4 gpt-5.6-sol no retirement scheduled
|
|
83
|
+
earliest risk: 2026-07-23 (o3-deep-research-2025-06-26)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Exit 1 on findings at or past the threshold - wire it into CI as-is. The badge JSON
|
|
87
|
+
plugs into the [shields endpoint](https://shields.io/endpoint) from any
|
|
88
|
+
CI-accessible URL.
|
|
89
|
+
|
|
90
|
+
**No API keys, no accounts.** Scanning is static analysis and the feeds are data
|
|
91
|
+
files, so the checker, PR gate, schedule, SBOM export, and badge all run with zero
|
|
92
|
+
credentials. The bot needs only a GitHub token (a fine-grained PAT if you want its
|
|
93
|
+
PRs to trigger checks), and provider API keys enter the picture in exactly two
|
|
94
|
+
optional places: your own eval hook command, and the feed-refresh models-endpoint
|
|
95
|
+
coverage.
|
|
96
|
+
|
|
97
|
+
## Same weights, different clocks
|
|
98
|
+
|
|
99
|
+
The same model retires on different dates per channel, and a checker that ignores
|
|
100
|
+
this is wrong in both directions: o3-deep-research died at OpenAI on July 23 but
|
|
101
|
+
lives on Azure AI Foundry until December 26; claude-opus-4-1 retires at Anthropic on
|
|
102
|
+
August 5 but lives on Amazon Bedrock until January 8, 2027. `distributions` in the
|
|
103
|
+
spec carries these per-channel clocks, `--via <distributor>` judges your repo by the
|
|
104
|
+
channel you actually call, and the bedrock distributor fetcher keeps the clocks
|
|
105
|
+
current from AWS's own lifecycle page. Treat a distributor's later date as runway
|
|
106
|
+
for the same migration, not as a destination.
|
|
107
|
+
|
|
108
|
+
## Policy floors - a forward guarantee from absence
|
|
109
|
+
|
|
110
|
+
A feed entry with no `announced` and no `shutdown` is an affirmative "nothing
|
|
111
|
+
scheduled as of `generated`" - something scraping HTML can never say. Publishers
|
|
112
|
+
with a stated minimum notice period (Anthropic commits to >=60 days) carry a
|
|
113
|
+
`policy` floor, so the schedule can say `guaranteed until <date> per anthropic
|
|
114
|
+
stated policy`. Stated policy, not a contract - the wording says so. OpenAI
|
|
115
|
+
publishes no formal floor, so its feed makes no forward claim.
|
|
116
|
+
|
|
117
|
+
## Direct-first inventory
|
|
118
|
+
|
|
119
|
+
Direct API usage (OpenAI/Anthropic/Gemini/etc. SDK calls with real model IDs) is
|
|
120
|
+
scanned and checked immediately. Cloud providers and gateways - Azure Foundry,
|
|
121
|
+
Bedrock, Vertex, OpenRouter, LiteLLM, Portkey - often hide the model behind
|
|
122
|
+
deployment names or routing aliases, so those references stay in inventory as
|
|
123
|
+
resolver targets instead of pretending static code knows the deployed model:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
? src/ai.ts:12 azure-ai-foundry Azure Foundry/OpenAI deployment reference
|
|
127
|
+
? src/ai.ts:30 openrouter OpenRouter gateway reference
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use `--scope direct` to fail CI only on what static analysis can prove. Model-like
|
|
131
|
+
strings absent from the feeds surface as non-failing **candidates**, so feed gaps
|
|
132
|
+
stay visible without generating false alarms.
|
|
133
|
+
|
|
134
|
+
The scanner covers code and configuration files with [`CODE_EXT`](lib/scanner.mjs)
|
|
135
|
+
as the source of truth - Python, TypeScript/JavaScript, JSON/YAML/TOML, shell,
|
|
136
|
+
Ruby, Go, Java, C#, Rust, C/C++, PHP, Swift, Kotlin, Terraform, SQL, Scala, and
|
|
137
|
+
Objective-C. Terraform files (`.tf`, `.tfvars`) are scanned while `.terraform`
|
|
138
|
+
directories stay excluded.
|
|
139
|
+
|
|
140
|
+
## The bot
|
|
141
|
+
|
|
142
|
+
Copy `bot.yml.example` to `.github/workflows/model-eol-bot.yml` for a weekly run
|
|
143
|
+
that maintains exactly one labelled migration PR per retiring model (full feed
|
|
144
|
+
context in the body, never auto-merged) and one issue per finding that needs a
|
|
145
|
+
human. Dismissals are respected; a changed shutdown date reopens. Configure via
|
|
146
|
+
`.model-eol.json`: thresholds, scope, ignores, and an optional eval hook that runs
|
|
147
|
+
your own regression command against the replacement before the PR opens. Preview
|
|
148
|
+
everything with no GitHub calls:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
node bot/bot.mjs --dry-run --target-dir . --repo OWNER/REPO
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Two operational notes the hard way teaches: PRs created with `GITHUB_TOKEN` do not
|
|
155
|
+
trigger `pull_request` workflows (use a fine-grained PAT or GitHub App token when
|
|
156
|
+
checks must run), and the workflow splits privileges - provider keys live only in
|
|
157
|
+
the read-only plan/eval job, write tokens only in the publish job.
|
|
158
|
+
|
|
159
|
+
## Keeping the feeds honest
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
node refresh/refresh.mjs --check # semantic diff vs live pages; exit 3 = PR-worthy
|
|
163
|
+
node refresh/refresh.mjs --distributor aws-bedrock # distributor clocks from AWS's lifecycle page
|
|
164
|
+
node scripts/feed-changelog.mjs # feeds' git history as an Atom feed
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Parse failures fail loudly and never emit a guessed feed. This runs automatically:
|
|
168
|
+
a weekly workflow (`.github/workflows/feed-refresh.yml`, Mondays 05:23 UTC) checks
|
|
169
|
+
the live sources and opens a PR with the semantic diff - and an auto-updated
|
|
170
|
+
README freshness line - when anything material changed. The first live runs earned
|
|
171
|
+
their keep: they caught the hand-compiled feeds drifting from Anthropic's
|
|
172
|
+
recommendations within one week, and corrected a hand-compiled Bedrock date that
|
|
173
|
+
was three months wrong.
|
|
174
|
+
|
|
175
|
+
## The actual ask
|
|
176
|
+
|
|
177
|
+
The endgame is not "another checker"; it's the format. Providers already publish
|
|
178
|
+
deprecation *pages* because customers asked. A `retirement_date` field in the models
|
|
179
|
+
endpoint, or this feed at a well-known URL, turns every existing tracker into an
|
|
180
|
+
ecosystem. endoflife.date now tracks Claude with nearly this field set - the
|
|
181
|
+
convergence is already starting. If you maintain one of the community trackers:
|
|
182
|
+
converging on a shared schema - this one or a better one - is the highest-leverage
|
|
183
|
+
move any of us can make here.
|
|
184
|
+
|
|
185
|
+
## Status / honesty
|
|
186
|
+
|
|
187
|
+
- Feeds refresh automatically: the weekly feed-refresh workflow is live in this
|
|
188
|
+
repo (parse failures fail the run; material changes become a reviewed PR), and
|
|
189
|
+
CI runs the full suite on every push and PR. The bot workflow still ships as
|
|
190
|
+
`bot.yml.example`.
|
|
191
|
+
- Current-model entries (and therefore policy-floor horizons) populate only when
|
|
192
|
+
refresh runs with provider API keys for the models endpoints.
|
|
193
|
+
- The checker matches known IDs only - it will not discover models absent from the
|
|
194
|
+
feeds. Deliberate: precision over discovery for a CI gate.
|
|
195
|
+
- Fetchers: OpenAI, Anthropic, Google, aws-bedrock, and vertex-ai are live. Azure
|
|
196
|
+
clocks are carried where OpenAI's own page publishes them; a standalone Azure
|
|
197
|
+
lifecycle fetcher is not built yet.
|
|
198
|
+
- Not yet: feed signing, gateway route resolvers, npm publish (prepped, pending).
|
|
199
|
+
|
|
200
|
+
## Contributing
|
|
201
|
+
|
|
202
|
+
The most valuable contribution is a feed correction with a source URL - and the
|
|
203
|
+
one rule is that `feeds/*.json` are generated, never hand-edited. See
|
|
204
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for that workflow, the zero-dependency
|
|
205
|
+
doctrine, and what a new publisher parser needs.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# model-eol - a machine-readable model deprecation feed (draft spec v0.1)
|
|
2
|
+
|
|
3
|
+
> Status: sketch, 2026-07-25. The problem: model retirement dates live in HTML docs
|
|
4
|
+
> and emails, so every deprecation checker is a scraper with a private registry
|
|
5
|
+
> format. Ordinary software solved this layer - endoflife.date for EOL data, OSV for
|
|
6
|
+
> vulnerability feeds. This is the model version: small enough that a provider could
|
|
7
|
+
> ship it in an afternoon, useful enough that scrapers converge on it in the meantime.
|
|
8
|
+
|
|
9
|
+
## The format
|
|
10
|
+
|
|
11
|
+
A feed is a JSON document: metadata + a list of model entries.
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"spec": "model-eol/0.1",
|
|
16
|
+
"publisher": "openai",
|
|
17
|
+
"generated": "2026-07-25T00:00:00Z",
|
|
18
|
+
"source": "https://developers.openai.com/api/docs/deprecations",
|
|
19
|
+
"models": [
|
|
20
|
+
{
|
|
21
|
+
"id": "o3-deep-research-2025-06-26",
|
|
22
|
+
"aliases": ["o3-deep-research"],
|
|
23
|
+
"announced": "2026-04-22",
|
|
24
|
+
"shutdown": "2026-07-23",
|
|
25
|
+
"replacement": "gpt-5.6-sol",
|
|
26
|
+
"notes": "Deep-research post-trained agent; replacement is a general model + tools, not a structural successor.",
|
|
27
|
+
"distributions": [
|
|
28
|
+
{ "via": "azure-ai-foundry", "shutdown": "2026-12-26",
|
|
29
|
+
"source": "https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/model-retirements" }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "gpt-5.6-sol"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Fields
|
|
40
|
+
|
|
41
|
+
| Field | Req | Meaning |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `id` | yes | The exact model identifier as sent over the wire (dated snapshot preferred) |
|
|
44
|
+
| `aliases` | no | Other strings that resolve to this model (undated aliases, marketing names) |
|
|
45
|
+
| `announced` | no | Date the deprecation was announced (absent = not deprecated) |
|
|
46
|
+
| `shutdown` | no | Date calls stop working on the publisher's own API (absent = none scheduled) |
|
|
47
|
+
| `replacement` | no | A single exact model ID that resolves against an id or alias in this feed and is safe as a drop-in string substitution. This is the only field plan/apply may patch from |
|
|
48
|
+
| `replacement_options` | no | An ordered array of exact model IDs for multiple or unresolved choices. These are issue-only and need not resolve in this feed |
|
|
49
|
+
| `replacement_note` | no | Free-text guidance such as parameter requirements or provider platform alternatives |
|
|
50
|
+
| `notes` | no | Human context: structural differences, migration gotchas |
|
|
51
|
+
| `distributions` | no | Per-distributor lifecycles - the same weights on different clocks (Azure, Bedrock, Vertex). Each entry: `via`, optional `announced`/`shutdown`/`status`/`date_precision`, `source` |
|
|
52
|
+
| `date_precision` | no | Qualifies `shutdown`: absent or `"exact"` means the stated day; `"earliest"` means the provider commits only that the event happens no sooner (Google's "earliest possible" dates). Consumers treat an earliest date as the scheduled date - it is the soonest legal death - and display it as a lower bound |
|
|
53
|
+
|
|
54
|
+
Dates are ISO 8601, UTC. A model with no `announced` and no `shutdown` is an
|
|
55
|
+
affirmative statement of "no retirement scheduled as of `generated`" - the absence
|
|
56
|
+
is data, which is exactly what scraping HTML can never give you.
|
|
57
|
+
|
|
58
|
+
`distributions[].status`, when present, is one of `active`, `legacy`,
|
|
59
|
+
`extended-access`, or `retired`. `legacy` means no new consumers but existing
|
|
60
|
+
usage works; `extended-access` means availability continues past the stated
|
|
61
|
+
`shutdown` under a paid or opt-in program (Bedrock offers this for some models) -
|
|
62
|
+
the `shutdown` date stays the base retirement, and the status flags that a
|
|
63
|
+
negotiated tail exists.
|
|
64
|
+
|
|
65
|
+
### Structured replacement contract
|
|
66
|
+
|
|
67
|
+
`replacement` is a single exact model ID that resolves against an id or alias in
|
|
68
|
+
the same feed and is safe as a drop-in string substitution. This is the ONLY
|
|
69
|
+
field plan/apply may patch from. Otherwise absent.
|
|
70
|
+
|
|
71
|
+
`replacement_options` is an optional array of exact model IDs. Each option must
|
|
72
|
+
pass the identifier grammar, but feed resolution is NOT required since a target
|
|
73
|
+
may not be carried yet. Options are ordered as the provider lists them.
|
|
74
|
+
|
|
75
|
+
`replacement_note` is optional free-text guidance. It can carry parameter
|
|
76
|
+
requirements such as `reasoning.mode: pro` or prose alternatives such as Google's
|
|
77
|
+
platform pointers.
|
|
78
|
+
|
|
79
|
+
Replacement options and notes are issue-only. The planner never auto-patches
|
|
80
|
+
from options; it patches `replacement` exclusively.
|
|
81
|
+
|
|
82
|
+
### Policy floors
|
|
83
|
+
|
|
84
|
+
A feed MAY include a publisher's stated minimum deprecation notice period:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
"policy": {
|
|
88
|
+
"min_notice_days": 60,
|
|
89
|
+
"source": "https://platform.claude.com/docs/en/about-claude/model-deprecations"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For an entry with neither `announced` nor `shutdown`, a policy floor means the
|
|
94
|
+
entry cannot become unavailable before `generated` + `min_notice_days` days,
|
|
95
|
+
provided the provider honors its stated policy. This is a stated policy, not a
|
|
96
|
+
contract or a guarantee that the provider will remain available.
|
|
97
|
+
|
|
98
|
+
The `policy` field is optional. Its absence means no forward claim can be made,
|
|
99
|
+
intentionally. OpenAI publishes no formal notice floor, so the OpenAI feed carries
|
|
100
|
+
no `policy` field.
|
|
101
|
+
|
|
102
|
+
### Why `distributions` is in the core and not an extension
|
|
103
|
+
|
|
104
|
+
The July 2026 evidence: OpenAI shut off `o3-deep-research` on July 23; Azure lists it
|
|
105
|
+
retiring December 26. Anthropic retired Claude 3.7 Sonnet on February 19; Bedrock
|
|
106
|
+
kept it to April 28. The same BOM entry has different sourcing terms per channel, and
|
|
107
|
+
a checker that doesn't know that will tell an Azure shop their model is dead five
|
|
108
|
+
months early - or tell an OpenAI-direct shop they're fine because Azure says so.
|
|
109
|
+
|
|
110
|
+
## Serving it
|
|
111
|
+
|
|
112
|
+
- **Providers** (the ask): serve the feed at `/.well-known/model-eol.json`, or add
|
|
113
|
+
`announced`/`shutdown`/`replacement` to the existing models endpoint. Either kills
|
|
114
|
+
the scraping industry overnight.
|
|
115
|
+
- **Aggregators** (the interim): community datasets re-publish provider pages in this
|
|
116
|
+
format, with `source` on every entry so claims are auditable. Multiple feeds
|
|
117
|
+
compose - a checker loads provider feeds where they exist and community feeds where
|
|
118
|
+
they don't.
|
|
119
|
+
- **Consumers**: anything that can read JSON - a CI step, a dashboard, a Dependabot
|
|
120
|
+
clone that opens migration PRs when `shutdown - today < threshold`.
|
|
121
|
+
|
|
122
|
+
## Non-goals (v0.1)
|
|
123
|
+
|
|
124
|
+
Pricing, capabilities, context windows (models.dev and friends already do this);
|
|
125
|
+
behavior-change tracking (that's an eval suite's job, not a manifest's); signing and
|
|
126
|
+
provenance (worth doing the moment anyone depends on this; premature today).
|
|
127
|
+
|
|
128
|
+
## Prior art, gratefully
|
|
129
|
+
|
|
130
|
+
endoflife.date (the shape of the solution), OSV (proof that a shared schema turns N
|
|
131
|
+
scrapers into an ecosystem), CycloneDX ML-BOM / SPDX AI profile (where an `eol` field
|
|
132
|
+
belongs once feeds exist), and the current scrapers - llm-model-deprecation,
|
|
133
|
+
llmstatus.ai, llm-info, ai-model-watch-data, modelradar-data - which are the demand
|
|
134
|
+
signal this spec is trying to give a common target.
|
package/action.yml
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: 'model-eol check'
|
|
2
|
+
description: 'Fail CI when a pinned AI model ID is retired or retiring within a threshold, using model-eol feeds.'
|
|
3
|
+
branding:
|
|
4
|
+
icon: 'alert-triangle'
|
|
5
|
+
color: 'red'
|
|
6
|
+
inputs:
|
|
7
|
+
command:
|
|
8
|
+
description: 'Command to run: check, inventory, or schedule'
|
|
9
|
+
required: false
|
|
10
|
+
default: 'check'
|
|
11
|
+
paths:
|
|
12
|
+
description: 'Paths to scan. Newline-separated paths are recommended for paths containing spaces; single-line whitespace-separated paths remain supported for legacy compatibility.'
|
|
13
|
+
required: false
|
|
14
|
+
default: '.'
|
|
15
|
+
days:
|
|
16
|
+
description: 'Fail when a model retires within this many days'
|
|
17
|
+
required: false
|
|
18
|
+
default: '90'
|
|
19
|
+
via:
|
|
20
|
+
description: 'Judge lifecycles by a distributor clock (e.g. azure-ai-foundry, aws-bedrock) instead of the publisher''s'
|
|
21
|
+
required: false
|
|
22
|
+
default: ''
|
|
23
|
+
scope:
|
|
24
|
+
description: 'Check scope: all tracked model IDs, or direct to leave cloud/gateway refs as inventory hints'
|
|
25
|
+
required: false
|
|
26
|
+
default: 'all'
|
|
27
|
+
changed:
|
|
28
|
+
description: 'Base ref for the PR gate: check only lines added relative to this ref (requires fetch-depth: 0)'
|
|
29
|
+
required: false
|
|
30
|
+
default: ''
|
|
31
|
+
format:
|
|
32
|
+
description: 'Alert output format when command is alert: github, markdown, or badge'
|
|
33
|
+
required: false
|
|
34
|
+
default: 'github'
|
|
35
|
+
feeds:
|
|
36
|
+
description: 'Directory of model-eol feeds (defaults to the feeds bundled with the action)'
|
|
37
|
+
required: false
|
|
38
|
+
default: ''
|
|
39
|
+
runs:
|
|
40
|
+
using: 'composite'
|
|
41
|
+
steps:
|
|
42
|
+
- name: model-eol check
|
|
43
|
+
shell: bash
|
|
44
|
+
env:
|
|
45
|
+
MODEL_EOL_COMMAND: ${{ inputs.command }}
|
|
46
|
+
MODEL_EOL_PATHS: ${{ inputs.paths }}
|
|
47
|
+
MODEL_EOL_DAYS: ${{ inputs.days }}
|
|
48
|
+
MODEL_EOL_VIA: ${{ inputs.via }}
|
|
49
|
+
MODEL_EOL_CHANGED: ${{ inputs.changed }}
|
|
50
|
+
MODEL_EOL_SCOPE: ${{ inputs.scope }}
|
|
51
|
+
MODEL_EOL_FORMAT: ${{ inputs.format }}
|
|
52
|
+
MODEL_EOL_FEEDS: ${{ inputs.feeds }}
|
|
53
|
+
run: |
|
|
54
|
+
split_model_eol_paths() {
|
|
55
|
+
PATH_ARGS=()
|
|
56
|
+
if [[ "$MODEL_EOL_PATHS" == *$'\n'* ]]; then
|
|
57
|
+
while IFS= read -r path || [[ -n "$path" ]]; do
|
|
58
|
+
if [[ -n "$path" ]]; then PATH_ARGS+=("$path"); fi
|
|
59
|
+
done <<< "$MODEL_EOL_PATHS"
|
|
60
|
+
else
|
|
61
|
+
read -r -a PATH_ARGS <<< "$MODEL_EOL_PATHS"
|
|
62
|
+
fi
|
|
63
|
+
}
|
|
64
|
+
split_model_eol_paths
|
|
65
|
+
ARGS=("$MODEL_EOL_COMMAND" "${PATH_ARGS[@]}" --days "$MODEL_EOL_DAYS")
|
|
66
|
+
if [ -n "$MODEL_EOL_VIA" ]; then ARGS+=(--via "$MODEL_EOL_VIA"); fi
|
|
67
|
+
if [ -n "$MODEL_EOL_CHANGED" ]; then ARGS+=(--changed "$MODEL_EOL_CHANGED"); fi
|
|
68
|
+
if [ -n "$MODEL_EOL_SCOPE" ]; then ARGS+=(--scope "$MODEL_EOL_SCOPE"); fi
|
|
69
|
+
if [ -n "$MODEL_EOL_FORMAT" ]; then ARGS+=(--format "$MODEL_EOL_FORMAT"); fi
|
|
70
|
+
if [ -n "$MODEL_EOL_FEEDS" ]; then ARGS+=(--feeds "$MODEL_EOL_FEEDS"); fi
|
|
71
|
+
node "${{ github.action_path }}/check.mjs" "${ARGS[@]}"
|