@entelekheia/ref-id 0.3.0 → 0.4.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/README.md CHANGED
@@ -7,7 +7,20 @@ The package embeds `spec/ref-id.json` and is held to it by that file's conforman
7
7
  ref:[<version>:]<type>:<locator>[;<qualifier>=<value>]*[#<declared-name-path>[;<refinement>=<value>]*]
8
8
  ```
9
9
 
10
- <!-- PROOF PLACEHOLDER: copy the smallest runnable example from test/ once the vectors pass. -->
10
+ ```ts
11
+ import { parse } from "@entelekheia/ref-id"
12
+
13
+ parse("ref:pkg:npm/@acme/scanner-core@0.1.0#Observation")
14
+ // => {
15
+ // status: "ok",
16
+ // type: "pkg",
17
+ // locator: "npm/@acme/scanner-core@0.1.0",
18
+ // fragment: { path: "Observation", refinements: [] },
19
+ // delegated: "pkg:npm/@acme/scanner-core@0.1.0",
20
+ // canonical: "pkg:npm/%40acme/scanner-core@0.1.0",
21
+ // ...
22
+ // }
23
+ ```
11
24
 
12
25
  ## Why
13
26
 
@@ -25,13 +38,66 @@ npm install @entelekheia/ref-id
25
38
 
26
39
  ## Usage
27
40
 
28
- The entry points are `parse`, `serialise`, `digest` and `validateEnvelope`; their contracts are the
29
- vectors in `spec/ref-id.json` (`parse`, `roundtrip`, `digest`, `envelope`). An unknown locator type parses
30
- and degrades to `uncovered`; it never throws.
41
+ The entry points are `parse`, `serialise`, `digest`, `validateEnvelope`, `samePackage` and `covers`; their
42
+ contracts are the vectors in `spec/ref-id.json` (`parse`, `roundtrip`, `digest`, `envelope`,
43
+ `comparison`). An unknown locator type parses and degrades to `uncovered`; it never throws.
44
+
45
+ Three questions get three answers, and they are not interchangeable:
46
+
47
+ ```ts
48
+ import { covers, sameIdentifier, samePackage } from "@entelekheia/ref-id"
49
+
50
+ sameIdentifier("pkg:npm/x@1.0.0", "pkg:npm/x@2.0.0") // false — two identifiers, compared byte for byte
51
+ samePackage("ref:pkg:npm/x@1.0.0", "ref:pkg:npm/x@2.0.0") // true — one released thing, two versions
52
+ covers("ref:pkg:npm/x", "ref:pkg:npm/x@1.0.0") // true — the general covers the specific
53
+ covers("ref:pkg:npm/x@1.0.0", "ref:pkg:npm/x") // false — and never the reverse
54
+ ```
55
+
56
+ `sameIdentifier` is what a digest and an envelope are built on, so it stays strict. `samePackage` is
57
+ symmetric and ignores the locator's version, and only where the type declares it carries one — a mailbox's
58
+ `@` and a served model's quantisation key are not versions. `covers` is **asymmetric**: what the first
59
+ identifier leaves undeclared, the second may declare freely; what the first declares, the second must
60
+ declare identically. That is what makes a partial identifier a query over a store keyed by identifier.
61
+
62
+ ## Environments
63
+
64
+ **Node.js 22 or later, and any browser reached through a bundler.** The package ships two builds of one
65
+ source, and `exports` picks between them: a bundler that honours the `browser` condition takes the browser
66
+ build, everything else takes the default. Nothing to configure, and the public API is the same either way.
67
+
68
+ | | Node | Browser |
69
+ |---|---|---|
70
+ | Where the specification comes from | `spec/ref-id.json`, read from disk on first use | a constant compiled into the build |
71
+ | When its integrity is established | at load — the bytes are hashed and compared with the sidecar, and a mismatch throws | at build — the generator refuses to emit from a specification that fails its sidecar, and a staleness check refuses a compiled constant that no longer matches the file |
72
+ | sha256 for `digest()` | Node's `crypto` | `@noble/hashes` (pure JavaScript, no dependencies) |
73
+
74
+ **What the browser build gives up**, stated rather than discovered:
75
+
76
+ - **No integrity check at runtime.** There is no file to compare the constant against, and hashing it
77
+ against a digest compiled from the same source in the same build would be a check that cannot fail —
78
+ which reads as a guarantee and is a tautology. The build exports `SPEC_DIGEST`, the digest
79
+ `spec/ref-id.json` carried when the constant was generated; it is a statement about provenance, not a
80
+ verification, and it is labelled as one.
81
+ - **No `loadSpecFrom`.** It takes a directory to read a specification and its sidecar from. A browser has
82
+ neither, so the browser build does not export it; every other export is present and identical.
83
+ **TypeScript will not warn you about that on the default configuration.** Under
84
+ `"moduleResolution": "bundler"` without `customConditions`, the compiler resolves types through the
85
+ default condition — which does declare `loadSpecFrom` — while the bundler picks the browser build at
86
+ build time. `tsc --noEmit` then passes on an import that fails when the bundle is produced. Add
87
+ `"customConditions": ["browser"]` to your `tsconfig.json` and the compiler sees the same surface your
88
+ bundler does.
89
+ - **`digest()` is sha256 only.** The algorithm is specification data, and the browser implementation
90
+ honours `sha256` alone: a specification naming another one throws a `SpecVersionError` naming it, where
91
+ Node would honour whatever its own `crypto` honours.
92
+ - **The compiled-in specification costs bundle size.** It is the whole published file, including the parts
93
+ a given consumer never reads.
31
94
 
32
- ## Requirements
95
+ The two builds are held to each other by `npm run test:differential`, which runs the browser build as a
96
+ fourth implementation beside Node, Rust and Swift over every input the specification names, and fails on
97
+ the first disagreement. `npm run build` additionally walks the emitted browser module graph and refuses it
98
+ if any Node builtin — or anything resolving a path against a module's own location — survives into it.
33
99
 
34
- Node.js 22 or later. Runtime dependency: `packageurl-js` (pure JavaScript). No native modules.
100
+ Runtime dependencies: `packageurl-js` and `@noble/hashes`, both pure JavaScript. No native modules.
35
101
 
36
102
  ## License
37
103
 
package/dist/digest.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // sha256 over the UTF-8 bytes of the joined identifier strings, per `spec.digest`: declared order,
4
4
  // no deduplication, and a refusal for a member that carries the join character — without it two
5
5
  // different sequences could share one digest, and the envelope invariant would admit both.
6
- import { createHash } from "node:crypto";
6
+ import { hashHex } from "./hash.js";
7
7
  import { DigestError } from "./errors.js";
8
8
  import { FIELD } from "./grammar.js";
9
9
  import { loadSpec, part } from "./spec.js";
@@ -20,7 +20,7 @@ export function digest(members) {
20
20
  }
21
21
  }
22
22
  const joined = snapshot.join(spec.digest.join);
23
- const hex = createHash(spec.digest.algorithm).update(joined, spec.digest.encoding).digest("hex");
23
+ const hex = hashHex(spec.digest.algorithm, spec.digest.encoding, joined);
24
24
  return `${spec.digest.algorithm}${FIELD}${hex}`;
25
25
  }
26
26
  //# sourceMappingURL=digest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,mGAAmG;AACnG,gGAAgG;AAChG,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE1C,2EAA2E;AAC3E,MAAM,UAAU,MAAM,CAAC,OAA0B;IAC/C,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,qCAAqC,CAAC,CAAA;IACpF,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACpC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,kEAAkE,CAAC,CAAA;QACjH,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,QAA0B,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAClH,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,KAAK,GAAG,GAAG,EAAE,CAAA;AACjD,CAAC"}
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,mGAAmG;AACnG,gGAAgG;AAChG,2FAA2F;AAE3F,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE1C,2EAA2E;AAC3E,MAAM,UAAU,MAAM,CAAC,OAA0B;IAC/C,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,qCAAqC,CAAC,CAAA;IACpF,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACpC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,kEAAkE,CAAC,CAAA;QACjH,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACxE,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,KAAK,GAAG,GAAG,EAAE,CAAA;AACjD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { HashHex } from "./hash.ts";
2
+ /**
3
+ * sha256 only, and it refuses the rest by name.
4
+ *
5
+ * Refusing rather than substituting is the same contract `status()` and `part()` hold: the code names
6
+ * what it can do, the spec owns the list, and a spec naming something this build cannot honour is a
7
+ * version mismatch reported as one — never a different answer computed quietly.
8
+ */
9
+ export declare const hashHexBrowser: HashHex;
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The browser hash: @noble/hashes, a pure-JavaScript sha256 with no dependencies and no native
4
+ // runtime. Installed by index.browser.ts; index.ts installs hash.node.ts instead and never imports
5
+ // this file, so neither implementation reaches the other's build.
6
+ import { sha256 } from "@noble/hashes/sha2.js";
7
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
8
+ import { SpecVersionError } from "./spec.js";
9
+ /** The encodings this implementation can read a member string as. Both spellings of UTF-8 are accepted. */
10
+ const ENCODINGS = new Set(["utf-8", "utf8"]);
11
+ /**
12
+ * sha256 only, and it refuses the rest by name.
13
+ *
14
+ * Refusing rather than substituting is the same contract `status()` and `part()` hold: the code names
15
+ * what it can do, the spec owns the list, and a spec naming something this build cannot honour is a
16
+ * version mismatch reported as one — never a different answer computed quietly.
17
+ */
18
+ export const hashHexBrowser = (algorithm, encoding, text) => {
19
+ if (algorithm !== "sha256") {
20
+ throw new SpecVersionError(`the browser build implements sha256 only; spec.digest.algorithm declares "${algorithm}"`);
21
+ }
22
+ if (!ENCODINGS.has(encoding)) {
23
+ throw new SpecVersionError(`the browser build reads members as UTF-8 only; spec.digest.encoding declares "${encoding}"`);
24
+ }
25
+ return bytesToHex(sha256(utf8ToBytes(text)));
26
+ };
27
+ //# sourceMappingURL=hash.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.browser.js","sourceRoot":"","sources":["../src/hash.browser.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,+FAA+F;AAC/F,mGAAmG;AACnG,kEAAkE;AAElE,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,2GAA2G;AAC3G,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACnE,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,gBAAgB,CACxB,6EAA6E,SAAS,GAAG,CAC1F,CAAA;IACH,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,gBAAgB,CACxB,iFAAiF,QAAQ,GAAG,CAC7F,CAAA;IACH,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAC9C,CAAC,CAAA"}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Hashes `text` under a spec-declared algorithm and encoding, returning lowercase hex. The algorithm
3
+ * and encoding are passed through from `spec.digest` rather than fixed here: the spec owns the choice,
4
+ * and an implementation that cannot honour it says so instead of substituting one it can.
5
+ */
6
+ export type HashHex = (algorithm: string, encoding: string, text: string) => string;
7
+ /** Installs the hash `digest()` uses. Called exactly once, by an entry point. */
8
+ export declare function installHash(hash: HashHex): void;
9
+ /** Hashes through the installed implementation. */
10
+ export declare function hashHex(algorithm: string, encoding: string, text: string): string;
package/dist/hash.js ADDED
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The seam through which the digest's hash function reaches this package, for the same reason
4
+ // spec.ts has one: `node:crypto` is not available in a browser, and `digest()` is synchronous.
5
+ //
6
+ // WHY NOT WebCrypto. `crypto.subtle.digest` is the only hash the web platform publishes and it is
7
+ // asynchronous, so using it would make `digest()` and `validateEnvelope()` return promises — a change
8
+ // to the public API, which Plan-004 puts out of scope, and one that would leave the two builds with
9
+ // different surfaces to compare. Blocking on it is not available either: `Atomics.wait` throws on a
10
+ // browser's main thread by specification. So the browser build carries a synchronous implementation
11
+ // instead (hash.browser.ts, @noble/hashes), and Node keeps `node:crypto` (hash.node.ts).
12
+ //
13
+ // WHERE THE TWO CAN DIVERGE, SAID OUT LOUD. The algorithm is spec data (`spec.digest.algorithm`), and
14
+ // the two implementations do not honour the same set: Node honours everything `node:crypto` does,
15
+ // the browser honours sha256 alone. A specification that moved to another algorithm would therefore
16
+ // keep working in Node and throw a SpecVersionError in the browser. That is disagreement discovered
17
+ // loudly, at the first call, naming the algorithm — the failure mode Plan-004 rejects is the silent
18
+ // one, where both builds answer and answer differently.
19
+ import { SpecVersionError } from "./spec.js";
20
+ let implementation;
21
+ /** Installs the hash `digest()` uses. Called exactly once, by an entry point. */
22
+ export function installHash(hash) {
23
+ implementation = hash;
24
+ }
25
+ /** Hashes through the installed implementation. */
26
+ export function hashHex(algorithm, encoding, text) {
27
+ if (!implementation) {
28
+ // Same unreachable-by-accident case as loadSpec(): `exports` publishes no deep path into this
29
+ // package, so arriving here means a module was imported directly rather than an entry point.
30
+ throw new SpecVersionError("no hash implementation is installed; import this package through its entry point rather than a module inside it");
31
+ }
32
+ return implementation(algorithm, encoding, text);
33
+ }
34
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,8FAA8F;AAC9F,+FAA+F;AAC/F,EAAE;AACF,kGAAkG;AAClG,sGAAsG;AACtG,oGAAoG;AACpG,oGAAoG;AACpG,oGAAoG;AACpG,yFAAyF;AACzF,EAAE;AACF,sGAAsG;AACtG,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,oGAAoG;AACpG,wDAAwD;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAS5C,IAAI,cAAmC,CAAA;AAEvC,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,cAAc,GAAG,IAAI,CAAA;AACvB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,OAAO,CAAC,SAAiB,EAAE,QAAgB,EAAE,IAAY;IACvE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,8FAA8F;QAC9F,6FAA6F;QAC7F,MAAM,IAAI,gBAAgB,CACxB,iHAAiH,CAClH,CAAA;IACH,CAAC;IACD,OAAO,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;AAClD,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { HashHex } from "./hash.ts";
2
+ /**
3
+ * Whatever `node:crypto` honours, honoured. No allow-list of algorithms is imposed here: this is the
4
+ * behaviour the package already had, and narrowing it would refuse specs Node can serve in order to
5
+ * match a limit that belongs to the other build.
6
+ */
7
+ export declare const hashHexNode: HashHex;
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The Node hash: `node:crypto`, exactly as digest.ts called it before the seam existed. Installed by
4
+ // index.ts; index.browser.ts installs hash.browser.ts instead and never imports this file.
5
+ import { createHash } from "node:crypto";
6
+ /**
7
+ * Whatever `node:crypto` honours, honoured. No allow-list of algorithms is imposed here: this is the
8
+ * behaviour the package already had, and narrowing it would refuse specs Node can serve in order to
9
+ * match a limit that belongs to the other build.
10
+ */
11
+ export const hashHexNode = (algorithm, encoding, text) => createHash(algorithm).update(text, encoding).digest("hex");
12
+ //# sourceMappingURL=hash.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.node.js","sourceRoot":"","sources":["../src/hash.node.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qGAAqG;AACrG,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAChE,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,QAA0B,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA"}
@@ -0,0 +1,11 @@
1
+ export { build } from "./build.ts";
2
+ export { canonical, sameIdentifier } from "./canonical.ts";
3
+ export { digest } from "./digest.ts";
4
+ export { validateEnvelope } from "./envelope.ts";
5
+ export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.ts";
6
+ export { parse } from "./parse.ts";
7
+ export { covers, samePackage } from "./relations.ts";
8
+ export { serialise } from "./serialise.ts";
9
+ export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError, type RefIdSpec } from "./spec.ts";
10
+ export { SPEC_DIGEST } from "./spec.browser.ts";
11
+ export type { BuildParts, EnvelopeResult, NestedQualifierValue, Pair, ParsedFragment, ParseResult, ParseStatus } from "./types.ts";
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The browser entry point (Plan-004, Track 2). Served through the `browser` condition of this
4
+ // package's `exports`, so a bundler picks it up and a consumer changes nothing.
5
+ //
6
+ // It differs from index.ts in exactly two lines — the spec source and the hash it installs — and in
7
+ // one export it does not carry (`loadSpecFrom`, which takes a directory to read from). What it buys
8
+ // is a module graph that reaches no Node builtin and nothing that resolves a path against the
9
+ // module's own location, which scripts/check-browser-purity.mjs proves over the built closure rather
10
+ // than by reading.
11
+ //
12
+ // THE TWO NAMES ARE SPELLED OUT NOWHERE IN THIS FILE, ON PURPOSE. Plan-004's success criteria grep
13
+ // this emitted module for the builtin prefix and for the meta-URL expression, and a comment carrying
14
+ // either literal turns that criterion red while describing their absence. The honest check is the
15
+ // closure walk, which reads code rather than prose; this file simply refuses to lie to the grep.
16
+ //
17
+ // The spec here is a constant that scripts/gen-spec.mjs compiled in, having first verified
18
+ // spec/ref-id.json against its sidecar. Nothing recomputes that digest at import time: it would hash
19
+ // a constant against a digest compiled from it in the same build, a check that cannot fail. The
20
+ // guarantee lives in the generator and in the staleness guard that regenerates and diffs
21
+ // (Plan-004's Decision Log).
22
+ import { installHash } from "./hash.js";
23
+ import { hashHexBrowser } from "./hash.browser.js";
24
+ import { installSpecSource } from "./spec.js";
25
+ import { SPEC } from "./spec.browser.js";
26
+ installSpecSource(() => SPEC);
27
+ installHash(hashHexBrowser);
28
+ export { build } from "./build.js";
29
+ export { canonical, sameIdentifier } from "./canonical.js";
30
+ export { digest } from "./digest.js";
31
+ export { validateEnvelope } from "./envelope.js";
32
+ export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.js";
33
+ export { parse } from "./parse.js";
34
+ export { covers, samePackage } from "./relations.js";
35
+ export { serialise } from "./serialise.js";
36
+ export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError } from "./spec.js";
37
+ // The digest spec/ref-id.json carried when this build's constant was generated. A statement about
38
+ // provenance, not a verification — spec.browser.ts says so at length, and re-exporting it here does
39
+ // not make it one.
40
+ export { SPEC_DIGEST } from "./spec.browser.js";
41
+ //# sourceMappingURL=index.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,8FAA8F;AAC9F,gFAAgF;AAChF,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,8FAA8F;AAC9F,qGAAqG;AACrG,mBAAmB;AACnB,EAAE;AACF,mGAAmG;AACnG,qGAAqG;AACrG,kGAAkG;AAClG,iGAAiG;AACjG,EAAE;AACF,2FAA2F;AAC3F,qGAAqG;AACrG,gGAAgG;AAChG,yFAAyF;AACzF,6BAA6B;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAExC,iBAAiB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;AAC7B,WAAW,CAAC,cAAc,CAAC,CAAA;AAE3B,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,MAAM,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAA;AACxG,kGAAkG;AAClG,oGAAoG;AACpG,mBAAmB;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ export { digest } from "./digest.ts";
4
4
  export { validateEnvelope } from "./envelope.ts";
5
5
  export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.ts";
6
6
  export { parse } from "./parse.ts";
7
+ export { covers, samePackage } from "./relations.ts";
7
8
  export { serialise } from "./serialise.ts";
8
- export { canonicalise, loadSpec, loadSpecFrom, SpecIntegrityError, SpecVersionError, type RefIdSpec } from "./spec.ts";
9
+ export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError, type RefIdSpec } from "./spec.ts";
10
+ export { loadSpecFrom } from "./spec.node.ts";
9
11
  export type { BuildParts, EnvelopeResult, NestedQualifierValue, Pair, ParsedFragment, ParseResult, ParseStatus } from "./types.ts";
package/dist/index.js CHANGED
@@ -1,10 +1,23 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The Node entry point. It differs from index.browser.ts in exactly two lines: which spec source and
4
+ // which hash it installs. Everything below is the same public surface, served by the same modules.
5
+ import { installHash } from "./hash.js";
6
+ import { hashHexNode } from "./hash.node.js";
7
+ import { installSpecSource } from "./spec.js";
8
+ import { loadSpecFrom, loadSpecFromDisk } from "./spec.node.js";
9
+ installSpecSource(loadSpecFromDisk);
10
+ installHash(hashHexNode);
2
11
  export { build } from "./build.js";
3
12
  export { canonical, sameIdentifier } from "./canonical.js";
4
13
  export { digest } from "./digest.js";
5
14
  export { validateEnvelope } from "./envelope.js";
6
15
  export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.js";
7
16
  export { parse } from "./parse.js";
17
+ export { covers, samePackage } from "./relations.js";
8
18
  export { serialise } from "./serialise.js";
9
- export { canonicalise, loadSpec, loadSpecFrom, SpecIntegrityError, SpecVersionError } from "./spec.js";
19
+ export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError } from "./spec.js";
20
+ // Disk-only, and therefore the one export the browser build does not carry: it takes a directory to
21
+ // read a spec + sidecar pair from, which is a thing a browser does not have.
22
+ export { loadSpecFrom } from "./spec.node.js";
10
23
  //# sourceMappingURL=index.js.map
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,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qGAAqG;AACrG,mGAAmG;AAEnG,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAE/D,iBAAiB,CAAC,gBAAgB,CAAC,CAAA;AACnC,WAAW,CAAC,WAAW,CAAC,CAAA;AAExB,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,MAAM,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAA;AACxG,oGAAoG;AACpG,6EAA6E;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,29 @@
1
+ import type { ParseResult } from "./types.ts";
2
+ /**
3
+ * Whether two identifiers name the same released thing, at whatever version each declares.
4
+ *
5
+ * Symmetric, and version-blind in exactly one place: the locator of a type that declares it carries a
6
+ * version. Everything else still distinguishes — the declared name, its refinements, and every
7
+ * qualifier — because two readings taken at different moments are two readings of one trait rather than
8
+ * one reading, and collapsing them is the defect `when` exists to prevent.
9
+ *
10
+ * A malformed identifier, and one at an identifier version this package does not implement, name nothing
11
+ * here and so are the same as nothing, including themselves.
12
+ */
13
+ export declare function samePackage(a: string | ParseResult, b: string | ParseResult): boolean;
14
+ /**
15
+ * Whether the first identifier is the second with less declared — the general covering the specific.
16
+ *
17
+ * **Asymmetric, and the direction is the whole point.** `pkg:npm/x` covers `pkg:npm/x@1.0.0`, and
18
+ * `pkg:npm/x@1.0.0` does not cover `pkg:npm/x`: a name that declares no version stands for every version
19
+ * of itself, while a name that declares one stands for that version alone. The same holds for the
20
+ * declared name, for each of its refinements, and for each qualifier — what the first leaves unsaid, the
21
+ * second may say freely; what the first says, the second must say identically.
22
+ *
23
+ * This is what makes a partial identifier a query. A store keyed by identifier answers "every reading of
24
+ * this trait" by asking which of its keys a trait-only identifier covers, with no query language at all.
25
+ *
26
+ * Every identifier this package vouches for covers itself; one it does not — malformed, or at an
27
+ * identifier version it does not implement — covers nothing, itself included.
28
+ */
29
+ export declare function covers(general: string | ParseResult, specific: string | ParseResult): boolean;
@@ -0,0 +1,135 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // The two questions equality cannot answer. `sameIdentifier` says whether two strings name one thing,
4
+ // which is what a digest and an envelope need and is deliberately strict. A store wants two other
5
+ // answers: whether two identifiers name the same released thing at different versions, and whether a
6
+ // partial identifier stands for a whole family of complete ones. Neither is equality, and neither can be
7
+ // expressed by relaxing it, because they disagree with each other on direction.
8
+ import { parse } from "./parse.js";
9
+ import { loadSpec, status } from "./spec.js";
10
+ /**
11
+ * The locator without its version, and the version it carried.
12
+ *
13
+ * **Whether a locator carries a version at all is the type's business, not this function's**, so it is
14
+ * read from `dispatch.<type>.versionTail` rather than guessed from the punctuation. An `@` means a
15
+ * released version in a Package URL and a host path; it separates a mailbox from its domain, keys a
16
+ * quantisation on a served model, and appears in neither a telephone number nor an article number.
17
+ * Guessing reported two different mailboxes as one thing.
18
+ *
19
+ * Within a type that does carry one, an `@` that opens a segment — preceded by `/`, or first — belongs
20
+ * to a namespace, so `npm/@acme/x` has no version. An `@` inside a segment closes the name: the version
21
+ * runs from it to the next `/`, and whatever follows that `/` is a path inside the named thing.
22
+ *
23
+ * **The path stays in the stem, and only the version leaves it.** The stem answers *which thing*, and a
24
+ * file at two releases is one file — so `npm/x@1.0.0/docs/guide.md` and `npm/x@2.0.0/docs/guide.md`
25
+ * share the stem `npm/x/docs/guide.md` and are the same package, while two files in one release do not.
26
+ */
27
+ function split(spec, type, locator) {
28
+ if (!spec.dispatch[type]?.versionTail)
29
+ return { stem: locator };
30
+ for (let index = 1; index < locator.length; index += 1) {
31
+ if (locator[index] !== "@" || locator[index - 1] === "/")
32
+ continue;
33
+ const slash = locator.indexOf("/", index);
34
+ if (slash < 0)
35
+ return { stem: locator.slice(0, index), version: locator.slice(index + 1) };
36
+ return { stem: locator.slice(0, index) + locator.slice(slash), version: locator.slice(index + 1, slash) };
37
+ }
38
+ return { stem: locator };
39
+ }
40
+ /**
41
+ * Whether the general identifier's stem reaches the specific one's.
42
+ *
43
+ * Equal stems name one thing. Otherwise the general one covers the specific when its stem is a whole
44
+ * **segment** prefix of it — `acme-tools` reaching `acme-tools/docs/guide.md`. The segment boundary is
45
+ * the whole of the rule: a bare string prefix would make `acme-tools` cover `acme-tools-extra`, two
46
+ * corpora that share nothing but their first characters.
47
+ */
48
+ function stemReaches(general, specific) {
49
+ return general === specific || specific.startsWith(`${general}/`);
50
+ }
51
+ /**
52
+ * The identifier, when this package vouches for how it was decomposed.
53
+ *
54
+ * `ok` and `uncovered` both carry a decomposition this grammar produced — an uncovered type parses, and
55
+ * only its locator's own grammar is unknown. `malformed` has no decomposition to compare, and
56
+ * `unsupported` has one produced by the wrong grammar: a later identifier version is minted for a change
57
+ * to normalisation, percent-encoding, separators or shape, so its parts read through this version's
58
+ * expression are not reliably its parts.
59
+ */
60
+ function read(identifier) {
61
+ const spec = loadSpec();
62
+ const parsed = typeof identifier === "string" ? parse(identifier) : identifier;
63
+ const usable = [status(spec, "ok"), status(spec, "uncovered")];
64
+ return usable.includes(parsed.status) ? parsed : undefined;
65
+ }
66
+ /** Whether every pair the first declares appears identically in the second. */
67
+ function subsumes(theirs, mine) {
68
+ const map = new Map(theirs);
69
+ return mine.every(([key, value]) => map.get(key) === value);
70
+ }
71
+ /** Whether two pair lists hold the same pairs, order aside. */
72
+ function equal(a, b) {
73
+ return a.length === b.length && subsumes(b, a);
74
+ }
75
+ /**
76
+ * Whether two identifiers name the same released thing, at whatever version each declares.
77
+ *
78
+ * Symmetric, and version-blind in exactly one place: the locator of a type that declares it carries a
79
+ * version. Everything else still distinguishes — the declared name, its refinements, and every
80
+ * qualifier — because two readings taken at different moments are two readings of one trait rather than
81
+ * one reading, and collapsing them is the defect `when` exists to prevent.
82
+ *
83
+ * A malformed identifier, and one at an identifier version this package does not implement, name nothing
84
+ * here and so are the same as nothing, including themselves.
85
+ */
86
+ export function samePackage(a, b) {
87
+ const spec = loadSpec();
88
+ const [x, y] = [read(a), read(b)];
89
+ if (!x || !y)
90
+ return false;
91
+ if (x.type !== y.type || x.version !== y.version)
92
+ return false;
93
+ if (split(spec, x.type, x.locator).stem !== split(spec, y.type, y.locator).stem)
94
+ return false;
95
+ if ((x.fragment?.path ?? null) !== (y.fragment?.path ?? null))
96
+ return false;
97
+ if (!equal(x.fragment?.refinements ?? [], y.fragment?.refinements ?? []))
98
+ return false;
99
+ return equal(x.qualifiers, y.qualifiers);
100
+ }
101
+ /**
102
+ * Whether the first identifier is the second with less declared — the general covering the specific.
103
+ *
104
+ * **Asymmetric, and the direction is the whole point.** `pkg:npm/x` covers `pkg:npm/x@1.0.0`, and
105
+ * `pkg:npm/x@1.0.0` does not cover `pkg:npm/x`: a name that declares no version stands for every version
106
+ * of itself, while a name that declares one stands for that version alone. The same holds for the
107
+ * declared name, for each of its refinements, and for each qualifier — what the first leaves unsaid, the
108
+ * second may say freely; what the first says, the second must say identically.
109
+ *
110
+ * This is what makes a partial identifier a query. A store keyed by identifier answers "every reading of
111
+ * this trait" by asking which of its keys a trait-only identifier covers, with no query language at all.
112
+ *
113
+ * Every identifier this package vouches for covers itself; one it does not — malformed, or at an
114
+ * identifier version it does not implement — covers nothing, itself included.
115
+ */
116
+ export function covers(general, specific) {
117
+ const spec = loadSpec();
118
+ const [x, y] = [read(general), read(specific)];
119
+ if (!x || !y)
120
+ return false;
121
+ if (x.type !== y.type || x.version !== y.version)
122
+ return false;
123
+ const [gen, spe] = [split(spec, x.type, x.locator), split(spec, y.type, y.locator)];
124
+ if (!stemReaches(gen.stem, spe.stem))
125
+ return false;
126
+ if (gen.version !== undefined && gen.version !== spe.version)
127
+ return false;
128
+ const path = x.fragment?.path;
129
+ if (path !== undefined && path !== y.fragment?.path)
130
+ return false;
131
+ if (!subsumes(y.fragment?.refinements ?? [], x.fragment?.refinements ?? []))
132
+ return false;
133
+ return subsumes(y.qualifiers, x.qualifiers);
134
+ }
135
+ //# sourceMappingURL=relations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.js","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { RefIdSpec } from "./spec.ts";
2
+ /**
3
+ * The sidecar digest `spec/ref-id.json.sha256` carried at the moment this file was generated.
4
+ *
5
+ * This is a STATEMENT about provenance, not a verification: nothing in this module recomputes it,
6
+ * and there is no live spec/ref-id.json here to recompute it against. The guarantee that this digest
7
+ * is the one that was actually verified belongs to the generator (scripts/gen-spec.mjs, which refuses
8
+ * to emit from a spec that fails its sidecar) and to the staleness guard that regenerates and diffs
9
+ * this file in CI. Reading this field as a check performed at import time would be a mistake.
10
+ */
11
+ export declare const SPEC_DIGEST: string;
12
+ export declare const SPEC: RefIdSpec;