@mnemom/mnemom 0.17.35 → 0.17.37

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