model-eol 0.4.1 → 0.5.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/README.md +147 -20
- 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 +20 -11
- 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
|
|
@@ -16,7 +16,7 @@ tooling that turns it into a Dependabot for models.**
|
|
|
16
16
|
|
|
17
17
|
## The problem
|
|
18
18
|
|
|
19
|
-
On July 23, 2026, OpenAI shut down
|
|
19
|
+
On July 23, 2026, OpenAI shut down a scheduled wave of model snapshots. One of my
|
|
20
20
|
tuned research workflows returned a model-not-found error that morning even though
|
|
21
21
|
the retirement had been documented for months. A model ID is the one dependency
|
|
22
22
|
my toolchain could not see. When an npm package is deprecated, the warning prints
|
|
@@ -36,14 +36,18 @@ second, so the existing trackers can converge instead of each scraping alone.
|
|
|
36
36
|
- **`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
37
|
dated entry carries a source URL.
|
|
38
38
|
- **`check.mjs`** - zero-dependency CLI: CI gate, PR diff gate, inventory, CycloneDX
|
|
39
|
-
ML-BOM export, retirement schedule, alerts and badges, migration plan/apply
|
|
39
|
+
ML-BOM export, retirement schedule, alerts and badges, migration plan/apply,
|
|
40
|
+
and public document validation.
|
|
40
41
|
- **`refresh/`** - regenerates the feeds from provider pages, models endpoints, and
|
|
41
42
|
distributor lifecycle pages, with a semantic diff for human review.
|
|
42
43
|
- **`bot/`** - the Dependabot part: a cron GitHub workflow that maintains one
|
|
43
44
|
migration PR or issue per retiring model, published as the `model-eol-bot`
|
|
44
45
|
binary in the same npm package.
|
|
45
|
-
-
|
|
46
|
-
|
|
46
|
+
- **Public contract** - versioned schemas, hosted feeds, refresh health, a
|
|
47
|
+
publication manifest, and an Atom changelog are live at
|
|
48
|
+
[`thossullivan.github.io/model-eol`](https://thossullivan.github.io/model-eol/).
|
|
49
|
+
Publication requires a refresh receipt for the exact feeds on `main`, followed
|
|
50
|
+
by an exact-byte check against every deployed asset.
|
|
47
51
|
|
|
48
52
|
## Try it
|
|
49
53
|
|
|
@@ -72,6 +76,9 @@ node check.mjs path/to/your/repo --via aws-bedrock
|
|
|
72
76
|
# inventory, and a CycloneDX 1.6 ML-BOM for your SBOM pipeline
|
|
73
77
|
node check.mjs inventory path/to/your/repo
|
|
74
78
|
node check.mjs inventory path/to/your/repo --format cyclonedx > model-bom.json
|
|
79
|
+
# the primary CI gate is also a schema-valid public report
|
|
80
|
+
node check.mjs check path/to/your/repo --json > model-eol-check.json
|
|
81
|
+
node check.mjs validate model-eol-check.json
|
|
75
82
|
# retirement schedule with the repo-level worst case
|
|
76
83
|
node check.mjs schedule path/to/your/repo
|
|
77
84
|
# GitHub Actions annotations, Markdown, or a shields.io badge
|
|
@@ -80,6 +87,8 @@ node check.mjs alert path/to/your/repo --format badge > model-eol-badge.json
|
|
|
80
87
|
# migration plan (only high-confidence direct API refs are patchable) and safe apply
|
|
81
88
|
node check.mjs plan path/to/your/repo --days 90 > plan.json
|
|
82
89
|
node check.mjs apply --plan plan.json --dry-run
|
|
90
|
+
# validate provider feeds, repository policy, or model-eol machine reports
|
|
91
|
+
node check.mjs validate feeds/openai.json .model-eol.json plan.json
|
|
83
92
|
```
|
|
84
93
|
|
|
85
94
|
</details>
|
|
@@ -118,6 +127,10 @@ the `v0` line:
|
|
|
118
127
|
- uses: actions/checkout@v7
|
|
119
128
|
with:
|
|
120
129
|
fetch-depth: 0 # --changed needs history to diff against the base ref
|
|
130
|
+
- uses: actions/setup-node@v7
|
|
131
|
+
with:
|
|
132
|
+
node-version: 22
|
|
133
|
+
package-manager-cache: false
|
|
121
134
|
- uses: thossullivan/model-eol@v0
|
|
122
135
|
with:
|
|
123
136
|
command: check
|
|
@@ -131,10 +144,16 @@ or retiring within the threshold. Add `via: aws-bedrock` (or `azure-ai-foundry`)
|
|
|
131
144
|
to judge by a distributor's clock. The copy-paste version with both gates - PR
|
|
132
145
|
diff plus a weekly full-repository check - is
|
|
133
146
|
[`examples/workflows/model-eol.yml`](examples/workflows/model-eol.yml). The
|
|
134
|
-
Action also exposes `inventory`, `schedule`, `alert`, and `
|
|
147
|
+
Action also exposes `inventory`, `schedule`, `alert`, `plan`, and `validate`. Use
|
|
135
148
|
`format: cyclonedx` for an ML-BOM, or `output-file` to retain any report as a
|
|
136
149
|
workflow artifact while keeping the same output in the job log.
|
|
137
150
|
|
|
151
|
+
CycloneDX exports use one machine-learning component per canonical model and
|
|
152
|
+
lifecycle channel. Direct publisher (`publisher-direct`), Azure, Bedrock, and
|
|
153
|
+
other routed references therefore retain their own status and shutdown clock
|
|
154
|
+
under deterministic, channel-qualified `bom-ref` values; occurrences are sorted
|
|
155
|
+
and scoped to the component whose clock they used.
|
|
156
|
+
|
|
138
157
|
## One repository policy
|
|
139
158
|
|
|
140
159
|
The CLI, Action, and bot all discover a strict `.model-eol.json` at the target
|
|
@@ -151,7 +170,7 @@ Git repository root. CLI flags or Action inputs override configured values, and
|
|
|
151
170
|
"paths": ["test/fixtures/**", "vendor"]
|
|
152
171
|
},
|
|
153
172
|
"issues": { "enabled": true },
|
|
154
|
-
"eval": { "command": "
|
|
173
|
+
"eval": { "command": "node scripts/model-eol-eval.mjs" }
|
|
155
174
|
}
|
|
156
175
|
```
|
|
157
176
|
|
|
@@ -160,8 +179,28 @@ in `ignore.models` suppresses the whole alias family; `ignore.paths` uses
|
|
|
160
179
|
repo-relative `*`, `**`, and `?` globs and excludes matching files before scan
|
|
161
180
|
coverage limits are counted. With no config the CLI keeps its historical
|
|
162
181
|
`scope: all`; once a config exists, omitted values use the shared bot defaults
|
|
163
|
-
(`days: 90`, `scope: direct`, and the publisher clock).
|
|
164
|
-
|
|
182
|
+
(`days: 90`, `scope: direct`, and the publisher clock). Portable structure is
|
|
183
|
+
defined by
|
|
184
|
+
[`schema/model-eol.bot-config.schema.json`](schema/model-eol.bot-config.schema.json),
|
|
185
|
+
while `model-eol validate` also enforces runtime semantic and safety checks that
|
|
186
|
+
Draft-07 cannot express cleanly.
|
|
187
|
+
|
|
188
|
+
For content-heavy repositories, start with `inventory`, then exclude caches,
|
|
189
|
+
generated catalogs, archived fixtures, and documentation that are not live model
|
|
190
|
+
dependencies. Published-repository UAT deliberately verifies both outcomes: the
|
|
191
|
+
unconfigured scan reports evidence, while an explicit path policy produces a
|
|
192
|
+
clean check and empty bot decision table without changing repository content.
|
|
193
|
+
|
|
194
|
+
Parent-repository scans do not recurse into tracked Git submodules. A detected
|
|
195
|
+
gitlink is reported as incomplete coverage, so `check` and `plan` fail closed
|
|
196
|
+
unless the path is explicitly ignored or `--allow-incomplete` is chosen. To
|
|
197
|
+
inspect submodule contents, run model-eol against the checked-out submodule as a
|
|
198
|
+
separate target/repository policy.
|
|
199
|
+
|
|
200
|
+
Untracked nested Git repositories are handled the same way: the parent reports
|
|
201
|
+
`nested-repository-skipped` and never recurses into the nested checkout. Ignore
|
|
202
|
+
an intentional nested repository explicitly, allow incomplete coverage, or scan
|
|
203
|
+
it separately with its own repository policy.
|
|
165
204
|
|
|
166
205
|
Mixed-provider monorepos can keep those top-level values as repository defaults,
|
|
167
206
|
then apply path policies and a lifecycle channel per reference:
|
|
@@ -250,8 +289,9 @@ human. The workflow resolves `model-eol@0` from npm once and reuses that exact
|
|
|
250
289
|
version across its plan, read-only evaluation, and write-token publication jobs;
|
|
251
290
|
consumer repositories do not copy `check.mjs` or the `bot/` source directory.
|
|
252
291
|
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
|
|
292
|
+
bot-owned PRs/issues close when their finding disappears. In a repository without
|
|
293
|
+
a configured eval, preview everything with no GitHub calls and without adding a
|
|
294
|
+
project dependency:
|
|
255
295
|
|
|
256
296
|
```sh
|
|
257
297
|
npx --yes --package=model-eol@0 model-eol-bot --dry-run --target-dir . --repo OWNER/REPO
|
|
@@ -262,31 +302,105 @@ it uses the bot defaults of 90 days and direct references. `eval.command` also
|
|
|
262
302
|
comes from the config unless the `MODEL_EOL_EVAL_COMMAND` repository variable is
|
|
263
303
|
set as an explicit workflow override. Each patchable model is applied in an
|
|
264
304
|
isolated checkout and evaluated independently with its own old/new IDs, timeout,
|
|
265
|
-
bounded report, and explicitly
|
|
305
|
+
bounded report, and explicitly forwarded environment names. The content-bound result
|
|
266
306
|
manifest is also pinned to the evaluated Git commit, letting passing migrations
|
|
267
307
|
proceed while a failing peer remains blocked without publishing against a newer,
|
|
268
308
|
unevaluated default branch.
|
|
269
309
|
|
|
310
|
+
### Prove the swap works in your repository
|
|
311
|
+
|
|
312
|
+
Model behavior is repository-specific, so model-eol never guesses which test is
|
|
313
|
+
good enough to authorize a migration. Check a harness into the repository and
|
|
314
|
+
name it explicitly in `.model-eol.json`. A non-empty
|
|
315
|
+
`MODEL_EOL_EVAL_COMMAND` repository variable overrides that command; otherwise
|
|
316
|
+
the config is authoritative. With neither configured, the result manifest says
|
|
317
|
+
that evaluation was not configured.
|
|
318
|
+
|
|
319
|
+
Start by copying [`examples/model-eol-eval.mjs`](examples/model-eol-eval.mjs) to
|
|
320
|
+
`scripts/model-eol-eval.mjs`, then replace its `VERIFY` command with the smallest
|
|
321
|
+
deterministic test that proves the behavior your application relies on. A useful
|
|
322
|
+
harness checks response or tool-call shape, structured-output parsing, required
|
|
323
|
+
capabilities, and a small golden task set. A generic build passing is supporting
|
|
324
|
+
evidence, but it does not by itself prove model behavior. The starter invokes a
|
|
325
|
+
dedicated `npm run eval:model-eol` script so adopting it requires an explicit
|
|
326
|
+
repository test rather than accidentally treating an unrelated unit suite as an
|
|
327
|
+
LLM-behavior eval.
|
|
328
|
+
Point `eval:model-eol` at the underlying behavior test, not back at the harness
|
|
329
|
+
itself.
|
|
330
|
+
|
|
331
|
+
The harness runs after the proposed migration in an isolated checkout and gets:
|
|
332
|
+
|
|
333
|
+
- `MODEL_EOL_OLD_ID` and `MODEL_EOL_NEW_ID` for the current migration.
|
|
334
|
+
- `MODEL_EOL_PLAN`, a one-model plan containing the exact changed references.
|
|
335
|
+
- `MODEL_EOL_REPORT`, the required path for a bounded, publishable Markdown receipt.
|
|
336
|
+
|
|
337
|
+
Exit zero plus a regular report file means pass; any other exit, timeout, missing
|
|
338
|
+
report, tracked workspace mutation, or checkout drift fails that migration. Test
|
|
339
|
+
stdout and stderr are intentionally not published. Put only non-secret evidence
|
|
340
|
+
in the report, and forward only credentials required by trusted eval code through
|
|
341
|
+
`eval.pass_env`. That allowlist controls normal subprocess environment forwarding;
|
|
342
|
+
it is not an OS sandbox and does not isolate code running as the same user. The
|
|
343
|
+
workflow's security boundary is that this job has no repository write token and
|
|
344
|
+
the publication job never executes repository-owned eval code.
|
|
345
|
+
|
|
346
|
+
Before enabling the scheduled bot, commit the harness and policy on a clean
|
|
347
|
+
branch and exercise the same isolated evaluator locally:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
set -euo pipefail
|
|
351
|
+
repo="$(pwd -P)"
|
|
352
|
+
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)})')"
|
|
353
|
+
tmp="$(mktemp -d)"
|
|
354
|
+
trap 'rm -rf -- "$tmp"' EXIT
|
|
355
|
+
cd "$tmp"
|
|
356
|
+
if [ -f "$repo/.model-eol.json" ]; then
|
|
357
|
+
MODEL_EOL_UAT_REPO="$repo" npx --yes --package="model-eol@$version" \
|
|
358
|
+
-c 'cd "$MODEL_EOL_UAT_REPO" && model-eol plan .' > "$tmp/plan.json"
|
|
359
|
+
else
|
|
360
|
+
MODEL_EOL_UAT_REPO="$repo" npx --yes --package="model-eol@$version" \
|
|
361
|
+
-c 'cd "$MODEL_EOL_UAT_REPO" && model-eol plan . --days 90 --scope direct' > "$tmp/plan.json"
|
|
362
|
+
fi
|
|
363
|
+
npx --yes --package="model-eol@$version" model-eol-bot evaluate \
|
|
364
|
+
--target-dir "$repo" \
|
|
365
|
+
--plan-file "$tmp/plan.json" \
|
|
366
|
+
--output-file "$tmp/eval.json"
|
|
367
|
+
node -e 'const r=require(process.argv[1]); console.log(r.results)' "$tmp/eval.json"
|
|
368
|
+
MODEL_EOL_EVAL_RESULTS_FILE="$tmp/eval.json" \
|
|
369
|
+
npx --yes --package="model-eol@$version" model-eol-bot \
|
|
370
|
+
--dry-run --target-dir "$repo" --repo OWNER/REPO
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Each planned model is patched and evaluated independently. The cleanup trap
|
|
374
|
+
removes the temporary artifacts when this shell exits; copy `eval.json` elsewhere
|
|
375
|
+
after the dry run if you want to retain the receipt as UAT evidence. Then let
|
|
376
|
+
`bot.yml.example` run the same contract with any explicitly opted-in provider
|
|
377
|
+
keys available to its read-only evaluate job, not its write-capable publish job.
|
|
378
|
+
|
|
379
|
+
Inline `model-eol-bot --eval` and the legacy unbound report/status artifacts are
|
|
380
|
+
refused. A configured publication must consume the commit-bound manifest from
|
|
381
|
+
`model-eol-bot evaluate`; missing results fail before GitHub API access.
|
|
382
|
+
|
|
270
383
|
On its first actionable run, the bot creates the `model-eol` label. If repository
|
|
271
384
|
policy prevents label creation or assignment, it fails closed rather than publish
|
|
272
385
|
work whose ownership cannot be authenticated. Existing work is trusted only when
|
|
273
386
|
the label, metadata, deterministic branch, repository, base, and Git lease agree.
|
|
274
|
-
For HTTPS remotes
|
|
387
|
+
For HTTPS remotes - including private repositories - the bot passes `GITHUB_TOKEN` to
|
|
275
388
|
temporary Git processes as a host-scoped authorization header and never places it
|
|
276
389
|
in a remote URL.
|
|
277
390
|
|
|
278
391
|
Two operational notes the hard way teaches: PRs created with `GITHUB_TOKEN` do not
|
|
279
392
|
trigger `pull_request` workflows (use a fine-grained PAT or GitHub App token when
|
|
280
393
|
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
|
|
394
|
+
workflow splits privileges - only provider secrets explicitly added for the
|
|
395
|
+
trusted eval live in the read-only evaluation job, while write tokens live only
|
|
396
|
+
in the reconciliation job.
|
|
283
397
|
|
|
284
398
|
## Keeping the feeds honest
|
|
285
399
|
|
|
286
400
|
```sh
|
|
287
401
|
node refresh/refresh.mjs --check # semantic diff vs live pages; exit 3 = PR-worthy
|
|
288
402
|
node refresh/refresh.mjs --distributor aws-bedrock,vertex-ai # distributor lifecycle clocks
|
|
289
|
-
node scripts/feed-changelog.mjs #
|
|
403
|
+
node scripts/feed-changelog.mjs # local rendering of the hosted Atom feed
|
|
290
404
|
```
|
|
291
405
|
|
|
292
406
|
Parse failures fail loudly and never emit a guessed feed. This runs automatically:
|
|
@@ -297,13 +411,20 @@ their keep: they caught the hand-compiled feeds drifting from Anthropic's
|
|
|
297
411
|
recommendations within one week, and corrected a hand-compiled Bedrock date that
|
|
298
412
|
was three months wrong.
|
|
299
413
|
|
|
414
|
+
Every successful live refresh emits a byte-exact receipt. The Pages workflow
|
|
415
|
+
publishes the [public contract](https://thossullivan.github.io/model-eol/) only
|
|
416
|
+
when that receipt hashes the exact feeds on `main`: a no-change check can advance
|
|
417
|
+
`last_checked` immediately, while a material-change run waits for its generated
|
|
418
|
+
feed PR to merge. Feed `generated` remains semantic - it changes only when source
|
|
419
|
+
data changes. The hosted contract contains canonical Draft-07 schemas, feeds
|
|
420
|
+
with SHA-256 receipts, and the Atom changelog.
|
|
421
|
+
|
|
300
422
|
The refresh also travels in the other direction. On August 3, Google removed the
|
|
301
423
|
previously listed October 16 earliest shutdown dates for the Gemini 2.5 Pro,
|
|
302
424
|
Flash, and Flash-Lite GA models. The pipeline opened
|
|
303
425
|
[a PR with the semantic retraction](https://github.com/thossullivan/model-eol/pull/60),
|
|
304
|
-
and
|
|
305
|
-
|
|
306
|
-
as add new ones.
|
|
426
|
+
and downstream checks stopped warning from dates Google no longer listed. A
|
|
427
|
+
lifecycle tracker has to retract stale alarms as well as add new ones.
|
|
307
428
|
|
|
308
429
|
## The actual ask
|
|
309
430
|
|
|
@@ -322,6 +443,12 @@ move any of us can make here.
|
|
|
322
443
|
CI runs the full suite on every push and PR. The copy-ready bot workflow ships
|
|
323
444
|
as `bot.yml.example` and consumes the published package rather than local copies
|
|
324
445
|
of the tooling.
|
|
446
|
+
- The [public contract](https://thossullivan.github.io/model-eol/) publishes
|
|
447
|
+
canonical schemas, feeds, refresh health, a publication manifest, and Atom.
|
|
448
|
+
Each deployment is bound to an exact feed-refresh receipt and verified against
|
|
449
|
+
every live asset. `model-eol validate` checks feeds, repository policy, check
|
|
450
|
+
reports, inventories, schedules, alerts, and plans against the same
|
|
451
|
+
zero-dependency runtime contracts.
|
|
325
452
|
- Current-model entries (and therefore policy-floor horizons) populate only when
|
|
326
453
|
refresh runs with provider API keys for the models endpoints.
|
|
327
454
|
- The checker matches known IDs only - it will not discover models absent from the
|
|
@@ -333,7 +460,7 @@ move any of us can make here.
|
|
|
333
460
|
changes republish automatically as patch versions (trusted publishing with
|
|
334
461
|
provenance), while manual code releases require an explicit stable version, so
|
|
335
462
|
`npx model-eol` always checks against current dates.
|
|
336
|
-
- Not yet: feed signing, gateway
|
|
463
|
+
- Not yet: feed signing, authenticated account-level gateway/cloud resolvers.
|
|
337
464
|
|
|
338
465
|
## Contributing
|
|
339
466
|
|
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
|