model-eol 0.5.2 → 0.6.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.
@@ -9,8 +9,9 @@ export const MAX_TIMEOUT_MS = 3600000
9
9
  const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f-\u009f]/
10
10
  const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/
11
11
  const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/
12
- const DATE_PRECISIONS = new Set(['exact', 'earliest'])
12
+ const DATE_PRECISIONS = new Set(['exact', 'earliest', 'tentative'])
13
13
  const DISTRIBUTION_STATUSES = new Set(['active', 'legacy', 'extended-access', 'retired'])
14
+ const RESERVED_DISTRIBUTION_VIAS = new Set(['publisher', 'publisher-fallback'])
14
15
 
15
16
  export const assertIsoDate = (value, label = 'date') => {
16
17
  if (typeof value !== 'string' || !DATE_PATTERN.test(value)) {
@@ -181,7 +182,13 @@ export const validateFeed = feed => {
181
182
  const shutdownValid = checkDate(model.shutdown, `${at}.shutdown`)
182
183
  if (announcedValid && shutdownValid) validDates.push({ at, announced: model.announced, shutdown: model.shutdown })
183
184
  if (model.date_precision !== undefined) {
184
- if (typeof model.date_precision !== 'string' || !DATE_PRECISIONS.has(model.date_precision)) fail(`${at}.date_precision`, 'must be exact or earliest')
185
+ if (typeof model.date_precision !== 'string' || !DATE_PRECISIONS.has(model.date_precision)) fail(`${at}.date_precision`, 'must be exact, earliest, or tentative')
186
+ }
187
+ if (model.date_precision === 'tentative' && model.announced !== undefined) {
188
+ fail(`${at}.announced`, 'must be absent when date_precision is tentative')
189
+ }
190
+ if (model.date_precision === 'tentative' && model.shutdown === undefined) {
191
+ fail(`${at}.shutdown`, 'is required when date_precision is tentative')
185
192
  }
186
193
  if (model.replacement !== undefined) checkIdentifier(model.replacement, `${at}.replacement`)
187
194
  if (model.replacement_options !== undefined) {
@@ -210,6 +217,9 @@ export const validateFeed = feed => {
210
217
  if (!checkObjectKeys(distribution, dAt, new Set(['via', 'announced', 'shutdown', 'date_precision', 'status', 'source']))) continue
211
218
  if (!checkString(distribution.via, `${dAt}.via`, MAX_IDENTIFIER_LENGTH) || !distribution.via) fail(`${dAt}.via`, 'must be a non-empty string')
212
219
  if (typeof distribution.via === 'string' && distribution.via.length > 0) {
220
+ if (RESERVED_DISTRIBUTION_VIAS.has(distribution.via.toLowerCase())) {
221
+ fail(`${dAt}.via`, 'must not use a reserved publisher clock name')
222
+ }
213
223
  const previous = seenDistributionVias.get(distribution.via)
214
224
  if (previous) fail(`${dAt}.via`, `duplicate distributor via "${distribution.via}" also used at ${previous}`)
215
225
  else seenDistributionVias.set(distribution.via, `${dAt}.via`)
@@ -217,8 +227,17 @@ export const validateFeed = feed => {
217
227
  const distributionAnnounced = checkDate(distribution.announced, `${dAt}.announced`)
218
228
  const distributionShutdown = checkDate(distribution.shutdown, `${dAt}.shutdown`)
219
229
  if (distributionAnnounced && distributionShutdown && distribution.shutdown < distribution.announced) fail(dAt, 'shutdown precedes announced')
220
- if (distribution.date_precision !== undefined && (typeof distribution.date_precision !== 'string' || !DATE_PRECISIONS.has(distribution.date_precision))) fail(`${dAt}.date_precision`, 'must be exact or earliest')
230
+ if (distribution.date_precision !== undefined && (typeof distribution.date_precision !== 'string' || !DATE_PRECISIONS.has(distribution.date_precision))) fail(`${dAt}.date_precision`, 'must be exact, earliest, or tentative')
221
231
  if (distribution.status !== undefined && (typeof distribution.status !== 'string' || !DISTRIBUTION_STATUSES.has(distribution.status))) fail(`${dAt}.status`, 'has an unsupported status')
232
+ if (distribution.date_precision === 'tentative' && distribution.announced !== undefined) {
233
+ fail(`${dAt}.announced`, 'must be absent when date_precision is tentative')
234
+ }
235
+ if (distribution.date_precision === 'tentative' && distribution.shutdown === undefined) {
236
+ fail(`${dAt}.shutdown`, 'is required when date_precision is tentative')
237
+ }
238
+ if (distribution.date_precision === 'tentative' && distribution.status === 'retired') {
239
+ fail(`${dAt}.status`, 'must not be retired when date_precision is tentative')
240
+ }
222
241
  checkUrl(distribution.source, `${dAt}.source`)
223
242
  if ((distribution.announced || distribution.shutdown) && !(distribution.source || model.source || feed.source)) fail(dAt, 'dated distribution needs a source')
224
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "model-eol",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "AI model deprecation feeds with a zero-dependency checker, inventory CLI, and migration bot.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,6 +54,13 @@
54
54
  },
55
55
  "default": { "models": [], "paths": [] }
56
56
  },
57
+ "waivers": {
58
+ "type": "array",
59
+ "maxItems": 500,
60
+ "items": { "$ref": "#/definitions/waiver" },
61
+ "default": [],
62
+ "description": "Owned, expiring exceptions that remain visible in reports while active."
63
+ },
57
64
  "overrides": {
58
65
  "type": "array",
59
66
  "items": { "$ref": "#/definitions/pathOverride" },
@@ -120,6 +127,7 @@
120
127
  "via": null,
121
128
  "feeds": { "allow_vendored_fallback": false },
122
129
  "ignore": { "models": [], "paths": [] },
130
+ "waivers": [],
123
131
  "overrides": [],
124
132
  "routes": [],
125
133
  "issues": { "enabled": true },
@@ -144,6 +152,24 @@
144
152
  "paths": { "$ref": "#/definitions/stringArray" }
145
153
  }
146
154
  },
155
+ "waiver": {
156
+ "type": "object",
157
+ "additionalProperties": false,
158
+ "required": ["model", "reason", "owner", "expires"],
159
+ "properties": {
160
+ "model": { "type": "string", "minLength": 1 },
161
+ "paths": { "$ref": "#/definitions/stringArray" },
162
+ "via": { "type": "string", "minLength": 1 },
163
+ "reason": { "type": "string", "minLength": 1, "maxLength": 500 },
164
+ "owner": { "type": "string", "minLength": 1, "maxLength": 200 },
165
+ "expires": { "type": "string", "format": "date" }
166
+ }
167
+ },
168
+ "waivers": {
169
+ "type": "array",
170
+ "maxItems": 500,
171
+ "items": { "$ref": "#/definitions/waiver" }
172
+ },
147
173
  "pathOverride": {
148
174
  "type": "object",
149
175
  "additionalProperties": false,
@@ -153,7 +179,8 @@
153
179
  "days": { "type": "integer", "minimum": 0 },
154
180
  "scope": { "enum": ["direct", "all"] },
155
181
  "via": { "type": ["string", "null"], "minLength": 1 },
156
- "ignore": { "$ref": "#/definitions/ignore" }
182
+ "ignore": { "$ref": "#/definitions/ignore" },
183
+ "waivers": { "$ref": "#/definitions/waivers" }
157
184
  }
158
185
  },
159
186
  "referenceRoute": {
@@ -32,7 +32,7 @@
32
32
  "confidence": { "$ref": "https://thossullivan.github.io/model-eol/schema/0.1/model-eol.inventory.schema.json#/definitions/confidence" },
33
33
  "status": { "$ref": "https://thossullivan.github.io/model-eol/schema/0.1/model-eol.inventory.schema.json#/definitions/status" },
34
34
  "shutdown": { "type": ["string", "null"], "format": "date" },
35
- "date_precision": { "enum": ["exact", "earliest", null] },
35
+ "date_precision": { "enum": ["exact", "earliest", "tentative", null] },
36
36
  "distribution_status": { "enum": ["active", "legacy", "extended-access", "retired", null] },
37
37
  "via": { "type": ["string", "null"], "minLength": 1 },
38
38
  "days": { "type": ["integer", "null"] },
@@ -47,6 +47,7 @@
47
47
  "threshold_days": { "type": "integer", "minimum": 0 },
48
48
  "effective_scope": { "enum": ["all", "direct"] },
49
49
  "requested_via": { "type": ["string", "null"], "minLength": 1 },
50
+ "waiver": { "$ref": "https://thossullivan.github.io/model-eol/schema/0.1/model-eol.inventory.schema.json#/definitions/waiver" },
50
51
  "policy_provenance": { "$ref": "https://thossullivan.github.io/model-eol/schema/0.1/model-eol.inventory.schema.json#/definitions/policyProvenance" },
51
52
  "mapped_from": { "type": "string", "minLength": 1 }
52
53
  },
@@ -60,7 +60,7 @@
60
60
  "evidence": { "type": "string", "minLength": 1 },
61
61
  "status": { "$ref": "#/definitions/status" },
62
62
  "shutdown": { "type": ["string", "null"], "format": "date" },
63
- "date_precision": { "enum": ["exact", "earliest", null] },
63
+ "date_precision": { "enum": ["exact", "earliest", "tentative", null] },
64
64
  "distribution_status": { "enum": ["active", "legacy", "extended-access", "retired", null] },
65
65
  "via": { "type": ["string", "null"], "minLength": 1 },
66
66
  "requested_via": { "type": ["string", "null"], "minLength": 1 },
@@ -77,6 +77,7 @@
77
77
  "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" }
78
78
  },
79
79
  "replacement_note": { "type": "string", "minLength": 1 },
80
+ "waiver": { "$ref": "#/definitions/waiver" },
80
81
  "source": { "type": ["string", "null"], "format": "uri" },
81
82
  "policy": {
82
83
  "type": ["object", "null"],
@@ -147,6 +148,17 @@
147
148
  "route_index": { "type": ["integer", "null"], "minimum": 0 }
148
149
  },
149
150
  "additionalProperties": false
151
+ },
152
+ "waiver": {
153
+ "type": ["object", "null"],
154
+ "required": ["reason", "owner", "expires", "active"],
155
+ "properties": {
156
+ "reason": { "type": "string", "minLength": 1, "maxLength": 500 },
157
+ "owner": { "type": "string", "minLength": 1, "maxLength": 200 },
158
+ "expires": { "type": "string", "format": "date" },
159
+ "active": { "type": "boolean" }
160
+ },
161
+ "additionalProperties": false
150
162
  }
