model-eol 0.4.0 → 0.5.0
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/README.md +174 -18
- package/SPEC.md +21 -6
- package/action.yml +30 -9
- package/bot/bot.mjs +361 -165
- package/bot/lib/git.mjs +49 -0
- package/bot/lib/github.mjs +4 -0
- package/bot.yml.example +37 -25
- package/check.mjs +55 -4
- package/examples/model-eol-eval.mjs +84 -0
- package/examples/workflows/model-eol.yml +52 -0
- package/feeds/amazon.json +5 -1
- package/feeds/anthropic.json +4 -1
- package/lib/apply.mjs +393 -231
- package/lib/config.mjs +16 -1
- package/lib/feeds.mjs +22 -3
- package/lib/json-schema.mjs +291 -0
- package/lib/reports.mjs +95 -22
- package/lib/scanner.mjs +159 -25
- package/lib/validate-document.mjs +320 -0
- package/lib/validate-feed.mjs +7 -1
- package/package.json +4 -3
- package/schema/model-eol.alert.schema.json +46 -10
- package/schema/model-eol.bot-config.schema.json +8 -4
- package/schema/model-eol.check.schema.json +56 -0
- package/schema/model-eol.inventory.schema.json +82 -81
- package/schema/model-eol.plan.schema.json +5 -5
- package/schema/model-eol.schedule.schema.json +10 -10
- package/schema/model-eol.schema.json +24 -8
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://github.com/thossullivan/model-eol/actions/workflows/feed-refresh.yml)
|
|
7
7
|

|
|
8
8
|

|
|
9
|
-

|
|
10
10
|

|
|
11
11
|
|
|
12
12
|
**A machine-readable deprecation feed format for AI models, plus the reference
|
|
@@ -14,9 +14,34 @@ tooling that turns it into a Dependabot for models.**
|
|
|
14
14
|
|
|
15
15
|

