@typedstandards/verify-core 0.15.0 → 0.15.2

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 CHANGED
@@ -6,6 +6,40 @@ 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.15.2 — 2026-11-07
10
+
11
+ The S2-A patch (anchor npstorey/civic-ai-tools#309). **A patch**: no export, signature or type
12
+ changes.
13
+
14
+ - **The `ai-assisted-analysis/civicaitools-v1` row pins revision 3 of its guidance bundle**
15
+ (2026-11-07), as hub ADR-0038's amendment of 2026-11-07 records:
16
+ `https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.15-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json`,
17
+ SHA-256 `db8175703601a5de9d785a2cda93db194eb05cdeeb222dcfd509ea8ab249f016`. The row's key and
18
+ `admits` are unchanged. The local copy (not published) is byte-identical to the hub file at
19
+ tree `d958f067`; its provenance is in `src/__fixtures__/README.md`.
20
+
21
+ ## 0.15.1 — 2026-11-02
22
+
23
+ The R11 patch (npstorey/civic-ai-tools#293; anchor npstorey/civic-ai-tools#294). **A patch**: no
24
+ export, signature or type changes.
25
+
26
+ - **Check #8's inclusion verdict requires the log entry to record this record's hash.**
27
+ `rekorInclusion.inclusionVerified` and `checkpointVerified` are true only when the entry body's
28
+ `spec.data.hash` is `sha512` with the value `rekorHashForPackage(packageHash)`. That holds for an
29
+ entry body and proof the input carries (`verifyRecord`) and for an entry `verifyRekorEntry`
30
+ fetches (its `inclusion`). An entry that does not record the hash, or an empty `packageHash`,
31
+ reads both false, with the proof's `treeSize` and `leafIndex` and no `reason`.
32
+ `verifyRekorInclusion` is unchanged: it checks inclusion alone.
33
+ - **Every genuine pairing reads as before.** Over the package objects and the carried log
34
+ inclusions in this repository's fixtures, and two host-served commitment views, `verifyRecord`
35
+ gives output deep-equal to 0.15.0's, with the entry carried, fetched, or both.
36
+ - **The `ai-assisted-analysis/civicaitools-v1` row pins revision 2 of its guidance bundle**
37
+ (2026-11-02), as hub ADR-0038's amendment of 2026-11-02 records:
38
+ `https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.14-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json`,
39
+ SHA-256 `45bf3ab479325a791829ed9987f5f291fea44238b54bfb598f32f306f574eb9b`. The row's key and
40
+ `admits` are unchanged. The local copy (not published) is byte-identical to the hub file at
41
+ tree `bcd06c5f`; its provenance is in `src/__fixtures__/README.md`.
42
+
9
43
  ## 0.15.0 — 2026-10-30
10
44
 
11
45
  The R10 release (civic-ai-tools#289; hub ADR-0038 §3, §5 and §6, specification v0.1.13 §8.1.1,
@@ -33,9 +33,9 @@ export interface ProducerProfileSubtypeRow {
33
33
  }
34
34
  /**
35
35
  * The subtype table, keyed by the full `producerProfile` value. The
36
- * `ai-assisted-analysis/civicaitools-v1` bundle is revision 1 (2026-10-30), as
37
- * hub ADR-0038 §5 records its address and digest; a later revision moves the
38
- * digest and this pin, and nothing else.
36
+ * `ai-assisted-analysis/civicaitools-v1` bundle is revision 3 (2026-11-07), as
37
+ * hub ADR-0038's amendment of 2026-11-07 records its address and digest; a later
38
+ * revision moves the digest and this pin, and nothing else.
39
39
  */
40
40
  export declare const PRODUCER_PROFILE_SUBTYPES: Readonly<Record<string, ProducerProfileSubtypeRow>>;
41
41
  /**
package/dist/profiles.js CHANGED
@@ -45,9 +45,9 @@ export function captureVocabForProfile(producerProfile, contentProfile) {
45
45
  }
46
46
  /**
47
47
  * The subtype table, keyed by the full `producerProfile` value. The
48
- * `ai-assisted-analysis/civicaitools-v1` bundle is revision 1 (2026-10-30), as
49
- * hub ADR-0038 §5 records its address and digest; a later revision moves the
50
- * digest and this pin, and nothing else.
48
+ * `ai-assisted-analysis/civicaitools-v1` bundle is revision 3 (2026-11-07), as
49
+ * hub ADR-0038's amendment of 2026-11-07 records its address and digest; a later
50
+ * revision moves the digest and this pin, and nothing else.
51
51
  */
52
52
  export const PRODUCER_PROFILE_SUBTYPES = Object.freeze({
53
53
  // No bundle pinned: the integration partner's, not this table's to name.
@@ -55,8 +55,8 @@ export const PRODUCER_PROFILE_SUBTYPES = Object.freeze({
55
55
  'ai-assisted-analysis/civicaitools-v1': Object.freeze({
56
56
  admits: Object.freeze(['datHere', 'default']),
57
57
  bundle: Object.freeze({
58
- url: 'https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.13-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json',
59
- sha256: '724e399e02db8993041b64982eec731505084bec2ec5e6ca58fda2de8087a281',
58
+ url: 'https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.15-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json',
59
+ sha256: 'db8175703601a5de9d785a2cda93db194eb05cdeeb222dcfd509ea8ab249f016',
60
60
  }),
61
61
  }),
62
62
  });
@@ -0,0 +1,12 @@
1
+ import { type RekorInclusionProof, type RekorInclusionResult } from './rekor-inclusion.ts';
2
+ /** Whether a log entry body records `packageHash`: `spec.data.hash` is `sha512`
3
+ * with the value `rekorHashForPackage(packageHash)`. An empty hash, or a body that
4
+ * does not decode, records nothing. */
5
+ export declare function entryRecordsPackage(entryBody: string | Uint8Array, packageHash: string): boolean;
6
+ /**
7
+ * Check 8's inclusion verdict for a record. An entry that does not record
8
+ * `packageHash` reads `inclusionVerified: false` and `checkpointVerified: false`,
9
+ * with the proof's `treeSize` and `leafIndex` and no `reason` (the proof is not
10
+ * evaluated). Otherwise the verdict is `verifyRekorInclusion`'s.
11
+ */
12
+ export declare function verifyRekorInclusionForRecord(entryBody: string | Uint8Array, inclusionProof: RekorInclusionProof, packageHash: string): RekorInclusionResult;
@@ -0,0 +1,48 @@
1
+ // Check 8's inclusion verdict, bound to the record (spec §9.2 check #8) —
2
+ // browser-safe, offline.
3
+ //
4
+ // `verifyRekorInclusion` says whether a log entry is in a log the pinned key
5
+ // vouches for. A verdict about a record also needs the entry to be that record's:
6
+ // its body's `spec.data.hash` is `sha512` with the value
7
+ // `rekorHashForPackage(packageHash)`, the comparison `verifyRekorEntry` makes for
8
+ // hash parity. Both sources of a verdict read it here: the entry body and proof an
9
+ // input carries (`verifyRecord`) and an entry `verifyRekorEntry` fetches.
10
+ //
11
+ // Internal: `index.ts` does not re-export this module.
12
+ import { base64ToBytes } from "./primitives.js";
13
+ import { rekorHashForPackage } from "./signature.js";
14
+ import { verifyRekorInclusion, } from "./rekor-inclusion.js";
15
+ /** Whether a log entry body records `packageHash`: `spec.data.hash` is `sha512`
16
+ * with the value `rekorHashForPackage(packageHash)`. An empty hash, or a body that
17
+ * does not decode, records nothing. */
18
+ export function entryRecordsPackage(entryBody, packageHash) {
19
+ if (!packageHash)
20
+ return false;
21
+ try {
22
+ const bytes = typeof entryBody === 'string' ? base64ToBytes(entryBody) : entryBody;
23
+ const body = JSON.parse(new TextDecoder().decode(bytes));
24
+ const hash = body
25
+ ?.spec?.data?.hash;
26
+ return hash?.algorithm === 'sha512' && hash.value === rekorHashForPackage(packageHash);
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Check 8's inclusion verdict for a record. An entry that does not record
34
+ * `packageHash` reads `inclusionVerified: false` and `checkpointVerified: false`,
35
+ * with the proof's `treeSize` and `leafIndex` and no `reason` (the proof is not
36
+ * evaluated). Otherwise the verdict is `verifyRekorInclusion`'s.
37
+ */
38
+ export function verifyRekorInclusionForRecord(entryBody, inclusionProof, packageHash) {
39
+ if (!entryRecordsPackage(entryBody, packageHash)) {
40
+ return {
41
+ inclusionVerified: false,
42
+ checkpointVerified: false,
43
+ treeSize: inclusionProof.treeSize,
44
+ leafIndex: inclusionProof.logIndex,
45
+ };
46
+ }
47
+ return verifyRekorInclusion(entryBody, inclusionProof);
48
+ }
@@ -53,7 +53,9 @@ export interface RekorInclusionResult {
53
53
  origin?: string;
54
54
  /** The matched anchor's key hint (hex), for display / provenance. */
55
55
  keyHint?: string;
56
- /** The first thing that failed, when not fully verified. */
56
+ /** The first thing that failed, when not fully verified. Absent when
57
+ * `verifyRecord` or `verifyRekorEntry` found that the entry does not record the
58
+ * package's hash: the proof is then not evaluated. */
57
59
  reason?: RekorInclusionFailReason;
58
60
  }
59
61
  /**
@@ -103,7 +105,9 @@ export declare function verifyCheckpointSignature(checkpoint: ParsedCheckpoint,
103
105
  * is the carried `verification.inclusionProof`. Returns a graded verdict:
104
106
  * `inclusionVerified` (the proof folds to its root) and `checkpointVerified` (that
105
107
  * root is in a checkpoint a pinned log key signed). The strong property — "this
106
- * entry is in a log the sigstore key vouches for" — is both being true.
108
+ * entry is in a log the sigstore key vouches for" — is both being true. This
109
+ * export checks inclusion alone; `verifyRecord` (`verifyEvidence`) applies the
110
+ * record binding.
107
111
  */
108
112
  export declare function verifyRekorInclusion(entryBody: string | Uint8Array, inclusionProof: RekorInclusionProof, anchors?: readonly RekorLogAnchor[]): RekorInclusionResult;
109
113
  export {};
@@ -239,7 +239,9 @@ export function verifyCheckpointSignature(checkpoint, anchors) {
239
239
  * is the carried `verification.inclusionProof`. Returns a graded verdict:
240
240
  * `inclusionVerified` (the proof folds to its root) and `checkpointVerified` (that
241
241
  * root is in a checkpoint a pinned log key signed). The strong property — "this
242
- * entry is in a log the sigstore key vouches for" — is both being true.
242
+ * entry is in a log the sigstore key vouches for" — is both being true. This
243
+ * export checks inclusion alone; `verifyRecord` (`verifyEvidence`) applies the
244
+ * record binding.
243
245
  */
244
246
  export function verifyRekorInclusion(entryBody, inclusionProof, anchors = REKOR_LOG_ANCHORS) {
245
247
  const base = {
package/dist/rekor.d.ts CHANGED
@@ -7,7 +7,8 @@ export interface RekorVerifyResult {
7
7
  integratedTime?: number;
8
8
  logEntryUrl?: string;
9
9
  /** Cryptographic Merkle-inclusion verdict (#119), when the fetched entry carries
10
- * an inclusion proof. Additive — `verified` (hash-parity) is unchanged. */
10
+ * an inclusion proof; not verified when the entry does not record this package's
11
+ * hash. Additive — `verified` (hash-parity) is unchanged. */
11
12
  inclusion?: RekorInclusionResult;
12
13
  }
13
14
  /**
package/dist/rekor.js CHANGED
@@ -9,7 +9,7 @@
9
9
  // instead of `Buffer`, and the network fetch is injected.
10
10
  import { rekorHashForPackage } from "./signature.js";
11
11
  import { base64ToBytes } from "./primitives.js";
12
- import { verifyRekorInclusion, } from "./rekor-inclusion.js";
12
+ import { verifyRekorInclusionForRecord } from "./rekor-binding.js";
13
13
  /**
14
14
  * Verify that a Rekor transparency log entry is consistent with a published
15
15
  * package.
@@ -43,9 +43,11 @@ export async function verifyRekorEntry(entryId, packageHash, options = {}) {
43
43
  const verified = recordedAlgo === 'sha512' && recordedHash === expectedHash;
44
44
  // Deepen #8 (civic-ai-tools-website#119): when the entry carries an inclusion
45
45
  // proof, cryptographically verify Merkle inclusion against the pinned log key —
46
- // the same offline check a carried bundle runs, here over the fetched entry.
46
+ // the same offline check a carried bundle runs, here over the fetched entry, and
47
+ // bound to this record: an entry that does not record `packageHash` is not
48
+ // verified (`rekor-binding.ts`).
47
49
  const proof = entry.verification?.inclusionProof;
48
- const inclusion = proof && entry.body ? verifyRekorInclusion(entry.body, proof) : undefined;
50
+ const inclusion = proof && entry.body ? verifyRekorInclusionForRecord(entry.body, proof, packageHash) : undefined;
49
51
  return {
50
52
  verified,
51
53
  logIndex: entry.logIndex,
package/dist/verify.d.ts CHANGED
@@ -140,7 +140,8 @@ export interface VerifyResult {
140
140
  } | null;
141
141
  rekorIntegratedTime?: number;
142
142
  /** Cryptographic Merkle-inclusion verdict (#119): null when no proof was
143
- * available, else the graded result (`inclusionVerified` / `checkpointVerified`).
143
+ * available, else the graded result (`inclusionVerified` / `checkpointVerified`),
144
+ * both false when the log entry does not record `packageHash`.
144
145
  * Additive to `rekorVerified` (hash-parity). */
145
146
  rekorInclusion: RekorInclusionResult | null;
146
147
  hasRekor: boolean;
package/dist/verify.js CHANGED
@@ -31,7 +31,7 @@
31
31
  import { recomputePackageHash } from "./checks.js";
32
32
  import { verifySignature } from "./signature.js";
33
33
  import { verifyRekorEntry } from "./rekor.js";
34
- import { verifyRekorInclusion, } from "./rekor-inclusion.js";
34
+ import { verifyRekorInclusionForRecord } from "./rekor-binding.js";
35
35
  import { verifyRfc3161Timestamp } from "./rfc3161.js";
36
36
  import { verifyKeyTrust, legacyEmbeddedKeyTrust, } from "./trust-registry.js";
37
37
  import { applyKeyDerivedRegistryRule, applySelfCertifiedKeyTrust, compareKeyDerivedIdentifier, } from "./self-certifying.js";
@@ -128,6 +128,7 @@ export async function verifyRecord(input, deps) {
128
128
  // Merkle inclusion (#119) is verified cryptographically. The carried proof + body
129
129
  // verify inclusion OFFLINE (no fetch); otherwise the online fetch's entry carries
130
130
  // the proof. Carried-offline takes precedence — it is the zero-dependency property.
131
+ // Either way the entry must record this record's hash (`rekor-binding.ts`).
131
132
  let rekorVerified = null;
132
133
  let rekorDetails = null;
133
134
  let rekorIntegratedTime;
@@ -146,7 +147,7 @@ export async function verifyRecord(input, deps) {
146
147
  rekorInclusion = rekorResult.inclusion;
147
148
  }
148
149
  if (input.rekorInclusionProof && input.rekorEntryBody) {
149
- rekorInclusion = verifyRekorInclusion(input.rekorEntryBody, input.rekorInclusionProof);
150
+ rekorInclusion = verifyRekorInclusionForRecord(input.rekorEntryBody, input.rekorInclusionProof, packageHash);
150
151
  }
151
152
  // Step 3c — RFC 3161 timestamp (check #7, deep). Presence is `hasTimestamp`; when a
152
153
  // token + package hash are present, cryptographically verify the TSA signature over
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typedstandards/verify-core",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Portable, browser-safe verification core for Typed Standards record 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",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22",
40
- "typescript": "^5.9.3"
40
+ "typescript": "^6.0.3"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=18"