verifyhash 0.1.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/LICENSE +201 -0
- package/README.md +883 -0
- package/cli/abi/ContributionRegistry.json +881 -0
- package/cli/agent.js +2173 -0
- package/cli/anchor-artifact.js +853 -0
- package/cli/anchor.js +400 -0
- package/cli/claim.js +881 -0
- package/cli/core/agent-commit.js +448 -0
- package/cli/core/agent-session.js +598 -0
- package/cli/core/anchor-binding.js +663 -0
- package/cli/core/attestation.js +580 -0
- package/cli/core/evidence-plans.js +495 -0
- package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
- package/cli/core/fulfill-intake.js +1082 -0
- package/cli/core/go-live-preflight.js +481 -0
- package/cli/core/license.js +534 -0
- package/cli/core/manifest.js +243 -0
- package/cli/core/packetseal.js +591 -0
- package/cli/core/registryArtifact.js +49 -0
- package/cli/core/revocation.js +539 -0
- package/cli/core/rfc3161.js +389 -0
- package/cli/core/timestamp.js +482 -0
- package/cli/core/trust-asof.js +479 -0
- package/cli/dataset.js +2950 -0
- package/cli/evidence.js +2227 -0
- package/cli/fulfill-webhook-http.js +438 -0
- package/cli/git.js +220 -0
- package/cli/hash.js +550 -0
- package/cli/identity.js +1072 -0
- package/cli/journal-cli.js +1110 -0
- package/cli/journal-log.js +454 -0
- package/cli/journal.js +334 -0
- package/cli/lineage.js +447 -0
- package/cli/list.js +287 -0
- package/cli/parcel.js +1509 -0
- package/cli/proof.js +578 -0
- package/cli/prove.js +300 -0
- package/cli/receipt.js +631 -0
- package/cli/registry.js +331 -0
- package/cli/reputation.js +344 -0
- package/cli/revocation.js +495 -0
- package/cli/serve-verify-http.js +298 -0
- package/cli/serve-verify.js +333 -0
- package/cli/show.js +339 -0
- package/cli/verify.js +383 -0
- package/cli/vh.js +3927 -0
- package/docs/ADOPT.md +183 -0
- package/docs/ADOPTION.json +11 -0
- package/docs/AGENTTRACE.md +247 -0
- package/docs/ANCHORING.md +167 -0
- package/docs/AUDIT.md +55 -0
- package/docs/CONFORMANCE.md +107 -0
- package/docs/DATALEDGER.md +638 -0
- package/docs/DECIDE.md +47 -0
- package/docs/DECISIONS-PENDING.md +27 -0
- package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
- package/docs/ENGINE-LEDGER.json +12 -0
- package/docs/EVIDENCE.md +519 -0
- package/docs/GO-LIVE.md +66 -0
- package/docs/IDENTITY.md +123 -0
- package/docs/INDEPENDENT-VERIFICATION.md +377 -0
- package/docs/INTEGRITY-JOURNAL.md +337 -0
- package/docs/KEY-LIFECYCLE.md +179 -0
- package/docs/LICENSING.md +46 -0
- package/docs/LINEAGE.md +307 -0
- package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
- package/docs/LOOP-HARDENING-PLAN.md +44 -0
- package/docs/MERKLE-LEAVES.md +113 -0
- package/docs/METRICS.jsonl +31 -0
- package/docs/MORNING.md +204 -0
- package/docs/PILOT.md +444 -0
- package/docs/PROOFPARCEL.md +227 -0
- package/docs/PROOFS.md +262 -0
- package/docs/RECEIPTS.md +341 -0
- package/docs/REPUTATION.md +158 -0
- package/docs/SDK.md +301 -0
- package/docs/STRATEGY-ARCHIVE.md +5055 -0
- package/docs/SUPERVISOR-RUNBOOK.md +52 -0
- package/docs/TRUST-BOUNDARIES.md +335 -0
- package/docs/TRUSTLEDGER.md +1976 -0
- package/docs/USAGE-BUDGET.json +121 -0
- package/docs/VERIFY-SERVICE.md +168 -0
- package/index.js +160 -0
- package/package.json +41 -0
- package/trustledger/build-standalone.js +796 -0
- package/trustledger/cli.js +3179 -0
- package/trustledger/close.js +391 -0
- package/trustledger/corpus.js +159 -0
- package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
- package/trustledger/dist/trustledger-standalone.html +6197 -0
- package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
- package/trustledger/door-core.js +442 -0
- package/trustledger/fixtures/bank.csv +7 -0
- package/trustledger/fixtures/bank.malformed.csv +3 -0
- package/trustledger/fixtures/bank.noalias.csv +5 -0
- package/trustledger/fixtures/bank.ofx +34 -0
- package/trustledger/fixtures/bank.real.csv +5 -0
- package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
- package/trustledger/fixtures/e2e/bank.csv +4 -0
- package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
- package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
- package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.csv +6 -0
- package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
- package/trustledger/fixtures/plans/baseline.json +25 -0
- package/trustledger/fixtures/plans/price-binding.example.json +27 -0
- package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
- package/trustledger/fixtures/policy/baseline.json +19 -0
- package/trustledger/fixtures/policy/ca-example.json +12 -0
- package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
- package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
- package/trustledger/fixtures/quickbooks.csv +7 -0
- package/trustledger/fixtures/quickbooks.real.csv +5 -0
- package/trustledger/fixtures/rentroll.csv +6 -0
- package/trustledger/fixtures/rentroll.real.csv +4 -0
- package/trustledger/ingest.js +1163 -0
- package/trustledger/lib/policy-bundled-loader.js +44 -0
- package/trustledger/lib/sha256-vendored.js +227 -0
- package/trustledger/license.js +563 -0
- package/trustledger/match.js +551 -0
- package/trustledger/plans.js +551 -0
- package/trustledger/policy.js +398 -0
- package/trustledger/public/index.html +512 -0
- package/trustledger/reconcile.js +1486 -0
- package/trustledger/report.js +887 -0
- package/trustledger/seal.js +854 -0
- package/trustledger/server.js +391 -0
- package/trustledger/valueproof.js +350 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// cli/core/manifest.js — the GENERIC, product-agnostic provenance MANIFEST engine.
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS EXISTS
|
|
6
|
+
// verifyhash is growing into a FAMILY of provenance products (DataLedger — AI training-data
|
|
7
|
+
// provenance; ProofParcel — B2B data-delivery receipts; AttestKit later) that all pin EXACTLY what a
|
|
8
|
+
// directory contained — byte-for-byte, including file names/paths — at the moment it was manifested.
|
|
9
|
+
// The Merkle/manifest math is IDENTICAL across all of them; only the product FRAMING differs (its
|
|
10
|
+
// on-disk `kind` discriminator and the human label in its messages). This module is the SINGLE,
|
|
11
|
+
// tested implementation of that shared math + validation; each product is a THIN adapter that calls
|
|
12
|
+
// it with its OWN `kind`/label/note (see cli/dataset.js for DataLedger).
|
|
13
|
+
//
|
|
14
|
+
// It reuses the EXISTING path-bound, domain-separated Merkle convention from cli/hash.js verbatim
|
|
15
|
+
// (pathLeaf, the same DIR_LEAF_DOMAIN/LEAF_TAG/NODE_TAG the contract uses) — NO new hashing
|
|
16
|
+
// convention, so a manifest root is the SAME value `vh hash <dir>` and the contract's verifyLeaf
|
|
17
|
+
// produce for the same tree. NO product-specific knowledge lives here: a product passes its `kind`,
|
|
18
|
+
// its `supportedSchemaVersions`, a `note` string, and a `label` (the noun used in error messages),
|
|
19
|
+
// and gets back a builder + a strict validator with byte-identical behaviour to the per-product code
|
|
20
|
+
// it replaces.
|
|
21
|
+
//
|
|
22
|
+
// IMPORTANT: this module NEVER requires a product module (no `require("../dataset")`), so the
|
|
23
|
+
// dependency points product → core, never the reverse — no back-edge.
|
|
24
|
+
|
|
25
|
+
const { pathLeaf } = require("../hash");
|
|
26
|
+
|
|
27
|
+
// Same hex shape cli/receipt.js / cli/proof.js validate against, so the modules never drift. Exported
|
|
28
|
+
// so a product can reuse the EXACT regex rather than redefining (and risking drifting from) it.
|
|
29
|
+
const HEX32_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
30
|
+
|
|
31
|
+
// The SHARED trust caveat carried IN-BAND in every manifest/attestation/report across the product
|
|
32
|
+
// family. It lives in EXACTLY ONE place (here) and is imported by every product, so the caveats can
|
|
33
|
+
// NEVER drift between DataLedger, ProofParcel, and any future adapter. It states the load-bearing
|
|
34
|
+
// invariant of the whole engine: the root commits to (relPath, content) pairs — names AND bytes — and
|
|
35
|
+
// the per-file `hints` (source/license) are UNTRUSTED, self-asserted metadata bound into nothing.
|
|
36
|
+
const TRUST_NOTE =
|
|
37
|
+
"The Merkle root commits to the full set of (relPath, content) pairs (names AND bytes): any edit, " +
|
|
38
|
+
"rename, add, or remove changes the root. Per-file `hints` (source/license) are UNTRUSTED, " +
|
|
39
|
+
"self-asserted metadata — they are NOT bound into the root and prove nothing.";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalize raw per-file hints into a { relPath -> {source?,license?} } map of plain strings. Rejects
|
|
43
|
+
* a hint whose relPath is not in the tree, and a non-string source/license, so junk never lands in the
|
|
44
|
+
* manifest. Returns {} for absent hints. Product-agnostic — DataLedger and ProofParcel share it.
|
|
45
|
+
*
|
|
46
|
+
* @param {object|null|undefined} rawHints raw per-file hints keyed by relPath
|
|
47
|
+
* @param {Set<string>} knownPaths the set of relPaths actually present in the tree
|
|
48
|
+
* @returns {Object<string,{source?:string,license?:string}>}
|
|
49
|
+
*/
|
|
50
|
+
function normalizeHints(rawHints, knownPaths) {
|
|
51
|
+
if (rawHints == null) return {};
|
|
52
|
+
if (typeof rawHints !== "object" || Array.isArray(rawHints)) {
|
|
53
|
+
throw new Error("hints must be an object keyed by relPath");
|
|
54
|
+
}
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const [rel, h] of Object.entries(rawHints)) {
|
|
57
|
+
if (!knownPaths.has(rel)) {
|
|
58
|
+
throw new Error(`hint for unknown path (not in the dataset): ${JSON.stringify(rel)}`);
|
|
59
|
+
}
|
|
60
|
+
if (h == null || typeof h !== "object" || Array.isArray(h)) {
|
|
61
|
+
throw new Error(`hint for ${JSON.stringify(rel)} must be an object with source/license`);
|
|
62
|
+
}
|
|
63
|
+
const entry = {};
|
|
64
|
+
for (const k of ["source", "license"]) {
|
|
65
|
+
if (h[k] === undefined || h[k] === null) continue;
|
|
66
|
+
if (typeof h[k] !== "string") {
|
|
67
|
+
throw new Error(`hint ${k} for ${JSON.stringify(rel)} must be a string`);
|
|
68
|
+
}
|
|
69
|
+
entry[k] = h[k];
|
|
70
|
+
}
|
|
71
|
+
// Only record a hint that actually carries at least one labeled field.
|
|
72
|
+
if (Object.keys(entry).length > 0) out[rel] = entry;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build a normalized, fully-validated manifest object from a streamed directory result plus optional
|
|
79
|
+
* per-file hints, PARAMETERIZED by the product's `kind`/`note`/`label`/schema. Throws if the result is
|
|
80
|
+
* malformed, so a corrupt manifest is never produced. This is the GENERIC core every product wraps.
|
|
81
|
+
*
|
|
82
|
+
* @param {{ root: string, leaves: {path:string,contentHash:string,leaf:string}[] }} built
|
|
83
|
+
* the object cli/hash.js › hashDirStream returns
|
|
84
|
+
* @param {object} cfg the product's manifest framing
|
|
85
|
+
* @param {string} cfg.kind the on-disk `kind` discriminator (e.g. "verifyhash.dataset-manifest")
|
|
86
|
+
* @param {number} cfg.schemaVersion the schemaVersion to stamp
|
|
87
|
+
* @param {number[]} cfg.supportedSchemaVersions versions the validator accepts
|
|
88
|
+
* @param {string} cfg.note the in-band trust caveat (typically TRUST_NOTE)
|
|
89
|
+
* @param {string} [cfg.label] the noun used in error messages (default "dataset manifest")
|
|
90
|
+
* @param {object} [opts]
|
|
91
|
+
* @param {Object<string,{source?:string,license?:string}>} [opts.hints] OPTIONAL untrusted per-file hints
|
|
92
|
+
* @returns {object} a validated manifest object
|
|
93
|
+
*/
|
|
94
|
+
function buildItemManifest(built, cfg, opts = {}) {
|
|
95
|
+
_requireCfg(cfg);
|
|
96
|
+
if (!built || typeof built !== "object" || !Array.isArray(built.leaves)) {
|
|
97
|
+
throw new Error("buildItemManifest requires the object hashDirStream() returns");
|
|
98
|
+
}
|
|
99
|
+
const knownPaths = new Set(built.leaves.map((l) => l.path));
|
|
100
|
+
const hints = normalizeHints(opts.hints, knownPaths);
|
|
101
|
+
|
|
102
|
+
const manifest = {
|
|
103
|
+
kind: cfg.kind,
|
|
104
|
+
schemaVersion: cfg.schemaVersion,
|
|
105
|
+
note: cfg.note,
|
|
106
|
+
root: built.root,
|
|
107
|
+
fileCount: built.leaves.length,
|
|
108
|
+
files: built.leaves.map((l) => {
|
|
109
|
+
const entry = { relPath: l.path, contentHash: l.contentHash, leaf: l.leaf };
|
|
110
|
+
// Attach the untrusted hint INLINE on the file entry (only when present) so a consumer reads
|
|
111
|
+
// path/content/leaf/hint together. The hint never participates in the leaf or the root.
|
|
112
|
+
const h = hints[l.path];
|
|
113
|
+
if (h) entry.hints = h;
|
|
114
|
+
return entry;
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
validateItemManifest(manifest, cfg);
|
|
118
|
+
return manifest;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Strictly validate a parsed manifest object against a product's framing. Throws an Error describing the
|
|
123
|
+
* FIRST problem; never mutates and never fills defaults — a manifest either is complete and well-formed
|
|
124
|
+
* or it is rejected outright (mirroring cli/proof.js › _validate). The `label` parameterizes ONLY the
|
|
125
|
+
* human noun in error messages (default "dataset manifest", so DataLedger's strings are byte-identical);
|
|
126
|
+
* the STRUCTURAL checks (kind, schemaVersion, hex root, per-file leaf == pathLeaf(relPath, contentHash))
|
|
127
|
+
* are shared verbatim across the product family.
|
|
128
|
+
*
|
|
129
|
+
* @param {any} obj
|
|
130
|
+
* @param {object} cfg the product's manifest framing (see buildItemManifest)
|
|
131
|
+
* @returns {object} the same object, if valid
|
|
132
|
+
*/
|
|
133
|
+
function validateItemManifest(obj, cfg) {
|
|
134
|
+
_requireCfg(cfg);
|
|
135
|
+
const label = cfg.label || "dataset manifest";
|
|
136
|
+
if (obj == null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
137
|
+
throw new Error(`${label} must be a JSON object`);
|
|
138
|
+
}
|
|
139
|
+
if (obj.kind !== cfg.kind) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`not a verifyhash ${label} (kind: ${JSON.stringify(obj.kind)}; expected ${JSON.stringify(
|
|
142
|
+
cfg.kind
|
|
143
|
+
)})`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (!cfg.supportedSchemaVersions.includes(obj.schemaVersion)) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`unsupported ${label} schemaVersion: ${JSON.stringify(obj.schemaVersion)} ` +
|
|
149
|
+
`(this build understands ${JSON.stringify(cfg.supportedSchemaVersions)})`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (typeof obj.root !== "string" || !HEX32_RE.test(obj.root)) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`${label} root must be a 0x-prefixed 32-byte hex string, got: ${String(obj.root)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (!Array.isArray(obj.files)) {
|
|
158
|
+
throw new Error(`${label} field files must be an array`);
|
|
159
|
+
}
|
|
160
|
+
if (obj.files.length === 0) {
|
|
161
|
+
throw new Error(`${label} files must be non-empty (a manifest over zero files is invalid)`);
|
|
162
|
+
}
|
|
163
|
+
// fileCount, when present, must agree with the files array (catch a hand-edited count).
|
|
164
|
+
if (obj.fileCount !== undefined && obj.fileCount !== obj.files.length) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`${label} fileCount (${String(obj.fileCount)}) does not match files length (${obj.files.length})`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
obj.files.forEach((entry, i) => {
|
|
172
|
+
if (entry == null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
173
|
+
throw new Error(`${label} files[${i}] must be an object`);
|
|
174
|
+
}
|
|
175
|
+
if (typeof entry.relPath !== "string" || entry.relPath.length === 0) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`${label} files[${i}].relPath must be a non-empty string, got: ${String(entry.relPath)}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (seen.has(entry.relPath)) {
|
|
181
|
+
throw new Error(`${label} has a duplicate relPath: ${JSON.stringify(entry.relPath)}`);
|
|
182
|
+
}
|
|
183
|
+
seen.add(entry.relPath);
|
|
184
|
+
for (const f of ["contentHash", "leaf"]) {
|
|
185
|
+
if (typeof entry[f] !== "string" || !HEX32_RE.test(entry[f])) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`${label} files[${i}].${f} must be a 0x-prefixed 32-byte hex string, got: ${String(
|
|
188
|
+
entry[f]
|
|
189
|
+
)}`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// The leaf MUST be the path-bound digest of (relPath, contentHash) — re-derive it and reject a
|
|
194
|
+
// manifest whose leaf was tampered with independently of its relPath/contentHash. This is a
|
|
195
|
+
// structural self-consistency check (no content needed): it binds the three fields together so an
|
|
196
|
+
// edited leaf (or relPath, or contentHash) is caught here rather than producing a false proof.
|
|
197
|
+
const expectedLeaf = pathLeaf(entry.relPath, entry.contentHash);
|
|
198
|
+
if (entry.leaf.toLowerCase() !== expectedLeaf.toLowerCase()) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`${label} files[${i}].leaf is inconsistent with its relPath+contentHash ` +
|
|
201
|
+
`(expected ${expectedLeaf}, got ${entry.leaf})`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
// Optional hint shape, when present.
|
|
205
|
+
if (entry.hints !== undefined && entry.hints !== null) {
|
|
206
|
+
if (typeof entry.hints !== "object" || Array.isArray(entry.hints)) {
|
|
207
|
+
throw new Error(`${label} files[${i}].hints must be an object when present`);
|
|
208
|
+
}
|
|
209
|
+
for (const k of ["source", "license"]) {
|
|
210
|
+
if (entry.hints[k] !== undefined && typeof entry.hints[k] !== "string") {
|
|
211
|
+
throw new Error(`${label} files[${i}].hints.${k} must be a string when present`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return obj;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Internal: assert a product passed a structurally complete manifest config. Defends the core against a
|
|
222
|
+
* product adapter that forgot a required field (so the failure is a clear programmer error here, not a
|
|
223
|
+
* confusing downstream symptom).
|
|
224
|
+
*/
|
|
225
|
+
function _requireCfg(cfg) {
|
|
226
|
+
if (!cfg || typeof cfg !== "object") {
|
|
227
|
+
throw new Error("manifest core requires a { kind, schemaVersion, supportedSchemaVersions, note } config");
|
|
228
|
+
}
|
|
229
|
+
if (typeof cfg.kind !== "string" || cfg.kind.length === 0) {
|
|
230
|
+
throw new Error("manifest core config requires a non-empty string `kind`");
|
|
231
|
+
}
|
|
232
|
+
if (!Array.isArray(cfg.supportedSchemaVersions) || cfg.supportedSchemaVersions.length === 0) {
|
|
233
|
+
throw new Error("manifest core config requires a non-empty `supportedSchemaVersions` array");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = {
|
|
238
|
+
HEX32_RE,
|
|
239
|
+
TRUST_NOTE,
|
|
240
|
+
normalizeHints,
|
|
241
|
+
buildItemManifest,
|
|
242
|
+
validateItemManifest,
|
|
243
|
+
};
|