@mnemom/mnemom 0.17.35 → 0.17.36
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/dist/commands/agent-run.d.ts +4 -0
- package/dist/commands/agent-run.js +9 -1
- package/dist/commands/agent-share-render.js +1 -1
- package/dist/commands/scan.d.ts +13 -3
- package/dist/commands/scan.js +48 -10
- package/dist/index.js +3 -2
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js +21 -0
- package/dist/lib/scan/pr-source.js +2 -0
- package/dist/lib/scan/repo-picker.d.ts +32 -0
- package/dist/lib/scan/repo-picker.js +80 -0
- package/node_modules/@mnemom/aip-verifier/LICENSE +190 -0
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.cts +478 -0
- package/node_modules/@mnemom/aip-verifier/dist/claim-lint-CVITIRcU.d.ts +478 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs +971 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.cjs.map +1 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.d.cts +330 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.d.ts +330 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.js +933 -0
- package/node_modules/@mnemom/aip-verifier/dist/index.js.map +1 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs +879 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.cjs.map +1 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.cts +96 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.d.ts +96 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js +875 -0
- package/node_modules/@mnemom/aip-verifier/dist/render-lines.js.map +1 -0
- package/node_modules/@mnemom/aip-verifier/package.json +86 -0
- package/node_modules/@noble/ed25519/LICENSE +21 -0
- package/node_modules/@noble/ed25519/README.md +371 -0
- package/node_modules/@noble/ed25519/index.d.ts +391 -0
- package/node_modules/@noble/ed25519/index.js +933 -0
- package/node_modules/@noble/ed25519/index.ts +1134 -0
- package/node_modules/@noble/ed25519/package.json +54 -0
- package/node_modules/@noble/hashes/LICENSE +21 -0
- package/node_modules/@noble/hashes/README.md +478 -0
- package/node_modules/@noble/hashes/_blake.d.ts +20 -0
- package/node_modules/@noble/hashes/_blake.js +59 -0
- package/node_modules/@noble/hashes/_md.d.ts +95 -0
- package/node_modules/@noble/hashes/_md.js +209 -0
- package/node_modules/@noble/hashes/_u64.d.ts +36 -0
- package/node_modules/@noble/hashes/_u64.js +77 -0
- package/node_modules/@noble/hashes/argon2.d.ts +148 -0
- package/node_modules/@noble/hashes/argon2.js +577 -0
- package/node_modules/@noble/hashes/blake1.d.ts +162 -0
- package/node_modules/@noble/hashes/blake1.js +630 -0
- package/node_modules/@noble/hashes/blake2.d.ts +203 -0
- package/node_modules/@noble/hashes/blake2.js +573 -0
- package/node_modules/@noble/hashes/blake3.d.ts +68 -0
- package/node_modules/@noble/hashes/blake3.js +310 -0
- package/node_modules/@noble/hashes/eskdf.d.ts +91 -0
- package/node_modules/@noble/hashes/eskdf.js +225 -0
- package/node_modules/@noble/hashes/hkdf.d.ts +70 -0
- package/node_modules/@noble/hashes/hkdf.js +152 -0
- package/node_modules/@noble/hashes/hmac.d.ts +50 -0
- package/node_modules/@noble/hashes/hmac.js +93 -0
- package/node_modules/@noble/hashes/index.d.ts +1 -0
- package/node_modules/@noble/hashes/index.js +34 -0
- package/node_modules/@noble/hashes/legacy.d.ts +107 -0
- package/node_modules/@noble/hashes/legacy.js +348 -0
- package/node_modules/@noble/hashes/package.json +91 -0
- package/node_modules/@noble/hashes/pbkdf2.d.ts +66 -0
- package/node_modules/@noble/hashes/pbkdf2.js +176 -0
- package/node_modules/@noble/hashes/scrypt.d.ts +98 -0
- package/node_modules/@noble/hashes/scrypt.js +304 -0
- package/node_modules/@noble/hashes/sha2.d.ts +176 -0
- package/node_modules/@noble/hashes/sha2.js +446 -0
- package/node_modules/@noble/hashes/sha3-addons.d.ts +533 -0
- package/node_modules/@noble/hashes/sha3-addons.js +973 -0
- package/node_modules/@noble/hashes/sha3.d.ts +208 -0
- package/node_modules/@noble/hashes/sha3.js +475 -0
- package/node_modules/@noble/hashes/src/_blake.ts +67 -0
- package/node_modules/@noble/hashes/src/_md.ts +227 -0
- package/node_modules/@noble/hashes/src/_u64.ts +111 -0
- package/node_modules/@noble/hashes/src/argon2.ts +693 -0
- package/node_modules/@noble/hashes/src/blake1.ts +677 -0
- package/node_modules/@noble/hashes/src/blake2.ts +648 -0
- package/node_modules/@noble/hashes/src/blake3.ts +344 -0
- package/node_modules/@noble/hashes/src/eskdf.ts +265 -0
- package/node_modules/@noble/hashes/src/hkdf.ts +161 -0
- package/node_modules/@noble/hashes/src/hmac.ts +130 -0
- package/node_modules/@noble/hashes/src/index.ts +33 -0
- package/node_modules/@noble/hashes/src/legacy.ts +361 -0
- package/node_modules/@noble/hashes/src/pbkdf2.ts +219 -0
- package/node_modules/@noble/hashes/src/scrypt.ts +375 -0
- package/node_modules/@noble/hashes/src/sha2.ts +486 -0
- package/node_modules/@noble/hashes/src/sha3-addons.ts +1157 -0
- package/node_modules/@noble/hashes/src/sha3.ts +525 -0
- package/node_modules/@noble/hashes/src/utils.ts +978 -0
- package/node_modules/@noble/hashes/src/webcrypto.ts +283 -0
- package/node_modules/@noble/hashes/utils.d.ts +560 -0
- package/node_modules/@noble/hashes/utils.js +687 -0
- package/node_modules/@noble/hashes/webcrypto.d.ts +121 -0
- package/node_modules/@noble/hashes/webcrypto.js +228 -0
- package/package.json +5 -2
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ed25519 = require('@noble/ed25519');
|
|
4
|
+
var sha2_js = require('@noble/hashes/sha2.js');
|
|
5
|
+
var utils_js = require('@noble/hashes/utils.js');
|
|
6
|
+
|
|
7
|
+
// src/crypto.ts
|
|
8
|
+
function base64ToUint8(b64) {
|
|
9
|
+
const binary = atob(b64);
|
|
10
|
+
const bytes = new Uint8Array(binary.length);
|
|
11
|
+
for (let i = 0; i < binary.length; i++) {
|
|
12
|
+
bytes[i] = binary.charCodeAt(i);
|
|
13
|
+
}
|
|
14
|
+
return bytes;
|
|
15
|
+
}
|
|
16
|
+
function hexToUint8(hex) {
|
|
17
|
+
return utils_js.hexToBytes(hex);
|
|
18
|
+
}
|
|
19
|
+
function sha256Hex(input) {
|
|
20
|
+
return utils_js.bytesToHex(sha2_js.sha256(utils_js.utf8ToBytes(input)));
|
|
21
|
+
}
|
|
22
|
+
async function verifySignature(signature, payload, publicKey) {
|
|
23
|
+
try {
|
|
24
|
+
const sigBytes = base64ToUint8(signature);
|
|
25
|
+
const msgBytes = utils_js.utf8ToBytes(payload);
|
|
26
|
+
return await ed25519.verifyAsync(sigBytes, msgBytes, publicKey);
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function verifyMerkleProof(leafHash, siblings, expectedRoot) {
|
|
32
|
+
let current = leafHash;
|
|
33
|
+
for (const sibling of siblings) {
|
|
34
|
+
const pair = sibling.position === "left" ? sibling.hash + current : current + sibling.hash;
|
|
35
|
+
current = sha256Hex(pair);
|
|
36
|
+
}
|
|
37
|
+
return current === expectedRoot;
|
|
38
|
+
}
|
|
39
|
+
function computeChainHash(prevChainHash, checkpointId, verdict, thinkingBlockHash, inputCommitment, timestamp) {
|
|
40
|
+
const prev = prevChainHash ?? "genesis";
|
|
41
|
+
const preimage = [
|
|
42
|
+
prev,
|
|
43
|
+
checkpointId,
|
|
44
|
+
verdict,
|
|
45
|
+
thinkingBlockHash,
|
|
46
|
+
inputCommitment,
|
|
47
|
+
timestamp
|
|
48
|
+
].join("|");
|
|
49
|
+
return sha256Hex(preimage);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/verify.ts
|
|
53
|
+
async function verifyCertificate(certificate, options) {
|
|
54
|
+
const publicKey = resolvePublicKey(options.publicKey);
|
|
55
|
+
const signature = await checkSignature(certificate, publicKey);
|
|
56
|
+
const chain = checkChain(certificate);
|
|
57
|
+
const merkle = checkMerkle(certificate, options.merkleRoot);
|
|
58
|
+
const verdictDerivation = checkVerdictDerivation(
|
|
59
|
+
certificate,
|
|
60
|
+
options.verdictDerivationImageId
|
|
61
|
+
);
|
|
62
|
+
const allValid = signature.valid && chain.valid && (merkle === null || merkle.valid) && (verdictDerivation === null || verdictDerivation.valid);
|
|
63
|
+
return {
|
|
64
|
+
valid: allValid,
|
|
65
|
+
checks: { signature, chain, merkle, verdict_derivation: verdictDerivation },
|
|
66
|
+
certificate_id: certificate.certificate_id,
|
|
67
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function resolvePublicKey(key) {
|
|
71
|
+
if (typeof key === "string") {
|
|
72
|
+
return hexToUint8(key);
|
|
73
|
+
}
|
|
74
|
+
return key;
|
|
75
|
+
}
|
|
76
|
+
async function checkSignature(cert, publicKey) {
|
|
77
|
+
const { value, signed_payload } = cert.proofs.signature;
|
|
78
|
+
const valid = await verifySignature(value, signed_payload, publicKey);
|
|
79
|
+
return {
|
|
80
|
+
valid,
|
|
81
|
+
details: valid ? "Ed25519 signature is valid" : "Ed25519 signature verification failed"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function checkChain(cert) {
|
|
85
|
+
const expected = computeChainHash(
|
|
86
|
+
cert.proofs.chain.prev_chain_hash,
|
|
87
|
+
cert.subject.checkpoint_id,
|
|
88
|
+
cert.claims.verdict,
|
|
89
|
+
cert.input_commitments.thinking_block_hash,
|
|
90
|
+
cert.input_commitments.combined_commitment,
|
|
91
|
+
cert.issued_at
|
|
92
|
+
);
|
|
93
|
+
const valid = expected === cert.proofs.chain.chain_hash;
|
|
94
|
+
return {
|
|
95
|
+
valid,
|
|
96
|
+
details: valid ? "Chain hash matches recomputed value" : `Chain hash mismatch: expected ${expected}, got ${cert.proofs.chain.chain_hash}`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function checkMerkle(cert, merkleRoot) {
|
|
100
|
+
if (cert.proofs.merkle === null) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (merkleRoot === void 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const { leaf_hash, inclusion_proof, root } = cert.proofs.merkle;
|
|
107
|
+
if (root !== merkleRoot) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
details: `Merkle root mismatch: certificate claims ${root}, expected ${merkleRoot}`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const valid = verifyMerkleProof(leaf_hash, inclusion_proof, merkleRoot);
|
|
114
|
+
return {
|
|
115
|
+
valid,
|
|
116
|
+
details: valid ? "Merkle inclusion proof is valid" : "Merkle inclusion proof verification failed"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function checkVerdictDerivation(cert, expectedImageId) {
|
|
120
|
+
const proof = cert.proofs.verdict_derivation;
|
|
121
|
+
if (proof === null || proof === void 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (!["SP1-STARK", "RISC-Zero-STARK"].includes(proof.method)) {
|
|
125
|
+
return {
|
|
126
|
+
valid: false,
|
|
127
|
+
details: `Unknown verdict derivation method: ${proof.method}`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!proof.journal || !proof.image_id || !proof.receipt) {
|
|
131
|
+
return {
|
|
132
|
+
valid: false,
|
|
133
|
+
details: "Verdict derivation proof is missing required fields (journal, image_id, or receipt)"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (expectedImageId !== void 0 && proof.image_id !== expectedImageId) {
|
|
137
|
+
return {
|
|
138
|
+
valid: false,
|
|
139
|
+
details: `Image ID mismatch: proof has ${proof.image_id}, expected ${expectedImageId}`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
let journal;
|
|
143
|
+
try {
|
|
144
|
+
journal = JSON.parse(proof.journal);
|
|
145
|
+
} catch {
|
|
146
|
+
return {
|
|
147
|
+
valid: false,
|
|
148
|
+
details: "Failed to parse verdict derivation journal as JSON"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (journal.verdict !== void 0) {
|
|
152
|
+
const certVerdict = cert.claims.verdict;
|
|
153
|
+
if (journal.verdict !== certVerdict) {
|
|
154
|
+
return {
|
|
155
|
+
valid: false,
|
|
156
|
+
details: `Journal verdict "${journal.verdict}" does not match certificate verdict "${certVerdict}"`
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (journal.thinking_hash !== void 0) {
|
|
161
|
+
const certThinkingHash = cert.input_commitments.thinking_block_hash;
|
|
162
|
+
if (journal.thinking_hash !== certThinkingHash) {
|
|
163
|
+
return {
|
|
164
|
+
valid: false,
|
|
165
|
+
details: `Journal thinking_hash does not match certificate input_commitments.thinking_block_hash`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (journal.card_hash !== void 0) {
|
|
170
|
+
const certCardHash = cert.input_commitments.card_hash;
|
|
171
|
+
if (journal.card_hash !== certCardHash) {
|
|
172
|
+
return {
|
|
173
|
+
valid: false,
|
|
174
|
+
details: `Journal card_hash does not match certificate input_commitments.card_hash`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (journal.values_hash !== void 0) {
|
|
179
|
+
const certValuesHash = cert.input_commitments.values_hash;
|
|
180
|
+
if (journal.values_hash !== certValuesHash) {
|
|
181
|
+
return {
|
|
182
|
+
valid: false,
|
|
183
|
+
details: `Journal values_hash does not match certificate input_commitments.values_hash`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
valid: true,
|
|
189
|
+
details: "Verdict derivation proof is structurally valid (STARK receipt verification deferred to server)"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/canonical.ts
|
|
194
|
+
function canonicalJson(value) {
|
|
195
|
+
if (value === null || typeof value !== "object") {
|
|
196
|
+
return JSON.stringify(value);
|
|
197
|
+
}
|
|
198
|
+
if (Array.isArray(value)) {
|
|
199
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
200
|
+
}
|
|
201
|
+
const record = value;
|
|
202
|
+
const entries = Object.keys(record).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(record[k])}`);
|
|
203
|
+
return `{${entries.join(",")}}`;
|
|
204
|
+
}
|
|
205
|
+
function base64urlToUint8(input) {
|
|
206
|
+
if (typeof input !== "string" || !/^[A-Za-z0-9_-]*={0,2}$/.test(input)) {
|
|
207
|
+
throw new Error("not base64url");
|
|
208
|
+
}
|
|
209
|
+
const b64 = input.replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
210
|
+
const padded = b64 + "===".slice(0, (4 - b64.length % 4) % 4);
|
|
211
|
+
const binary = atob(padded);
|
|
212
|
+
const out = new Uint8Array(binary.length);
|
|
213
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/statements.ts
|
|
218
|
+
var STATEMENT_TYPES = [
|
|
219
|
+
"AAP-ConductRecord/v1",
|
|
220
|
+
"AAP-PRRecord/v1",
|
|
221
|
+
"AAP-Outcome/v1",
|
|
222
|
+
"AAP-ShareCard/v1"
|
|
223
|
+
];
|
|
224
|
+
var BUNDLE_TYP = "AAP-RecordBundle/v1";
|
|
225
|
+
var CARD_LINE_CLASSES = ["measured", "verdict", "outcome"];
|
|
226
|
+
var REASONS = {
|
|
227
|
+
not_an_object: "not_an_object",
|
|
228
|
+
unknown_typ: "unknown_typ",
|
|
229
|
+
unsupported_alg: "unsupported_alg",
|
|
230
|
+
signing_key_id_missing: "signing_key_id_missing",
|
|
231
|
+
signature_missing: "signature_missing",
|
|
232
|
+
signature_malformed: "signature_malformed",
|
|
233
|
+
key_not_found: "key_not_found",
|
|
234
|
+
key_not_ed25519: "key_not_ed25519",
|
|
235
|
+
signature_invalid: "signature_invalid",
|
|
236
|
+
bundle_not_an_object: "bundle_not_an_object",
|
|
237
|
+
bundle_unknown_typ: "bundle_unknown_typ",
|
|
238
|
+
bundle_statements_missing: "bundle_statements_missing",
|
|
239
|
+
digest_malformed: "digest_malformed",
|
|
240
|
+
linked_statement_wrong_typ: "linked_statement_wrong_typ",
|
|
241
|
+
card_line_class_not_allowed: "card_line_class_not_allowed",
|
|
242
|
+
card_verdict_without_coverage: "card_verdict_without_coverage"
|
|
243
|
+
};
|
|
244
|
+
var FLAGS = {
|
|
245
|
+
card_hash_not_recorded: "card hash not recorded",
|
|
246
|
+
card_not_bound: "card not bound",
|
|
247
|
+
linked_statement_not_in_bundle: "linked statement not in bundle"
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// src/verify-record.ts
|
|
251
|
+
var DIGEST_RE = /^[0-9a-f]{64}$/;
|
|
252
|
+
function isObject(v) {
|
|
253
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
254
|
+
}
|
|
255
|
+
function statementCommitment(statement) {
|
|
256
|
+
const { signature: _signature, ...rest } = statement;
|
|
257
|
+
return canonicalJson(rest);
|
|
258
|
+
}
|
|
259
|
+
function statementDigest(statement) {
|
|
260
|
+
return utils_js.bytesToHex(sha2_js.sha256(utils_js.utf8ToBytes(statementCommitment(statement))));
|
|
261
|
+
}
|
|
262
|
+
function selectKey(jwks, kid) {
|
|
263
|
+
const keys = isObject(jwks) && Array.isArray(jwks.keys) ? jwks.keys : [];
|
|
264
|
+
const jwk = keys.find((k) => isObject(k) && k.kid === kid);
|
|
265
|
+
if (!jwk) return { reason: REASONS.key_not_found };
|
|
266
|
+
if (jwk.alg !== "EdDSA" || jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || typeof jwk.x !== "string") {
|
|
267
|
+
return { reason: REASONS.key_not_ed25519 };
|
|
268
|
+
}
|
|
269
|
+
let key;
|
|
270
|
+
try {
|
|
271
|
+
key = base64urlToUint8(jwk.x);
|
|
272
|
+
} catch {
|
|
273
|
+
return { reason: REASONS.key_not_ed25519 };
|
|
274
|
+
}
|
|
275
|
+
if (key.length !== 32) return { reason: REASONS.key_not_ed25519 };
|
|
276
|
+
return { key };
|
|
277
|
+
}
|
|
278
|
+
function checkpointFlags(checkpoints) {
|
|
279
|
+
if (!Array.isArray(checkpoints)) return [];
|
|
280
|
+
const flags = [];
|
|
281
|
+
for (const c of checkpoints) {
|
|
282
|
+
if (!isObject(c)) continue;
|
|
283
|
+
const subject = typeof c.checkpoint_id === "string" ? c.checkpoint_id : "<no checkpoint_id>";
|
|
284
|
+
if (typeof c.card_hash !== "string" || c.card_hash.length === 0) {
|
|
285
|
+
flags.push({ flag: FLAGS.card_hash_not_recorded, subject });
|
|
286
|
+
}
|
|
287
|
+
const bound = typeof c.combined_commitment === "string" && c.combined_commitment.length > 0 && typeof c.commitment_signature === "string" && c.commitment_signature.length > 0;
|
|
288
|
+
if (!bound) flags.push({ flag: FLAGS.card_not_bound, subject });
|
|
289
|
+
}
|
|
290
|
+
return flags;
|
|
291
|
+
}
|
|
292
|
+
function shareCardReasons(lines) {
|
|
293
|
+
if (!Array.isArray(lines)) return [];
|
|
294
|
+
const reasons = [];
|
|
295
|
+
for (const line of lines) {
|
|
296
|
+
if (!isObject(line) || !CARD_LINE_CLASSES.includes(line.class)) {
|
|
297
|
+
reasons.push(REASONS.card_line_class_not_allowed);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (line.class === "verdict") {
|
|
301
|
+
const cov = line.coverage;
|
|
302
|
+
const ok = isObject(cov) && Number.isInteger(cov.judged) && Number.isInteger(cov.total) && cov.judged >= 0 && cov.total >= cov.judged;
|
|
303
|
+
if (!ok) reasons.push(REASONS.card_verdict_without_coverage);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return [...new Set(reasons)];
|
|
307
|
+
}
|
|
308
|
+
function digestFieldsOf(statement) {
|
|
309
|
+
const out = [];
|
|
310
|
+
if (statement.previous_digest !== void 0 && statement.previous_digest !== null) {
|
|
311
|
+
out.push(statement.previous_digest);
|
|
312
|
+
}
|
|
313
|
+
if (statement.pr_record_digest !== void 0) out.push(statement.pr_record_digest);
|
|
314
|
+
if (statement.source_digest !== void 0) out.push(statement.source_digest);
|
|
315
|
+
if (statement.conduct_digests !== void 0) {
|
|
316
|
+
if (Array.isArray(statement.conduct_digests)) out.push(...statement.conduct_digests);
|
|
317
|
+
else out.push(null);
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
async function verifyStatement(statement, jwks) {
|
|
322
|
+
const fail = (reasons2, partial = {}) => ({
|
|
323
|
+
valid: false,
|
|
324
|
+
typ: null,
|
|
325
|
+
digest: null,
|
|
326
|
+
signing_key_id: null,
|
|
327
|
+
flags: [],
|
|
328
|
+
...partial,
|
|
329
|
+
reasons: reasons2
|
|
330
|
+
});
|
|
331
|
+
if (!isObject(statement)) return fail([REASONS.not_an_object]);
|
|
332
|
+
const typ = typeof statement.typ === "string" ? statement.typ : null;
|
|
333
|
+
const digest = statementDigest(statement);
|
|
334
|
+
const kid = typeof statement.signing_key_id === "string" ? statement.signing_key_id : null;
|
|
335
|
+
const base = { typ, digest, signing_key_id: kid };
|
|
336
|
+
if (!typ || !STATEMENT_TYPES.includes(typ)) {
|
|
337
|
+
return fail([REASONS.unknown_typ], base);
|
|
338
|
+
}
|
|
339
|
+
if (statement.alg !== void 0 && statement.alg !== "EdDSA") {
|
|
340
|
+
return fail([REASONS.unsupported_alg], base);
|
|
341
|
+
}
|
|
342
|
+
if (!kid) return fail([REASONS.signing_key_id_missing], base);
|
|
343
|
+
if (typeof statement.signature !== "string" || statement.signature.length === 0) {
|
|
344
|
+
return fail([REASONS.signature_missing], base);
|
|
345
|
+
}
|
|
346
|
+
let sig;
|
|
347
|
+
try {
|
|
348
|
+
sig = base64urlToUint8(statement.signature);
|
|
349
|
+
} catch {
|
|
350
|
+
return fail([REASONS.signature_malformed], base);
|
|
351
|
+
}
|
|
352
|
+
if (sig.length !== 64) return fail([REASONS.signature_malformed], base);
|
|
353
|
+
const selected = selectKey(jwks, kid);
|
|
354
|
+
if (!selected.key) return fail([selected.reason], base);
|
|
355
|
+
let sigOk = false;
|
|
356
|
+
try {
|
|
357
|
+
sigOk = await ed25519.verifyAsync(sig, utils_js.utf8ToBytes(statementCommitment(statement)), selected.key);
|
|
358
|
+
} catch {
|
|
359
|
+
sigOk = false;
|
|
360
|
+
}
|
|
361
|
+
if (!sigOk) return fail([REASONS.signature_invalid], base);
|
|
362
|
+
const reasons = [];
|
|
363
|
+
for (const d of digestFieldsOf(statement)) {
|
|
364
|
+
if (typeof d !== "string" || !DIGEST_RE.test(d)) {
|
|
365
|
+
reasons.push(REASONS.digest_malformed);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (typ === "AAP-ShareCard/v1") reasons.push(...shareCardReasons(statement.lines));
|
|
370
|
+
const flags = typ === "AAP-ConductRecord/v1" ? checkpointFlags(statement.checkpoints) : [];
|
|
371
|
+
return {
|
|
372
|
+
valid: reasons.length === 0,
|
|
373
|
+
typ,
|
|
374
|
+
digest,
|
|
375
|
+
signing_key_id: kid,
|
|
376
|
+
reasons,
|
|
377
|
+
flags
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
var LINK_TARGETS = {
|
|
381
|
+
previous_digest: "same",
|
|
382
|
+
conduct_digests: ["AAP-ConductRecord/v1"],
|
|
383
|
+
pr_record_digest: ["AAP-PRRecord/v1"],
|
|
384
|
+
source_digest: ["AAP-ConductRecord/v1", "AAP-PRRecord/v1", "AAP-Outcome/v1"]
|
|
385
|
+
};
|
|
386
|
+
async function verify(input, jwks) {
|
|
387
|
+
const verified_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
388
|
+
const result = (valid, reasons2, flags2, statements2) => ({ valid, reasons: reasons2, flags: flags2, statements: statements2, verified_at });
|
|
389
|
+
if (!isObject(input)) return result(false, [REASONS.bundle_not_an_object], [], []);
|
|
390
|
+
if (input.typ !== BUNDLE_TYP && Array.isArray(input.statements) && !STATEMENT_TYPES.includes(input.typ)) {
|
|
391
|
+
return result(false, [REASONS.bundle_unknown_typ], [], []);
|
|
392
|
+
}
|
|
393
|
+
if (input.typ !== BUNDLE_TYP) {
|
|
394
|
+
const one = await verifyStatement(input, jwks);
|
|
395
|
+
return result(one.valid, one.reasons.map((r) => `0:${r}`), one.flags, [one]);
|
|
396
|
+
}
|
|
397
|
+
const rawStatements = Array.isArray(input.statements) ? input.statements : null;
|
|
398
|
+
if (!rawStatements) {
|
|
399
|
+
return result(false, [REASONS.bundle_statements_missing], [], []);
|
|
400
|
+
}
|
|
401
|
+
const statements = [];
|
|
402
|
+
for (const s of rawStatements) statements.push(await verifyStatement(s, jwks));
|
|
403
|
+
const byDigest = /* @__PURE__ */ new Map();
|
|
404
|
+
statements.forEach((s) => {
|
|
405
|
+
if (s.digest) byDigest.set(s.digest, s);
|
|
406
|
+
});
|
|
407
|
+
const reasons = [];
|
|
408
|
+
const flags = [];
|
|
409
|
+
statements.forEach((s, i) => {
|
|
410
|
+
s.reasons.forEach((r) => reasons.push(`${i}:${r}`));
|
|
411
|
+
flags.push(...s.flags);
|
|
412
|
+
const raw = rawStatements[i];
|
|
413
|
+
if (!s.valid || !isObject(raw)) return;
|
|
414
|
+
for (const [field, targets] of Object.entries(LINK_TARGETS)) {
|
|
415
|
+
const value = raw[field];
|
|
416
|
+
const digests = Array.isArray(value) ? value : [value];
|
|
417
|
+
for (const d of digests) {
|
|
418
|
+
if (typeof d !== "string") continue;
|
|
419
|
+
const linked = byDigest.get(d);
|
|
420
|
+
if (!linked) {
|
|
421
|
+
flags.push({ flag: FLAGS.linked_statement_not_in_bundle, subject: d });
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const allowed = targets === "same" ? [s.typ] : targets;
|
|
425
|
+
if (!linked.typ || !allowed.includes(linked.typ)) {
|
|
426
|
+
reasons.push(`${i}:${REASONS.linked_statement_wrong_typ}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
return result(reasons.length === 0, reasons, flags, statements);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/claim-table.ts
|
|
435
|
+
var CLAIM_CLASSES = ["measured", "verdict", "estimate", "benchmark", "outcome"];
|
|
436
|
+
var SURFACES = ["record_page", "check_summary", "pr_comment", "share_card", "period_card"];
|
|
437
|
+
var CARD_SURFACES = ["share_card", "period_card"];
|
|
438
|
+
var ESTIMATE_FORBIDDEN_SURFACES = ["pr_comment", "share_card", "period_card"];
|
|
439
|
+
var MODES = ["off", "observe", "nudge", "enforce"];
|
|
440
|
+
var MODE_VERBS = {
|
|
441
|
+
off: "not checked",
|
|
442
|
+
observe: "flagged",
|
|
443
|
+
nudge: "flagged, agent nudged",
|
|
444
|
+
enforce: "stopped"
|
|
445
|
+
};
|
|
446
|
+
var PROTECTION_VERDICTS = ["pass", "warn", "nudge", "quarantine", "block"];
|
|
447
|
+
function protectionVerb(mode, verdict) {
|
|
448
|
+
if (mode === "off" || verdict === "pass") return null;
|
|
449
|
+
if (mode === "observe") return MODE_VERBS.observe;
|
|
450
|
+
if (mode === "nudge") return verdict === "nudge" ? MODE_VERBS.nudge : MODE_VERBS.observe;
|
|
451
|
+
return verdict === "block" || verdict === "quarantine" ? MODE_VERBS.enforce : MODE_VERBS.observe;
|
|
452
|
+
}
|
|
453
|
+
var GOAL_MODES = ["sealed", "implicit", "off"];
|
|
454
|
+
var CLAIM_TABLE = {
|
|
455
|
+
commits_recorded: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
456
|
+
messages_from_person: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
457
|
+
compactions: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
458
|
+
tokens_sent_vs_reaching: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
459
|
+
peak_context: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
460
|
+
model_cost: { class: "measured", verb_by_mode: false, money_allowed: true },
|
|
461
|
+
mu_cost: { class: "measured", verb_by_mode: false, money_allowed: true },
|
|
462
|
+
approval: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
463
|
+
/** How the goal was set: "sealed contract", "implicit goal" or "not checked" (M7: its own measured line). */
|
|
464
|
+
goal_mode: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
465
|
+
goal: { class: "verdict", verb_by_mode: true, money_allowed: false },
|
|
466
|
+
/** The protection area's mode when no act is shown: "not checked" under off. */
|
|
467
|
+
protection_mode: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
468
|
+
protection: { class: "verdict", verb_by_mode: true, money_allowed: false },
|
|
469
|
+
autonomy: { class: "measured", verb_by_mode: false, money_allowed: false },
|
|
470
|
+
savings_estimate: {
|
|
471
|
+
class: "estimate",
|
|
472
|
+
verb_by_mode: false,
|
|
473
|
+
money_allowed: true,
|
|
474
|
+
surfaces: ["record_page", "check_summary"]
|
|
475
|
+
},
|
|
476
|
+
benchmark: { class: "benchmark", verb_by_mode: false, money_allowed: false },
|
|
477
|
+
outcome: { class: "outcome", verb_by_mode: false, money_allowed: false }
|
|
478
|
+
};
|
|
479
|
+
function isClaimField(field) {
|
|
480
|
+
return typeof field === "string" && Object.prototype.hasOwnProperty.call(CLAIM_TABLE, field);
|
|
481
|
+
}
|
|
482
|
+
var LINE_KEY_FIELDS = {
|
|
483
|
+
"cost.model": "model_cost",
|
|
484
|
+
"cost.mu": "mu_cost",
|
|
485
|
+
"goal.mode": "goal_mode",
|
|
486
|
+
"goal.verdict": "goal",
|
|
487
|
+
"protection.mode": "protection_mode",
|
|
488
|
+
"people.messages": "messages_from_person",
|
|
489
|
+
"context.compactions": "compactions"
|
|
490
|
+
};
|
|
491
|
+
var PROTECTION_ACT_KEY = /^protection\.act\.\d+$/;
|
|
492
|
+
function fieldForLineKey(key) {
|
|
493
|
+
if (typeof key !== "string") return void 0;
|
|
494
|
+
if (PROTECTION_ACT_KEY.test(key)) return "protection";
|
|
495
|
+
return Object.prototype.hasOwnProperty.call(LINE_KEY_FIELDS, key) ? LINE_KEY_FIELDS[key] : void 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/claim-lint.ts
|
|
499
|
+
var LINT_RULES = {
|
|
500
|
+
one_class_per_line: "one_class_per_line",
|
|
501
|
+
forbidden_word: "forbidden_word",
|
|
502
|
+
money_on_goal_or_protection: "money_on_goal_or_protection",
|
|
503
|
+
implicit_goal_sealed: "implicit_goal_sealed",
|
|
504
|
+
goal_off_not_checked: "goal_off_not_checked",
|
|
505
|
+
estimate_on_forbidden_surface: "estimate_on_forbidden_surface",
|
|
506
|
+
verb_mismatch: "verb_mismatch",
|
|
507
|
+
enforce_without_tool_name: "enforce_without_tool_name",
|
|
508
|
+
tool_name_on_card: "tool_name_on_card",
|
|
509
|
+
tool_arguments_shown: "tool_arguments_shown",
|
|
510
|
+
trust_claim: "trust_claim",
|
|
511
|
+
savings_percentage: "savings_percentage",
|
|
512
|
+
benchmark_not_published: "benchmark_not_published",
|
|
513
|
+
verdict_without_coverage: "verdict_without_coverage",
|
|
514
|
+
card_class_not_allowed: "card_class_not_allowed",
|
|
515
|
+
period_card_without_revert: "period_card_without_revert",
|
|
516
|
+
field_not_on_surface: "field_not_on_surface",
|
|
517
|
+
provisional_mu_on_card: "provisional_mu_on_card",
|
|
518
|
+
provisional_mu_off_cost_line: "provisional_mu_off_cost_line",
|
|
519
|
+
unknown_surface: "unknown_surface"
|
|
520
|
+
};
|
|
521
|
+
function clauseHas(text, rule) {
|
|
522
|
+
const seen = /* @__PURE__ */ new Set();
|
|
523
|
+
for (const m of text.matchAll(rule.tokens)) {
|
|
524
|
+
const kind = Object.entries(m.groups ?? {}).find(([, v]) => v !== void 0)?.[0];
|
|
525
|
+
if (kind === void 0) continue;
|
|
526
|
+
if (kind === "brk") {
|
|
527
|
+
seen.clear();
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (rule.alone?.includes(kind)) return true;
|
|
531
|
+
if (rule.pairs.some(([first, then]) => then === kind && seen.has(first))) return true;
|
|
532
|
+
seen.add(kind);
|
|
533
|
+
}
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
var FORBIDDEN_WORD = /\b(safe|safely|unsafe|correct|correctly|incorrect|prevent|prevents|prevented|preventing|prevention)\b/i;
|
|
537
|
+
var MONEY = /(?:\$|€|£|¥)\s?\d|\b(?<!\d,+)\d[\d,]*(?:\.\d+)?\s?(?:USD|EUR|GBP|dollars?|cents?)\b/i;
|
|
538
|
+
var SEALED = /\bsealed\b/i;
|
|
539
|
+
var ESTIMATE_WORDS = /\b(estimate[ds]?|estimation|saving|savings|saved|would have cost|counterfactual)\b/i;
|
|
540
|
+
var TRUST_CLAIM = /\b(minutes?|seconds?|hours?)\s+to\s+trust\b|\btrust(ed)?\s+in\s+(under\s+|about\s+)?\d+\s*(minutes?|seconds?|hours?|min|s|h)\b|\breview[- ]time\b|\btime\s+to\s+review\b|\bwithout\s+re-?check(ing)?\b|\bno\s+need\s+to\s+(re-?check|review)\b|\btrust(ed)?\s+without\b/i;
|
|
541
|
+
var SAVINGS_PERCENTAGE = {
|
|
542
|
+
tokens: /\d+(?<percent>(?:\.\d+)?\s?%)?|\b(?<less>less)\b|\b(?<saving>sav(?:e[sd]?|ings?)|cheaper|reduction|reduced)\b|(?<brk>[.\n])/gi,
|
|
543
|
+
pairs: [
|
|
544
|
+
["percent", "saving"],
|
|
545
|
+
["percent", "less"],
|
|
546
|
+
["saving", "percent"]
|
|
547
|
+
]
|
|
548
|
+
};
|
|
549
|
+
var REVERT = /\brevert(s|ed)?\b/i;
|
|
550
|
+
var PROVISIONAL_MU = {
|
|
551
|
+
tokens: /(?<approx>(?:≈|~|\bapprox(?:\.|imately)?\b|\babout\b)\s*\d[\d,]*(?:\.\d+)?\s*(?:mmu\b|mu\b|μ|µ))|\d[\d,]*(?<amount>(?:\.\d+)?\s*(?:mmu\b|mu\b|μ|µ))?|\b(?<pending>pending)\b|(?<brk>[.\n])/gi,
|
|
552
|
+
alone: ["approx"],
|
|
553
|
+
pairs: [
|
|
554
|
+
["amount", "pending"],
|
|
555
|
+
["pending", "amount"]
|
|
556
|
+
]
|
|
557
|
+
};
|
|
558
|
+
var NOT_CHECKED = /^not checked$/i;
|
|
559
|
+
var COVERAGE_IN_TEXT = /\b(\d+)\s+of\s+(\d+)\s+turns\s+judged\b/i;
|
|
560
|
+
var MODE_VERB_REGEX = {
|
|
561
|
+
off: NOT_CHECKED,
|
|
562
|
+
observe: /\bflagged\b/i,
|
|
563
|
+
nudge: /\bflagged, agent nudged\b/i,
|
|
564
|
+
enforce: /\bstopped\b/i
|
|
565
|
+
};
|
|
566
|
+
var OTHER_MODE_VERBS = {
|
|
567
|
+
off: [/\bflagged\b/i, /\bnudged\b/i, /\bstopped\b/i],
|
|
568
|
+
observe: [/\bnudged\b/i, /\bstopped\b/i],
|
|
569
|
+
nudge: [/\bstopped\b/i],
|
|
570
|
+
enforce: [/\bflagged\b/i, /\bnudged\b/i]
|
|
571
|
+
};
|
|
572
|
+
function isObject2(v) {
|
|
573
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
574
|
+
}
|
|
575
|
+
function coverageObjectValid(cov) {
|
|
576
|
+
return isObject2(cov) && Number.isInteger(cov.judged) && Number.isInteger(cov.total) && cov.judged >= 0 && cov.total >= cov.judged;
|
|
577
|
+
}
|
|
578
|
+
function coverageShown(line) {
|
|
579
|
+
const cov = line.coverage;
|
|
580
|
+
if (cov !== void 0) return coverageObjectValid(cov);
|
|
581
|
+
const m = COVERAGE_IN_TEXT.exec(line.text);
|
|
582
|
+
return m !== null && Number(m[2]) >= Number(m[1]);
|
|
583
|
+
}
|
|
584
|
+
function expectedVerb(mode, verdict) {
|
|
585
|
+
if (mode === "off") return "off";
|
|
586
|
+
if (verdict === void 0) return mode === "observe" ? "observe" : void 0;
|
|
587
|
+
if (!PROTECTION_VERDICTS.includes(verdict)) return void 0;
|
|
588
|
+
const verb = protectionVerb(mode, verdict);
|
|
589
|
+
return MODES.find((m) => MODE_VERBS[m] === verb);
|
|
590
|
+
}
|
|
591
|
+
function escapeRegExp(s) {
|
|
592
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
593
|
+
}
|
|
594
|
+
function argumentsAppear(text, args) {
|
|
595
|
+
if (args === void 0 || args === null) return false;
|
|
596
|
+
const needles = [];
|
|
597
|
+
const walk = (v) => {
|
|
598
|
+
if (typeof v === "string") {
|
|
599
|
+
if (v.length >= 3) needles.push(v);
|
|
600
|
+
} else if (typeof v === "number" || typeof v === "boolean") {
|
|
601
|
+
needles.push(String(v));
|
|
602
|
+
} else if (Array.isArray(v)) {
|
|
603
|
+
v.forEach(walk);
|
|
604
|
+
} else if (isObject2(v)) {
|
|
605
|
+
Object.values(v).forEach(walk);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
walk(args);
|
|
609
|
+
if (needles.length === 0) return false;
|
|
610
|
+
try {
|
|
611
|
+
needles.push(JSON.stringify(args));
|
|
612
|
+
} catch {
|
|
613
|
+
}
|
|
614
|
+
return needles.some((n) => n.length > 0 && text.includes(n));
|
|
615
|
+
}
|
|
616
|
+
function lintLine(surface, line, index, out) {
|
|
617
|
+
const push = (rule) => {
|
|
618
|
+
out.push({ rule, line: index, text: isObject2(line) && typeof line.text === "string" ? line.text : "" });
|
|
619
|
+
};
|
|
620
|
+
if (!isObject2(line) || typeof line.text !== "string" || typeof line.class !== "string") {
|
|
621
|
+
push(LINT_RULES.one_class_per_line);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const text = line.text;
|
|
625
|
+
const onCard = CARD_SURFACES.includes(surface);
|
|
626
|
+
const field = line.field !== void 0 ? line.field : fieldForLineKey(line.key);
|
|
627
|
+
const cls = line.class;
|
|
628
|
+
if (!CLAIM_CLASSES.includes(cls)) {
|
|
629
|
+
push(LINT_RULES.one_class_per_line);
|
|
630
|
+
} else if (field !== void 0) {
|
|
631
|
+
if (!isClaimField(field)) {
|
|
632
|
+
push(LINT_RULES.one_class_per_line);
|
|
633
|
+
} else {
|
|
634
|
+
const entry = CLAIM_TABLE[field];
|
|
635
|
+
if (entry.class !== cls) push(LINT_RULES.one_class_per_line);
|
|
636
|
+
if (entry.surfaces && !entry.surfaces.includes(surface)) {
|
|
637
|
+
push(LINT_RULES.field_not_on_surface);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (cls !== "estimate" && ESTIMATE_WORDS.test(text)) push(LINT_RULES.one_class_per_line);
|
|
642
|
+
if (FORBIDDEN_WORD.test(text)) push(LINT_RULES.forbidden_word);
|
|
643
|
+
const isGoal = field === "goal" || field === "goal_mode";
|
|
644
|
+
const isProtection = field === "protection" || field === "protection_mode";
|
|
645
|
+
if ((isGoal || isProtection) && MONEY.test(text)) push(LINT_RULES.money_on_goal_or_protection);
|
|
646
|
+
if (field !== void 0 && isClaimField(field) && !CLAIM_TABLE[field].money_allowed) {
|
|
647
|
+
if (!isGoal && !isProtection && MONEY.test(text)) push(LINT_RULES.one_class_per_line);
|
|
648
|
+
}
|
|
649
|
+
if (isGoal) {
|
|
650
|
+
if (line.goal_mode === "implicit" && SEALED.test(text)) push(LINT_RULES.implicit_goal_sealed);
|
|
651
|
+
if (line.goal_mode === "off" && !NOT_CHECKED.test(text.trim())) push(LINT_RULES.goal_off_not_checked);
|
|
652
|
+
}
|
|
653
|
+
if (cls === "estimate" || ESTIMATE_WORDS.test(text)) {
|
|
654
|
+
if (ESTIMATE_FORBIDDEN_SURFACES.includes(surface)) push(LINT_RULES.estimate_on_forbidden_surface);
|
|
655
|
+
}
|
|
656
|
+
if (line.mode !== void 0) {
|
|
657
|
+
const verb = MODES.includes(line.mode) ? expectedVerb(line.mode, line.verdict) : void 0;
|
|
658
|
+
const name = typeof line.tool_name === "string" ? line.tool_name.trim() : "";
|
|
659
|
+
const nameShown = name.length > 0 && new RegExp(`(^|[^A-Za-z0-9_])${escapeRegExp(name)}([^A-Za-z0-9_]|$)`).test(text);
|
|
660
|
+
if (verb === void 0) {
|
|
661
|
+
push(LINT_RULES.verb_mismatch);
|
|
662
|
+
} else if (!MODE_VERB_REGEX[verb].test(verb === "off" ? text.trim() : text)) {
|
|
663
|
+
push(LINT_RULES.verb_mismatch);
|
|
664
|
+
} else if (OTHER_MODE_VERBS[verb].some((re) => re.test(text))) {
|
|
665
|
+
push(LINT_RULES.verb_mismatch);
|
|
666
|
+
} else if (verb !== "enforce" && nameShown && !onCard) {
|
|
667
|
+
push(LINT_RULES.verb_mismatch);
|
|
668
|
+
}
|
|
669
|
+
if (onCard) {
|
|
670
|
+
if (nameShown) push(LINT_RULES.tool_name_on_card);
|
|
671
|
+
} else if (verb === "enforce" && !nameShown) {
|
|
672
|
+
push(LINT_RULES.enforce_without_tool_name);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (argumentsAppear(text, line.tool_arguments)) push(LINT_RULES.tool_arguments_shown);
|
|
676
|
+
if (clauseHas(text, PROVISIONAL_MU)) {
|
|
677
|
+
if (onCard) push(LINT_RULES.provisional_mu_on_card);
|
|
678
|
+
if (field !== "mu_cost") push(LINT_RULES.provisional_mu_off_cost_line);
|
|
679
|
+
}
|
|
680
|
+
if (TRUST_CLAIM.test(text)) push(LINT_RULES.trust_claim);
|
|
681
|
+
if (clauseHas(text, SAVINGS_PERCENTAGE)) push(LINT_RULES.savings_percentage);
|
|
682
|
+
if (cls === "benchmark") push(LINT_RULES.benchmark_not_published);
|
|
683
|
+
if (cls === "verdict") {
|
|
684
|
+
const covered = onCard ? coverageObjectValid(line.coverage) : field === "protection" || coverageShown(line);
|
|
685
|
+
if (!covered) push(LINT_RULES.verdict_without_coverage);
|
|
686
|
+
}
|
|
687
|
+
if (onCard && cls !== "measured" && cls !== "verdict" && cls !== "outcome") push(LINT_RULES.card_class_not_allowed);
|
|
688
|
+
}
|
|
689
|
+
function lintRendering(rendering) {
|
|
690
|
+
const violations = [];
|
|
691
|
+
if (!isObject2(rendering) || !Array.isArray(rendering.lines)) {
|
|
692
|
+
return { ok: false, violations: [{ rule: LINT_RULES.unknown_surface, line: -1, text: "" }] };
|
|
693
|
+
}
|
|
694
|
+
const surface = rendering.surface;
|
|
695
|
+
if (!SURFACES.includes(surface)) {
|
|
696
|
+
return { ok: false, violations: [{ rule: LINT_RULES.unknown_surface, line: -1, text: String(surface) }] };
|
|
697
|
+
}
|
|
698
|
+
const lines = rendering.lines;
|
|
699
|
+
lines.forEach((line, i) => lintLine(surface, line, i, violations));
|
|
700
|
+
if (surface === "period_card") {
|
|
701
|
+
const hasRevert = lines.some(
|
|
702
|
+
(l) => isObject2(l) && l.class === "outcome" && typeof l.text === "string" && REVERT.test(l.text)
|
|
703
|
+
);
|
|
704
|
+
if (!hasRevert) violations.push({ rule: LINT_RULES.period_card_without_revert, line: -1, text: "" });
|
|
705
|
+
}
|
|
706
|
+
return { ok: violations.length === 0, violations };
|
|
707
|
+
}
|
|
708
|
+
function lintRenderings(renderings) {
|
|
709
|
+
return renderings.map(lintRendering);
|
|
710
|
+
}
|
|
711
|
+
function attachLineContext(lines, goal, protection) {
|
|
712
|
+
return lines.map((line) => {
|
|
713
|
+
const out = { class: line.class, key: line.key, text: line.text };
|
|
714
|
+
if (line.key.startsWith("goal.") && goal) out.goal_mode = goal.mode;
|
|
715
|
+
if (line.key === "protection.mode") out.mode = "off";
|
|
716
|
+
const act = /^protection\.act\.(\d+)$/.exec(line.key);
|
|
717
|
+
if (act && protection) {
|
|
718
|
+
const entry = protection.acts[Number(act[1])];
|
|
719
|
+
if (entry) {
|
|
720
|
+
out.mode = entry.mode;
|
|
721
|
+
out.verdict = entry.verdict;
|
|
722
|
+
if (entry.tool_name) out.tool_name = entry.tool_name;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return out;
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/render-estimate.ts
|
|
730
|
+
var ESTIMATE_COUNTING_FLOOR_TOKENS = 2e5;
|
|
731
|
+
var ESTIMATE_DISQUALIFIERS = {
|
|
732
|
+
not_an_object: "not_an_object",
|
|
733
|
+
method_not_lower_bound: "method_not_lower_bound",
|
|
734
|
+
counting_floor_below_200k: "counting_floor_below_200k",
|
|
735
|
+
not_net_of_mu: "not_net_of_mu",
|
|
736
|
+
never_shows_loss: "never_shows_loss",
|
|
737
|
+
value_not_finite: "value_not_finite"
|
|
738
|
+
};
|
|
739
|
+
function isObject3(v) {
|
|
740
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
741
|
+
}
|
|
742
|
+
function estimateQualifies(estimate) {
|
|
743
|
+
if (!isObject3(estimate)) return { qualifies: false, reasons: [ESTIMATE_DISQUALIFIERS.not_an_object] };
|
|
744
|
+
const reasons = [];
|
|
745
|
+
if (estimate.method !== "lower_bound") reasons.push(ESTIMATE_DISQUALIFIERS.method_not_lower_bound);
|
|
746
|
+
const floor = estimate.counting_floor_tokens;
|
|
747
|
+
if (typeof floor !== "number" || !Number.isFinite(floor) || floor < ESTIMATE_COUNTING_FLOOR_TOKENS) {
|
|
748
|
+
reasons.push(ESTIMATE_DISQUALIFIERS.counting_floor_below_200k);
|
|
749
|
+
}
|
|
750
|
+
if (estimate.net_of_mu !== true) reasons.push(ESTIMATE_DISQUALIFIERS.not_net_of_mu);
|
|
751
|
+
if (estimate.reports_loss !== true) reasons.push(ESTIMATE_DISQUALIFIERS.never_shows_loss);
|
|
752
|
+
if (typeof estimate.net_lower_bound !== "number" || !Number.isFinite(estimate.net_lower_bound)) {
|
|
753
|
+
reasons.push(ESTIMATE_DISQUALIFIERS.value_not_finite);
|
|
754
|
+
}
|
|
755
|
+
return { qualifies: reasons.length === 0, reasons };
|
|
756
|
+
}
|
|
757
|
+
var RENDER_ESTIMATE_REASONS = {
|
|
758
|
+
unknown_surface: "unknown_surface",
|
|
759
|
+
surface_never_shows_estimate: "surface_never_shows_estimate",
|
|
760
|
+
estimate_does_not_qualify: "estimate_does_not_qualify"
|
|
761
|
+
};
|
|
762
|
+
function shouldRenderEstimate(estimate, surface) {
|
|
763
|
+
if (!SURFACES.includes(surface)) {
|
|
764
|
+
return { render: false, reasons: [RENDER_ESTIMATE_REASONS.unknown_surface] };
|
|
765
|
+
}
|
|
766
|
+
if (ESTIMATE_FORBIDDEN_SURFACES.includes(surface)) {
|
|
767
|
+
return { render: false, reasons: [RENDER_ESTIMATE_REASONS.surface_never_shows_estimate] };
|
|
768
|
+
}
|
|
769
|
+
const q = estimateQualifies(estimate);
|
|
770
|
+
if (!q.qualifies) {
|
|
771
|
+
return { render: false, reasons: [RENDER_ESTIMATE_REASONS.estimate_does_not_qualify, ...q.reasons] };
|
|
772
|
+
}
|
|
773
|
+
return { render: true, reasons: [] };
|
|
774
|
+
}
|
|
775
|
+
var SHARE_CARD_TYP = "AAP-ShareCard/v1";
|
|
776
|
+
var IDENTIFYING_KEYS = /* @__PURE__ */ new Set([
|
|
777
|
+
"repo",
|
|
778
|
+
"repository",
|
|
779
|
+
"repo_name",
|
|
780
|
+
"branch",
|
|
781
|
+
"branch_name",
|
|
782
|
+
"ref",
|
|
783
|
+
"pr",
|
|
784
|
+
"pr_title",
|
|
785
|
+
"pull_request",
|
|
786
|
+
"title",
|
|
787
|
+
"commit",
|
|
788
|
+
"commit_message",
|
|
789
|
+
"commit_sha",
|
|
790
|
+
"message",
|
|
791
|
+
"author",
|
|
792
|
+
"email",
|
|
793
|
+
"url",
|
|
794
|
+
"path"
|
|
795
|
+
]);
|
|
796
|
+
var CARD_KEYS = ["typ", "card_id", "period", "issued_at", "signing_key_id", "statements", "lines", "signature"];
|
|
797
|
+
var PERIOD_KEYS = ["start", "end"];
|
|
798
|
+
var STATEMENT_KEYS = ["typ", "hash"];
|
|
799
|
+
var LINE_KEYS = ["id", "kind", "label", "value", "statement_hash"];
|
|
800
|
+
var HEX64 = /^[0-9a-f]{64}$/;
|
|
801
|
+
var DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
802
|
+
var OPAQUE_ID = /^[a-z0-9_-]{1,64}$/;
|
|
803
|
+
var LABEL = /^[A-Za-z0-9 .,()+-]{1,40}$/;
|
|
804
|
+
var LABEL_MAX = 40;
|
|
805
|
+
var SHARE_CARD_STATEMENT_TYPES = [
|
|
806
|
+
"AAP-ConductRecord/v1",
|
|
807
|
+
"AAP-PRRecord/v1",
|
|
808
|
+
"AAP-Outcome/v1"
|
|
809
|
+
];
|
|
810
|
+
var SIGNATURE_LEN = 86;
|
|
811
|
+
var RFC3339 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-])(\d{2}):(\d{2}))$/;
|
|
812
|
+
function isCalendarDate(y, m, d) {
|
|
813
|
+
if (m < 1 || m > 12 || d < 1) return false;
|
|
814
|
+
const dim = [31, y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1];
|
|
815
|
+
return d <= dim;
|
|
816
|
+
}
|
|
817
|
+
function isRfc3339(v) {
|
|
818
|
+
const m = RFC3339.exec(v);
|
|
819
|
+
if (!m) return false;
|
|
820
|
+
const [y, mo, d, h, mi, se] = m.slice(1, 7).map(Number);
|
|
821
|
+
if (!isCalendarDate(y, mo, d) || h > 23 || mi > 59 || se > 59) return false;
|
|
822
|
+
if (m[7] !== void 0 && (Number(m[8]) > 23 || Number(m[9]) > 59)) return false;
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
function shareCardSigningInput(card) {
|
|
826
|
+
const { signature: _signature, ...rest } = card;
|
|
827
|
+
return canonicalJson(rest);
|
|
828
|
+
}
|
|
829
|
+
function bytesToBase64url(b) {
|
|
830
|
+
let bin = "";
|
|
831
|
+
for (const x of b) bin += String.fromCharCode(x);
|
|
832
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
833
|
+
}
|
|
834
|
+
function base64urlToBytes(s) {
|
|
835
|
+
if (s.length !== SIGNATURE_LEN || !/^[A-Za-z0-9_-]+$/.test(s)) throw new Error("bad base64url");
|
|
836
|
+
const padded = s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice(0, (4 - s.length % 4) % 4);
|
|
837
|
+
const bin = atob(padded);
|
|
838
|
+
const out = new Uint8Array(bin.length);
|
|
839
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
840
|
+
if (out.length !== 64 || bytesToBase64url(out) !== s) throw new Error("non-canonical base64url");
|
|
841
|
+
return out;
|
|
842
|
+
}
|
|
843
|
+
function isObj(v) {
|
|
844
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
845
|
+
}
|
|
846
|
+
function exactKeys(obj, allowed, where, errors) {
|
|
847
|
+
for (const k of Object.keys(obj)) {
|
|
848
|
+
if (IDENTIFYING_KEYS.has(k.toLowerCase())) errors.push(`${where}: identifying field "${k}" is not allowed`);
|
|
849
|
+
else if (!allowed.includes(k)) errors.push(`${where}: unknown field "${k}"`);
|
|
850
|
+
}
|
|
851
|
+
for (const k of allowed) if (!(k in obj)) errors.push(`${where}: missing field "${k}"`);
|
|
852
|
+
}
|
|
853
|
+
function validateShareCardShape(card) {
|
|
854
|
+
const errors = [];
|
|
855
|
+
if (!isObj(card)) return ["card: not an object"];
|
|
856
|
+
exactKeys(card, CARD_KEYS, "card", errors);
|
|
857
|
+
if (card.typ !== SHARE_CARD_TYP) errors.push(`card: typ must be ${SHARE_CARD_TYP}`);
|
|
858
|
+
if (typeof card.card_id !== "string" || !OPAQUE_ID.test(card.card_id)) errors.push("card: card_id must be an opaque id matching ^[a-z0-9_-]{1,64}$");
|
|
859
|
+
if (typeof card.issued_at !== "string" || !isRfc3339(card.issued_at)) errors.push("card: issued_at must be an RFC 3339 date-time");
|
|
860
|
+
if (typeof card.signing_key_id !== "string" || !card.signing_key_id) errors.push("card: signing_key_id must be a non-empty string");
|
|
861
|
+
if (typeof card.signature !== "string") errors.push("card: signature must be a string");
|
|
862
|
+
else {
|
|
863
|
+
try {
|
|
864
|
+
base64urlToBytes(card.signature);
|
|
865
|
+
} catch {
|
|
866
|
+
errors.push("card: signature must be a canonical 86-char unpadded base64url Ed25519 signature");
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (!isObj(card.period)) errors.push("period: not an object");
|
|
870
|
+
else {
|
|
871
|
+
exactKeys(card.period, PERIOD_KEYS, "period", errors);
|
|
872
|
+
for (const k of PERIOD_KEYS) {
|
|
873
|
+
const v = card.period[k];
|
|
874
|
+
if (typeof v !== "string" || !DATE.test(v) || !isCalendarDate(Number(v.slice(0, 4)), Number(v.slice(5, 7)), Number(v.slice(8, 10)))) {
|
|
875
|
+
errors.push(`period.${k}: must be a real calendar date YYYY-MM-DD`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const { start, end } = card.period;
|
|
879
|
+
if (typeof start === "string" && typeof end === "string" && DATE.test(start) && DATE.test(end) && start > end) {
|
|
880
|
+
errors.push("period: start must be <= end");
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
884
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
885
|
+
const lineIds = /* @__PURE__ */ new Set();
|
|
886
|
+
if (!Array.isArray(card.statements) || card.statements.length === 0) errors.push("statements: must be a non-empty array");
|
|
887
|
+
else
|
|
888
|
+
card.statements.forEach((s, i) => {
|
|
889
|
+
if (!isObj(s)) return void errors.push(`statements[${i}]: not an object`);
|
|
890
|
+
exactKeys(s, STATEMENT_KEYS, `statements[${i}]`, errors);
|
|
891
|
+
if (typeof s.typ !== "string" || !SHARE_CARD_STATEMENT_TYPES.includes(s.typ)) {
|
|
892
|
+
errors.push(`statements[${i}].typ: must be one of ${SHARE_CARD_STATEMENT_TYPES.join(", ")}`);
|
|
893
|
+
}
|
|
894
|
+
if (typeof s.hash !== "string" || !HEX64.test(s.hash)) errors.push(`statements[${i}].hash: must be 64 lowercase hex`);
|
|
895
|
+
else hashes.add(s.hash);
|
|
896
|
+
});
|
|
897
|
+
if (!Array.isArray(card.lines) || card.lines.length === 0) errors.push("lines: must be a non-empty array");
|
|
898
|
+
else
|
|
899
|
+
card.lines.forEach((l, i) => {
|
|
900
|
+
if (!isObj(l)) return void errors.push(`lines[${i}]: not an object`);
|
|
901
|
+
exactKeys(l, LINE_KEYS, `lines[${i}]`, errors);
|
|
902
|
+
if (typeof l.id !== "string" || !OPAQUE_ID.test(l.id)) errors.push(`lines[${i}].id: must be an opaque id matching ^[a-z0-9_-]{1,64}$`);
|
|
903
|
+
else if (lineIds.has(l.id)) errors.push(`lines[${i}].id: duplicate line id "${l.id}"`);
|
|
904
|
+
else lineIds.add(l.id);
|
|
905
|
+
if (l.kind !== "count" && l.kind !== "revert") errors.push(`lines[${i}].kind: must be "count" or "revert"`);
|
|
906
|
+
if (typeof l.label !== "string" || !l.label || l.label.length > LABEL_MAX || /[/#]|:\/\/|www\./i.test(l.label) || !LABEL.test(l.label)) {
|
|
907
|
+
errors.push(`lines[${i}].label: must be short plain text (max ${LABEL_MAX}), no "/", "#" or URLs`);
|
|
908
|
+
}
|
|
909
|
+
if (typeof l.value !== "number" || !Number.isSafeInteger(l.value) || l.value < 0) errors.push(`lines[${i}].value: must be a non-negative integer`);
|
|
910
|
+
if (typeof l.statement_hash !== "string" || !HEX64.test(l.statement_hash)) errors.push(`lines[${i}].statement_hash: must be 64 lowercase hex`);
|
|
911
|
+
else if (!hashes.has(l.statement_hash)) errors.push(`lines[${i}].statement_hash: not committed in statements`);
|
|
912
|
+
else referenced.add(l.statement_hash);
|
|
913
|
+
});
|
|
914
|
+
for (const h of hashes) if (!referenced.has(h)) errors.push(`statements: ${h} is not referenced by any line`);
|
|
915
|
+
return errors;
|
|
916
|
+
}
|
|
917
|
+
async function verifyShareCard(card, options) {
|
|
918
|
+
const fail = (errors) => ({ valid: false, lines: [], errors });
|
|
919
|
+
const shapeErrors = validateShareCardShape(card);
|
|
920
|
+
if (shapeErrors.length > 0) return fail(shapeErrors);
|
|
921
|
+
const c = card;
|
|
922
|
+
let ok = false;
|
|
923
|
+
try {
|
|
924
|
+
const pk = typeof options.publicKey === "string" ? hexToUint8(options.publicKey) : options.publicKey;
|
|
925
|
+
ok = await ed25519.verifyAsync(base64urlToBytes(c.signature), utils_js.utf8ToBytes(shareCardSigningInput(c)), pk, { zip215: false });
|
|
926
|
+
} catch {
|
|
927
|
+
ok = false;
|
|
928
|
+
}
|
|
929
|
+
if (!ok) return fail(["signature: verification failed"]);
|
|
930
|
+
return { valid: true, lines: c.lines.map((l) => ({ ...l })), errors: [] };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
exports.BUNDLE_TYP = BUNDLE_TYP;
|
|
934
|
+
exports.CARD_LINE_CLASSES = CARD_LINE_CLASSES;
|
|
935
|
+
exports.CARD_SURFACES = CARD_SURFACES;
|
|
936
|
+
exports.CLAIM_CLASSES = CLAIM_CLASSES;
|
|
937
|
+
exports.CLAIM_TABLE = CLAIM_TABLE;
|
|
938
|
+
exports.ESTIMATE_COUNTING_FLOOR_TOKENS = ESTIMATE_COUNTING_FLOOR_TOKENS;
|
|
939
|
+
exports.ESTIMATE_DISQUALIFIERS = ESTIMATE_DISQUALIFIERS;
|
|
940
|
+
exports.ESTIMATE_FORBIDDEN_SURFACES = ESTIMATE_FORBIDDEN_SURFACES;
|
|
941
|
+
exports.FLAGS = FLAGS;
|
|
942
|
+
exports.GOAL_MODES = GOAL_MODES;
|
|
943
|
+
exports.LINE_KEY_FIELDS = LINE_KEY_FIELDS;
|
|
944
|
+
exports.LINT_RULES = LINT_RULES;
|
|
945
|
+
exports.MODES = MODES;
|
|
946
|
+
exports.MODE_VERBS = MODE_VERBS;
|
|
947
|
+
exports.PROTECTION_VERDICTS = PROTECTION_VERDICTS;
|
|
948
|
+
exports.REASONS = REASONS;
|
|
949
|
+
exports.RENDER_ESTIMATE_REASONS = RENDER_ESTIMATE_REASONS;
|
|
950
|
+
exports.SHARE_CARD_TYP = SHARE_CARD_TYP;
|
|
951
|
+
exports.STATEMENT_TYPES = STATEMENT_TYPES;
|
|
952
|
+
exports.SURFACES = SURFACES;
|
|
953
|
+
exports.attachLineContext = attachLineContext;
|
|
954
|
+
exports.canonicalJson = canonicalJson;
|
|
955
|
+
exports.estimateQualifies = estimateQualifies;
|
|
956
|
+
exports.fieldForLineKey = fieldForLineKey;
|
|
957
|
+
exports.isClaimField = isClaimField;
|
|
958
|
+
exports.lintRendering = lintRendering;
|
|
959
|
+
exports.lintRenderings = lintRenderings;
|
|
960
|
+
exports.protectionVerb = protectionVerb;
|
|
961
|
+
exports.shareCardSigningInput = shareCardSigningInput;
|
|
962
|
+
exports.shouldRenderEstimate = shouldRenderEstimate;
|
|
963
|
+
exports.statementCommitment = statementCommitment;
|
|
964
|
+
exports.statementDigest = statementDigest;
|
|
965
|
+
exports.validateShareCardShape = validateShareCardShape;
|
|
966
|
+
exports.verify = verify;
|
|
967
|
+
exports.verifyCertificate = verifyCertificate;
|
|
968
|
+
exports.verifyShareCard = verifyShareCard;
|
|
969
|
+
exports.verifyStatement = verifyStatement;
|
|
970
|
+
//# sourceMappingURL=index.cjs.map
|
|
971
|
+
//# sourceMappingURL=index.cjs.map
|