|
|
16
16
|
|
|
17
|
+
## Release line
|
|
18
|
+
|
|
19
|
+
**0.4.1 - trusted migrations.** The 0.4 line made bot migrations independently
|
|
20
|
+
evaluable and bound each result to the exact default-branch commit, plan, eval
|
|
21
|
+
configuration, and feed digests. The workflow resolves the package version once
|
|
22
|
+
and reuses that exact version across planning, evaluation, and publication. It added
|
|
23
|
+
path-scoped policy and lifecycle routing for mixed-provider repositories,
|
|
24
|
+
trusted PR/issue ownership, stale-work reconciliation, and fresh-work behavior
|
|
25
|
+
when a retired model returns. The 0.4.1 patch moved the copy-ready workflows to
|
|
26
|
+
the current Node-backed Actions and fixed Google models-endpoint pagination. The
|
|
27
|
+
bundled feeds contain 313 entries across Amazon, Anthropic, Google, and OpenAI.
|
|
28
|
+
|
|
29
|
+
**0.5.0 - public contract and trust.** This milestone makes the data contract public:
|
|
30
|
+
canonical hosted schemas, a zero-dependency `validate` command, hosted feeds,
|
|
31
|
+
an Atom changelog, and byte-exact refresh receipts whose digests must match the
|
|
32
|
+
published data. It adds status-aware distributor refresh including Bedrock Public
|
|
33
|
+
Extended Access, preserves structured replacement guidance across JSON reports
|
|
34
|
+
and preserves channel-specific lifecycle clocks in CycloneDX,
|
|
35
|
+
preflights multi-file apply plan-wide and rolls back later commit failures,
|
|
36
|
+
fails explicitly on unapproved symlink coverage, and keeps configured eval
|
|
37
|
+
failures as distinct durable issues. It also
|
|
38
|
+
ships a repository-owned eval-harness starter, permanent exact-package consumer
|
|
39
|
+
UAT, and a Node 22 support floor. The hosted URLs become authoritative only after
|
|
40
|
+
the first green Pages deployment.
|
|
41
|
+
|
|
17
42
|
## The problem
|
|
18
43
|
|
|
19
|
-
On July 23, 2026, OpenAI shut down
|
|
44
|
+
On July 23, 2026, OpenAI shut down a scheduled wave of model snapshots. One of my
|
|
20
45
|
tuned research workflows returned a model-not-found error that morning even though
|
|
21
46
|
the retirement had been documented for months. A model ID is the one dependency
|
|
22
47
|
my toolchain could not see. When an npm package is deprecated, the warning prints
|
|
@@ -36,14 +61,17 @@ second, so the existing trackers can converge instead of each scraping alone.
|
|
|
36
61
|
- **`feeds/`** - <!-- feeds-status -->Amazon (4 entries), Anthropic (29 entries), Google (86 entries) and OpenAI (194 entries), generated from the providers' live deprecation pages plus the AWS Bedrock and Google Vertex AI lifecycle pages, feed data generated 2026-08-18<!-- /feeds-status -->. Every
|
|
37
62
|
dated entry carries a source URL.
|
|
38
63
|
- **`check.mjs`** - zero-dependency CLI: CI gate, PR diff gate, inventory, CycloneDX
|
|
39
|
-
ML-BOM export, retirement schedule, alerts and badges, migration plan/apply
|
|
64
|
+
ML-BOM export, retirement schedule, alerts and badges, migration plan/apply,
|
|
65
|
+
and public document validation.
|
|
40
66
|
- **`refresh/`** - regenerates the feeds from provider pages, models endpoints, and
|
|
41
67
|
distributor lifecycle pages, with a semantic diff for human review.
|
|
42
68
|
- **`bot/`** - the Dependabot part: a cron GitHub workflow that maintains one
|
|
43
69
|
migration PR or issue per retiring model, published as the `model-eol-bot`
|
|
44
70
|
binary in the same npm package.
|
|
45
|
-
-
|
|
46
|
-
|
|
71
|
+
- **Public contract rollout** - the 0.5 Pages workflow publishes versioned
|
|
72
|
+
schemas, hosted feeds, refresh health, and an Atom changelog. Those URLs
|
|
73
|
+
become authoritative only after Pages is enabled and the exact-byte deployment
|
|
74
|
+
check passes.
|
|
47
75
|
|
|
48
76
|
## Try it
|
|
49
77
|
|
|
@@ -72,6 +100,9 @@ node check.mjs path/to/your/repo --via aws-bedrock
|
|
|
72
100
|
# inventory, and a CycloneDX 1.6 ML-BOM for your SBOM pipeline
|
|
73
101
|
node check.mjs inventory path/to/your/repo
|
|
74
102
|
node check.mjs inventory path/to/your/repo --format cyclonedx > model-bom.json
|
|
103
|
+
# the primary CI gate is also a schema-valid public report
|
|
104
|
+
node check.mjs check path/to/your/repo --json > model-eol-check.json
|
|
105
|
+
node check.mjs validate model-eol-check.json
|
|
75
106
|
# retirement schedule with the repo-level worst case
|
|
76
107
|
node check.mjs schedule path/to/your/repo
|
|
77
108
|
# GitHub Actions annotations, Markdown, or a shields.io badge
|
|
@@ -80,6 +111,8 @@ node check.mjs alert path/to/your/repo --format badge > model-eol-badge.json
|
|
|
80
111
|
# migration plan (only high-confidence direct API refs are patchable) and safe apply
|
|
81
112
|
node check.mjs plan path/to/your/repo --days 90 > plan.json
|
|
82
113
|
node check.mjs apply --plan plan.json --dry-run
|
|
114
|
+
# validate provider feeds, repository policy, or model-eol machine reports
|
|
115
|
+
node check.mjs validate feeds/openai.json .model-eol.json plan.json
|
|
83
116
|
```
|
|
84
117
|
|
|
85
118
|
</details>
|
|
@@ -115,9 +148,13 @@ The same gate as a composite action - no npm install, feeds bundled, pinned to
|
|
|
115
148
|
the `v0` line:
|
|
116
149
|
|
|
117
150
|
```yaml
|
|
118
|
-
- uses: actions/checkout@
|
|
151
|
+
- uses: actions/checkout@v7
|
|
119
152
|
with:
|
|
120
153
|
fetch-depth: 0 # --changed needs history to diff against the base ref
|
|
154
|
+
- uses: actions/setup-node@v7
|
|
155
|
+
with:
|
|
156
|
+
node-version: 22
|
|
157
|
+
package-manager-cache: false
|
|
121
158
|
- uses: thossullivan/model-eol@v0
|
|
122
159
|
with:
|
|
123
160
|
command: check
|
|
@@ -131,10 +168,16 @@ or retiring within the threshold. Add `via: aws-bedrock` (or `azure-ai-foundry`)
|
|
|
131
168
|
to judge by a distributor's clock. The copy-paste version with both gates - PR
|
|
132
169
|
diff plus a weekly full-repository check - is
|
|
133
170
|
[`examples/workflows/model-eol.yml`](examples/workflows/model-eol.yml). The
|
|
134
|
-
Action also exposes `inventory`, `schedule`, `alert`, and `
|
|
171
|
+
Action also exposes `inventory`, `schedule`, `alert`, `plan`, and `validate`. Use
|
|
135
172
|
`format: cyclonedx` for an ML-BOM, or `output-file` to retain any report as a
|
|
136
173
|
workflow artifact while keeping the same output in the job log.
|
|
137
174
|
|
|
175
|
+
CycloneDX exports use one machine-learning component per canonical model and
|
|
176
|
+
lifecycle channel. Direct publisher (`publisher-direct`), Azure, Bedrock, and
|
|
177
|
+
other routed references therefore retain their own status and shutdown clock
|
|
178
|
+
under deterministic, channel-qualified `bom-ref` values; occurrences are sorted
|
|
179
|
+
and scoped to the component whose clock they used.
|
|
180
|
+
|
|
138
181
|
## One repository policy
|
|
139
182
|
|
|
140
183
|
The CLI, Action, and bot all discover a strict `.model-eol.json` at the target
|
|
@@ -151,7 +194,7 @@ Git repository root. CLI flags or Action inputs override configured values, and
|
|
|
151
194
|
"paths": ["test/fixtures/**", "vendor"]
|
|
152
195
|
},
|
|
153
196
|
"issues": { "enabled": true },
|
|
154
|
-
"eval": { "command": "
|
|
197
|
+
"eval": { "command": "node scripts/model-eol-eval.mjs" }
|
|
155
198
|
}
|
|
156
199
|
```
|
|
157
200
|
|
|
@@ -160,8 +203,28 @@ in `ignore.models` suppresses the whole alias family; `ignore.paths` uses
|
|
|
160
203
|
repo-relative `*`, `**`, and `?` globs and excludes matching files before scan
|
|
161
204
|
coverage limits are counted. With no config the CLI keeps its historical
|
|
162
205
|
`scope: all`; once a config exists, omitted values use the shared bot defaults
|
|
163
|
-
(`days: 90`, `scope: direct`, and the publisher clock).
|
|
164
|
-
|
|
206
|
+
(`days: 90`, `scope: direct`, and the publisher clock). Portable structure is
|
|
207
|
+
defined by
|
|
208
|
+
[`schema/model-eol.bot-config.schema.json`](schema/model-eol.bot-config.schema.json),
|
|
209
|
+
while `model-eol validate` also enforces runtime semantic and safety checks that
|
|
210
|
+
Draft-07 cannot express cleanly.
|
|
211
|
+
|
|
212
|
+
For content-heavy repositories, start with `inventory`, then exclude caches,
|
|
213
|
+
generated catalogs, archived fixtures, and documentation that are not live model
|
|
214
|
+
dependencies. Published-repository UAT deliberately verifies both outcomes: the
|
|
215
|
+
unconfigured scan reports evidence, while an explicit path policy produces a
|
|
216
|
+
clean check and empty bot decision table without changing repository content.
|
|
217
|
+
|
|
218
|
+
Parent-repository scans do not recurse into tracked Git submodules. A detected
|
|
219
|
+
gitlink is reported as incomplete coverage, so `check` and `plan` fail closed
|
|
220
|
+
unless the path is explicitly ignored or `--allow-incomplete` is chosen. To
|
|
221
|
+
inspect submodule contents, run model-eol against the checked-out submodule as a
|
|
222
|
+
separate target/repository policy.
|
|
223
|
+
|
|
224
|
+
Untracked nested Git repositories are handled the same way: the parent reports
|
|
225
|
+
`nested-repository-skipped` and never recurses into the nested checkout. Ignore
|
|
226
|
+
an intentional nested repository explicitly, allow incomplete coverage, or scan
|
|
227
|
+
it separately with its own repository policy.
|
|
165
228
|
|
|
166
229
|
Mixed-provider monorepos can keep those top-level values as repository defaults,
|
|
167
230
|
then apply path policies and a lifecycle channel per reference:
|
|
@@ -250,8 +313,9 @@ human. The workflow resolves `model-eol@0` from npm once and reuses that exact
|
|
|
250
313
|
version across its plan, read-only evaluation, and write-token publication jobs;
|
|
251
314
|
consumer repositories do not copy `check.mjs` or the `bot/` source directory.
|
|
252
315
|
Dismissals are respected, reintroduced retired models create fresh work, and
|
|
253
|
-
bot-owned PRs/issues close when their finding disappears.
|
|
254
|
-
no GitHub calls and without adding a
|
|
316
|
+
bot-owned PRs/issues close when their finding disappears. In a repository without
|
|
317
|
+
a configured eval, preview everything with no GitHub calls and without adding a
|
|
318
|
+
project dependency:
|
|
255
319
|
|
|
256
320
|
```sh
|
|
257
321
|
npx --yes --package=model-eol@0 model-eol-bot --dry-run --target-dir . --repo OWNER/REPO
|
|
@@ -262,31 +326,106 @@ it uses the bot defaults of 90 days and direct references. `eval.command` also
|
|
|
262
326
|
comes from the config unless the `MODEL_EOL_EVAL_COMMAND` repository variable is
|
|
263
327
|
set as an explicit workflow override. Each patchable model is applied in an
|
|
264
328
|
isolated checkout and evaluated independently with its own old/new IDs, timeout,
|
|
265
|
-
bounded report, and explicitly
|
|
329
|
+
bounded report, and explicitly forwarded environment names. The content-bound result
|
|
266
330
|
manifest is also pinned to the evaluated Git commit, letting passing migrations
|
|
267
331
|
proceed while a failing peer remains blocked without publishing against a newer,
|
|
268
332
|
unevaluated default branch.
|
|
269
333
|
|
|
334
|
+
### Prove the swap works in your repository
|
|
335
|
+
|
|
336
|
+
Model behavior is repository-specific, so model-eol never guesses which test is
|
|
337
|
+
good enough to authorize a migration. Check a harness into the repository and
|
|
338
|
+
name it explicitly in `.model-eol.json`. A non-empty
|
|
339
|
+
`MODEL_EOL_EVAL_COMMAND` repository variable overrides that command; otherwise
|
|
340
|
+
the config is authoritative. With neither configured, the result manifest says
|
|
341
|
+
that evaluation was not configured.
|
|
342
|
+
|
|
343
|
+
Start by copying [`examples/model-eol-eval.mjs`](examples/model-eol-eval.mjs) to
|
|
344
|
+
`scripts/model-eol-eval.mjs`, then replace its `VERIFY` command with the smallest
|
|
345
|
+
deterministic test that proves the behavior your application relies on. A useful
|
|
346
|
+
harness checks response or tool-call shape, structured-output parsing, required
|
|
347
|
+
capabilities, and a small golden task set. A generic build passing is supporting
|
|
348
|
+
evidence, but it does not by itself prove model behavior. The starter invokes a
|
|
349
|
+
dedicated `npm run eval:model-eol` script so adopting it requires an explicit
|
|
350
|
+
repository test rather than accidentally treating an unrelated unit suite as an
|
|
351
|
+
LLM-behavior eval.
|
|
352
|
+
Point `eval:model-eol` at the underlying behavior test, not back at the harness
|
|
353
|
+
itself.
|
|
354
|
+
|
|
355
|
+
The harness runs after the proposed migration in an isolated checkout and gets:
|
|
356
|
+
|
|
357
|
+
- `MODEL_EOL_OLD_ID` and `MODEL_EOL_NEW_ID` for the current migration.
|
|
358
|
+
- `MODEL_EOL_PLAN`, a one-model plan containing the exact changed references.
|
|
359
|
+
- `MODEL_EOL_REPORT`, the required path for a bounded, publishable Markdown receipt.
|
|
360
|
+
|
|
361
|
+
Exit zero plus a regular report file means pass; any other exit, timeout, missing
|
|
362
|
+
report, tracked workspace mutation, or checkout drift fails that migration. Test
|
|
363
|
+
stdout and stderr are intentionally not published. Put only non-secret evidence
|
|
364
|
+
in the report, and forward only credentials required by trusted eval code through
|
|
365
|
+
`eval.pass_env`. That allowlist controls normal subprocess environment forwarding;
|
|
366
|
+
it is not an OS sandbox and does not isolate code running as the same user. The
|
|
367
|
+
workflow's security boundary is that this job has no repository write token and
|
|
368
|
+
the publication job never executes repository-owned eval code.
|
|
369
|
+
|
|
370
|
+
Before enabling the scheduled bot, commit the harness and policy on a clean
|
|
371
|
+
branch and exercise the same isolated evaluator locally:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
set -euo pipefail
|
|
375
|
+
repo="$(pwd -P)"
|
|
376
|
+
version="$(npm view model-eol@0 version --json | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s);const r=Array.isArray(v)?v.at(-1):v;if(typeof r!=="string")process.exit(1);process.stdout.write(r)})')"
|
|
377
|
+
tmp="$(mktemp -d)"
|
|
378
|
+
trap 'rm -rf -- "$tmp"' EXIT
|
|
379
|
+
cd "$tmp"
|
|
380
|
+
if [ -f "$repo/.model-eol.json" ]; then
|
|
381
|
+
MODEL_EOL_UAT_REPO="$repo" npx --yes --package="model-eol@$version" \
|
|
382
|
+
-c 'cd "$MODEL_EOL_UAT_REPO" && model-eol plan .' > "$tmp/plan.json"
|
|
383
|
+
else
|
|
384
|
+
MODEL_EOL_UAT_REPO="$repo" npx --yes --package="model-eol@$version" \
|
|
385
|
+
-c 'cd "$MODEL_EOL_UAT_REPO" && model-eol plan . --days 90 --scope direct' > "$tmp/plan.json"
|
|
386
|
+
fi
|
|
387
|
+
npx --yes --package="model-eol@$version" model-eol-bot evaluate \
|
|
388
|
+
--target-dir "$repo" \
|
|
389
|
+
--plan-file "$tmp/plan.json" \
|
|
390
|
+
--output-file "$tmp/eval.json"
|
|
391
|
+
node -e 'const r=require(process.argv[1]); console.log(r.results)' "$tmp/eval.json"
|
|
392
|
+
MODEL_EOL_EVAL_RESULTS_FILE="$tmp/eval.json" \
|
|
393
|
+
npx --yes --package="model-eol@$version" model-eol-bot \
|
|
394
|
+
--dry-run --target-dir "$repo" --repo OWNER/REPO
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Each planned model is patched and evaluated independently. The cleanup trap
|
|
398
|
+
removes the temporary artifacts when this shell exits; copy `eval.json` elsewhere
|
|
399
|
+
after the dry run if you want to retain the receipt as UAT evidence. Then let
|
|
400
|
+
`bot.yml.example` run the same contract with any explicitly opted-in provider
|
|
401
|
+
keys available to its read-only evaluate job, not its write-capable publish job.
|
|
402
|
+
|
|
403
|
+
Starting in 0.5, inline `model-eol-bot --eval` and the legacy unbound report/status
|
|
404
|
+
artifacts are refused. A configured publication must consume the commit-bound
|
|
405
|
+
manifest from `model-eol-bot evaluate`; missing results fail before GitHub API
|
|
406
|
+
access.
|
|
407
|
+
|
|
270
408
|
On its first actionable run, the bot creates the `model-eol` label. If repository
|
|
271
409
|
policy prevents label creation or assignment, it fails closed rather than publish
|
|
272
410
|
work whose ownership cannot be authenticated. Existing work is trusted only when
|
|
273
411
|
the label, metadata, deterministic branch, repository, base, and Git lease agree.
|
|
274
|
-
For HTTPS remotes
|
|
412
|
+
For HTTPS remotes - including private repositories - the bot passes `GITHUB_TOKEN` to
|
|
275
413
|
temporary Git processes as a host-scoped authorization header and never places it
|
|
276
414
|
in a remote URL.
|
|
277
415
|
|
|
278
416
|
Two operational notes the hard way teaches: PRs created with `GITHUB_TOKEN` do not
|
|
279
417
|
trigger `pull_request` workflows (use a fine-grained PAT or GitHub App token when
|
|
280
418
|
checks must run, stored as the optional `MODEL_EOL_BOT_TOKEN` secret), and the
|
|
281
|
-
workflow splits privileges
|
|
282
|
-
job, while write tokens live only
|
|
419
|
+
workflow splits privileges - only provider secrets explicitly added for the
|
|
420
|
+
trusted eval live in the read-only evaluation job, while write tokens live only
|
|
421
|
+
in the reconciliation job.
|
|
283
422
|
|
|
284
423
|
## Keeping the feeds honest
|
|
285
424
|
|
|
286
425
|
```sh
|
|
287
426
|
node refresh/refresh.mjs --check # semantic diff vs live pages; exit 3 = PR-worthy
|
|
288
427
|
node refresh/refresh.mjs --distributor aws-bedrock,vertex-ai # distributor lifecycle clocks
|
|
289
|
-
node scripts/feed-changelog.mjs #
|
|
428
|
+
node scripts/feed-changelog.mjs # local rendering of the hosted Atom feed
|
|
290
429
|
```
|
|
291
430
|
|
|
292
431
|
Parse failures fail loudly and never emit a guessed feed. This runs automatically:
|
|
@@ -297,6 +436,14 @@ their keep: they caught the hand-compiled feeds drifting from Anthropic's
|
|
|
297
436
|
recommendations within one week, and corrected a hand-compiled Bedrock date that
|
|
298
437
|
was three months wrong.
|
|
299
438
|
|
|
439
|
+
Every successful live refresh emits a byte-exact receipt. The Pages workflow
|
|
440
|
+
publishes the [public contract](https://thossullivan.github.io/model-eol/) only
|
|
441
|
+
when that receipt hashes the exact feeds on `main`: a no-change check can advance
|
|
442
|
+
`last_checked` immediately, while a material-change run waits for its generated
|
|
443
|
+
feed PR to merge. Feed `generated` remains semantic - it changes only when source
|
|
444
|
+
data changes. The hosted contract contains canonical Draft-07 schemas, feeds
|
|
445
|
+
with SHA-256 receipts, and the Atom changelog.
|
|
446
|
+
|
|
300
447
|
The refresh also travels in the other direction. On August 3, Google removed the
|
|
301
448
|
previously listed October 16 earliest shutdown dates for the Gemini 2.5 Pro,
|
|
302
449
|
Flash, and Flash-Lite GA models. The pipeline opened
|
|
@@ -322,6 +469,15 @@ move any of us can make here.
|
|
|
322
469
|
CI runs the full suite on every push and PR. The copy-ready bot workflow ships
|
|
323
470
|
as `bot.yml.example` and consumes the published package rather than local copies
|
|
324
471
|
of the tooling.
|
|
472
|
+
- The 0.5 Pages workflow publishes canonical schemas, feeds, refresh health, and
|
|
473
|
+
Atom at `thossullivan.github.io/model-eol`. Treat that host as authoritative
|
|
474
|
+
only after repository Pages is enabled and its first deployment passes the
|
|
475
|
+
exact-byte live check. `model-eol validate` checks feeds, repository policy,
|
|
476
|
+
check reports, inventories, schedules, alerts, and plans against the same zero-dependency
|
|
477
|
+
runtime contracts.
|
|
478
|
+
Maintainer rollout: set Pages source to GitHub Actions, dispatch `feed-refresh`
|
|
479
|
+
from `main`, and require the resulting `public-contract` deployment to pass
|
|
480
|
+
before releasing 0.5.
|
|
325
481
|
- Current-model entries (and therefore policy-floor horizons) populate only when
|
|
326
482
|
refresh runs with provider API keys for the models endpoints.
|
|
327
483
|
- The checker matches known IDs only - it will not discover models absent from the
|
|
@@ -333,7 +489,7 @@ move any of us can make here.
|
|
|
333
489
|
changes republish automatically as patch versions (trusted publishing with
|
|
334
490
|
provenance), while manual code releases require an explicit stable version, so
|
|
335
491
|
`npx model-eol` always checks against current dates.
|
|
336
|
-
- Not yet: feed signing, gateway
|
|
492
|
+
- Not yet: feed signing, authenticated account-level gateway/cloud resolvers.
|
|
337
493
|
|
|
338
494
|
## Contributing
|
|
339
495
|
|
package/SPEC.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# model-eol - a machine-readable model deprecation feed (draft
|
|
1
|
+
# model-eol - a machine-readable model deprecation feed (public draft v0.1)
|
|
2
2
|
|
|
3
|
-
> Status:
|
|
3
|
+
> Status: public draft, updated 2026-08-18. The problem: model retirement dates live in HTML docs
|
|
4
4
|
> and emails, so every deprecation checker is a scraper with a private registry
|
|
5
5
|
> format. Ordinary software solved this layer - endoflife.date for EOL data, OSV for
|
|
6
6
|
> vulnerability feeds. This is the model version: small enough that a provider could
|
|
@@ -57,10 +57,20 @@ is data, which is exactly what scraping HTML can never give you.
|
|
|
57
57
|
|
|
58
58
|
`distributions[].status`, when present, is one of `active`, `legacy`,
|
|
59
59
|
`extended-access`, or `retired`. `legacy` means no new consumers but existing
|
|
60
|
-
usage works
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
usage works. `extended-access` means the distributor has announced or entered a
|
|
61
|
+
distinct extended-access portion of that lifecycle; it does not imply availability
|
|
62
|
+
after `shutdown`. On Amazon Bedrock, public extended access is a potentially
|
|
63
|
+
higher-priced portion of the Legacy period for active users and ends at EOL, so
|
|
64
|
+
`shutdown` remains the Bedrock EOL date. Bedrock still calls the encompassing
|
|
65
|
+
state Legacy; `extended-access` is this feed's finer-grained channel signal.
|
|
66
|
+
Private arrangements after EOL are not represented by this status. `retired`
|
|
67
|
+
means the distributor explicitly reports the model as no longer generally
|
|
68
|
+
available even when it does not publish an exact shutdown date.
|
|
69
|
+
|
|
70
|
+
A model may contain at most one distribution for each `via`. Duplicate channel
|
|
71
|
+
records are ambiguous and fail runtime semantic validation. Draft-07 can enforce
|
|
72
|
+
whole-object uniqueness, but cannot express uniqueness keyed by the `via` field,
|
|
73
|
+
so `model-eol validate` and feed loading enforce this constraint.
|
|
64
74
|
|
|
65
75
|
### Structured replacement contract
|
|
66
76
|
|
|
@@ -119,6 +129,11 @@ months early - or tell an OpenAI-direct shop they're fine because Azure says so.
|
|
|
119
129
|
- **Consumers**: anything that can read JSON - a CI step, a dashboard, a Dependabot
|
|
120
130
|
clone that opens migration PRs when `shutdown - today < threshold`.
|
|
121
131
|
|
|
132
|
+
Repository tools that export CycloneDX SHOULD represent a canonical model once
|
|
133
|
+
per lifecycle channel, with a deterministic channel-qualified `bom-ref`. This
|
|
134
|
+
keeps direct publisher and distributor clocks distinct and binds each source
|
|
135
|
+
occurrence to the lifecycle decision that evaluated it.
|
|
136
|
+
|
|
122
137
|
## Non-goals (v0.1)
|
|
123
138
|
|
|
124
139
|
Pricing, capabilities, context windows (models.dev and friends already do this);
|
package/action.yml
CHANGED
|
@@ -5,7 +5,7 @@ branding:
|
|
|
5
5
|
color: 'red'
|
|
6
6
|
inputs:
|
|
7
7
|
command:
|
|
8
|
-
description: 'Command to run: check, inventory, schedule, alert, or
|
|
8
|
+
description: 'Command to run: check, inventory, schedule, alert, plan, or validate'
|
|
9
9
|
required: false
|
|
10
10
|
default: 'check'
|
|
11
11
|
paths:
|
|
@@ -40,6 +40,10 @@ inputs:
|
|
|
40
40
|
description: 'Path to a model-eol JSON config (defaults to repository config discovery)'
|
|
41
41
|
required: false
|
|
42
42
|
default: ''
|
|
43
|
+
document-type:
|
|
44
|
+
description: 'Optional validate document type: feed, config, check, inventory, schedule, alert, or plan'
|
|
45
|
+
required: false
|
|
46
|
+
default: ''
|
|
43
47
|
include-docs:
|
|
44
48
|
description: 'Scan documentation files in addition to the default source and config files'
|
|
45
49
|
required: false
|
|
@@ -72,6 +76,7 @@ runs:
|
|
|
72
76
|
MODEL_EOL_FORMAT: ${{ inputs.format }}
|
|
73
77
|
MODEL_EOL_FEEDS: ${{ inputs.feeds }}
|
|
74
78
|
MODEL_EOL_CONFIG: ${{ inputs.config }}
|
|
79
|
+
MODEL_EOL_DOCUMENT_TYPE: ${{ inputs['document-type'] }}
|
|
75
80
|
MODEL_EOL_INCLUDE_DOCS: ${{ inputs['include-docs'] }}
|
|
76
81
|
MODEL_EOL_ALLOW_INCOMPLETE: ${{ inputs['allow-incomplete'] }}
|
|
77
82
|
MODEL_EOL_JSON: ${{ inputs.json }}
|
|
@@ -79,6 +84,12 @@ runs:
|
|
|
79
84
|
run: |
|
|
80
85
|
set -euo pipefail
|
|
81
86
|
|
|
87
|
+
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]')"
|
|
88
|
+
if ! [[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] || [ "$NODE_MAJOR" -lt 22 ]; then
|
|
89
|
+
echo "model-eol action: Node 22 or newer is required; found $(node --version 2>/dev/null || echo unknown)" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
|
|
82
93
|
split_model_eol_paths() {
|
|
83
94
|
PATH_ARGS=()
|
|
84
95
|
if [[ "$MODEL_EOL_PATHS" == *$'\n'* ]]; then
|
|
@@ -92,9 +103,9 @@ runs:
|
|
|
92
103
|
split_model_eol_paths
|
|
93
104
|
|
|
94
105
|
case "$MODEL_EOL_COMMAND" in
|
|
95
|
-
check|inventory|schedule|alert|plan) ;;
|
|
106
|
+
check|inventory|schedule|alert|plan|validate) ;;
|
|
96
107
|
*)
|
|
97
|
-
echo "model-eol action: command must be check, inventory, schedule, alert, or
|
|
108
|
+
echo "model-eol action: command must be check, inventory, schedule, alert, plan, or validate" >&2
|
|
98
109
|
exit 2
|
|
99
110
|
;;
|
|
100
111
|
esac
|
|
@@ -106,8 +117,12 @@ runs:
|
|
|
106
117
|
echo "model-eol action: format is only supported by the inventory and alert commands" >&2
|
|
107
118
|
exit 2
|
|
108
119
|
fi
|
|
120
|
+
if [ -n "$MODEL_EOL_DOCUMENT_TYPE" ] && [ "$MODEL_EOL_COMMAND" != 'validate' ]; then
|
|
121
|
+
echo "model-eol action: document-type is only supported by the validate command" >&2
|
|
122
|
+
exit 2
|
|
123
|
+
fi
|
|
109
124
|
|
|
110
|
-
ARGS=("$MODEL_EOL_COMMAND"
|
|
125
|
+
ARGS=("$MODEL_EOL_COMMAND")
|
|
111
126
|
if [ -n "$MODEL_EOL_DAYS" ]; then ARGS+=(--days "$MODEL_EOL_DAYS"); fi
|
|
112
127
|
if [ -n "$MODEL_EOL_VIA" ]; then ARGS+=(--via "$MODEL_EOL_VIA"); fi
|
|
113
128
|
if [ -n "$MODEL_EOL_CHANGED" ]; then ARGS+=(--changed "$MODEL_EOL_CHANGED"); fi
|
|
@@ -115,6 +130,7 @@ runs:
|
|
|
115
130
|
if [ -n "$MODEL_EOL_FORMAT" ]; then ARGS+=(--format "$MODEL_EOL_FORMAT"); fi
|
|
116
131
|
if [ -n "$MODEL_EOL_FEEDS" ]; then ARGS+=(--feeds "$MODEL_EOL_FEEDS"); fi
|
|
117
132
|
if [ -n "$MODEL_EOL_CONFIG" ]; then ARGS+=(--config "$MODEL_EOL_CONFIG"); fi
|
|
133
|
+
if [ -n "$MODEL_EOL_DOCUMENT_TYPE" ]; then ARGS+=(--type "$MODEL_EOL_DOCUMENT_TYPE"); fi
|
|
118
134
|
|
|
119
135
|
append_boolean_flag() {
|
|
120
136
|
local value="$1"
|
|
@@ -146,11 +162,13 @@ runs:
|
|
|
146
162
|
esac
|
|
147
163
|
case "$MODEL_EOL_JSON" in
|
|
148
164
|
true)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
case "$MODEL_EOL_COMMAND" in
|
|
166
|
+
check|inventory|schedule|alert) ARGS+=(--json) ;;
|
|
167
|
+
*)
|
|
168
|
+
echo "model-eol action: json is only supported by the check, inventory, schedule, and alert commands" >&2
|
|
169
|
+
exit 2
|
|
170
|
+
;;
|
|
171
|
+
esac
|
|
154
172
|
;;
|
|
155
173
|
false|'') ;;
|
|
156
174
|
*)
|
|
@@ -159,6 +177,9 @@ runs:
|
|
|
159
177
|
;;
|
|
160
178
|
esac
|
|
161
179
|
|
|
180
|
+
# Keep every consumer-controlled path after the option terminator.
|
|
181
|
+
ARGS+=(-- "${PATH_ARGS[@]}")
|
|
182
|
+
|
|
162
183
|
if [ -z "$MODEL_EOL_OUTPUT_FILE" ]; then
|
|
163
184
|
exec node "$MODEL_EOL_ACTION_PATH/check.mjs" "${ARGS[@]}"
|
|
164
185
|
fi
|