@typedstandards/verify-core 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +7 -3
- package/dist/blob-ref.d.ts +9 -3
- package/dist/blob-ref.js +9 -3
- package/dist/signature.d.ts +11 -0
- package/dist/signature.js +11 -6
- package/dist/trust-registry.d.ts +2 -2
- package/dist/trust-registry.js +14 -11
- package/dist/types.d.ts +9 -4
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,58 @@ references are to `npstorey/civic-ai-tools-website` (#119 is the offline-crypto
|
|
|
6
6
|
hardening arc; #116 is the standalone-verifier arc this package was extracted
|
|
7
7
|
in).
|
|
8
8
|
|
|
9
|
+
## 0.8.1 — 2026-08-17
|
|
10
|
+
|
|
11
|
+
- Removed the `prepare` (install-time build) script (typedstandards#39).
|
|
12
|
+
A fresh `npm ci` at the monorepo root previously failed in workspace
|
|
13
|
+
`prepare` ordering (produce-core's tsc ran before this package's dist
|
|
14
|
+
existed), and npm 10.x runs workspace `prepare` even under
|
|
15
|
+
`--ignore-scripts`. Builds are now explicit (`npm run build:verify-core`,
|
|
16
|
+
then `npm run build`; CI does the same). Published tarballs are unchanged —
|
|
17
|
+
`prepublishOnly` still builds at publish — but installing this package from
|
|
18
|
+
git no longer auto-builds `dist`.
|
|
19
|
+
- Docs only, no behavior change: removed reference-deployment framing from
|
|
20
|
+
published JSDoc/README (typedstandards#44 P3, findings B10/B11). Four
|
|
21
|
+
sites — `verifyBlobRef`'s JSDoc, the exported `FetchLike` type's JSDoc, the
|
|
22
|
+
README's Fetch note, and `trust-registry.ts` — no longer treat
|
|
23
|
+
civicaitools.org as the rule's rationale or "the platform" as the sole
|
|
24
|
+
publisher. The plain-GET/no-custom-headers requirement now derives from
|
|
25
|
+
CORS behavior itself (a custom header triggers a preflight, rejectable at
|
|
26
|
+
a redirecting host), with the reference redirect kept as a named example;
|
|
27
|
+
`trust-registry.ts`'s header states its actual contract (pure, registry
|
|
28
|
+
passed in, loading is the caller's job) instead of pointing at an unshipped
|
|
29
|
+
server file; each publisher is understood to have its own registry, named
|
|
30
|
+
by `trustRegistryUrl`. No exported type, signature, or runtime behavior
|
|
31
|
+
changed — most consumer-visible via `FetchLike`'s JSDoc in the published
|
|
32
|
+
`.d.ts`.
|
|
33
|
+
- Docs correction, no behavior change: scopes the plain-GET/CORS claim above
|
|
34
|
+
by execution environment (typedstandards#44 P4). The unscoped version was
|
|
35
|
+
disproved during a live browser session: a cross-origin redirect response
|
|
36
|
+
must itself carry CORS headers, or a browser's fetch fails regardless of
|
|
37
|
+
how simple the triggering request was — a plain GET only avoids the
|
|
38
|
+
*preflight* rejection, it does not make a redirect chain transparent in a
|
|
39
|
+
browser. The claim had been verified only from Node/curl, where that
|
|
40
|
+
constraint does not exist, then stated unconditionally. Now scoped: a
|
|
41
|
+
plain GET avoids the preflight failure mode (kept, unchanged); a browser
|
|
42
|
+
additionally needs the redirect response itself to carry CORS headers
|
|
43
|
+
(the missing half, now stated); server-side/Node fetches follow redirects
|
|
44
|
+
without that constraint (why the unscoped claim read true when written).
|
|
45
|
+
Same three verify-core sites P3 touched for this class — `verifyBlobRef`'s
|
|
46
|
+
JSDoc (`blob-ref.ts`), the exported `FetchLike` type's JSDoc (`types.ts`),
|
|
47
|
+
and the README's Fetch note — plus the equivalent explanation and its
|
|
48
|
+
cross-reference in typedstandards.org's client-side verify flow
|
|
49
|
+
(`apps/web`, outside this published package). No exported type, signature,
|
|
50
|
+
or runtime behavior changed — consumer-visible via `FetchLike`'s JSDoc in
|
|
51
|
+
the published `.d.ts` and the README.
|
|
52
|
+
|
|
53
|
+
## 0.8.0 — 2026-08-01
|
|
54
|
+
|
|
55
|
+
- New export: `ED25519_SPKI_PREFIX` (`signature.ts`) — the fixed 12-byte
|
|
56
|
+
Ed25519 SPKI DER prefix this module asserts in `extractRawPublicKey`.
|
|
57
|
+
Previously duplicated locally by `@typedstandards/produce-core`'s
|
|
58
|
+
`derivePublicKeySpki`, which now imports it (typedstandards#36). No behavior
|
|
59
|
+
change; the bytes are identical.
|
|
60
|
+
|
|
9
61
|
## 0.7.0 — 2026-06-16
|
|
10
62
|
|
|
11
63
|
- #1 envelope integrity is now TRI-STATE. `VerifyResult` gains an
|
package/README.md
CHANGED
|
@@ -34,9 +34,13 @@ edge, and in Node.
|
|
|
34
34
|
injects its `fetch`, the browser injects `window.fetch`, tests inject a stub.
|
|
35
35
|
|
|
36
36
|
> **Fetch note.** A `FetchLike` should issue **plain GETs with no custom request
|
|
37
|
-
> headers**. A custom header
|
|
38
|
-
>
|
|
39
|
-
>
|
|
37
|
+
> headers**. A custom header makes the request non-simple and triggers a CORS
|
|
38
|
+
> preflight; a preflight `OPTIONS` against a host that redirects can be rejected
|
|
39
|
+
> there, so a plain GET avoids that failure mode — e.g. civicaitools.org's
|
|
40
|
+
> site-wide 307 to its canonical host. That's necessary but not sufficient in a
|
|
41
|
+
> browser, though: a cross-origin redirect response must itself carry CORS
|
|
42
|
+
> headers, or the fetch fails regardless of the request's simplicity.
|
|
43
|
+
> Server-side/Node fetches follow redirects without that constraint.
|
|
40
44
|
|
|
41
45
|
## Usage
|
|
42
46
|
|
package/dist/blob-ref.d.ts
CHANGED
|
@@ -43,9 +43,15 @@ export interface BlobFetchOptions {
|
|
|
43
43
|
* Fetch a blob and verify that its SHA-256 matches the reference hash and that
|
|
44
44
|
* its byte size matches the metadata. Returns a structured result; never throws.
|
|
45
45
|
*
|
|
46
|
-
* Fetches over HTTPS
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* Fetches over HTTPS with no auth and no custom request headers — evidence
|
|
47
|
+
* content is expected to be publicly readable, and a custom header would make
|
|
48
|
+
* the request non-simple, triggering a CORS preflight. A preflight `OPTIONS`
|
|
49
|
+
* against a host that redirects can be rejected there, so a plain GET avoids
|
|
50
|
+
* that failure mode — e.g. civicaitools.org's canonical-host 307. That's
|
|
51
|
+
* necessary but not sufficient in a browser, though: a cross-origin redirect
|
|
52
|
+
* response must itself carry CORS headers, or the fetch fails regardless of
|
|
53
|
+
* the request's simplicity. Server-side/Node fetches follow redirects
|
|
54
|
+
* without that constraint.
|
|
49
55
|
*/
|
|
50
56
|
export declare function verifyBlobRef(ref: BlobRef, options?: BlobFetchOptions): Promise<BlobRefVerifyResult>;
|
|
51
57
|
/**
|
package/dist/blob-ref.js
CHANGED
|
@@ -56,9 +56,15 @@ function resolveFetch(options) {
|
|
|
56
56
|
* Fetch a blob and verify that its SHA-256 matches the reference hash and that
|
|
57
57
|
* its byte size matches the metadata. Returns a structured result; never throws.
|
|
58
58
|
*
|
|
59
|
-
* Fetches over HTTPS
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Fetches over HTTPS with no auth and no custom request headers — evidence
|
|
60
|
+
* content is expected to be publicly readable, and a custom header would make
|
|
61
|
+
* the request non-simple, triggering a CORS preflight. A preflight `OPTIONS`
|
|
62
|
+
* against a host that redirects can be rejected there, so a plain GET avoids
|
|
63
|
+
* that failure mode — e.g. civicaitools.org's canonical-host 307. That's
|
|
64
|
+
* necessary but not sufficient in a browser, though: a cross-origin redirect
|
|
65
|
+
* response must itself carry CORS headers, or the fetch fails regardless of
|
|
66
|
+
* the request's simplicity. Server-side/Node fetches follow redirects
|
|
67
|
+
* without that constraint.
|
|
62
68
|
*/
|
|
63
69
|
export async function verifyBlobRef(ref, options = {}) {
|
|
64
70
|
let parsed;
|
package/dist/signature.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fixed 12-byte prefix of an Ed25519 public key in SPKI DER (SEQUENCE →
|
|
3
|
+
* AlgorithmIdentifier{ OID 1.3.101.112 } → BIT STRING, 0 unused bits); the
|
|
4
|
+
* full 44-byte structure is this prefix followed by the 32-byte raw key.
|
|
5
|
+
* Asserting the prefix + length before slicing the tail makes a malformed key
|
|
6
|
+
* throw rather than yield 32 arbitrary bytes — matching the old JWK path,
|
|
7
|
+
* which threw on a bad key. Exported (typedstandards#36) so producers
|
|
8
|
+
* assembling SPKI DER — `@typedstandards/produce-core`'s
|
|
9
|
+
* `derivePublicKeySpki` — build from the same bytes this module asserts.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ED25519_SPKI_PREFIX: Uint8Array<ArrayBuffer>;
|
|
1
12
|
/**
|
|
2
13
|
* Extract the raw 32-byte Ed25519 public key from a base64 SPKI DER encoding.
|
|
3
14
|
* Browser-safe replacement for Node's `crypto.createPublicKey(...).export({jwk})`:
|
package/dist/signature.js
CHANGED
|
@@ -8,12 +8,17 @@
|
|
|
8
8
|
// reused as-is, INCLUDING the #111 algorithm dispatch.
|
|
9
9
|
import { ed25519, ed25519ph } from '@noble/curves/ed25519.js';
|
|
10
10
|
import { base64ToBytes, sha512Hex, utf8ToBytes } from "./primitives.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The fixed 12-byte prefix of an Ed25519 public key in SPKI DER (SEQUENCE →
|
|
13
|
+
* AlgorithmIdentifier{ OID 1.3.101.112 } → BIT STRING, 0 unused bits); the
|
|
14
|
+
* full 44-byte structure is this prefix followed by the 32-byte raw key.
|
|
15
|
+
* Asserting the prefix + length before slicing the tail makes a malformed key
|
|
16
|
+
* throw rather than yield 32 arbitrary bytes — matching the old JWK path,
|
|
17
|
+
* which threw on a bad key. Exported (typedstandards#36) so producers
|
|
18
|
+
* assembling SPKI DER — `@typedstandards/produce-core`'s
|
|
19
|
+
* `derivePublicKeySpki` — build from the same bytes this module asserts.
|
|
20
|
+
*/
|
|
21
|
+
export const ED25519_SPKI_PREFIX = Uint8Array.from([
|
|
17
22
|
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
|
|
18
23
|
]);
|
|
19
24
|
const ED25519_SPKI_LENGTH = 44;
|
package/dist/trust-registry.d.ts
CHANGED
|
@@ -40,8 +40,8 @@ export interface KeyTrustResult {
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function legacyEmbeddedKeyTrust(): KeyTrustResult;
|
|
42
42
|
/**
|
|
43
|
-
* Verify that a `(kid, publicKey)` pair is trusted by the
|
|
44
|
-
*
|
|
43
|
+
* Verify that a `(kid, publicKey)` pair is trusted by the registry, applying
|
|
44
|
+
* the rotation semantics documented in the P5 plan:
|
|
45
45
|
* - `active` → trusted.
|
|
46
46
|
* - `deprecated` → trusted only when `packageIntegratedTime` precedes
|
|
47
47
|
* `deprecatedAt` (preventive rotation — pre-deprecation signatures remain
|
package/dist/trust-registry.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
// Trust registry types + key-trust evaluation (spec §8.3.3, §9.2 check #5) —
|
|
2
2
|
// browser-safe.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// registry
|
|
6
|
-
// `
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// Each publisher publishes its own authorized signing keys at a
|
|
5
|
+
// `/.well-known/*` registry, named by the package's declared
|
|
6
|
+
// `trustRegistryUrl`. Verification treats the resolved registry as the source
|
|
7
|
+
// of truth for which `(kid, publicKey)` pairs are allowed and their rotation
|
|
8
|
+
// state: keys absent or revoked fail verification regardless of cryptographic
|
|
9
|
+
// correctness — a locally valid signature from an unrecognised key is still
|
|
10
|
+
// not "trusted evidence".
|
|
9
11
|
//
|
|
10
|
-
// These functions are pure
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
12
|
+
// These functions are pure: the registry is passed IN as already-parsed data.
|
|
13
|
+
// Loading it — however a caller chooses to (a build-time embed, a filesystem
|
|
14
|
+
// read, a network fetch, or some mix) — is entirely the caller's job; this
|
|
15
|
+
// module has no opinion on how a registry was obtained, only on what it means
|
|
16
|
+
// once handed to `verifyKeyTrust`.
|
|
14
17
|
export const KEY_TRUST_STATUSES = [
|
|
15
18
|
'active', // active key — package is trusted
|
|
16
19
|
'deprecated_valid', // deprecated key, but package was signed before deprecation
|
|
@@ -30,8 +33,8 @@ export function legacyEmbeddedKeyTrust() {
|
|
|
30
33
|
return { status: 'legacy_embedded', verified: false };
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
* Verify that a `(kid, publicKey)` pair is trusted by the
|
|
34
|
-
*
|
|
36
|
+
* Verify that a `(kid, publicKey)` pair is trusted by the registry, applying
|
|
37
|
+
* the rotation semantics documented in the P5 plan:
|
|
35
38
|
* - `active` → trusted.
|
|
36
39
|
* - `deprecated` → trusted only when `packageIntegratedTime` precedes
|
|
37
40
|
* `deprecatedAt` (preventive rotation — pre-deprecation signatures remain
|
package/dist/types.d.ts
CHANGED
|
@@ -24,10 +24,15 @@ export type CaptureMethod = 'chat-flow-stream' | 'claude-code-jsonl-readback' |
|
|
|
24
24
|
* never reaches for `node:*` or a hardcoded global — see the ESLint
|
|
25
25
|
* `no-restricted-imports` guard scoped to this directory.
|
|
26
26
|
*
|
|
27
|
-
* Per the WS1 prod smoke test: the injected fetcher should issue plain GETs
|
|
28
|
-
* NO custom request headers
|
|
29
|
-
*
|
|
30
|
-
* host can be rejected
|
|
27
|
+
* Per the WS1 prod smoke test: the injected fetcher should issue plain GETs
|
|
28
|
+
* with NO custom request headers — a custom header makes the request
|
|
29
|
+
* non-simple and triggers a CORS preflight; a preflight `OPTIONS` against a
|
|
30
|
+
* host that redirects can be rejected there, so a plain GET avoids that
|
|
31
|
+
* failure mode — e.g. civicaitools.org's site-wide 307 to its canonical
|
|
32
|
+
* host. That's necessary but not sufficient in a browser, though: per the
|
|
33
|
+
* Fetch spec, a cross-origin redirect response must itself carry CORS
|
|
34
|
+
* headers, or the fetch fails regardless of the request's simplicity.
|
|
35
|
+
* Server-side/Node fetches follow redirects without that constraint.
|
|
31
36
|
*/
|
|
32
37
|
export type FetchLike = (input: string, init?: {
|
|
33
38
|
signal?: AbortSignal;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typedstandards/verify-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Portable, browser-safe verification core for Typed Standards evidence packages — the §9.2 check suite, single-sourced so it verifies identically on a server and in the browser.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nathan Storey",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
29
29
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
30
|
"test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
|
|
31
|
-
"prepare": "npm run build",
|
|
32
31
|
"prepublishOnly": "npm run clean && npm run build"
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|