@typedstandards/verify-core 0.14.0 → 0.15.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 +82 -1
- package/dist/checks.d.ts +32 -3
- package/dist/checks.js +51 -13
- package/dist/lifecycle.js +3 -2
- package/dist/profiles.d.ts +29 -0
- package/dist/profiles.js +34 -1
- package/dist/rekor-binding.d.ts +12 -0
- package/dist/rekor-binding.js +48 -0
- package/dist/rekor-inclusion.d.ts +6 -2
- package/dist/rekor-inclusion.js +3 -1
- package/dist/rekor.d.ts +2 -1
- package/dist/rekor.js +6 -4
- package/dist/verify.d.ts +6 -2
- package/dist/verify.js +8 -4
- package/dist/x509.js +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,92 @@
|
|
|
1
1
|
# Changelog — @typedstandards/verify-core
|
|
2
2
|
|
|
3
|
-
Factual record of what changed per published version. Check numbers (#1–#
|
|
3
|
+
Factual record of what changed per published version. Check numbers (#1–#17)
|
|
4
4
|
refer to the Typed Standards specification §9.2 verification sequence. Issue
|
|
5
5
|
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.1 — 2026-11-02
|
|
10
|
+
|
|
11
|
+
The R11 patch (npstorey/civic-ai-tools#293; anchor npstorey/civic-ai-tools#294). **A patch**: no
|
|
12
|
+
export, signature or type changes.
|
|
13
|
+
|
|
14
|
+
- **Check #8's inclusion verdict requires the log entry to record this record's hash.**
|
|
15
|
+
`rekorInclusion.inclusionVerified` and `checkpointVerified` are true only when the entry body's
|
|
16
|
+
`spec.data.hash` is `sha512` with the value `rekorHashForPackage(packageHash)`. That holds for an
|
|
17
|
+
entry body and proof the input carries (`verifyRecord`) and for an entry `verifyRekorEntry`
|
|
18
|
+
fetches (its `inclusion`). An entry that does not record the hash, or an empty `packageHash`,
|
|
19
|
+
reads both false, with the proof's `treeSize` and `leafIndex` and no `reason`.
|
|
20
|
+
`verifyRekorInclusion` is unchanged: it checks inclusion alone.
|
|
21
|
+
- **Every genuine pairing reads as before.** Over the package objects and the carried log
|
|
22
|
+
inclusions in this repository's fixtures, and two host-served commitment views, `verifyRecord`
|
|
23
|
+
gives output deep-equal to 0.15.0's, with the entry carried, fetched, or both.
|
|
24
|
+
- **The `ai-assisted-analysis/civicaitools-v1` row pins revision 2 of its guidance bundle**
|
|
25
|
+
(2026-11-02), as hub ADR-0038's amendment of 2026-11-02 records:
|
|
26
|
+
`https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.14-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json`,
|
|
27
|
+
SHA-256 `45bf3ab479325a791829ed9987f5f291fea44238b54bfb598f32f306f574eb9b`. The row's key and
|
|
28
|
+
`admits` are unchanged. The local copy (not published) is byte-identical to the hub file at
|
|
29
|
+
tree `bcd06c5f`; its provenance is in `src/__fixtures__/README.md`.
|
|
30
|
+
|
|
31
|
+
## 0.15.0 — 2026-10-30
|
|
32
|
+
|
|
33
|
+
The R10 release (civic-ai-tools#289; hub ADR-0038 §3, §5 and §6, specification v0.1.13 §8.1.1,
|
|
34
|
+
§8.6 and §9.2). **A minor bump**: new exports, a new `VerifyResult` field, and one combination
|
|
35
|
+
check #16 newly reads `ok`.
|
|
36
|
+
|
|
37
|
+
- **The subtype table (new exports `PRODUCER_PROFILE_SUBTYPES`, `isContentProfileConsistent`, and
|
|
38
|
+
the types `ProducerProfileSubtypeRow` and `ProducerProfileBundleRef`).** Beside the profile-type
|
|
39
|
+
table, one frozen row per built subtype of `ai-assisted-analysis`, keyed by the full
|
|
40
|
+
`producerProfile` value:
|
|
41
|
+
- `ai-assisted-analysis/datHere` admits `datHere`, with no bundle pinned;
|
|
42
|
+
- `ai-assisted-analysis/civicaitools-v1` admits `datHere` and `default`, with its guidance
|
|
43
|
+
bundle pinned by URL
|
|
44
|
+
(`https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.13-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json`)
|
|
45
|
+
and SHA-256 (`724e399e02db8993041b64982eec731505084bec2ec5e6ca58fda2de8087a281`), revision 1.
|
|
46
|
+
|
|
47
|
+
`isContentProfileConsistent(producerProfile, contentProfile)` is the §8.1.1 invariant over the
|
|
48
|
+
table: the content profile is one the subtype's row admits; a subtype with no row admits
|
|
49
|
+
`"datHere"` exactly when it begins with `ai-assisted-analysis/datHere`, and `"default"` exactly
|
|
50
|
+
when it does not. Only the table's own keys are rows. The row does not change the capture
|
|
51
|
+
vocabulary: `PROFILE_CAPTURE_VOCAB`, `resolveProfileType` and `captureVocabForProfile` (check #15)
|
|
52
|
+
are unchanged.
|
|
53
|
+
- **Check #16 reads the predicate.** `checkContentProfile` reports `contentProfile_inconsistent`
|
|
54
|
+
when `isContentProfileConsistent` is false, in place of the `startsWith` comparison. The no-row
|
|
55
|
+
clause is that comparison, so every status is unchanged except one: `datHere` under
|
|
56
|
+
`ai-assisted-analysis/civicaitools-v1` reads `ok` (it read `contentProfile_inconsistent`). No
|
|
57
|
+
published record carries that combination.
|
|
58
|
+
- **Check #3's implicit rule reads the predicate.** For a package with no
|
|
59
|
+
`contentCanonicalization`, a `producerProfile` reads as datHere when its subtype admits
|
|
60
|
+
`"datHere"` and not `"default"`, which is the `startsWith` reading for the `datHere` row and every
|
|
61
|
+
subtype with no row; under `ai-assisted-analysis/civicaitools-v1` the choice is
|
|
62
|
+
`metadata.contentProfile`'s. Every result is unchanged.
|
|
63
|
+
- **Check #17, the content-profile system prompt (new exports `checkSystemPrompt`,
|
|
64
|
+
`SYSTEM_PROMPT_STATUSES`, and the types `SystemPromptStatus` and `SystemPromptCheck`).** When
|
|
65
|
+
`metadata.contentProfile` is `"datHere"`, `skillMetadata.skillText` as a non-empty string reads
|
|
66
|
+
`ok` (`form: 'string'`) and as a BlobRef `ok` (`form: 'blobRef'`); absent, empty, or neither
|
|
67
|
+
reads `skillText_absent`. Any other content profile (absent, `"default"` or unknown) reads
|
|
68
|
+
`not_applicable`. `skillText_absent` is an attention status and never a failure. The check does
|
|
69
|
+
not resolve a BlobRef (check #9 does).
|
|
70
|
+
- **`VerifyResult` gains `systemPrompt`** (`SystemPromptCheck | null`, null when the package is
|
|
71
|
+
absent), set by `verifyRecord` beside `contentProfile`. Every other field of the result is
|
|
72
|
+
unchanged. A consumer that builds a `VerifyResult` literal needs the new field.
|
|
73
|
+
- **Every earlier fixture reads as before.** Over the signed bundles, signed documents and package
|
|
74
|
+
objects in this repository's fixtures and the hub harness's `reference-golden.json` and
|
|
75
|
+
`in-loop-golden.json`, `verifyRecord`, `checkContentProfile`, `resolveContentCanonicalization`
|
|
76
|
+
and `captureVocabForProfile` give byte-identical output to 0.14.0 once `systemPrompt` is removed.
|
|
77
|
+
Check #17 reads `skillText_absent` on the five fixture packages that declare `datHere` without
|
|
78
|
+
`skillText` (the hub reference golden's `v01-dathere-empty-notebook` and
|
|
79
|
+
`v01-dathere-executed-notebook`, their copies in produce-core's `reference-golden.json`, and the
|
|
80
|
+
hub's `in-loop-golden.json`), `ok` on the site's `q15-d67b8e` record, and `not_applicable` on
|
|
81
|
+
every other.
|
|
82
|
+
- **Every lookup of a verify-core table by a value a record or bundle supplies reads only the
|
|
83
|
+
table's own entries;** a malformed value reads as an unknown one. Every well-formed value reads as
|
|
84
|
+
before.
|
|
85
|
+
- **A local copy of the bundle**, `src/__fixtures__/civicaitools-v1-guidance-bundle.json` (not
|
|
86
|
+
published), byte-identical to the hub file at tree `7060f5e3`. A test asserts its SHA-256,
|
|
87
|
+
`subtype`, `url` and admitted set against the row, without the network. Its provenance is in
|
|
88
|
+
`src/__fixtures__/README.md`.
|
|
89
|
+
|
|
9
90
|
## 0.14.0 — 2026-10-16
|
|
10
91
|
|
|
11
92
|
The W5 release (civic-ai-tools#260, shipped in #146 and #147). **A minor bump**: new exports
|
package/dist/checks.d.ts
CHANGED
|
@@ -22,7 +22,12 @@ export interface ContentCanonicalizationResolution {
|
|
|
22
22
|
* `unknown_canonicalization_rule` (renders; check #4 then cannot recompute). An
|
|
23
23
|
* absent field on a pre-v0.1 package → `implicit`, inferring the rule from
|
|
24
24
|
* contentProfile / producerProfile (datHere → dathere-ag-jupyter/v1; otherwise
|
|
25
|
-
* legacy-json/v1).
|
|
25
|
+
* legacy-json/v1). A `producerProfile` reads as datHere when the subtype admits
|
|
26
|
+
* `"datHere"` and not `"default"` under the §8.1.1 invariant
|
|
27
|
+
* (`isContentProfileConsistent`): with no row, exactly when it begins with
|
|
28
|
+
* `ai-assisted-analysis/datHere`, as before; a subtype whose row admits both, as
|
|
29
|
+
* `ai-assisted-analysis/civicaitools-v1` does, leaves the choice to
|
|
30
|
+
* `contentProfile`.
|
|
26
31
|
*/
|
|
27
32
|
export declare function resolveContentCanonicalization(pkg: Record<string, unknown>): ContentCanonicalizationResolution;
|
|
28
33
|
export declare const CONTENT_HASH_STATUSES: readonly ["ok", "content_hash_mismatch", "contentHash_no_supported_algorithm", "unresolved_rule", "legacy_relabeled", "content_bytes_unavailable"];
|
|
@@ -94,14 +99,38 @@ export interface ContentProfileCheck {
|
|
|
94
99
|
* - key absent → `contentProfile_absent` (read as `"default"`, spec §8.1.2).
|
|
95
100
|
* - present and neither `"default"` nor `"datHere"` → `contentProfile_unknown`
|
|
96
101
|
* (reported, not passed; consistency is not judged).
|
|
97
|
-
* - present, known, `producerProfile` present, and the
|
|
98
|
-
*
|
|
102
|
+
* - present, known, `producerProfile` present, and the §8.1.1 invariant fails
|
|
103
|
+
* (ADR-0006 §2 as amended by hub ADR-0038 §3: the content profile is not one
|
|
104
|
+
* the subtype's row admits, `isContentProfileConsistent`; a subtype with no
|
|
105
|
+
* row admits `"datHere"` exactly when it begins with
|
|
99
106
|
* `ai-assisted-analysis/datHere`) → `contentProfile_inconsistent`.
|
|
100
107
|
* - otherwise → `ok`.
|
|
101
108
|
* The invariant is compared only when both fields are present. No status is an
|
|
102
109
|
* integrity failure: both labels are signature-covered.
|
|
103
110
|
*/
|
|
104
111
|
export declare function checkContentProfile(pkg: Record<string, unknown>): ContentProfileCheck;
|
|
112
|
+
export declare const SYSTEM_PROMPT_STATUSES: readonly ["ok", "skillText_absent", "not_applicable"];
|
|
113
|
+
export type SystemPromptStatus = (typeof SYSTEM_PROMPT_STATUSES)[number];
|
|
114
|
+
export interface SystemPromptCheck {
|
|
115
|
+
status: SystemPromptStatus;
|
|
116
|
+
/** On `ok`, how the text is carried: inline, or a BlobRef (which check #9 resolves). */
|
|
117
|
+
form?: 'string' | 'blobRef';
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check #17 — the system prompt §8.7.1 requirement 2 requires of a
|
|
121
|
+
* `datHere`-content-profile package (hub ADR-0038 §6).
|
|
122
|
+
* - `metadata.contentProfile` not `"datHere"` (absent, `"default"` or unknown)
|
|
123
|
+
* → `not_applicable`, and nothing else is read.
|
|
124
|
+
* - `"datHere"` and `skillMetadata.skillText` a non-empty string → `ok`
|
|
125
|
+
* (`form: 'string'`), or a BlobRef → `ok` (`form: 'blobRef'`).
|
|
126
|
+
* - `"datHere"` and the key absent, empty, or neither a string nor a BlobRef
|
|
127
|
+
* → `skillText_absent`.
|
|
128
|
+
* `skillText_absent` is an attention status, never a failure: it says nothing
|
|
129
|
+
* about the envelope, the signature or the notebook. The check does not resolve
|
|
130
|
+
* a BlobRef (check #9 does), and cannot check that the text is the prompt the
|
|
131
|
+
* model ran under.
|
|
132
|
+
*/
|
|
133
|
+
export declare function checkSystemPrompt(pkg: Record<string, unknown>): SystemPromptCheck;
|
|
105
134
|
export declare const CONTENT_LAYOUT = "content/layout/v1";
|
|
106
135
|
export declare const KNOWN_TYPE_URIS: readonly string[];
|
|
107
136
|
export declare const TYPE_RESOLUTION_STATUSES: readonly ["ok", "implicit", "unknown_type"];
|
package/dist/checks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Content-canonicalization, content-hash, and typed-standards envelope checks
|
|
2
|
-
// (spec §9.2 checks #3, #4, #6, #12, #13, #14, #15, #16) + the package-level
|
|
2
|
+
// (spec §9.2 checks #3, #4, #6, #12, #13, #14, #15, #16, #17) + the package-level
|
|
3
3
|
// blob-ref walker (#9) — browser-safe.
|
|
4
4
|
//
|
|
5
5
|
// Factored verbatim from the server `verify.ts` (WS2); these functions were
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// for pre-v0.1 packages that omit the corresponding field.
|
|
9
9
|
import { computeEnvelopeHash, computeContentHashSha256, isMultihashContentHash, KNOWN_CANONICALIZATION_RULES, LEGACY_JSON_CANONICALIZATION, DATHERE_AG_JUPYTER_CANONICALIZATION, RAW_BYTES_CANONICALIZATION, } from "./canonicalization.js";
|
|
10
10
|
import { sha256Hex } from "./primitives.js";
|
|
11
|
-
import { captureVocabForProfile, resolveProfileType } from "./profiles.js";
|
|
11
|
+
import { captureVocabForProfile, isContentProfileConsistent, resolveProfileType } from "./profiles.js";
|
|
12
12
|
import { isBlobRef, parseBlobRef, verifyBlobRef, } from "./blob-ref.js";
|
|
13
13
|
import { isKeyDerivedIdentifier } from "./did-key.js";
|
|
14
14
|
import { compareKeyDerivedIdentifier } from "./self-certifying.js";
|
|
@@ -34,7 +34,12 @@ export const CONTENT_CANONICALIZATION_STATUSES = [
|
|
|
34
34
|
* `unknown_canonicalization_rule` (renders; check #4 then cannot recompute). An
|
|
35
35
|
* absent field on a pre-v0.1 package → `implicit`, inferring the rule from
|
|
36
36
|
* contentProfile / producerProfile (datHere → dathere-ag-jupyter/v1; otherwise
|
|
37
|
-
* legacy-json/v1).
|
|
37
|
+
* legacy-json/v1). A `producerProfile` reads as datHere when the subtype admits
|
|
38
|
+
* `"datHere"` and not `"default"` under the §8.1.1 invariant
|
|
39
|
+
* (`isContentProfileConsistent`): with no row, exactly when it begins with
|
|
40
|
+
* `ai-assisted-analysis/datHere`, as before; a subtype whose row admits both, as
|
|
41
|
+
* `ai-assisted-analysis/civicaitools-v1` does, leaves the choice to
|
|
42
|
+
* `contentProfile`.
|
|
38
43
|
*/
|
|
39
44
|
export function resolveContentCanonicalization(pkg) {
|
|
40
45
|
const raw = pkg['contentCanonicalization'];
|
|
@@ -54,7 +59,9 @@ export function resolveContentCanonicalization(pkg) {
|
|
|
54
59
|
? pkg['producerProfile']
|
|
55
60
|
: undefined;
|
|
56
61
|
const isDatHere = contentProfile === 'datHere' ||
|
|
57
|
-
(producerProfile
|
|
62
|
+
(producerProfile !== undefined &&
|
|
63
|
+
isContentProfileConsistent(producerProfile, 'datHere') &&
|
|
64
|
+
!isContentProfileConsistent(producerProfile, 'default'));
|
|
58
65
|
return {
|
|
59
66
|
status: 'implicit',
|
|
60
67
|
rule: isDatHere
|
|
@@ -197,7 +204,7 @@ export async function verifyContentHashWithFetch(pkg, resolution, legacyExternal
|
|
|
197
204
|
return first;
|
|
198
205
|
return verifyContentHash(pkg, resolution, legacyExternalHash, bytes);
|
|
199
206
|
}
|
|
200
|
-
// --- Content-profile check (spec §9.2 check #16; hub ADR-0029 §5) ---
|
|
207
|
+
// --- Content-profile check (spec §9.2 check #16; hub ADR-0029 §5, ADR-0038 §3) ---
|
|
201
208
|
/** The known `metadata.contentProfile` values (spec §8.1.2). */
|
|
202
209
|
export const KNOWN_CONTENT_PROFILES = ['default', 'datHere'];
|
|
203
210
|
export const CONTENT_PROFILE_STATUSES = [
|
|
@@ -211,8 +218,10 @@ export const CONTENT_PROFILE_STATUSES = [
|
|
|
211
218
|
* - key absent → `contentProfile_absent` (read as `"default"`, spec §8.1.2).
|
|
212
219
|
* - present and neither `"default"` nor `"datHere"` → `contentProfile_unknown`
|
|
213
220
|
* (reported, not passed; consistency is not judged).
|
|
214
|
-
* - present, known, `producerProfile` present, and the
|
|
215
|
-
*
|
|
221
|
+
* - present, known, `producerProfile` present, and the §8.1.1 invariant fails
|
|
222
|
+
* (ADR-0006 §2 as amended by hub ADR-0038 §3: the content profile is not one
|
|
223
|
+
* the subtype's row admits, `isContentProfileConsistent`; a subtype with no
|
|
224
|
+
* row admits `"datHere"` exactly when it begins with
|
|
216
225
|
* `ai-assisted-analysis/datHere`) → `contentProfile_inconsistent`.
|
|
217
226
|
* - otherwise → `ok`.
|
|
218
227
|
* The invariant is compared only when both fields are present. No status is an
|
|
@@ -234,15 +243,44 @@ export function checkContentProfile(pkg) {
|
|
|
234
243
|
if (typeof raw !== 'string' || !KNOWN_CONTENT_PROFILES.includes(raw)) {
|
|
235
244
|
return { status: 'contentProfile_unknown', ...base };
|
|
236
245
|
}
|
|
237
|
-
if (producerProfile !== undefined) {
|
|
238
|
-
|
|
239
|
-
const profileIsDatHere = producerProfile.startsWith('ai-assisted-analysis/datHere');
|
|
240
|
-
if (isDatHere !== profileIsDatHere) {
|
|
241
|
-
return { status: 'contentProfile_inconsistent', ...base };
|
|
242
|
-
}
|
|
246
|
+
if (producerProfile !== undefined && !isContentProfileConsistent(producerProfile, raw)) {
|
|
247
|
+
return { status: 'contentProfile_inconsistent', ...base };
|
|
243
248
|
}
|
|
244
249
|
return { status: 'ok', ...base };
|
|
245
250
|
}
|
|
251
|
+
// --- Content-profile system prompt (spec §9.2 check #17; hub ADR-0038 §6) ---
|
|
252
|
+
export const SYSTEM_PROMPT_STATUSES = ['ok', 'skillText_absent', 'not_applicable'];
|
|
253
|
+
/**
|
|
254
|
+
* Check #17 — the system prompt §8.7.1 requirement 2 requires of a
|
|
255
|
+
* `datHere`-content-profile package (hub ADR-0038 §6).
|
|
256
|
+
* - `metadata.contentProfile` not `"datHere"` (absent, `"default"` or unknown)
|
|
257
|
+
* → `not_applicable`, and nothing else is read.
|
|
258
|
+
* - `"datHere"` and `skillMetadata.skillText` a non-empty string → `ok`
|
|
259
|
+
* (`form: 'string'`), or a BlobRef → `ok` (`form: 'blobRef'`).
|
|
260
|
+
* - `"datHere"` and the key absent, empty, or neither a string nor a BlobRef
|
|
261
|
+
* → `skillText_absent`.
|
|
262
|
+
* `skillText_absent` is an attention status, never a failure: it says nothing
|
|
263
|
+
* about the envelope, the signature or the notebook. The check does not resolve
|
|
264
|
+
* a BlobRef (check #9 does), and cannot check that the text is the prompt the
|
|
265
|
+
* model ran under.
|
|
266
|
+
*/
|
|
267
|
+
export function checkSystemPrompt(pkg) {
|
|
268
|
+
const metadata = pkg['metadata'];
|
|
269
|
+
const contentProfile = typeof metadata === 'object' && metadata !== null && !Array.isArray(metadata)
|
|
270
|
+
? metadata['contentProfile']
|
|
271
|
+
: undefined;
|
|
272
|
+
if (contentProfile !== 'datHere')
|
|
273
|
+
return { status: 'not_applicable' };
|
|
274
|
+
const skillMetadata = pkg['skillMetadata'];
|
|
275
|
+
const skillText = typeof skillMetadata === 'object' && skillMetadata !== null && !Array.isArray(skillMetadata)
|
|
276
|
+
? skillMetadata['skillText']
|
|
277
|
+
: undefined;
|
|
278
|
+
if (typeof skillText === 'string' && skillText.length > 0)
|
|
279
|
+
return { status: 'ok', form: 'string' };
|
|
280
|
+
if (isBlobRef(skillText))
|
|
281
|
+
return { status: 'ok', form: 'blobRef' };
|
|
282
|
+
return { status: 'skillText_absent' };
|
|
283
|
+
}
|
|
246
284
|
// --- Typed-standards envelope checks (spec §9.2 checks #12, #14, #15) ---
|
|
247
285
|
// The `content/layout/v1` sub-type (spec §8.14, hub ADR-0034): a standalone
|
|
248
286
|
// assertion of how a host's page arranges a node's parts. Registered for check
|
package/dist/lifecycle.js
CHANGED
|
@@ -319,7 +319,8 @@ function isAbsent(v) {
|
|
|
319
319
|
}
|
|
320
320
|
/** Each payload field §8.12.1 requires of `type` that `node` lacks, by name. */
|
|
321
321
|
function missingRequiredFields(node, type) {
|
|
322
|
-
|
|
322
|
+
const required = Object.hasOwn(ATTESTATION_REQUIRED_FIELDS, type) ? ATTESTATION_REQUIRED_FIELDS[type] : [];
|
|
323
|
+
return required.filter((f) => isAbsent(node[f]));
|
|
323
324
|
}
|
|
324
325
|
/**
|
|
325
326
|
* Check one `attestation/*` node on its own (spec §8.12.3), for `withdraws`,
|
|
@@ -343,7 +344,7 @@ function missingRequiredFields(node, type) {
|
|
|
343
344
|
export function checkAttestationNode(carried, context = {}) {
|
|
344
345
|
const node = carried.node;
|
|
345
346
|
const type = pickString(node['type']) ?? '';
|
|
346
|
-
const rule = ATTESTATION_AUTHORIZATION_RULES[type]
|
|
347
|
+
const rule = Object.hasOwn(ATTESTATION_AUTHORIZATION_RULES, type) ? ATTESTATION_AUTHORIZATION_RULES[type] : null;
|
|
347
348
|
const verdict = verifyAttestationNode(node, carried.nodeId, carried.signature ?? null);
|
|
348
349
|
const missingFields = missingRequiredFields(node, type);
|
|
349
350
|
const publicKey = carried.signature?.publicKey ?? '';
|
package/dist/profiles.d.ts
CHANGED
|
@@ -18,3 +18,32 @@ export declare function resolveProfileType(producerProfile?: string | null, cont
|
|
|
18
18
|
* `producerProfile_bundle_unresolved` degraded case).
|
|
19
19
|
*/
|
|
20
20
|
export declare function captureVocabForProfile(producerProfile?: string | null, contentProfile?: string | null): readonly CaptureMethod[] | undefined;
|
|
21
|
+
/** A guidance bundle pinned by its address and the SHA-256 of its bytes. */
|
|
22
|
+
export interface ProducerProfileBundleRef {
|
|
23
|
+
readonly url: string;
|
|
24
|
+
/** Lowercase hex SHA-256 of the bundle file's bytes. */
|
|
25
|
+
readonly sha256: string;
|
|
26
|
+
}
|
|
27
|
+
/** One built subtype's row. */
|
|
28
|
+
export interface ProducerProfileSubtypeRow {
|
|
29
|
+
/** The `metadata.contentProfile` values the subtype admits. */
|
|
30
|
+
readonly admits: readonly string[];
|
|
31
|
+
/** The subtype's guidance bundle, when one is pinned. */
|
|
32
|
+
readonly bundle?: ProducerProfileBundleRef;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The subtype table, keyed by the full `producerProfile` value. The
|
|
36
|
+
* `ai-assisted-analysis/civicaitools-v1` bundle is revision 2 (2026-11-02), as
|
|
37
|
+
* hub ADR-0038's amendment of 2026-11-02 records its address and digest; a later
|
|
38
|
+
* revision moves the digest and this pin, and nothing else.
|
|
39
|
+
*/
|
|
40
|
+
export declare const PRODUCER_PROFILE_SUBTYPES: Readonly<Record<string, ProducerProfileSubtypeRow>>;
|
|
41
|
+
/**
|
|
42
|
+
* The §8.1.1 consistency invariant, for a package that carries both
|
|
43
|
+
* `producerProfile` and `metadata.contentProfile`: whether the content profile is
|
|
44
|
+
* one the subtype's row admits. A subtype with no row admits `"datHere"` exactly
|
|
45
|
+
* when it begins with `ai-assisted-analysis/datHere`, and `"default"` exactly when
|
|
46
|
+
* it does not: the reading every earlier release applied, so a record whose
|
|
47
|
+
* subtype has no row reads as before. Check #16 reads it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function isContentProfileConsistent(producerProfile: string, contentProfile: string): boolean;
|
package/dist/profiles.js
CHANGED
|
@@ -40,5 +40,38 @@ export function resolveProfileType(producerProfile, contentProfile) {
|
|
|
40
40
|
* `producerProfile_bundle_unresolved` degraded case).
|
|
41
41
|
*/
|
|
42
42
|
export function captureVocabForProfile(producerProfile, contentProfile) {
|
|
43
|
-
|
|
43
|
+
const profileType = resolveProfileType(producerProfile, contentProfile);
|
|
44
|
+
return Object.hasOwn(PROFILE_CAPTURE_VOCAB, profileType) ? PROFILE_CAPTURE_VOCAB[profileType] : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The subtype table, keyed by the full `producerProfile` value. The
|
|
48
|
+
* `ai-assisted-analysis/civicaitools-v1` bundle is revision 2 (2026-11-02), as
|
|
49
|
+
* hub ADR-0038's amendment of 2026-11-02 records its address and digest; a later
|
|
50
|
+
* revision moves the digest and this pin, and nothing else.
|
|
51
|
+
*/
|
|
52
|
+
export const PRODUCER_PROFILE_SUBTYPES = Object.freeze({
|
|
53
|
+
// No bundle pinned: the integration partner's, not this table's to name.
|
|
54
|
+
'ai-assisted-analysis/datHere': Object.freeze({ admits: Object.freeze(['datHere']) }),
|
|
55
|
+
'ai-assisted-analysis/civicaitools-v1': Object.freeze({
|
|
56
|
+
admits: Object.freeze(['datHere', 'default']),
|
|
57
|
+
bundle: Object.freeze({
|
|
58
|
+
url: 'https://raw.githubusercontent.com/npstorey/civic-ai-tools/v0.1.14-typed-standards-spec/docs/profiles/ai-assisted-analysis/civicaitools-v1/bundle.json',
|
|
59
|
+
sha256: '45bf3ab479325a791829ed9987f5f291fea44238b54bfb598f32f306f574eb9b',
|
|
60
|
+
}),
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
const DATHERE_PREFIX = 'ai-assisted-analysis/datHere';
|
|
64
|
+
/**
|
|
65
|
+
* The §8.1.1 consistency invariant, for a package that carries both
|
|
66
|
+
* `producerProfile` and `metadata.contentProfile`: whether the content profile is
|
|
67
|
+
* one the subtype's row admits. A subtype with no row admits `"datHere"` exactly
|
|
68
|
+
* when it begins with `ai-assisted-analysis/datHere`, and `"default"` exactly when
|
|
69
|
+
* it does not: the reading every earlier release applied, so a record whose
|
|
70
|
+
* subtype has no row reads as before. Check #16 reads it.
|
|
71
|
+
*/
|
|
72
|
+
export function isContentProfileConsistent(producerProfile, contentProfile) {
|
|
73
|
+
if (Object.hasOwn(PRODUCER_PROFILE_SUBTYPES, producerProfile)) {
|
|
74
|
+
return PRODUCER_PROFILE_SUBTYPES[producerProfile].admits.includes(contentProfile);
|
|
75
|
+
}
|
|
76
|
+
return producerProfile.startsWith(DATHERE_PREFIX) ? contentProfile === 'datHere' : contentProfile === 'default';
|
|
44
77
|
}
|
|
@@ -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 {};
|
package/dist/rekor-inclusion.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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.
|
|
@@ -31,7 +31,7 @@ export async function verifyRekorEntry(entryId, packageHash, options = {}) {
|
|
|
31
31
|
return { verified: false };
|
|
32
32
|
}
|
|
33
33
|
const result = (await response.json());
|
|
34
|
-
const entry = result[entryId] || Object.values(result)[0];
|
|
34
|
+
const entry = (Object.hasOwn(result, entryId) ? result[entryId] : undefined) || Object.values(result)[0];
|
|
35
35
|
if (!entry)
|
|
36
36
|
return { verified: false };
|
|
37
37
|
// Decode the base64 body and cross-check both hash algorithm and value.
|
|
@@ -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 ?
|
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type RekorInclusionProof, type RekorInclusionResult } from './rekor-inclusion.ts';
|
|
2
2
|
import { type Rfc3161VerifyResult } from './rfc3161.ts';
|
|
3
3
|
import { type KeyTrustResult, type TrustRegistry, type TrustRegistryProvenance } from './trust-registry.ts';
|
|
4
|
-
import { type ContentCanonicalizationResolution, type ContentHashCheck, type TypeResolution, type SignerIdentityCheck, type SigningKeyIdConsistencyCheck, type CaptureMethodVocabCheck, type ContentProfileCheck, type BlobRefVerification } from './checks.ts';
|
|
4
|
+
import { type ContentCanonicalizationResolution, type ContentHashCheck, type TypeResolution, type SignerIdentityCheck, type SigningKeyIdConsistencyCheck, type CaptureMethodVocabCheck, type ContentProfileCheck, type SystemPromptCheck, type BlobRefVerification } from './checks.ts';
|
|
5
5
|
import { type LifecycleResolution } from './lifecycle.ts';
|
|
6
6
|
import type { FetchLike } from './types.ts';
|
|
7
7
|
/** The parsed signature envelope (`buildCommitmentView`'s `signature`). */
|
|
@@ -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;
|
|
@@ -163,6 +164,9 @@ export interface VerifyResult {
|
|
|
163
164
|
captureMethodVocab: CaptureMethodVocabCheck | null;
|
|
164
165
|
/** Check #16 — `metadata.contentProfile` (hub ADR-0029 §5). */
|
|
165
166
|
contentProfile: ContentProfileCheck | null;
|
|
167
|
+
/** Check #17 — whether a `datHere`-content-profile package carries
|
|
168
|
+
* `skillMetadata.skillText` (hub ADR-0038 §6). Null when the package is absent. */
|
|
169
|
+
systemPrompt: SystemPromptCheck | null;
|
|
166
170
|
lifecycle: LifecycleResolution;
|
|
167
171
|
}
|
|
168
172
|
/**
|
package/dist/verify.js
CHANGED
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
import { recomputePackageHash } from "./checks.js";
|
|
32
32
|
import { verifySignature } from "./signature.js";
|
|
33
33
|
import { verifyRekorEntry } from "./rekor.js";
|
|
34
|
-
import {
|
|
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";
|
|
38
|
-
import { resolveContentCanonicalization, verifyContentHashWithFetch, resolvePackageType, checkSignerIdentity, checkSigningKeyIdConsistency, checkCaptureMethodVocab, checkContentProfile, verifyPackageBlobRefs, } from "./checks.js";
|
|
38
|
+
import { resolveContentCanonicalization, verifyContentHashWithFetch, resolvePackageType, checkSignerIdentity, checkSigningKeyIdConsistency, checkCaptureMethodVocab, checkContentProfile, checkSystemPrompt, verifyPackageBlobRefs, } from "./checks.js";
|
|
39
39
|
import { resolveLifecycleFromLegacyColumns, } from "./lifecycle.js";
|
|
40
40
|
/**
|
|
41
41
|
* A fetcher that sends at most one request per URL and replays that request's
|
|
@@ -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 =
|
|
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
|
|
@@ -204,7 +205,7 @@ export async function verifyRecord(input, deps) {
|
|
|
204
205
|
: applyKeyDerivedRegistryRule(keyTrust, deps.registryProvenance);
|
|
205
206
|
}
|
|
206
207
|
// Step 5 — canonicalization, content-hash, and envelope checks
|
|
207
|
-
// (#3/#4/#6/#12/#14/#15/#16). #4 fetches only for a raw-bytes/v1 BlobRef output.
|
|
208
|
+
// (#3/#4/#6/#12/#14/#15/#16/#17). #4 fetches only for a raw-bytes/v1 BlobRef output.
|
|
208
209
|
let contentCanonicalization = null;
|
|
209
210
|
let contentHashCheck = null;
|
|
210
211
|
let typeResolution = null;
|
|
@@ -212,6 +213,7 @@ export async function verifyRecord(input, deps) {
|
|
|
212
213
|
let signingKeyIdConsistency = null;
|
|
213
214
|
let captureMethodVocab = null;
|
|
214
215
|
let contentProfile = null;
|
|
216
|
+
let systemPrompt = null;
|
|
215
217
|
if (pkg) {
|
|
216
218
|
contentCanonicalization = resolveContentCanonicalization(pkg);
|
|
217
219
|
contentHashCheck = await verifyContentHashWithFetch(pkg, contentCanonicalization, input.legacyExternalHash ?? packageHash, { fetch: blobFetch });
|
|
@@ -222,6 +224,7 @@ export async function verifyRecord(input, deps) {
|
|
|
222
224
|
}
|
|
223
225
|
captureMethodVocab = checkCaptureMethodVocab(pkg);
|
|
224
226
|
contentProfile = checkContentProfile(pkg);
|
|
227
|
+
systemPrompt = checkSystemPrompt(pkg);
|
|
225
228
|
}
|
|
226
229
|
// Step 6 — lifecycle (check #10). Server-deeper resolution wins when supplied;
|
|
227
230
|
// otherwise derive STATE from the sidecar's lifecycle (the portable depth).
|
|
@@ -257,6 +260,7 @@ export async function verifyRecord(input, deps) {
|
|
|
257
260
|
signingKeyIdConsistency,
|
|
258
261
|
captureMethodVocab,
|
|
259
262
|
contentProfile,
|
|
263
|
+
systemPrompt,
|
|
260
264
|
lifecycle,
|
|
261
265
|
};
|
|
262
266
|
}
|
package/dist/x509.js
CHANGED
|
@@ -162,7 +162,7 @@ function isImplementedCertSignatureAlgorithm(oid) {
|
|
|
162
162
|
* apart before it calls this.
|
|
163
163
|
*/
|
|
164
164
|
export async function verifyCertSignatureRsa(cert, issuerSpkiDer) {
|
|
165
|
-
const hash = RSA_SIG_HASH[cert.sigAlgOid];
|
|
165
|
+
const hash = isImplementedCertSignatureAlgorithm(cert.sigAlgOid) ? RSA_SIG_HASH[cert.sigAlgOid] : undefined;
|
|
166
166
|
if (!hash)
|
|
167
167
|
return false;
|
|
168
168
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typedstandards/verify-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
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",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"prepublishOnly": "npm run clean && npm run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@noble/curves": "^2.
|
|
34
|
+
"@noble/curves": "^2.4.0",
|
|
35
35
|
"@noble/hashes": "^2.2.0",
|
|
36
|
-
"canonicalize": "^
|
|
36
|
+
"canonicalize": "^5.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^
|
|
39
|
+
"@types/node": "^22",
|
|
40
40
|
"typescript": "^5.9.3"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|