151
163
  }
152
164
  }
@@ -36,6 +36,7 @@
36
36
  "effective_scope": { "enum": ["all", "direct"] },
37
37
  "policy_provenance": { "$ref": "#/definitions/policyProvenance" },
38
38
  "mapped_from": { "type": "string", "minLength": 1 },
39
+ "waiver": { "$ref": "#/definitions/waiver" },
39
40
  "sources": { "type": "array", "items": { "type": "string" } },
40
41
  "notes": { "type": ["string", "null"] }
41
42
  },
@@ -69,6 +70,7 @@
69
70
  },
70
71
  "replacement_note": { "type": "string" },
71
72
  "provider": { "type": "string" },
73
+ "waiver": { "$ref": "#/definitions/waiver" },
72
74
  "reason": { "type": "string", "minLength": 1 },
73
75
  "sources": { "type": "array", "items": { "type": "string" } },
74
76
  "notes": { "type": ["string", "null"] }
@@ -99,6 +101,17 @@
99
101
  "route_index": { "type": ["integer", "null"], "minimum": 0 }
100
102
  },
101
103
  "additionalProperties": false
104
+ },
105
+ "waiver": {
106
+ "type": ["object", "null"],
107
+ "required": ["reason", "owner", "expires", "active"],
108
+ "properties": {
109
+ "reason": { "type": "string", "minLength": 1, "maxLength": 500 },
110
+ "owner": { "type": "string", "minLength": 1, "maxLength": 200 },
111
+ "expires": { "type": "string", "format": "date" },
112
+ "active": { "type": "boolean" }
113
+ },
114
+ "additionalProperties": false
102
115
  }
103
116
  }
104
117
  }
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "announced": { "$ref": "#/definitions/isoDate" },
42
42
  "shutdown": { "$ref": "#/definitions/isoDate" },
43
- "date_precision": { "enum": ["exact", "earliest"] },
43
+ "date_precision": { "enum": ["exact", "earliest", "tentative"] },
44
44
  "replacement": { "type": "string", "maxLength": 256, "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
45
45
  "replacement_options": {
46
46
  "type": "array",
@@ -59,7 +59,7 @@
59
59
  "via": { "type": "string", "minLength": 1 },
60
60
  "announced": { "$ref": "#/definitions/isoDate" },
61
61
  "shutdown": { "$ref": "#/definitions/isoDate" },
62
- "date_precision": { "enum": ["exact", "earliest"] },
62
+ "date_precision": { "enum": ["exact", "earliest", "tentative"] },
63
63
  "status": { "enum": ["active", "legacy", "extended-access", "retired"] },
64
64
  "source": { "type": "string", "format": "uri" }
65
65
  },