@entelekheia/ref-id 0.1.2 → 0.3.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/dist/canonical.d.ts +17 -0
- package/dist/canonical.js +33 -0
- package/dist/canonical.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/spec/ref-id.json +367 -3
- package/spec/ref-id.json.sha256 +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ParseResult } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The canonical form of an identifier: the same identifier with its qualifiers sorted by key.
|
|
4
|
+
*
|
|
5
|
+
* Sorting is by UTF-16 code unit, the order this specification already uses for its own file, and it is
|
|
6
|
+
* the cheapest canonicalisation rather than the point. What is load-bearing is that one deterministic
|
|
7
|
+
* order exists, so any two implementations reach the same answer about whether two identifiers are one.
|
|
8
|
+
*
|
|
9
|
+
* Every other part is left exactly as parsed. **Refinements are not sorted** — they sit on the fragment
|
|
10
|
+
* side and are positional, `lines=1,20` being a range, so reordering them would change what is named.
|
|
11
|
+
*
|
|
12
|
+
* A malformed identifier has no canonical form and is refused, for the reason `serialise` gives: there is
|
|
13
|
+
* no faithful way to write back a string whose failing part was never decomposed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function canonical(identifier: string | ParseResult): string;
|
|
16
|
+
/** Whether two identifiers name one thing. Order of qualifiers does not distinguish; everything else does. */
|
|
17
|
+
export declare function sameIdentifier(a: string | ParseResult, b: string | ParseResult): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The form two identifiers are compared in. `;a=1;b=2` and `;b=2;a=1` name one thing, so identity is
|
|
4
|
+
// judged on this rather than on the bytes a producer happened to write.
|
|
5
|
+
import { parse } from "./parse.js";
|
|
6
|
+
import { serialise } from "./serialise.js";
|
|
7
|
+
import { loadSpec, status } from "./spec.js";
|
|
8
|
+
/**
|
|
9
|
+
* The canonical form of an identifier: the same identifier with its qualifiers sorted by key.
|
|
10
|
+
*
|
|
11
|
+
* Sorting is by UTF-16 code unit, the order this specification already uses for its own file, and it is
|
|
12
|
+
* the cheapest canonicalisation rather than the point. What is load-bearing is that one deterministic
|
|
13
|
+
* order exists, so any two implementations reach the same answer about whether two identifiers are one.
|
|
14
|
+
*
|
|
15
|
+
* Every other part is left exactly as parsed. **Refinements are not sorted** — they sit on the fragment
|
|
16
|
+
* side and are positional, `lines=1,20` being a range, so reordering them would change what is named.
|
|
17
|
+
*
|
|
18
|
+
* A malformed identifier has no canonical form and is refused, for the reason `serialise` gives: there is
|
|
19
|
+
* no faithful way to write back a string whose failing part was never decomposed.
|
|
20
|
+
*/
|
|
21
|
+
export function canonical(identifier) {
|
|
22
|
+
const spec = loadSpec();
|
|
23
|
+
const parsed = typeof identifier === "string" ? parse(identifier) : identifier;
|
|
24
|
+
if (parsed.status === status(spec, "malformed"))
|
|
25
|
+
return serialise(parsed); // throws, naming the failing part
|
|
26
|
+
const sorted = [...parsed.qualifiers].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
27
|
+
return serialise({ ...parsed, qualifiers: sorted });
|
|
28
|
+
}
|
|
29
|
+
/** Whether two identifiers name one thing. Order of qualifiers does not distinguish; everything else does. */
|
|
30
|
+
export function sameIdentifier(a, b) {
|
|
31
|
+
return canonical(a) === canonical(b);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qGAAqG;AACrG,wEAAwE;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAG5C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CAAC,UAAgC;IACxD,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,MAAM,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;IAC9E,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC;QAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA,CAAC,kCAAkC;IAC5G,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtF,OAAO,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAA;AACrD,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,cAAc,CAAC,CAAuB,EAAE,CAAuB;IAC7E,OAAO,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAA;AACtC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { build } from "./build.ts";
|
|
2
|
+
export { canonical, sameIdentifier } from "./canonical.ts";
|
|
2
3
|
export { digest } from "./digest.ts";
|
|
3
4
|
export { validateEnvelope } from "./envelope.ts";
|
|
4
5
|
export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
export { build } from "./build.js";
|
|
3
|
+
export { canonical, sameIdentifier } from "./canonical.js";
|
|
3
4
|
export { digest } from "./digest.js";
|
|
4
5
|
export { validateEnvelope } from "./envelope.js";
|
|
5
6
|
export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entelekheia/ref-id",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The ref: identifier scheme — a specification published as data with conformance vectors, and the package that consumes it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/spec/ref-id.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"specVersion": "1.
|
|
2
|
+
"specVersion": "1.3.0",
|
|
3
3
|
"scheme": "ref",
|
|
4
4
|
"grammar": {
|
|
5
5
|
"dialect": "ecmascript-2018",
|
|
@@ -128,6 +128,14 @@
|
|
|
128
128
|
"declaredBy": "the agent identifier grammar namespace/name[:version]: the namespace is a domain, a platform/user under a domain, a mailbox, or the literal unknown; the ~digest of the full form is carried by state= instead",
|
|
129
129
|
"corpus": "the agent bundle",
|
|
130
130
|
"reference": "dot-agent agent-id reference"
|
|
131
|
+
},
|
|
132
|
+
"ai-model": {
|
|
133
|
+
"locator": "served-model-id",
|
|
134
|
+
"validator": "declared-name",
|
|
135
|
+
"delegate": "verbatim",
|
|
136
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:@-]*(?:/[A-Za-z0-9][A-Za-z0-9._:@-]*)*$",
|
|
137
|
+
"declaredBy": "the id the model is served under — the exact string a caller sends to the serving process to select it, verbatim. Deliberately permissive: coercing a served name breaks the round-trip that makes it an identifier at all, so ':' (an Ollama tag), '/' (a hub-style namespace) and '@' (an LM Studio quantisation key, a revision pin) are all admitted, because a serving process accepts each of them today and rejecting one would be a defect rather than a safeguard. Every '/'-separated segment must open on an alphanumeric, which admits each of those and excludes '..', '//' and a trailing '/': the delegate is verbatim, so a consumer mapping a served id onto a path — a local weights cache — would otherwise inherit a traversal from a locator that parsed clean. Case is significant and there is no normalisation, which is a decision with no way back: two callers naming one model in two casings mint two identifiers, and adding a fold later would be a change to normalisation, which mints identifier version 2.",
|
|
138
|
+
"reference": "no published grammar names a hosted API model: Package URL registers artifact-registry namespaces only (huggingface, mlflow), CycloneDX carries the category as a component type and identifies with an ordinary purl, SPDX 3.0's AIPackage identifies by free-text name and delegates to packageUrl, and OpenTelemetry's gen_ai.request.model is explicitly free text — so this type is this specification's own, not a delegation."
|
|
131
139
|
}
|
|
132
140
|
},
|
|
133
141
|
"typeRegistry": "a type names the naming system that owns the locator grammar; an unregistered type parses to uncovered, never to malformed, and registering its validator here is what promotes it — the registry is this table",
|
|
@@ -190,7 +198,8 @@
|
|
|
190
198
|
"role": "population",
|
|
191
199
|
"forms": [
|
|
192
200
|
"ref",
|
|
193
|
-
"sha256"
|
|
201
|
+
"sha256",
|
|
202
|
+
"unknown"
|
|
194
203
|
]
|
|
195
204
|
},
|
|
196
205
|
"when": {
|
|
@@ -229,6 +238,10 @@
|
|
|
229
238
|
"nested": true,
|
|
230
239
|
"depth": 1,
|
|
231
240
|
"encoding": "nested"
|
|
241
|
+
},
|
|
242
|
+
"unknown": {
|
|
243
|
+
"pattern": "^unknown$",
|
|
244
|
+
"reference": "the declared level for a population nobody established. Deliberately not `none`, which `state` uses: a state cannot be empty, so `none` is unambiguous there, while a population over zero members is a real and different thing from a population nobody recorded — and an identifier must not let the two read alike. It is a floor, never a destination: a producer that can enumerate its population says so with `sha256`, which names which population rather than admitting there was one"
|
|
232
245
|
}
|
|
233
246
|
},
|
|
234
247
|
"refinements": {
|
|
@@ -288,6 +301,14 @@
|
|
|
288
301
|
"dedupe": false,
|
|
289
302
|
"output": "sha256:<64 lowercase hex>"
|
|
290
303
|
},
|
|
304
|
+
"identifierEquivalence": {
|
|
305
|
+
"qualifierOrder": "insignificant",
|
|
306
|
+
"refinementOrder": "significant",
|
|
307
|
+
"canonicalForm": "re-serialise the parsed identifier with its qualifiers sorted by key in UTF-16 code unit order; every other part verbatim, refinements included",
|
|
308
|
+
"comparison": "two identifiers name one thing when their canonical forms are equal byte for byte",
|
|
309
|
+
"reserialisation": "SHOULD preserve the order written; MUST NOT be relied on to tell two identifiers apart",
|
|
310
|
+
"note": "distinct from `canonicalisation`, which is the form of THIS FILE for its own digest"
|
|
311
|
+
},
|
|
291
312
|
"canonicalisation": {
|
|
292
313
|
"form": "json-sorted-keys-compact",
|
|
293
314
|
"rules": [
|
|
@@ -1127,6 +1148,316 @@
|
|
|
1127
1148
|
"status": "malformed",
|
|
1128
1149
|
"part": "locator"
|
|
1129
1150
|
}
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
"name": "ai-model served id is admitted: Qwen3-4B-Instruct-2507-4bit",
|
|
1154
|
+
"input": "ref:ai-model:Qwen3-4B-Instruct-2507-4bit",
|
|
1155
|
+
"expect": {
|
|
1156
|
+
"status": "ok",
|
|
1157
|
+
"version": 1,
|
|
1158
|
+
"qualifiers": [],
|
|
1159
|
+
"fragment": null,
|
|
1160
|
+
"type": "ai-model",
|
|
1161
|
+
"locator": "Qwen3-4B-Instruct-2507-4bit"
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
"name": "ai-model served id is admitted: gemma-4-26b-a4b-it-4bit",
|
|
1166
|
+
"input": "ref:ai-model:gemma-4-26b-a4b-it-4bit",
|
|
1167
|
+
"expect": {
|
|
1168
|
+
"status": "ok",
|
|
1169
|
+
"version": 1,
|
|
1170
|
+
"qualifiers": [],
|
|
1171
|
+
"fragment": null,
|
|
1172
|
+
"type": "ai-model",
|
|
1173
|
+
"locator": "gemma-4-26b-a4b-it-4bit"
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
"name": "ai-model served id is admitted: Qwen3.5-9B-OptiQ-4bit",
|
|
1178
|
+
"input": "ref:ai-model:Qwen3.5-9B-OptiQ-4bit",
|
|
1179
|
+
"expect": {
|
|
1180
|
+
"status": "ok",
|
|
1181
|
+
"version": 1,
|
|
1182
|
+
"qualifiers": [],
|
|
1183
|
+
"fragment": null,
|
|
1184
|
+
"type": "ai-model",
|
|
1185
|
+
"locator": "Qwen3.5-9B-OptiQ-4bit"
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
"name": "ai-model served id is admitted: gpt-oss-20b-MXFP4-Q8",
|
|
1190
|
+
"input": "ref:ai-model:gpt-oss-20b-MXFP4-Q8",
|
|
1191
|
+
"expect": {
|
|
1192
|
+
"status": "ok",
|
|
1193
|
+
"version": 1,
|
|
1194
|
+
"qualifiers": [],
|
|
1195
|
+
"fragment": null,
|
|
1196
|
+
"type": "ai-model",
|
|
1197
|
+
"locator": "gpt-oss-20b-MXFP4-Q8"
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
"name": "ai-model served id is admitted: Llama-3.2-1B-Instruct-4bit",
|
|
1202
|
+
"input": "ref:ai-model:Llama-3.2-1B-Instruct-4bit",
|
|
1203
|
+
"expect": {
|
|
1204
|
+
"status": "ok",
|
|
1205
|
+
"version": 1,
|
|
1206
|
+
"qualifiers": [],
|
|
1207
|
+
"fragment": null,
|
|
1208
|
+
"type": "ai-model",
|
|
1209
|
+
"locator": "Llama-3.2-1B-Instruct-4bit"
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
"name": "ai-model served id is admitted: Qwen3.5-27B-Claude-4.6-Opus-Distilled-MLX-4bit",
|
|
1214
|
+
"input": "ref:ai-model:Qwen3.5-27B-Claude-4.6-Opus-Distilled-MLX-4bit",
|
|
1215
|
+
"expect": {
|
|
1216
|
+
"status": "ok",
|
|
1217
|
+
"version": 1,
|
|
1218
|
+
"qualifiers": [],
|
|
1219
|
+
"fragment": null,
|
|
1220
|
+
"type": "ai-model",
|
|
1221
|
+
"locator": "Qwen3.5-27B-Claude-4.6-Opus-Distilled-MLX-4bit"
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
"name": "ai-model served id is admitted: SmolLM3-3B-4bit",
|
|
1226
|
+
"input": "ref:ai-model:SmolLM3-3B-4bit",
|
|
1227
|
+
"expect": {
|
|
1228
|
+
"status": "ok",
|
|
1229
|
+
"version": 1,
|
|
1230
|
+
"qualifiers": [],
|
|
1231
|
+
"fragment": null,
|
|
1232
|
+
"type": "ai-model",
|
|
1233
|
+
"locator": "SmolLM3-3B-4bit"
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
"name": "ai-model served id is admitted: T-mixed-gemma-4-12B-it-OptiQ-4bit",
|
|
1238
|
+
"input": "ref:ai-model:T-mixed-gemma-4-12B-it-OptiQ-4bit",
|
|
1239
|
+
"expect": {
|
|
1240
|
+
"status": "ok",
|
|
1241
|
+
"version": 1,
|
|
1242
|
+
"qualifiers": [],
|
|
1243
|
+
"fragment": null,
|
|
1244
|
+
"type": "ai-model",
|
|
1245
|
+
"locator": "T-mixed-gemma-4-12B-it-OptiQ-4bit"
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
"name": "ai-model served id is admitted: claude-opus-5",
|
|
1250
|
+
"input": "ref:ai-model:claude-opus-5",
|
|
1251
|
+
"expect": {
|
|
1252
|
+
"status": "ok",
|
|
1253
|
+
"version": 1,
|
|
1254
|
+
"qualifiers": [],
|
|
1255
|
+
"fragment": null,
|
|
1256
|
+
"type": "ai-model",
|
|
1257
|
+
"locator": "claude-opus-5"
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
"name": "ai-model served id is admitted: Ternary-Bonsai-27B-mlx-2bit",
|
|
1262
|
+
"input": "ref:ai-model:Ternary-Bonsai-27B-mlx-2bit",
|
|
1263
|
+
"expect": {
|
|
1264
|
+
"status": "ok",
|
|
1265
|
+
"version": 1,
|
|
1266
|
+
"qualifiers": [],
|
|
1267
|
+
"fragment": null,
|
|
1268
|
+
"type": "ai-model",
|
|
1269
|
+
"locator": "Ternary-Bonsai-27B-mlx-2bit"
|
|
1270
|
+
}
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
"name": "ai-model served id is admitted: gemini-2.5-flash",
|
|
1274
|
+
"input": "ref:ai-model:gemini-2.5-flash",
|
|
1275
|
+
"expect": {
|
|
1276
|
+
"status": "ok",
|
|
1277
|
+
"version": 1,
|
|
1278
|
+
"qualifiers": [],
|
|
1279
|
+
"fragment": null,
|
|
1280
|
+
"type": "ai-model",
|
|
1281
|
+
"locator": "gemini-2.5-flash"
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
"name": "ai-model served id is admitted: llama3:8b",
|
|
1286
|
+
"input": "ref:ai-model:llama3:8b",
|
|
1287
|
+
"expect": {
|
|
1288
|
+
"status": "ok",
|
|
1289
|
+
"version": 1,
|
|
1290
|
+
"qualifiers": [],
|
|
1291
|
+
"fragment": null,
|
|
1292
|
+
"type": "ai-model",
|
|
1293
|
+
"locator": "llama3:8b"
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
"name": "ai-model served id is admitted: gpt-4o-2024-08-06",
|
|
1298
|
+
"input": "ref:ai-model:gpt-4o-2024-08-06",
|
|
1299
|
+
"expect": {
|
|
1300
|
+
"status": "ok",
|
|
1301
|
+
"version": 1,
|
|
1302
|
+
"qualifiers": [],
|
|
1303
|
+
"fragment": null,
|
|
1304
|
+
"type": "ai-model",
|
|
1305
|
+
"locator": "gpt-4o-2024-08-06"
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
"name": "ai-model served id is admitted: mlx-community/Qwen3-1.7B-4bit",
|
|
1310
|
+
"input": "ref:ai-model:mlx-community/Qwen3-1.7B-4bit",
|
|
1311
|
+
"expect": {
|
|
1312
|
+
"status": "ok",
|
|
1313
|
+
"version": 1,
|
|
1314
|
+
"qualifiers": [],
|
|
1315
|
+
"fragment": null,
|
|
1316
|
+
"type": "ai-model",
|
|
1317
|
+
"locator": "mlx-community/Qwen3-1.7B-4bit"
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
"name": "an ai-model id cannot open on a separator",
|
|
1322
|
+
"input": "ref:ai-model:-Qwen3-4B",
|
|
1323
|
+
"expect": {
|
|
1324
|
+
"status": "malformed",
|
|
1325
|
+
"part": "locator"
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
"name": "unknown is a declared population level",
|
|
1330
|
+
"input": "ref:folder:acme-tools;over=unknown",
|
|
1331
|
+
"expect": {
|
|
1332
|
+
"status": "ok",
|
|
1333
|
+
"version": 1,
|
|
1334
|
+
"qualifiers": [
|
|
1335
|
+
[
|
|
1336
|
+
"over",
|
|
1337
|
+
"unknown"
|
|
1338
|
+
]
|
|
1339
|
+
],
|
|
1340
|
+
"fragment": null,
|
|
1341
|
+
"type": "folder",
|
|
1342
|
+
"locator": "acme-tools"
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
"name": "none is not a population level — a population of zero is not a population nobody recorded",
|
|
1347
|
+
"input": "ref:folder:acme-tools;over=none",
|
|
1348
|
+
"expect": {
|
|
1349
|
+
"status": "malformed",
|
|
1350
|
+
"part": "over"
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
"name": "unknown is not a state level — the fence from the other side",
|
|
1355
|
+
"input": "ref:folder:acme-tools;state=unknown",
|
|
1356
|
+
"expect": {
|
|
1357
|
+
"status": "malformed",
|
|
1358
|
+
"part": "state"
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
"name": "unknown is declared for over alone, never for by",
|
|
1363
|
+
"input": "ref:folder:acme-tools;by=unknown",
|
|
1364
|
+
"expect": {
|
|
1365
|
+
"status": "malformed",
|
|
1366
|
+
"part": "by"
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
"name": "an ai-model id cannot carry a character outside its declared class",
|
|
1371
|
+
"input": "ref:ai-model:a b",
|
|
1372
|
+
"expect": {
|
|
1373
|
+
"status": "malformed",
|
|
1374
|
+
"part": "locator"
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
"name": "an ai-model id carries a quantisation key after @",
|
|
1379
|
+
"input": "ref:ai-model:qwen2.5-7b-instruct@q4_k_m",
|
|
1380
|
+
"expect": {
|
|
1381
|
+
"status": "ok",
|
|
1382
|
+
"version": 1,
|
|
1383
|
+
"type": "ai-model",
|
|
1384
|
+
"locator": "qwen2.5-7b-instruct@q4_k_m",
|
|
1385
|
+
"qualifiers": [],
|
|
1386
|
+
"fragment": null
|
|
1387
|
+
}
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
"name": "an ai-model id cannot traverse — every segment opens on an alphanumeric",
|
|
1391
|
+
"input": "ref:ai-model:a/../../etc/passwd",
|
|
1392
|
+
"expect": {
|
|
1393
|
+
"status": "malformed",
|
|
1394
|
+
"part": "locator"
|
|
1395
|
+
}
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
"name": "an ai-model id cannot carry an empty segment",
|
|
1399
|
+
"input": "ref:ai-model:a//b",
|
|
1400
|
+
"expect": {
|
|
1401
|
+
"status": "malformed",
|
|
1402
|
+
"part": "locator"
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
],
|
|
1406
|
+
"canonical": [
|
|
1407
|
+
{
|
|
1408
|
+
"name": "one qualifier is already canonical",
|
|
1409
|
+
"input": "ref:eita:structured-output@1/validity;when=2026-08-02T18:31:31.870Z",
|
|
1410
|
+
"expect": "ref:eita:structured-output@1/validity;when=2026-08-02T18:31:31.870Z"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"name": "two qualifiers written out of order sort by key",
|
|
1414
|
+
"input": "ref:eita:structured-output@1/validity;when=2026-08-02T18:31:31.870Z;over=unknown",
|
|
1415
|
+
"expect": "ref:eita:structured-output@1/validity;over=unknown;when=2026-08-02T18:31:31.870Z"
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
"name": "the same qualifiers in the other order reach the same form",
|
|
1419
|
+
"input": "ref:eita:structured-output@1/validity;over=unknown;when=2026-08-02T18:31:31.870Z",
|
|
1420
|
+
"expect": "ref:eita:structured-output@1/validity;over=unknown;when=2026-08-02T18:31:31.870Z"
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
"name": "an unknown key sorts beside a declared one, carried through",
|
|
1424
|
+
"input": "ref:eita:citation-fidelity@1/exact;when=2026-08-14T23:38:26.089Z;context-tokens=8000;over=unknown",
|
|
1425
|
+
"expect": "ref:eita:citation-fidelity@1/exact;context-tokens=8000;over=unknown;when=2026-08-14T23:38:26.089Z"
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
"name": "sorting is by UTF-16 code unit, so a hyphen precedes a letter",
|
|
1429
|
+
"input": "ref:eita:t@1/o;alphaone=1;alpha-two=2;alpha=3",
|
|
1430
|
+
"expect": "ref:eita:t@1/o;alpha=3;alpha-two=2;alphaone=1"
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
"name": "a nested value is not decoded by canonicalisation",
|
|
1434
|
+
"input": "ref:eita:t@1/o;when=2026-08-02T18:31:31.870Z;over=ref:ai-model:Qwen3-4B-Instruct-2507-4bit",
|
|
1435
|
+
"expect": "ref:eita:t@1/o;over=ref:ai-model:Qwen3-4B-Instruct-2507-4bit;when=2026-08-02T18:31:31.870Z"
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
"name": "no qualifiers, nothing to sort",
|
|
1439
|
+
"input": "ref:pkg:npm/@acme/scanner-core@0.1.0#Observation",
|
|
1440
|
+
"expect": "ref:pkg:npm/@acme/scanner-core@0.1.0#Observation"
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
"name": "an explicit version survives canonicalisation verbatim",
|
|
1444
|
+
"input": "ref:1:pkg:npm/x@1.0.0;state=none;by=sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
1445
|
+
"expect": "ref:1:pkg:npm/x@1.0.0;by=sha256:0000000000000000000000000000000000000000000000000000000000000000;state=none"
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
"name": "refinements are positional and keep the order they were written",
|
|
1449
|
+
"input": "ref:folder:acme-governance;state=none#AGENTS.md;lines=1,20;item=3",
|
|
1450
|
+
"expect": "ref:folder:acme-governance;state=none#AGENTS.md;lines=1,20;item=3"
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
"name": "qualifiers sort while the fragment beside them is untouched",
|
|
1454
|
+
"input": "ref:folder:acme-governance;when=2026-08-02T18:31:31.870Z;state=none#AGENTS.md;lines=1,20",
|
|
1455
|
+
"expect": "ref:folder:acme-governance;state=none;when=2026-08-02T18:31:31.870Z#AGENTS.md;lines=1,20"
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
"name": "a malformed identifier has no canonical form",
|
|
1459
|
+
"input": "ref:eita:t@1/o;state=none;state=none",
|
|
1460
|
+
"expect": { "error": "state" }
|
|
1130
1461
|
}
|
|
1131
1462
|
],
|
|
1132
1463
|
"roundtrip": [
|
|
@@ -1149,7 +1480,10 @@
|
|
|
1149
1480
|
"ref:pkg:npm/acme-tools#acme/adr@2/0019",
|
|
1150
1481
|
"ref:folder:acme-tools;by=ref:2:pkg:npm/x@1.0.0",
|
|
1151
1482
|
"ref:doi:10.1000/182;state=swh:1:rev:7e29bb6000000000000000000000000000000000#S",
|
|
1152
|
-
"ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%2523src/y"
|
|
1483
|
+
"ref:folder:acme-tools;by=ref:pkg:npm/x@1.0.0%2523src/y",
|
|
1484
|
+
"ref:ai-model:mlx-community/Qwen3-1.7B-4bit",
|
|
1485
|
+
"ref:ai-model:llama3:8b",
|
|
1486
|
+
"ref:folder:acme-tools;over=unknown"
|
|
1153
1487
|
],
|
|
1154
1488
|
"build": [
|
|
1155
1489
|
{
|
|
@@ -1300,6 +1634,36 @@
|
|
|
1300
1634
|
]
|
|
1301
1635
|
},
|
|
1302
1636
|
"expect": "ref:folder:acme-tools;over=sha256:41b9caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
"name": "an ai-model locator with a hub-style separator builds as is",
|
|
1640
|
+
"parts": {
|
|
1641
|
+
"type": "ai-model",
|
|
1642
|
+
"locator": "mlx-community/Qwen3-1.7B-4bit"
|
|
1643
|
+
},
|
|
1644
|
+
"expect": "ref:ai-model:mlx-community/Qwen3-1.7B-4bit"
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
"name": "an ai-model locator with an Ollama tag builds as is",
|
|
1648
|
+
"parts": {
|
|
1649
|
+
"type": "ai-model",
|
|
1650
|
+
"locator": "llama3:8b"
|
|
1651
|
+
},
|
|
1652
|
+
"expect": "ref:ai-model:llama3:8b"
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
"name": "over=unknown builds as the declared level for an unestablished population",
|
|
1656
|
+
"parts": {
|
|
1657
|
+
"type": "folder",
|
|
1658
|
+
"locator": "acme-tools",
|
|
1659
|
+
"qualifiers": [
|
|
1660
|
+
[
|
|
1661
|
+
"over",
|
|
1662
|
+
"unknown"
|
|
1663
|
+
]
|
|
1664
|
+
]
|
|
1665
|
+
},
|
|
1666
|
+
"expect": "ref:folder:acme-tools;over=unknown"
|
|
1303
1667
|
}
|
|
1304
1668
|
],
|
|
1305
1669
|
"digest": [
|
package/spec/ref-id.json.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d10d07be0fbf02acef1717de1945ec4a2e2ceef954f80017051127a1c0fb3b0a
|