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 CHANGED
@@ -6,7 +6,7 @@
6
6
  [![feed refresh](https://github.com/thossullivan/model-eol/actions/workflows/feed-refresh.yml/badge.svg)](https://github.com/thossullivan/model-eol/actions/workflows/feed-refresh.yml)
7
7
  ![spec](https://img.shields.io/badge/spec-model--eol%2F0.1-blue)
8
8
  ![deps](https://img.shields.io/badge/runtime%20deps-zero-brightgreen)
9
- ![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)
9
+ ![node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)
10
10
  ![license](https://img.shields.io/badge/license-MIT-blue)
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 15 model snapshots on schedule. One of my
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
- - **`scripts/feed-changelog.mjs`** - the feeds' git history as Atom/markdown, so
46
- "model retirements as they are announced" is a feed you can subscribe to.
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 `plan`. Use
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": "npm test" }
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). The complete contract is
164
- [`schema/model-eol.bot-config.schema.json`](schema/model-eol.bot-config.schema.json).
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. Preview everything with
254
- no GitHub calls and without adding a project dependency:
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 allowed environment. The content-bound result
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—including private repositories—the bot passes `GITHUB_TOKEN` to
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—provider keys live only in the read-only evaluation
282
- job, while write tokens live only in the reconciliation job.
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 # feeds' git history as an Atom feed
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 merging it published v0.2.3 so downstream checks stopped warning from dates
305
- Google no longer listed. A lifecycle tracker has to retract stale alarms as well
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 route resolvers.
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 spec v0.1)
1
+ # model-eol - a machine-readable model deprecation feed (public draft v0.1)
2
2
 
3
- > Status: sketch, 2026-07-25. The problem: model retirement dates live in HTML docs
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; `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.
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 plan'
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 plan" >&2
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" "${PATH_ARGS[@]}")
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
- if [ "$MODEL_EOL_COMMAND" = 'plan' ]; then
150
- echo "model-eol action: json is only supported by the check, inventory, schedule, and alert commands" >&2
151
- exit 2
152
- fi
153
- ARGS+=(--json)
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