model-eol 0.4.1 → 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/bot/lib/git.mjs CHANGED
@@ -105,6 +105,55 @@ export const verifyBotBranch = (cwd, branch, expectedHead, auth = null) => {
105
105
  }
106
106
  }
107
107
 
108
+ export const verifyRemoteBranchHead = (cwd, branch, expectedHead, auth = null) => {
109
+ try {
110
+ run(cwd, ['fetch', 'origin', '--prune'], false, auth)
111
+ } catch (error) {
112
+ return { head: null, safe: false, error: error.message }
113
+ }
114
+ const head = run(cwd, ['rev-parse', '--verify', `refs/remotes/origin/${branch}`], true)
115
+ return {
116
+ head,
117
+ safe: head === expectedHead,
118
+ error: null,
119
+ }
120
+ }
121
+
122
+ const exactObjectId = value => typeof value === 'string' && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(value)
123
+
124
+ const requireExactObjectId = (value, name) => {
125
+ if (!exactObjectId(value)) throw new Error(`refusing remote branch mutation without an exact ${name} commit`)
126
+ }
127
+
128
+ export const isCommitAvailable = (cwd, head) => exactObjectId(head)
129
+ && run(cwd, ['cat-file', '-e', `${head}^{commit}`], true) !== null
130
+
131
+ export const deleteRemoteBranch = (cwd, branch, expectedHead, auth = null) => {
132
+ requireExactObjectId(expectedHead, 'expected')
133
+ const ref = `refs/heads/${branch}`
134
+ run(cwd, [
135
+ 'push',
136
+ `--force-with-lease=${ref}:${expectedHead}`,
137
+ 'origin',
138
+ `:${ref}`,
139
+ ], false, auth)
140
+ }
141
+
142
+ export const restoreRemoteBranch = (cwd, branch, expectedCurrentHead, restoreHead, auth = null) => {
143
+ requireExactObjectId(expectedCurrentHead, 'current')
144
+ requireExactObjectId(restoreHead, 'restore')
145
+ if (!isCommitAvailable(cwd, restoreHead)) {
146
+ throw new Error(`refusing remote branch rollback because restore commit ${restoreHead} is unavailable`)
147
+ }
148
+ const ref = `refs/heads/${branch}`
149
+ run(cwd, [
150
+ 'push',
151
+ `--force-with-lease=${ref}:${expectedCurrentHead}`,
152
+ 'origin',
153
+ `${restoreHead}:${ref}`,
154
+ ], false, auth)
155
+ }
156
+
108
157
  export const pushBranch = (cwd, branch, expectedHead = null, auth = null, { allowMissing = false } = {}) => {
109
158
  const destination = `HEAD:refs/heads/${branch}`
110
159
  if (expectedHead !== null) {
@@ -149,6 +149,10 @@ export class GitHubClient {
149
149
  return data
150
150
  }
151
151
 
152
+ async getPull(number) {
153
+ return (await this.request('GET', `/repos/${this.repo}/pulls/${number}`)).data
154
+ }
155
+
152
156
  async listIssues() {
153
157
  const issues = await this.listAll(`/repos/${this.repo}/issues`)
154
158
  return issues.filter(issue => !issue.pull_request)
package/bot.yml.example CHANGED
@@ -36,9 +36,12 @@ jobs:
36
36
  exit 1
37
37
  fi
38
38
  printf '%s\n' "$MODEL_EOL_VERSION" > "$RUNNER_TEMP/model-eol-plan/model-eol-version"
39
- PLAN_ARGS=(plan .)
40
- if [ ! -f .model-eol.json ]; then PLAN_ARGS+=(--days 90 --scope direct); fi
41
- npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol "${PLAN_ARGS[@]}" > "$RUNNER_TEMP/model-eol-plan/model-eol-plan.json"
39
+ # Resolve npx outside the consumer checkout so a repository whose own
40
+ # package is named model-eol cannot shadow the requested published bin.
41
+ cd "$RUNNER_TEMP/model-eol-plan"
42
+ npx --yes --package="model-eol@$MODEL_EOL_VERSION" \
43
+ -c 'cd "$GITHUB_WORKSPACE" && if [ -f .model-eol.json ]; then model-eol plan .; else model-eol plan . --days 90 --scope direct; fi' \
44
+ > "$RUNNER_TEMP/model-eol-plan/model-eol-plan.json"
42
45
  - name: Upload plan
43
46
  uses: actions/upload-artifact@v7
44
47
  with:
@@ -55,8 +58,9 @@ jobs:
55
58
  permissions:
56
59
  contents: read
57
60
  steps:
58
- # Provider credentials exist only in this read-only job. The evaluator
59
- # forwards only names explicitly allowed by eval.pass_env.
61
+ # Add only the provider secrets required by trusted default-branch eval
62
+ # code, and list the same names in eval.pass_env. Environment filtering is
63
+ # not an OS sandbox; this job's hard boundary is that it has no write token.
60
64
  - uses: actions/checkout@v7
61
65
  with:
62
66
  persist-credentials: false
@@ -71,10 +75,8 @@ jobs:
71
75
  path: ${{ runner.temp }}/model-eol-plan
72
76
  - name: Apply and evaluate each migration independently
73
77
  env:
74
- OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
75
- ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
76
- GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
77
- GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
78
+ # Example (opt in only when needed by eval.pass_env):
79
+ # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
78
80
  # A non-empty repository variable overrides eval.command. Otherwise
79
81
  # the strictly validated command in .model-eol.json is used.
80
82
  MODEL_EOL_EVAL_COMMAND: ${{ vars.MODEL_EOL_EVAL_COMMAND }}
@@ -85,8 +87,9 @@ jobs:
85
87
  echo "refusing invalid resolved model-eol version: $MODEL_EOL_VERSION" >&2
86
88
  exit 1
87
89
  fi
90
+ cd "$RUNNER_TEMP/model-eol-eval"
88
91
  npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol-bot evaluate \
89
- --target-dir . \
92
+ --target-dir "$GITHUB_WORKSPACE" \
90
93
  --plan-file "$RUNNER_TEMP/model-eol-plan/model-eol-plan.json" \
91
94
  --output-file "$RUNNER_TEMP/model-eol-eval/model-eol-eval-results.json"
92
95
  - name: Upload bounded eval results
@@ -134,15 +137,21 @@ jobs:
134
137
  MODEL_EOL_EVAL_COMMAND: ${{ vars.MODEL_EOL_EVAL_COMMAND }}
135
138
  MODEL_EOL_TOKEN_KIND: ${{ secrets.MODEL_EOL_BOT_TOKEN && 'external-token' || 'github-token' }}
136
139
  run: |
140
+ mkdir -p "$RUNNER_TEMP/model-eol-publish"
137
141
  MODEL_EOL_VERSION="$(cat "$RUNNER_TEMP/model-eol-plan/model-eol-version")"
138
142
  if [[ ! "$MODEL_EOL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
139
143
  echo "refusing invalid resolved model-eol version: $MODEL_EOL_VERSION" >&2
140
144
  exit 1
141
145
  fi
142
- npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol-bot --repo "${{ github.repository }}" --target-dir .
146
+ cd "$RUNNER_TEMP/model-eol-publish"
147
+ npx --yes --package="model-eol@$MODEL_EOL_VERSION" model-eol-bot \
148
+ --repo "${{ github.repository }}" \
149
+ --target-dir "$GITHUB_WORKSPACE"
143
150
 
144
151
  # A PR made with GITHUB_TOKEN does not trigger pull_request workflows. For
145
152
  # required checks, store a narrowly scoped fine-grained PAT or GitHub App token
146
153
  # as MODEL_EOL_BOT_TOKEN. Keep provider keys out of the publish job.
154
+ # For migration evidence, copy examples/model-eol-eval.mjs into the consumer,
155
+ # customize its dedicated eval command, and set eval.command in .model-eol.json.
147
156
  # Set feeds.allow_vendored_fallback: true only for degraded report-only runs.
148
157
  # Independent concurrent invocations need external mutual exclusion.
package/check.mjs CHANGED
@@ -10,9 +10,11 @@
10
10
  // node check.mjs alert [paths...] [--format github|markdown|badge] [--json]
11
11
  // node check.mjs plan [paths...] [--days N] [--scope all|direct] [--via DISTRIBUTOR] [--feeds DIR]
12
12
  // node check.mjs apply --plan plan.json [--dry-run]
13
+ // node check.mjs validate document.json [...] [--type feed|config|check|inventory|schedule|alert|plan]
13
14
  //
14
15
  // exit codes: check/alert 0 = clear, 1 = finding, 2 = usage error;
15
- // apply 0 = all items applied, 1 = item refused, 2 = usage or plan error.
16
+ // apply 0 = all items applied, 1 = item refused, 2 = usage or plan error;
17
+ // validate 0 = every document valid, 2 = usage, parse, schema, or semantic error.
16
18
 
17
19
  import fs from 'node:fs'
18
20
  import path from 'node:path'
@@ -45,6 +47,7 @@ import {
45
47
  formatSchedule,
46
48
  } from './lib/reports.mjs'
47
49
  import { addedLinesForTargets, filterFindingsToChanged, gitRootFor, incompleteScanNotes, scanTargets } from './lib/scanner.mjs'
50
+ import { DOCUMENT_TYPES, formatDocumentErrors, validateDocumentFile } from './lib/validate-document.mjs'
48
51
 
49
52
  const rootForTarget = target => {
50
53
  const absolute = path.resolve(target)
@@ -93,7 +96,7 @@ const repoPathForFile = (file, roots) => {
93
96
  }
94
97
 
95
98
  const main = () => {
96
- const COMMANDS = new Set(['check', 'inventory', 'schedule', 'alert', 'plan', 'apply', 'help'])
99
+ const COMMANDS = new Set(['check', 'inventory', 'schedule', 'alert', 'plan', 'apply', 'validate', 'help'])
97
100
  const args = process.argv.slice(2)
98
101
 
99
102
  if (args[0] === '--help' || args[0] === '-h') args[0] = 'help'
@@ -115,6 +118,7 @@ try {
115
118
  'allow-incomplete': { type: 'boolean' },
116
119
  'dry-run': { type: 'boolean' },
117
120
  changed: { type: 'string' },
121
+ type: { type: 'string' },
118
122
  help: { type: 'boolean', short: 'h' },
119
123
  },
120
124
  allowPositionals: true,
@@ -148,6 +152,7 @@ Usage:
148
152
  node check.mjs alert [paths...] [--config FILE] [--days N] [--scope all|direct] [--format github|markdown|badge] [--json]
149
153
  node check.mjs plan [paths...] [--config FILE] [--days N] [--scope all|direct] [--via DISTRIBUTOR] [--feeds DIR] [--allow-incomplete]
150
154
  node check.mjs apply --plan plan.json [--dry-run]
155
+ node check.mjs validate document.json [...] [--type feed|config|check|inventory|schedule|alert|plan]
151
156
 
152
157
  Commands:
153
158
  check Fail when tracked model IDs are retired or retiring within --days.
@@ -159,6 +164,8 @@ Commands:
159
164
  alert Emit GitHub Actions annotations, Markdown, or a Shields badge JSON and fail on errors.
160
165
  plan Emit a JSON migration plan with only safely patchable direct API references.
161
166
  apply Apply a plan with line-hash and occurrence checks; use --dry-run to preview.
167
+ validate Validate a feed, config, check, inventory, schedule, alert, or plan document.
168
+ The type is inferred from its discriminator or .model-eol.json filename unless --type is set.
162
169
 
163
170
  Scopes:
164
171
  all Check every tracked model ID found. This preserves the original behavior.
@@ -171,12 +178,21 @@ Configuration:
171
178
  Exit codes:
172
179
  0 Clean, or report generated successfully.
173
180
  1 Findings, alert errors, or refused apply items.
174
- 2 Usage, feed, scan, or plan errors.
181
+ 2 Usage, feed, scan, plan, or validation errors.
175
182
  3 Output stream failure other than EPIPE.
176
183
  `)
177
184
  return 0
178
185
  }
179
186
 
187
+ if (positionals.some(value => value.length === 0)) {
188
+ console.error('positional paths must be non-empty')
189
+ return 2
190
+ }
191
+ if (values.via === '') {
192
+ console.error('--via must be a non-empty lifecycle channel')
193
+ return 2
194
+ }
195
+
180
196
  if (command === 'apply') {
181
197
  if (!PLAN_FILE || positionals.length) {
182
198
  console.error('apply requires --plan plan.json and accepts no target paths')
@@ -191,6 +207,41 @@ if (command === 'apply') {
191
207
  }
192
208
  }
193
209
 
210
+ if (command === 'validate') {
211
+ const allowedOptions = new Set(['type'])
212
+ const unsupported = Object.keys(values).find(option => !allowedOptions.has(option))
213
+ if (positionals.length === 0 || unsupported) {
214
+ const detail = unsupported ? `; --${unsupported} is not supported by validate` : ''
215
+ console.error(`validate requires one or more JSON documents${detail}`)
216
+ return 2
217
+ }
218
+ if (values.type !== undefined && !DOCUMENT_TYPES.includes(values.type) && values.type !== 'bot-config' && values.type !== 'model-eol-config') {
219
+ console.error(`unknown document type ${JSON.stringify(values.type)}; expected ${DOCUMENT_TYPES.join(', ')}`)
220
+ return 2
221
+ }
222
+ let failed = false
223
+ for (const file of positionals) {
224
+ try {
225
+ const result = validateDocumentFile(file, { type: values.type ?? null })
226
+ if (result.errors.length) {
227
+ for (const error of formatDocumentErrors(result.errors)) console.error(`${file}: ${error}`)
228
+ failed = true
229
+ continue
230
+ }
231
+ console.log(`${file}: valid ${result.type} document`)
232
+ } catch (error) {
233
+ console.error(`${file}: validation failed: ${error.message}`)
234
+ failed = true
235
+ }
236
+ }
237
+ return failed ? 2 : 0
238
+ }
239
+
240
+ if (values.type !== undefined) {
241
+ console.error('--type is only supported by the validate command')
242
+ return 2
243
+ }
244
+
194
245
  let configLocation
195
246
  let repositoryConfig
196
247
  try {
@@ -355,7 +406,7 @@ if (command === 'plan') {
355
406
 
356
407
  if (command === 'check') {
357
408
  if (AS_JSON) {
358
- console.log(JSON.stringify({ threshold_days: DAYS, distributor: VIA, scope: SCOPE, scan_notes: scan.notes, findings: changedFindings }, null, 2))
409
+ console.log(JSON.stringify({ schema: 'model-eol/check@0.1', threshold_days: DAYS, distributor: VIA, scope: SCOPE, scan_notes: scan.notes, findings: changedFindings }, null, 2))
359
410
  } else {
360
411
  console.log(formatCheck({ findings: changedFindings, bad, scannedFiles: scan.files.length, days: DAYS, scope: SCOPE }))
361
412
  }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Copy this file into the consuming repository and replace VERIFY with the
4
+ // smallest command that proves the model-dependent behavior you rely on.
5
+ // model-eol runs it from an isolated checkout after applying one migration.
6
+
7
+ import fs from 'node:fs'
8
+ import path from 'node:path'
9
+ import { spawnSync } from 'node:child_process'
10
+
11
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
12
+ const VERIFY = Object.freeze({ command: npm, args: ['run', 'eval:model-eol'] })
13
+
14
+ const required = name => {
15
+ const value = process.env[name]
16
+ if (!value) throw new Error(`${name} is required`)
17
+ return value
18
+ }
19
+
20
+ const singleLine = value => String(value).replace(/[\r\n]+/g, ' ').trim()
21
+
22
+ let reportPath = process.env.MODEL_EOL_REPORT || null
23
+ let exitCode = 1
24
+ const report = ['## Repository migration eval', '']
25
+
26
+ try {
27
+ const oldId = required('MODEL_EOL_OLD_ID')
28
+ const newId = required('MODEL_EOL_NEW_ID')
29
+ const planPath = required('MODEL_EOL_PLAN')
30
+ reportPath = required('MODEL_EOL_REPORT')
31
+ const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'))
32
+ if (plan.plan_schema !== 'model-eol.plan/0.1' || !Array.isArray(plan.items) || plan.items.length === 0) {
33
+ throw new Error('MODEL_EOL_PLAN is not a non-empty model-eol plan')
34
+ }
35
+ if (plan.items.some(item => item.id !== oldId || item.replacement !== newId)) {
36
+ throw new Error('MODEL_EOL_PLAN contains a migration outside MODEL_EOL_OLD_ID/MODEL_EOL_NEW_ID')
37
+ }
38
+
39
+ const files = [...new Set(plan.items.map(item => item.file))].sort()
40
+ report.push(
41
+ `- Migration: \`${singleLine(oldId)}\` to \`${singleLine(newId)}\``,
42
+ `- Planned references: ${plan.items.length} across ${files.length} file(s)`,
43
+ `- Verification: \`${VERIFY.command} ${VERIFY.args.join(' ')}\``,
44
+ )
45
+
46
+ const started = Date.now()
47
+ const result = spawnSync(VERIFY.command, VERIFY.args, {
48
+ cwd: process.cwd(),
49
+ env: process.env,
50
+ encoding: 'utf8',
51
+ stdio: ['ignore', 'pipe', 'pipe'],
52
+ maxBuffer: 8 * 1024 * 1024,
53
+ })
54
+ const elapsedMs = Date.now() - started
55
+ const passed = !result.error && result.status === 0
56
+ report.push(
57
+ `- Result: ${passed ? 'pass' : 'fail'}`,
58
+ `- Exit code: ${Number.isInteger(result.status) ? result.status : 'unavailable'}`,
59
+ `- Duration: ${elapsedMs} ms`,
60
+ )
61
+ if (result.signal) report.push(`- Signal: ${singleLine(result.signal)}`)
62
+ if (result.error) report.push(`- Runner error: ${singleLine(result.error.message)}`)
63
+ report.push('', passed
64
+ ? 'The repository verification command passed in the isolated patched checkout.'
65
+ : 'The repository verification command failed. Reproduce it locally for full logs; command output is intentionally not copied into the publishable report.')
66
+ exitCode = passed ? 0 : 1
67
+ } catch (error) {
68
+ report.push(`- Result: fail`, '', `Harness error: ${singleLine(error.message)}`)
69
+ exitCode = 1
70
+ } finally {
71
+ if (reportPath) {
72
+ try {
73
+ fs.mkdirSync(path.dirname(path.resolve(reportPath)), { recursive: true })
74
+ fs.writeFileSync(reportPath, `${report.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 })
75
+ } catch (error) {
76
+ console.error(`could not write MODEL_EOL_REPORT: ${error.message}`)
77
+ exitCode = 1
78
+ }
79
+ } else {
80
+ console.error('MODEL_EOL_REPORT is required')
81
+ }
82
+ }
83
+
84
+ process.exitCode = exitCode
@@ -0,0 +1,52 @@
1
+ # model-eol - copy this file to .github/workflows/model-eol.yml in YOUR repo.
2
+ #
3
+ # Two gates:
4
+ # - every PR: fail only if THIS change adds a model that is retired or
5
+ # retiring within the threshold (existing references stay out of the way)
6
+ # - weekly: full-repository check, so legacy references surface on a schedule
7
+ # instead of in an outage
8
+ #
9
+ # Judged by a distributor's clock instead? Add `via: aws-bedrock` (or
10
+ # azure-ai-foundry) to the step's `with:` block.
11
+
12
+ name: model-eol
13
+
14
+ on:
15
+ pull_request:
16
+ schedule:
17
+ - cron: '17 6 * * 1' # weekly, Monday 06:17 UTC
18
+
19
+ jobs:
20
+ pr-gate:
21
+ if: github.event_name == 'pull_request'
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+ with:
26
+ fetch-depth: 0 # --changed needs history to diff against the base ref
27
+ - uses: actions/setup-node@v7
28
+ with:
29
+ node-version: 22
30
+ package-manager-cache: false
31
+ - uses: thossullivan/model-eol@v0
32
+ with:
33
+ command: check
34
+ changed: origin/${{ github.base_ref }}
35
+ days: 90
36
+ scope: direct
37
+
38
+ weekly-full-check:
39
+ if: github.event_name == 'schedule'
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v7
43
+ - uses: actions/setup-node@v7
44
+ with:
45
+ node-version: 22
46
+ package-manager-cache: false
47
+ - uses: thossullivan/model-eol@v0
48
+ with:
49
+ command: alert
50
+ days: 90
51
+ scope: direct
52
+ format: github
package/feeds/amazon.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "spec": "model-eol/0.1",
3
3
  "publisher": "amazon",
4
- "generated": "2026-08-18T00:47:44.931Z",
4
+ "generated": "2026-08-18T12:06:03.442Z",
5
5
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html",
6
6
  "models": [
7
7
  {
@@ -14,6 +14,7 @@
14
14
  "via": "aws-bedrock",
15
15
  "announced": "2026-03-30",
16
16
  "shutdown": "2026-09-30",
17
+ "status": "legacy",
17
18
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
18
19
  }
19
20
  ]
@@ -28,6 +29,7 @@
28
29
  "via": "aws-bedrock",
29
30
  "announced": "2026-03-30",
30
31
  "shutdown": "2026-09-30",
32
+ "status": "legacy",
31
33
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
32
34
  }
33
35
  ]
@@ -42,6 +44,7 @@
42
44
  "via": "aws-bedrock",
43
45
  "announced": "2026-03-13",
44
46
  "shutdown": "2026-09-14",
47
+ "status": "legacy",
45
48
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
46
49
  }
47
50
  ]
@@ -56,6 +59,7 @@
56
59
  "via": "aws-bedrock",
57
60
  "announced": "2026-03-13",
58
61
  "shutdown": "2026-09-14",
62
+ "status": "legacy",
59
63
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
60
64
  }
61
65
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "spec": "model-eol/0.1",
3
3
  "publisher": "anthropic",
4
- "generated": "2026-08-18T00:47:44.931Z",
4
+ "generated": "2026-08-18T12:06:03.442Z",
5
5
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
6
6
  "policy": {
7
7
  "min_notice_days": 60,
@@ -45,6 +45,7 @@
45
45
  "via": "aws-bedrock",
46
46
  "announced": "2026-03-10",
47
47
  "shutdown": "2026-09-10",
48
+ "status": "extended-access",
48
49
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
49
50
  }
50
51
  ],
@@ -74,6 +75,7 @@
74
75
  "via": "aws-bedrock",
75
76
  "announced": "2026-04-14",
76
77
  "shutdown": "2026-10-14",
78
+ "status": "extended-access",
77
79
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
78
80
  }
79
81
  ],
@@ -103,6 +105,7 @@
103
105
  "via": "aws-bedrock",
104
106
  "announced": "2026-07-08",
105
107
  "shutdown": "2027-01-08",
108
+ "status": "extended-access",
106
109
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html"
107
110
  }
108
111
  ],